Android手机有个挺好的功能,它允许你往桌面上放窗口小部件(widget),有一个叫相框的小部件,可以让你选择一张相片,截取一部分,放在相框里。我桌面上就放了几个相框,里面是我女儿的照片,隔阵子换一换,挺喜欢。这次的实例受相框小部件启发而成,我称之为挖头像,先看看运行效果。
运行效果
电脑上的运行效果如图1:
图1 电脑挖头像效果图
Android手机上运行效果如图2:
项目创建
项目名称是PickThumb,Android包名是an.qt.PickThumb,其它的木啥咧。
源码分析
C++代码
为了能够让PickThumb正常退出,我给QGuiApplication安装了事件过滤器,过滤BACK按键。下面是main.cpp文件:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QKeyEvent>
class KeyBackQuit: public QObject
{
public:
KeyBackQuit(QObject *parent = 0)
: QObject(parent)
{}
bool eventFilter(QObject *watched, QEvent * e)
{
switch(e->type())
{
case QEvent::KeyPress:
if( ((QKeyEvent*)e)->key() == Qt::Key_Back )
{
e->accept();
return true;
}
break;
case QEvent::KeyRelease:
if( ((QKeyEvent*)e)->key() == Qt::Key_Back )
{
e->accept();
qApp->quit();
return true;
}
break;
default:
break;
}
return QObject::eventFilter(watched, e);
}
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
app.installEventFilter(new KeyBackQuit);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
KeyBackQuit类重写eventFilter()方法来过滤Key_Back按键,调用QCoreApplication的quit()方法退出应用。过滤器在main()函数中被安装到QGuiApplication实例上。
QML代码分析
该主角登场了,main.qml文件有200多行代码,内容如下:
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import QtQuick.Dialogs 1.1
Window {
visible: true
width: 480;
height: 320;
minimumHeight: 320;
minimumWidth: 480;
color: "black";
onWidthChanged: mask.recalc();
onHeightChanged: mask.recalc();
Image {
id: source;
anchors.fill: parent;
fillMode: Image.PreserveAspectFit;
visible: false;
asynchronous: true;
onStatusChanged: {
if(status == Image.Ready){
console.log("image loaded");
mask.recalc();
}
}
}
FileDialog {
id: fileDialog;
title: "Please choose an Image File";
nameFilters: ["Image Files (*.jpg *.png *.gif)"];
onAccepted: {
source.source = fileDialog.fileUrl;
}
}
Canvas {
id: forSaveCanvas;
width: 128;
height: 128;
contextType: "2d";
visible: false;
z: 2;
anchors.top: parent.top;
anchors.right: parent.right;
anchors.margins: 4;
property var imageData: null;
onPaint: {
if(imageData != null){
context.drawImage(imageData, 0, 0);
}
}
function setImageData(data){
imageData = data;
requestPaint();
}
}
Canvas {
id: mask;
anchors.fill: parent;
z: 1;
property real w: width;
property real h: height;
property real dx: 0;
property real dy: 0;
property real dw: 0;
property real dh: 0;
property real frameX: 66;
property real frameY: 66;
function calc(){
var sw = source.sourceSize.width;
var sh = source.sourceSize.height;
if(sw > 0 && sh > 0){
if(sw <= w && sh <=h){
dw = sw;
dh = sh;
}else{
var sRatio = sw / sh;
dw = sRatio * h;
if(dw > w){
dh = w / sRatio;
dw = w;
}else{
dh = h;
}
}
dx = (w - dw)/2;
dy = (h - dh)/2;
}
}
function recalc(){
calc();
requestPaint();
}
function getImageData(){
return context.getImageData(frameX - 64, frameY - 64,
128, 128);
}
onPaint: {
var ctx = getContext("2d");
if(dw < 1 || dh < 1) {
ctx.fillStyle = "#0000a0";
ctx.font = "20pt sans-serif";
ctx.textAlign = "center";
ctx.fillText("Please Choose An Image File",
width/2, height/2);
return;
}
ctx.clearRect(0, 0, width, height);
ctx.drawImage(source, dx, dy, dw, dh);
var xStart = frameX - 66;
var yStart = frameY - 66;
ctx.save();
ctx.fillStyle = "#a0000000";
ctx.fillRect(0, 0, w, yStart);
var yOffset = yStart + 132;
ctx.fillRect(0, yOffset, w, h - yOffset);
ctx.fillRect(0, yStart, xStart, 132);
var xOffset = xStart + 132;
ctx.fillRect(xOffset, yStart, w - xOffset, 132);
//see through area
ctx.strokeStyle = "red";
ctx.fillStyle = "#00000000";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.rect(xStart, yStart, 132, 132);
ctx.fill();
ctx.stroke();
ctx.closePath ();
ctx.restore();
}
}
MultiPointTouchArea {
anchors.fill: parent;
minimumTouchPoints: 1;
maximumTouchPoints: 1;
touchPoints:[
TouchPoint{
id: point1;
}
]
onUpdated: {
mask.frameX = point1.x;
mask.frameY = point1.y;
mask.requestPaint();
}
onReleased: {
forSaveCanvas.setImageData(mask.getImageData());
actionPanel.visible = true;
}
onPressed: {
actionPanel.visible = false;
}
}
Component {
id: flatButton;
ButtonStyle {
background: Rectangle{
implicitWidth: 70;
implicitHeight: 30;
border.width: control.hovered ? 2: 1;
border.color: control.hovered ? "#c0c0c0" : "#909090";
color: control.pressed ? "#a0a0a0" : "#707070";
}
label: Text {
anchors.fill: parent;
font.pointSize: 12;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
text: control.text;
color: (control.hovered && !control.pressed) ?
"blue": "white";
}
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 20;
id: actionPanel;
z: 5;
spacing: 8;
Button {
style: flatButton;
text: "Open";
onClicked: fileDialog.open();
}
Button {
style: flatButton;
text: "Save";
onClicked: {
forSaveCanvas.save("selected.png");
actionPanel.visible = false;
}
}
Button {
style: flatButton;
text: "Cancel";
onClicked: actionPanel.visible = false;
}
}
}
代码的逻辑是这样的:点击“Open”按钮打开一个对话框,用户选择一张图片,使用隐藏的Image对象加载,加载成功后触发Canvas对象绘制图片;当用户用手指(或按下鼠标左键)拖动时,选中框中心跟随手指移动,框内图像是正常亮度;当用户抬起手指后,弹出操作菜单,如选择“Save”,则通过一个隐藏的Canvas把选中区域的图像保存到文件中。
QML中用到的Row、Button、ButtonStyle、Component、Image、FileDialog等我都有文章讲过,参考我的专栏《
Qt Quick简明教程》;MultiPointTouchArea和Canvas没讲过,参考Qt帮助吧。这里咱单说“整个照片变暗而唯有选中框内正常显示”这种效果的实现。
我定义了一个id为mask的Canvas,它使用id为source的Image对象绘制图片。图片在最底层绘制,然后在它上面绘制使用透明色填充的矩形,于是图片就变暗了。整个Canvas被分成一个“回”字形,中间是完全透明的矩形,周围是半透明的。半透明部门由顶部、底部、左面、右面四个矩形组成,分别填充即可。
图片是按比例显示的,等图片加载成功后,先计算了绘制时需要的目标矩形,绘制时直接引用,避免重复计算。而桌面版本为了适应窗口大小变化,实现了onWidthChanged和onHeightChanged两个信号处理器来更新绘制参数。
当用户选择保存时,把mask的透明区域内的像素挖出来(getImageData),生成一个CanvasImageData对象,交给另一个Canvas对象去显示,调用它的save()方法把内容写入到文件。
这就是全部了。
回顾我Qt Quick系列的文章:
分享到:
相关推荐
基于QtQuick的实例,不在是基础的讲解,喜欢可以加入我们的群 关注我的博客就能找到答案
Qt及Qt Quick开发实战精解Qt and Qt Quick Development Practical Digestion
使用 Qt Quick 实现的图像处理实例,支持黑白、锐化、底片、柔化、灰度、浮雕等特效。展示 Qt 中 QML 与 C++ 混合编程技术、多线程、自定义事件等关键技术。
Qt quick编程手册,专业学习qt quick
QML与C++混合编程、Canvas、定制及自定义控件等高级主题,《Qt Quick核心编程》也做了详细讲解,同时提供了多个精彩的实作实例,力求概念清晰,用途明确。《Qt Quick核心编程》的内容适用于桌面与Android平台。值得...
Qt及Qt Quick开发实战精解,Qt及Qt Quick开发实战精解
QtQuick的中文帮助文档,共75页,清晰明了,帮助用户开发QML程序
QML与C++混合编程、Canvas、定制及自定义控件等高级主题,《Qt Quick核心编程》也做了详细讲解,同时提供了多个精彩的实作实例,力求概念清晰,用途明确。《Qt Quick核心编程》的内容适用于桌面与Android平台。值得...
QtQuick核心编程,给予Qt5.3,介绍了qtquick的使用方法。
Qt及QtQuick 与 源码 书中包含了Qt的实例:俄罗斯方块,音乐播放器,聊天室。以及QML和Quick的讲解
Qt及Qt Quick开发实战精解.pdf
Qt Quick核心编程,高清,带目录,全部是自己加的目录。
《QML和Qt Quick快速入门》示例源码.zip
QML与C++混合编程、Canvas、定制及自定义控件等高级主题,《Qt Quick核心编程》也做了详细讲解,同时提供了多个精彩的实作实例,力求概念清晰,用途明确。《Qt Quick核心编程》的内容适用于桌面与Android平台。值得...
Qt Quick核心编程 随书源码 安晓辉 CD SRC
官方很好的教程,有QT移动 QT桌面, QML移动桌面
Qt Quick 核心编程 原版PDF 这是目前最全最易理解的QML书籍了 第一部分 由于上传限制文件大小 所有有两个压缩包