Qt Quick实例之挖头像

来源:互联网 发布:绝地求生透视源码 编辑:程序博客网 时间:2024/06/06 04:12

    Android手机有个挺好的功能,它允许你往桌面上放窗口小部件(widget),有一个叫相框的小部件,可以让你选择一张相片,截取一部分,放在相框里。我桌面上就放了几个相框,里面是我女儿的照片,隔阵子换一换,挺喜欢。这次的实例受相框小部件启发而成,我称之为挖头像,先看看运行效果。

运行效果

    电脑上的运行效果如图1:

                           图1 电脑挖头像效果图
    Android手机上运行效果如图2:


项目创建

    项目创建过程参考《Qt Quick 之 Hello World 图文详解》,安卓配置参考《Windows下Qt 5.2 for Android开发入门》和《Qt on Android:图文详解Hello World全过程》。
    项目名称是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.2import QtQuick.Window 2.1import QtQuick.Controls 1.2import QtQuick.Controls.Styles 1.2import QtQuick.Dialogs 1.1Window {    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系列的文章:
  • Qt Quick 简介
  • QML 语言基础
  • Qt Quick 之 Hello World 图文详解
  • Qt Quick 简单教程
  • Qt Quick 事件处理之信号与槽
  • Qt Quick事件处理之鼠标、键盘、定时器
  • Qt Quick 事件处理之捏拉缩放与旋转
  • Qt Quick 组件与对象动态创建详解
  • Qt Quick 布局介绍
  • Qt Quick 之 QML 与 C++ 混合编程详解
  • Qt Quick 图像处理实例之美图秀秀(附源码下载)
  • Qt Quick 之 PathView 详解

4 0