1. 程式人生 > >純QML實現畫圖工具

純QML實現畫圖工具

        前言,QT5 版本較Qt4 新增了Canvas(畫布),可以通過Js實現2D繪圖,與HTML5提供的API保持一致,使用非常靈活。下面將介紹使用QML製作簡單的畫圖工具。

    首先,介紹整體佈局為。最上側是選單,下面是工具條,中間是Canvas(畫布),最底側是狀態列。

    1.選單欄設計

     為實現分頁顯示不同的工具,採用TabView進行佈局。程式碼如下:

import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import QtQuick.Window 2.0
import QtQuick.Dialogs 1.2
//選單選項
Rectangle{
    id:root
    height: 120
    width: appWindow.width
    property color paintColor //繪製顏色
    property string picPath:""//圖片路徑
    TabView{
        width: appWindow.width
        id:tabView
        Tab{
            title: "檔案"
            Rectangle {
                id: see
                width: appWindow.width
                anchors {
                    horizontalCenter: parent.horizontalCenter
                    top: parent.top
                    topMargin: 8
                }
                color: "transparent"
                Row{
                    spacing: 5
                    Column{
                        Row{
                            spacing: 5
                            PicButton{
                                width: 50
                                height: 64
                                isImageVisible: true
                                imagePath: "res/new.png"
                                rectColor: "transparent"
                                describle: "新建"
                                onClicked: {
                                    messageDialog.text="是否儲存?"
                                    messageDialog.standardButtons=StandardButton.Yes | StandardButton.No
                                    messageDialog.icon=StandardIcon.Question
                                    messageDialog.open()
                                }
                            }
                            PicButton{
                                width: 50
                                height: 64
                                isImageVisible: true
                                imagePath: "res/open.png"
                                rectColor: "transparent"
                                describle: "開啟"
                                onClicked: {
                                    fileDialog.open()
                                    fileDialog.selectExisting=true
                                }
                            }
                            PicButton{
                                width: 50
                                height: 64
                                isImageVisible: true
                                imagePath: "res/save.png"
                                rectColor: "transparent"
                                describle: "儲存"
                                onClicked:{
                                    fileDialog.selectExisting=false
                                    fileDialog.open()
                                }
                            }
                            PicButton{
                                width: 50
                                height: 64
                                isImageVisible: true
                                imagePath: "res/saveOther.png"
                                describle: "另存"
                                onClicked:{
                                    fileDialog.selectExisting=false
                                    fileDialog.open()
                                }
                            }
                        }
                        Rectangle{
                            width: parent.width
                            height: 25
                            color:"#F5F6F7"
                            anchors.horizontalCenter: parent.horizontalCenter
                            Text{
                                text:"檔案操作"
                                color:"#929292"
                                anchors.centerIn: parent
                            }
                        }
                    }
                    //分隔符
                    Rectangle{height: 64;width: 1;color:"#E2E3E4";}
                    Column{
                        Row{
                            spacing: 5
                            PicButton{
                                width: 50
                                height: 64
                                isImageVisible: true
                                imagePath: "res/print.png"
                                rectColor: "transparent"
                                describle: "列印"
                                onClicked: appWindow.showFullScreen()
                            }
                            PicButton{
                                width: 50
                                height: 64
                                isImageVisible: true
                                imagePath: "res/exit.png"
                                rectColor: "transparent"
                                describle: "退出"
                                onClicked:  Qt.quit()
                            }
                        }
                        Rectangle{
                            width: parent.width
                            height: 25
                            color:"#F5F6F7"
                            anchors.horizontalCenter: parent.horizontalCenter
                            Text{
                                text:"操作"
                                color:"#929292"
                                anchors.centerIn: parent
                            }
                        }
                    }
                }
                FileDialog {
                    id: fileDialog;
                    title: qsTr("Please choose an image file");
                    nameFilters:["PNG(*.png)","JPEG(*.jpg *.jpeg *.jpe)","GIF(*.gif)","點陣圖(*.bmp)"]
                    property int isSave: 1 //判斷是開啟還是儲存操作
                    onAccepted: {
                        if(!fileDialog.selectExisting)
                            if(canvas.saveImage(picPath))
                            {
                                messageDialog.title="提示"
                                messageDialog.text="儲存成功!"
                                messageDialog.icon=StandardIcon.Information
                                messageDialog.standardButtons=StandardButton.Ok
                                messageDialog.open()
                            }
                        var filepath = new String(fileUrl);
                        if(Qt.platform.os == "windows")
                            root.picPath= filepath.slice(8);
                        else
                            root.picPath = filepath.slice(7);
                    }
                }
                //彈窗提示
                MessageDialog {
                    id: messageDialog
                    onAccepted: {
                        console.log("do samething")
                    }
                    onYes: {fileDialog.selectExisting=false;fileDialog.open();}
                    onNo: canvas.newImage()
                }
            }
        }
        Tab{
            title: "設定"
            Rectangle {
                id: colorTools
                width: appWindow.width
                anchors {
                    horizontalCenter: parent.horizontalCenter
                    top: parent.top
                    topMargin: 8
                }
                color: "transparent"
                Row{
                    spacing: 5
                    anchors.left: parent.left
                    anchors.leftMargin: 15
                    Column{
                        Row{
                            spacing: 5
                            //設定線寬
                            PicButton{
                                id:selectWeight
                                width:50
                                height: grid.height
                                imagePath: "res/weight.png"
                                describle: "粗細"
                                isImageVisible:true
                                onClicked: {
                                    listWeight.visible=listWeight.visible==true?false:true;
                                    listWeight.z=3
                                }
                            }
                            //分隔符
                            Rectangle{height: grid.height;width: 1;color:"#E2E3E4";}
                            //選中的顏色
                            PicButton{
                                id:selectRect
                                width:50
                                height: grid.height
                                describle: "顏色"
                            }
                            //分隔符
                            Rectangle{height: grid.height;width: 1;color:"#E2E3E4";}
                            //色彩網格
                            Grid{
                                id:grid
                                columns: 7
                                rows:2
                                spacing: 2
                                Repeater {
                                    model: ["#33B5E5", "#99CC00", "#FFBB33", "#FF4444","#DD6644","#AA4444","#FFBB33", "#FF4444","#DD6644","#AA4444","#FFBB33", "#FF4444","#DD6644","#AA4444"]
                                    ColorGrid {
                                        id: red
                                        color: modelData
                                        onClicked:{
                                            selectRect.rectColor=color
                                            root.paintColor = color
                                        }
                                    }
                                }
                            }
                            //分隔符
                            Rectangle{height: grid.height;width: 1;color:"#E2E3E4";}
                            //顏色對話方塊
                            PicButton{
                                id:editRect
                                width:50
                                height: grid.height
                                isImageVisible: true
                                imagePath: "res/pic.png"
                                rectColor: "transparent"
                                describle: "編輯"
                                onClicked: colorD.open()
                                ColorDialog{
                                    id:colorD
                                    onColorChanged: {selectRect.rectColor=color;root.paintColor=color;}
                                }
                            }
                            //分隔符
                            Rectangle{height: grid.height;width: 1;color:"#E2E3E4";}
                        }
                        Rectangle{
                            width: grid.width
                            height: 30
                            color:"#F5F6F7"
                            anchors.horizontalCenter: parent.horizontalCenter
                            Text{
                                text:"顏色"
                                color:"#929292"
                                anchors.centerIn: parent
                            }
                        }
                    }
                    //形狀設定
                    Column{
                        Grid{
                            id:xz
                            width: 200
                            height: 60
                            Repeater{
                                model:[]
                            }
                        }
                        Rectangle{
                            width: xz.width
                            height: 30
                            color:"#F5F6F7"
                            anchors.horizontalCenter: parent.horizontalCenter
                            Text{
                                text:"形狀"
                                color:"#929292"
                                anchors.centerIn: parent
                            }
                        }
                    }
                }
            }
        }
        Tab{
            title: "檢視"
            Rectangle {
                id: show
                width: appWindow.width
                anchors {
                    horizontalCenter: parent.horizontalCenter
                    top: parent.top
                    topMargin: 8
                }
                color: "transparent"
                Row{
                    spacing: 5
                    Column{
                        Row{
                            spacing: 5
                            PicButton{
                                width: 50
                                height: 64
                                isImageVisible: true
                                imagePath: "res/increase.png"
                                rectColor: "transparent"
                                describle: "放大"
                                onClicked: {
                                    if( statusbar.sliderValue+0.25>1)
                                        statusbar.sliderValue=1;
                                    else
                                        statusbar.sliderValue= statusbar.sliderValue+0.25
                                }
                            }
                            PicButton{
                                width: 50
                                height: 64
                                isImageVisible: true
                                imagePath: "res/decrease.png"
                                rectColor: "transparent"
                                describle: "縮小"
                                onClicked: {
                                    if( statusbar.sliderValue-0.25<-1)
                                        statusbar.sliderValue=-1;
                                    else
                                        statusbar.sliderValue= statusbar.sliderValue-0.25
                                }
                            }
                            PicButton{
                                width: 50
                                height: 64
                                isImageVisible: true
                                imagePath: "res/big.png"
                                describle: "100%"
                                onClicked: statusbar.sliderValue=0;
                            }
                        }
                        Rectangle{
                            width: parent.width
                            height: 25
                            color:"#F5F6F7"
                            anchors.horizontalCenter: parent.horizontalCenter
                            Text{
                                text:"縮放"
                                color:"#929292"
                                anchors.centerIn: parent
                            }
                        }
                    }
                    //分隔符
                    Rectangle{height: 64;width: 1;color:"#E2E3E4";}
                    Column{
                        Row{
                            spacing: 5
                            PicButton{
                                width: 50
                                height: 64
                                isImageVisible: true
                                imagePath: "res/fullScreen.png"
                                rectColor: "transparent"
                                describle: "全屏"
                                onClicked: appWindow.showFullScreen()
                            }
                            PicButton{
                                width: 50
                                height: 64
                                isImageVisible: true
                                imagePath: "res/exitFull.png"
                                rectColor: "transparent"
                                describle: "退出"
                                onClicked: appWindow.showNormal()
                            }
                        }
                        Rectangle{
                            width: parent.width
                            height: 25
                            color:"#F5F6F7"
                            anchors.horizontalCenter: parent.horizontalCenter
                            Text{
                                text:"顯示"
                                color:"#929292"
                                anchors.centerIn: parent
                            }
                        }
                    }
                }
            }
        }
        style:TabViewStyle{
            frameOverlap: 1
            tab: Rectangle {
                color: styleData.selected ? "#F5F6F7" :"#FFFFFF"
                implicitWidth: Math.max(text.width + 4, 100)
                implicitHeight: 30
                radius: 1
                Text {
                    id: text
                    anchors.centerIn: parent
                    text: styleData.title
                    color: "black"
                    font.pixelSize: 12
                }
            }
            frame: Rectangle {
                width: appWindow.width
                color: "#F5F6F7"
            }
        }
    }
}
    2.工具欄,採用ToolBar設計。程式碼如下:
     
ToolBar{
        id:toolBar
        anchors.top: menuBar.bottom
        Row{
            anchors.verticalCenter: parent.verticalCenter
            spacing: 5
            ToolButton{
                width:25
                height: 25
                tooltip: "儲存"
                iconSource: "./res/save.png"
            }
            ToolButton{
                width:25
                height: 25
                tooltip: "撤銷"
                iconSource: "./res/revoke.png"
            }
            ToolButton{
                width:25
                height: 25
                tooltip: "恢復"
                iconSource: "./res/undo.png"
            }
            Rectangle{
                width:25
                height: 25
                color: menuBar.paintColor
                border.color: "gray"
                border.width: 1
            }
        }
    }    
3. 中間畫布使用QML自己的Canvas 實現程式碼如下:
import QtQuick 2.3
import QtQml 2.0
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import QtQuick.Window 2.0
import QtGraphicalEffects 1.0
Rectangle{
    id:root
    width:500*statusbar.rate
    height: 300*statusbar.rate
    property alias mouseX: area.mouseX
    property alias mouseY: area.mouseY
    property string imgPath: menuBar.picPath
    signal save(string str)
    property double lineWidth: 1 //線寬
    property color paintColor: menuBar.paintColor //畫筆顏色
    property var ctx: canvas.getContext('2d')
    function saveImage(imgName)
    {
        return canvas.save(imgName)
    }
    //新建畫布,清除畫布
    function newImage()
    {
         ctx.clearRect(0,0,canvas.width,canvas.height)
         canvas.requestPaint()
    }
    function rePaint()
    {
    }
    Image{
        id:imgP
        source:{
            if(imgPath!="")
                "file:///"+imgPath;
            else
                "";
        }
        visible: false
        width:canvas.width
        height: canvas.height
    }
    //
    Timer{
        interval: 100
        running: true
        triggeredOnStart: true
        repeat: true
        onTriggered: canvas.requestPaint()
    }
//    Image{
//        id:picImg
//        anchors.fill: parent
//    }
    //畫板
    Canvas {
        id: canvas
        anchors.fill: parent
        antialiasing: true
        property real lastX //畫筆的終止位置
        property real lastY
        //opacity: 0
        onImageLoaded: {
            if(canvas.isImageLoaded(imgP.source))
            {
                console.log("imps")
            }
            if(canvas.isImageError(imgP.source))
            {
                console.log("impE")
            }
        }
        onPaint: {
            if(imgP.source!="")
                ctx.drawImage(imgP,0,0)
            ctx.lineWidth = lineWidth
            ctx.strokeStyle = paintColor
            ctx.beginPath()
            ctx.moveTo(lastX, lastY)
            lastX = area.mouseX
            lastY = area.mouseY
            ctx.lineTo(lastX, lastY)
            ctx.stroke()
        }
        MouseArea {
            id: area
            anchors.fill: parent
            acceptedButtons: Qt.AllButtons
            onPressed: {
                canvas.lastX = mouseX
                canvas.lastY = mouseY
            }
            onPositionChanged: {
                canvas.requestPaint()
            }
            onClicked: {
                if(mouse.button==Qt.RightButton)
                    contentMenu.popup();
//                var url=canvas.toDataURL('image/png');
//                picImg.source=url;
            }
            //滑鼠形狀改變
            cursorShape: (containsMouse? (pressed? Qt.CrossCursor: Qt.ArrowCursor): Qt.ArrowCursor);
            Menu { // 右鍵選單
                //title: "Edit"
                id: contentMenu
                MenuItem {
                    text: "新建"
                    shortcut: "Ctrl+N"
                    onTriggered: {}
                }
                MenuItem {
                    text: "儲存"
                    shortcut: "Ctrl+S"
                    onTriggered: {}
                }
                MenuItem {
                    text: "貼上"
                    shortcut: "Ctrl+V"
                    onTriggered: {}
                }
                MenuSeparator { }
                Menu {
                    title: "More Stuff"
                    MenuItem {
                        text: "Do Nothing"
                    }
                }
            }
        }
    }
    //左側
    Rectangle{
        width: 5
        height: 5
        x:parent.width
        y:parent.height/2
        border.color: "black"
        border.width: 1
        MouseArea{
            id:xRate
            anchors.fill: parent
            cursorShape: (containsMouse? (pressed? Qt.SizeHorCursor: Qt.ArrowCursor): Qt.ArrowCursor);
            drag.target: parent
            onPositionChanged: {
                root.width=parent.x
            }
        }
    }
    //下側
    Rectangle{
        width: 5
        height: 5
        border.color: "black"
        border.width: 1
        x:parent.width/2
        y:parent.height
        Drag.active: yRate.drag.active
        Drag.hotSpot.x: 10
        Drag.hotSpot.y: 10
        MouseArea{
            id:yRate
            anchors.fill: parent
            cursorShape: (containsMouse? (pressed? Qt.SizeVerCursor: Qt.ArrowCursor): Qt.ArrowCursor);
            drag.target: parent
            onPositionChanged: {
                root.height=parent.y
            }
        }
    }
    //對角
    Rectangle{
        width: 5
        height: 5
        x:parent.width
        y:parent.height
        border.color: "black"
        border.width: 1
        Drag.active: xyRate.drag.active
        Drag.hotSpot.x: 10
        Drag.hotSpot.y: 10
        MouseArea{
            id:xyRate
            anchors.fill: parent
            cursorShape: (containsMouse? (pressed? Qt.SizeFDiagCursor: Qt.ArrowCursor): Qt.ArrowCursor);
            drag.target: parent
            onPositionChanged: {
                root.width=parent.x
                root.height=parent.y
            }
        }
    }
}
3.底部狀態列設計,左側為滑鼠座標,中間為影象大小,右側為縮放比例調整。程式碼如下:
import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
Rectangle {
    color: "#F0F0F0";
    implicitHeight: 30;
    width: parent.width;
    property string position: "" //位置座標
    property int pWidth: 0  //影象寬度
    property int pHeight: 0 //影象高度
    property alias sliderValue:pslider.value //放大縮小值
    //放大倍數
    property double  rate: {
        if(pslider.value==0)1;
        else
            (1+pslider.value).toFixed(2)
    }
    Row{
        anchors.left: parent.left
        anchors.leftMargin: 10
        anchors.verticalCenter: parent.verticalCenter
        Image{
            width: 20
            height: 20
            source: "./res/pic.png"
            anchors.verticalCenter: parent.verticalCenter
        }
        Text{
            id:pos
            anchors.verticalCenter: parent.verticalCenter
            text:" "+position
        }
    }
    Row{
        anchors.verticalCenter: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
        Image{
            width: 20
            height: 20
            source: "./res/pic.png"
            anchors.verticalCenter: parent.verticalCenter
        }
        Text{
            id:pix
            text:" "+pWidth+" x "+pHeight+"畫素"
            anchors.verticalCenter: parent.verticalCenter
        }
    }
    Row{
        anchors.right: parent.right
        anchors.rightMargin: 10
        anchors.verticalCenter: parent.verticalCenter
        spacing: 5
        Text{
            id:pre //百分比
            color: "black"
            anchors.verticalCenter: parent.verticalCenter
            text:{
                if(pslider.value==0)
                    "100%"
                else
                    (1+pslider.value).toFixed(2)*100+"%"
            }
        }
        Image{
            width: 20
            height: 20
            source: "./res/decrease.png"
            MouseArea{
                anchors.fill: parent
                onClicked: {
                    if( pslider.value-0.25<-1)
                        pslider.value=-1;
                    else
                        pslider.value=pslider.value-0.25
                }
            }
        }
        Slider {
            id:pslider
            minimumValue: -1
            maximumValue: 1
            value:0
            style: SliderStyle {
                groove: Rectangle {
                    implicitWidth: 150
                    implicitHeight: 5
                    color: "#F0F0F0"
                    border.color: "lightgray"
                    border.width: 1
                }
                handle: Rectangle {
                    anchors.centerIn: parent
                    color: control.pressed ? "blue" : "lightgray"
                    implicitWidth: 12
                    implicitHeight: 20
                }
            }
            onValueChanged: {
                var img=canvas.ctx.getImageData(0, 0, canvas.width, canvas.height)
                  canvas.ctx.putImageData(img, 0, 0)
            }
        }
        Image{
            width: 20
            height: 20
            source: "./res/increase.png"
            MouseArea{
                anchors.fill: parent
                onClicked: {
                    if( pslider.value+0.25>1)
                        pslider.value=1;
                    else
                       pslider.value= pslider.value+0.25
                }
            }
        }
    }
}
最終的設計效果如下: