纯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:rootheight: 120width: appWindow.widthproperty color paintColor //绘制颜色property string picPath:""//图片路径TabView{width: appWindow.widthid:tabViewTab{title: "文件"Rectangle {id: seewidth: appWindow.widthanchors {horizontalCenter: parent.horizontalCentertop: parent.toptopMargin: 8}color: "transparent"Row{spacing: 5Column{Row{spacing: 5PicButton{width: 50height: 64isImageVisible: trueimagePath: "res/new.png"rectColor: "transparent"describle: "新建"onClicked: {messageDialog.text="是否保存?"messageDialog.standardButtons=StandardButton.Yes | StandardButton.NomessageDialog.icon=StandardIcon.QuestionmessageDialog.open()}}PicButton{width: 50height: 64isImageVisible: trueimagePath: "res/open.png"rectColor: "transparent"describle: "打开"onClicked: {fileDialog.open()fileDialog.selectExisting=true}}PicButton{width: 50height: 64isImageVisible: trueimagePath: "res/save.png"rectColor: "transparent"describle: "保存"onClicked:{fileDialog.selectExisting=falsefileDialog.open()}}PicButton{width: 50height: 64isImageVisible: trueimagePath: "res/saveOther.png"describle: "另存"onClicked:{fileDialog.selectExisting=falsefileDialog.open()}}}Rectangle{width: parent.widthheight: 25color:"#F5F6F7"anchors.horizontalCenter: parent.horizontalCenterText{text:"文件操作"color:"#929292"anchors.centerIn: parent}}}//分隔符Rectangle{height: 64;width: 1;color:"#E2E3E4";}Column{Row{spacing: 5PicButton{width: 50height: 64isImageVisible: trueimagePath: "res/print.png"rectColor: "transparent"describle: "打印"onClicked: appWindow.showFullScreen()}PicButton{width: 50height: 64isImageVisible: trueimagePath: "res/exit.png"rectColor: "transparent"describle: "退出"onClicked: Qt.quit()}}Rectangle{width: parent.widthheight: 25color:"#F5F6F7"anchors.horizontalCenter: parent.horizontalCenterText{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.InformationmessageDialog.standardButtons=StandardButton.OkmessageDialog.open()}var filepath = new String(fileUrl);if(Qt.platform.os == "windows")root.picPath= filepath.slice(8);elseroot.picPath = filepath.slice(7);}}//弹窗提示MessageDialog {id: messageDialogonAccepted: {console.log("do samething")}onYes: {fileDialog.selectExisting=false;fileDialog.open();}onNo: canvas.newImage()}}}Tab{title: "设置"Rectangle {id: colorToolswidth: appWindow.widthanchors {horizontalCenter: parent.horizontalCentertop: parent.toptopMargin: 8}color: "transparent"Row{spacing: 5anchors.left: parent.leftanchors.leftMargin: 15Column{Row{spacing: 5//设置线宽PicButton{id:selectWeightwidth:50height: grid.heightimagePath: "res/weight.png"describle: "粗细"isImageVisible:trueonClicked: {listWeight.visible=listWeight.visible==true?false:true;listWeight.z=3}}//分隔符Rectangle{height: grid.height;width: 1;color:"#E2E3E4";}//选中的颜色PicButton{id:selectRectwidth:50height: grid.heightdescrible: "颜色"}//分隔符Rectangle{height: grid.height;width: 1;color:"#E2E3E4";}//色彩网格Grid{id:gridcolumns: 7rows:2spacing: 2Repeater {model: ["#33B5E5", "#99CC00", "#FFBB33", "#FF4444","#DD6644","#AA4444","#FFBB33", "#FF4444","#DD6644","#AA4444","#FFBB33", "#FF4444","#DD6644","#AA4444"]ColorGrid {id: redcolor: modelDataonClicked:{selectRect.rectColor=colorroot.paintColor = color}}}}//分隔符Rectangle{height: grid.height;width: 1;color:"#E2E3E4";}//颜色对话框PicButton{id:editRectwidth:50height: grid.heightisImageVisible: trueimagePath: "res/pic.png"rectColor: "transparent"describle: "编辑"onClicked: colorD.open()ColorDialog{id:colorDonColorChanged: {selectRect.rectColor=color;root.paintColor=color;}}}//分隔符Rectangle{height: grid.height;width: 1;color:"#E2E3E4";}}Rectangle{width: grid.widthheight: 30color:"#F5F6F7"anchors.horizontalCenter: parent.horizontalCenterText{text:"颜色"color:"#929292"anchors.centerIn: parent}}}//形状设置Column{Grid{id:xzwidth: 200height: 60Repeater{model:[]}}Rectangle{width: xz.widthheight: 30color:"#F5F6F7"anchors.horizontalCenter: parent.horizontalCenterText{text:"形状"color:"#929292"anchors.centerIn: parent}}}}}}Tab{title: "查看"Rectangle {id: showwidth: appWindow.widthanchors {horizontalCenter: parent.horizontalCentertop: parent.toptopMargin: 8}color: "transparent"Row{spacing: 5Column{Row{spacing: 5PicButton{width: 50height: 64isImageVisible: trueimagePath: "res/increase.png"rectColor: "transparent"describle: "放大"onClicked: {if( statusbar.sliderValue+0.25>1)statusbar.sliderValue=1;elsestatusbar.sliderValue= statusbar.sliderValue+0.25}}PicButton{width: 50height: 64isImageVisible: trueimagePath: "res/decrease.png"rectColor: "transparent"describle: "缩小"onClicked: {if( statusbar.sliderValue-0.25<-1)statusbar.sliderValue=-1;elsestatusbar.sliderValue= statusbar.sliderValue-0.25}}PicButton{width: 50height: 64isImageVisible: trueimagePath: "res/big.png"describle: "100%"onClicked: statusbar.sliderValue=0;}}Rectangle{width: parent.widthheight: 25color:"#F5F6F7"anchors.horizontalCenter: parent.horizontalCenterText{text:"缩放"color:"#929292"anchors.centerIn: parent}}}//分隔符Rectangle{height: 64;width: 1;color:"#E2E3E4";}Column{Row{spacing: 5PicButton{width: 50height: 64isImageVisible: trueimagePath: "res/fullScreen.png"rectColor: "transparent"describle: "全屏"onClicked: appWindow.showFullScreen()}PicButton{width: 50height: 64isImageVisible: trueimagePath: "res/exitFull.png"rectColor: "transparent"describle: "退出"onClicked: appWindow.showNormal()}}Rectangle{width: parent.widthheight: 25color:"#F5F6F7"anchors.horizontalCenter: parent.horizontalCenterText{text:"显示"color:"#929292"anchors.centerIn: parent}}}}}}style:TabViewStyle{frameOverlap: 1tab: Rectangle {color: styleData.selected ? "#F5F6F7" :"#FFFFFF"implicitWidth: Math.max(text.width + 4, 100)implicitHeight: 30radius: 1Text {id: textanchors.centerIn: parenttext: styleData.titlecolor: "black"font.pixelSize: 12}}frame: Rectangle {width: appWindow.widthcolor: "#F5F6F7"}}}
}2.工具栏,采用ToolBar设计。代码如下:ToolBar{id:toolBaranchors.top: menuBar.bottomRow{anchors.verticalCenter: parent.verticalCenterspacing: 5ToolButton{width:25height: 25tooltip: "保存"iconSource: "./res/save.png"}ToolButton{width:25height: 25tooltip: "撤销"iconSource: "./res/revoke.png"}ToolButton{width:25height: 25tooltip: "恢复"iconSource: "./res/undo.png"}Rectangle{width:25height: 25color: menuBar.paintColorborder.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:rootwidth:500*statusbar.rateheight: 300*statusbar.rateproperty alias mouseX: area.mouseXproperty alias mouseY: area.mouseYproperty string imgPath: menuBar.picPathsignal 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:imgPsource:{if(imgPath!="")"file:///"+imgPath;else"";}visible: falsewidth:canvas.widthheight: canvas.height}//Timer{interval: 100running: truetriggeredOnStart: truerepeat: trueonTriggered: canvas.requestPaint()}
// Image{
// id:picImg
// anchors.fill: parent
// }//画板Canvas {id: canvasanchors.fill: parentantialiasing: trueproperty real lastX //画笔的终止位置property real lastY//opacity: 0onImageLoaded: {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 = lineWidthctx.strokeStyle = paintColorctx.beginPath()ctx.moveTo(lastX, lastY)lastX = area.mouseXlastY = area.mouseYctx.lineTo(lastX, lastY)ctx.stroke()}MouseArea {id: areaanchors.fill: parentacceptedButtons: Qt.AllButtonsonPressed: {canvas.lastX = mouseXcanvas.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: contentMenuMenuItem {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: 5height: 5x:parent.widthy:parent.height/2border.color: "black"border.width: 1MouseArea{id:xRateanchors.fill: parentcursorShape: (containsMouse? (pressed? Qt.SizeHorCursor: Qt.ArrowCursor): Qt.ArrowCursor);drag.target: parentonPositionChanged: {root.width=parent.x}}}//下侧Rectangle{width: 5height: 5border.color: "black"border.width: 1x:parent.width/2y:parent.heightDrag.active: yRate.drag.activeDrag.hotSpot.x: 10Drag.hotSpot.y: 10MouseArea{id:yRateanchors.fill: parentcursorShape: (containsMouse? (pressed? Qt.SizeVerCursor: Qt.ArrowCursor): Qt.ArrowCursor);drag.target: parentonPositionChanged: {root.height=parent.y}}}//对角Rectangle{width: 5height: 5x:parent.widthy:parent.heightborder.color: "black"border.width: 1Drag.active: xyRate.drag.activeDrag.hotSpot.x: 10Drag.hotSpot.y: 10MouseArea{id:xyRateanchors.fill: parentcursorShape: (containsMouse? (pressed? Qt.SizeFDiagCursor: Qt.ArrowCursor): Qt.ArrowCursor);drag.target: parentonPositionChanged: {root.width=parent.xroot.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.leftanchors.leftMargin: 10anchors.verticalCenter: parent.verticalCenterImage{width: 20height: 20source: "./res/pic.png"anchors.verticalCenter: parent.verticalCenter}Text{id:posanchors.verticalCenter: parent.verticalCentertext:" "+position}}Row{anchors.verticalCenter: parent.verticalCenteranchors.horizontalCenter: parent.horizontalCenterImage{width: 20height: 20source: "./res/pic.png"anchors.verticalCenter: parent.verticalCenter}Text{id:pixtext:" "+pWidth+" x "+pHeight+"像素"anchors.verticalCenter: parent.verticalCenter}}Row{anchors.right: parent.rightanchors.rightMargin: 10anchors.verticalCenter: parent.verticalCenterspacing: 5Text{id:pre //百分比color: "black"anchors.verticalCenter: parent.verticalCentertext:{if(pslider.value==0)"100%"else(1+pslider.value).toFixed(2)*100+"%"}}Image{width: 20height: 20source: "./res/decrease.png"MouseArea{anchors.fill: parentonClicked: {if( pslider.value-0.25<-1)pslider.value=-1;elsepslider.value=pslider.value-0.25}}}Slider {id:psliderminimumValue: -1maximumValue: 1value:0style: SliderStyle {groove: Rectangle {implicitWidth: 150implicitHeight: 5color: "#F0F0F0"border.color: "lightgray"border.width: 1}handle: Rectangle {anchors.centerIn: parentcolor: control.pressed ? "blue" : "lightgray"implicitWidth: 12implicitHeight: 20}}onValueChanged: {var img=canvas.ctx.getImageData(0, 0, canvas.width, canvas.height)canvas.ctx.putImageData(img, 0, 0)}}Image{width: 20height: 20source: "./res/increase.png"MouseArea{anchors.fill: parentonClicked: {if( pslider.value+0.25>1)pslider.value=1;elsepslider.value= pslider.value+0.25}}}}
}
最终的设计效果如下:


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
