純QML實現畫圖工具
阿新 • • 發佈:2019-02-04
前言,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 } } } } }
最終的設計效果如下: