天天看點

QML繪圖系統

文章目錄

  • ​​一、繪圖系統四要素​​
  • ​​1.1、基本繪圖模式​​
  • ​​二、繪制路徑​​
  • ​​三、繪制文本​​
  • ​​四、繪制圖檔​​
  • ​​五、變換​​
  • ​​六、裁切​​
  • ​​七、圖像合成​​

一、繪圖系統四要素

  • 畫布(Canvas,類似于Widget的QPaintDevice)
  • 畫師(Context2d,類似于Widget的QPainter)
  • 畫筆(strokeStyle屬性,類似于Widget的QPen)
  • 畫刷(fillStyle屬性,類似于Widget的QBrush)

1.1、基本繪圖模式

Canvas是Item的派生類,通過設定width和height屬性,就可以定一個繪圖區域,然後在onPaint()信号處理器内使用Context2D對象來繪圖。

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true

    //=========================================
    Canvas {
        width: 400;
        height: 200;

        onPaint: {
            var ctx = getContext("2d");    //擷取畫師,每個畫布都有一個獨一無二的畫師
            ctx.lineWidth = 2;    //設定畫布寬度
            ctx.strokeStyle = "red";    //設定畫布顔色
            ctx.fillStyle = "blue";    //設定畫刷顔色
            ctx.beginPath();    //開始繪圖信号
            ctx.rect(100,80,120,80);    //繪制一個矩形
            ctx.fill();    //使用畫刷顔色填充
            ctx.stroke();    //使用畫筆顔色勾勒邊框
        }
    }
    //=========================================
    
}      
QML繪圖系統
  • paint是Canvas的信号,當需要繪圖(更新)時會觸發,開發者通過實作名為onPaint的信号處理器來響應paint信号,在信号處理器内進行繪圖;
  • Context2D是QML中負責2D繪圖的對象,與Canvas結合使用,有兩種使用Context2D對象的方式:
  • 1、在onPaint信号處理器中調用getContext(“2d”)擷取Context2D對象;
  • 2、設定Canvas對象的contextType屬性(2D繪圖時取值為“2d”)後,context屬性就會儲存一個可用的Context2D對象;故上例也可以寫為如下方式:
{
        width: 400;
        height: 200;
        contextType: "2d";

        onPaint: {
            context.lineWidth = 2;    //設定畫布寬度
            context.strokeStyle = "red";    //設定畫布顔色
            context.fillStyle = "blue";    //設定畫刷顔色
            context.beginPath();    //開始繪圖信号
            context.rect(100,80,120,80);    //繪制一個矩形
            context.fill();    //使用畫刷顔色填充
            context.stroke();    //使用畫筆顔色勾勒邊框
        }
    }      
  • fillStyle和Qt C++中的QBrush類似,用于儲存填充圖元的畫刷,它可以是一個顔色值,也可以是CanvasGradient或CanvasPattern對象
  • 當使用顔色時,支援下列文法:
  • rgb:例如 rgb(255,100,55);
  • rgba:例如(255,100,50,1.0);
  • #RRGGBB:例如#00FFCC;
  • Qt.rgba(red,green,blue,alpha),例如 Qt.rgba(0.3,0.7,1,1.0),類似的還有Qt.hsla()、Qt.lighter()等;
  • Context2D的createLinearGradient()方法用于建立一個線性漸變對象,createRadialGradient()方法用于建立一個放射漸變對象(類型為CanvasGradient),CanvasGradient對象的addColorStop()可以添加漸變路徑上的關鍵點的顔色,例如:
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true

    Canvas {
        width: 400;
        height: 200;

        onPaint: {
            var ctx = getContext("2d");
            ctx.lineWidth = 2;
            var gradient = ctx.createLinearGradient(60,50,180,130);
            gradient.addColorStop(0.0,Qt.rgba(1,0,0,1.0));
            gradient.addColorStop(1.0,Qt.rgba(0,0,1,1.0));
            ctx.strokeStyle = "red";
            ctx.fillStyle = gradient;
            ctx.beginPath();
            ctx.rect(60,50,120,80);
            ctx.fill();
            ctx.stroke();

            gradient = ctx.createRadialGradient(230,160,30,260,200,20);
            gradient.addColorStop(0.0,Qt.rgba(1,0,0,1.0));
            gradient.addColorStop(1.0,Qt.rgba(0,0,1,1.0));
            ctx.fillStyle = gradient;
            ctx.beginPath();
            ctx.rect(200,140,80,80);
            ctx.fill();
            ctx.stroke();
        }
    }

}      
QML繪圖系統

二、繪制路徑

使用Context2D繪制路徑的一般步驟:

  • 調用beginPath();
  • 調用moveTo()【移動到】、lineTo()【連線到】、arcTo()【畫弧到】、rect()【矩形】、quadraticCurveTo()【二次方貝塞爾曲線】、arc()【弧線】、bezierCurveTo()【三次方貝塞爾曲線】、ellipse()【橢圓】、text()【文字】等構造路徑元素的方法;
  • 調用fill()或stroke();
  • 調用closePath()方法用于結束目前的路徑,從路徑終點到起點繪制一條直線來封閉路徑(可選非必須);
import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    width: 640
    height: 480
    visible: true

    Canvas {
        width: 400;
        height: 300;
        id: root;

        onPaint: {
            var ctx = getContext("2d");
            ctx.lineWidth = 2;
            ctx.strokeStyle = "red";
            ctx.font = "42px sans-serif";
            var gradient = ctx.createLinearGradient(0,0,width,height);
            gradient.addColorStop(0.0,Qt.rgba(0,1,0,1));
            gradient.addColorStop(1.0,Qt.rgba(0,0,1,1));
            ctx.fillStyle = gradient;

            ctx.beginPath();
            ctx.moveTo(4,4);
            ctx.bezierCurveTo(0,height-1,width-1,height/2,width/4,height/4);
            ctx.lineTo(width/2,height/4);
            ctx.arc(width*5/8,height/4,width/8,Math.PI,0,false);
            ctx.ellipse(width*11/16,height/4,width/8,height/4);
            ctx.lineTo(width/2,height*7/8);
            ctx.text("Complex Path",width/4,height*7/8);
            ctx.fill();
            ctx.stroke();
        }
    }

}      
QML繪圖系統

三、繪制文本

Context2D對象與文本相關的方法有三個:fillText()、strokeText()、text()

  • fillText()方法使用fillStyle填充文字;
  • strokeText()使用strokeStyle描畫文字邊框;
  • text()方法在路徑上添加一串文本作為構成路徑的元素之一
import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    width: 640
    height: 480
    visible: true

    Canvas {
        width: 400;
        height: 300;
        id: root;

        onPaint: {
            var ctx = getContext("2d");
            ctx.lineWidth = 2;
            ctx.strokeStyle = "red";
            ctx.font = "42px sans-serif";

            var gradient = ctx.createLinearGradient(0,0,width,height);
            gradient.addColorStop(0.0,Qt.rgba(0,1,0,1));
            gradient.addColorStop(1.0,Qt.rgba(0,0,1,1));
            ctx.fillStyle = gradient;

            ctx.beginPath();
            ctx.text("Fill Text On Path",10,50);
            ctx.fill();

            ctx.fillText("Fill Text",10,100);

            ctx.beginPath();
            ctx.text("Stroke Text On Path",10,150);
            ctx.stroke();

            ctx.strokeText("Stroke Text",10,200);

            ctx.beginPath();
            ctx.text("Stroke and Fill Text on Path",10,250);
            ctx.stroke();
            ctx.fill();
        }
    }

}      
QML繪圖系統
  • 描述font屬性的文法與CSS font屬性相同,font屬性的預設值為“ 10px sans-serif ”;
  • Context2D的font屬性,允許我們按順序設定字型的下列屬性:
  • font-style(可選):可以取normal、italic、oblique三值之一;
  • font-variant(可選):可以取normal、small-caps二值之一;
  • font-weight(可選):可以取normal、bold二值之一,或0-99的數字;
  • font-size:取Npx或Npt,其中N為數字,px代表像素,pt代表點,對于移動裝置,使用pt為機關更合适一些,能夠适應各種螢幕尺寸;
  • font-family:常見的有serif、sans-serif、cursive、fantasy、monospace,詳情參考​​http://www.w3.org/TR/CSS2/fonts.html#propdef-font-family​​

四、繪制圖檔

1、drawImage(variant image, real dx, read dy)

它在(dx,dy)位置繪制指定的image對象代表的圖檔,image可以是一個Image元素、一個圖檔URL,或者一個CanvasImageData對象
import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    width: 640
    height: 480
    visible: true

    Canvas {
        width: 400;
        height: 300;
        id: root;
        property var dartlikeWeapon: "dartlike_weapon.png"

        onPaint: {
            var ctx = getContext("2d");
            ctx.drawImage(dartlikeWeapon,0,0);
        }

        Component.onCompleted: {
            loadImage(dartlikeWeapon);
            console.log("圖檔加載:",isImageLoaded(dartlikeWeapon));
        }

        onImageLoaded: {
            requestPaint();
        }
    }

}      
注意:需要確定dartlike_weapon.png和QML檔案在同一目錄下,因為是通過相對路徑引用的;
  • Canvas對象内定義了一個dartlikeWeapon屬性來儲存圖檔URL;
  • 然後在Component.onCompleted附加信号處理器内調用Cancas的loadImage()方法來加載圖檔;該方法會異步加載圖檔,當圖檔加載完成時,發射imageLoaded信号;
  • 然後在對應的信号處理器onImageLoaded内調用了requestPaint()方法來重繪Canvas;
  • 可以通過Canvas的isImageError()、isImageLoaded()兩個方法來判斷圖檔是否加載成功,它們接受和loadImage()同樣的參數,傳回布爾值;
  • 隻有成功加載的圖檔,才可以使用Context2D來繪制:
  • 在Canvas内定義的darklikeWeapon,可以了解為圖檔索引,加載圖檔、判斷圖檔是否成功加載、繪制圖檔,都适用這個索引;

2、一個Canvas可以加載多張圖檔,既可以加載本地圖檔,也可以加載網絡圖檔

import QtQuick 2.2

Canvas {
  width: 400;
  height: 300;
  id: root;
  property var dartlikeWeapon: "dartlike_weapon.png";
  property var poster: "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png"

  onPaint: {
    var ctx = getContext("2d");
    ctx.drawImage(poster,120,0);
    ctx.drawImage(dartlikeWeapon,0,0);
  }

  Component.onCompleted: {
    loadImage(dartlikeWeapon);
    loadImage(poster)
  }

  onImageLoaded: requestPaint();
}      

3、繪制一個Image元素

import QtQuick 2.2

Canvas {
    width: 400;
    height: 300;
    id: root;

    Image {
        id: poter;
        source: "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png";
        visible: false;

        onStatusChanged: {
            if(status == Image.Ready) {
                root.requestPaint();
            }
        }
    }

    onPaint: {
        var ctx = getContext("2d");
        ctx.drawImage(poster,50,0);
    }

    Component.onCompleted: {
        loadImage(poster)
    }

    onImageLoaded: requestPaint();
}      

五、變換

就像QPainter一樣,Context2D也支援平移(translate())、旋轉(rotate())、縮放(scale())、錯切(shear())等簡單的圖像變換,它還支援簡單的矩陣變換(setTransform()):

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    width: 400
    height: 400
    visible: true

    Canvas {
        width: 300;
        height: 300;
        id: root;
        contextType: "2d"

        onPaint: {
            context.lineWidth = 2;
            context.strokeStyle = "blue";
            context.fillStyle = "red";
            context.save();
            context.translate(width/2,height/2);
            context.beginPath();
            context.arc(0,0,30,0,Math.PI*2);
            context.arc(0,0,50,0,Math.PI*2);
            context.arc(0,0,70,0,Math.PI*2);
            context.arc(0,0,90,0,Math.PI*2);
            context.stroke();
            context.restore();

            context.save();
            context.translate(width/2,30);
            context.font = "26px serif";
            context.textAlign = "center";
            context.fillText("concentric circles",0,0);
            context.restore();
        }
    }


}      
QML繪圖系統
  • 繪制同心圓時,我們以(0,0)做圓心,按道理應該是在畫布左上角繪制,但是結果确實在畫布中心繪制,是因為使用​

    ​context.translate(width/2,height/2);​

    ​将坐标系平移到了畫布中心,那麼畫布中心就是(0,0)點了;
  • 平移變換、繪圖操作完成後,應當調用​

    ​restore()​

    ​​來恢複之前的畫布狀态,否則發生重繪時(比如使用者拖動視窗改變大小),你就看不見繪圖的圖元了,而要​

    ​restore()​

    ​​必先​

    ​save()​

    ​;
  • ​translate(real x, real y)​

    ​方法,平移畫布原點,x、y兩個參數是相對于目前原點的偏移量;
  • 示例中為了使“concentric circles”在畫布上方居中顯示,先恢複畫布原點到左上角,然後sava(),接着再變換到(width/2,30),接着設定了textAlign屬性為“center”,最後調用fillText()在原點處繪制文本,畫完後再次調用restore();

當然,也可以不多次平移,後面的針對平移後的畫布原點計算坐标即可,例如上例:

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    width: 400
    height: 400
    visible: true

    Canvas {
        width: 300;
        height: 300;
        id: root;
        contextType: "2d"

        onPaint: {
            context.lineWidth = 2;
            context.strokeStyle = "blue";
            context.fillStyle = "red";
            //context.save();
            context.translate(width/2,height/2);
            context.beginPath();
            context.arc(0,0,30,0,Math.PI*2);
            context.arc(0,0,50,0,Math.PI*2);
            context.arc(0,0,70,0,Math.PI*2);
            context.arc(0,0,90,0,Math.PI*2);
            context.stroke();
            //context.restore();

            //context.save();
            //context.translate(width/2,30);
            context.font = "26px serif";
            context.textAlign = "center";
            context.fillText("concentric circles",0,-height/2+30);
            //context.restore();
        }
    }

}      

六、裁切

Context2D的​

​clip()​

​方法,讓我們能夠根據目前路徑包圍的區域來裁切後續的繪圖操作,在此區域之外的圖像會被毫不留情的丢棄掉。

步驟如下:

  • 調用beginPath();
  • 使用lineTo()、arc()、bezierCurveTo()、moveTo()、closePath()等建立路徑;
  • 調用clip()确定裁切區域;
  • 繪圖,如使用drawImage();
import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400
    height: 400
    visible: true

    Canvas {
        width: 400;
        height: 400;
        contextType: "2d";
        property var comicRole: "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png";

        onPaint: {
            context.lineWidth = 2;
            context.strokeStyle = "blue";
            context.fillStyle = Qt.rgba(0.3,0.5,0.7,0.3);

            context.save();
            context.beginPath();
            context.arc(180,150,80,0,Math.PI*2,true);
            context.moveTo(180,230);
            context.lineTo(420,280);
            context.lineTo(160,320);
            context.closePath();
            context.clip();
            context.drawImage(comicRole,0,0,600,600,0,0,400,400);
            context.stroke();
            context.fill();

            context.rotate(Math.PI / 5);
            context.font = " italic bold 32px serif";
            context.fillStyle = "red";
            context.fillText("the text will be clipped",110,70);
            context.restore();
        }

        Component.onCompleted: loadImage(comicRole);
        onImageLoaded: requestPaint();
    }

}      

七、圖像合成

Context2D允許我們繪制一個圖元,将其與已有的圖像按照​

​globalCompositeOperation​

​​屬性指定的模式合成,​

​globalCompositeOperation​

​支援下列模式:

模式 詳細說明
source-over 預設模式,新圖形覆寫在原有内容之上
source-in 新圖形中僅僅出現與原有内容重疊的部分,其它區域變成透明的
source-out 隻有新圖形中與原有内容不重疊的部分會被繪制出來
source-atop 新圖形中與原有内容重疊的部分會被繪制,并覆寫于原有内容之上
destination-over 在原有内容至下繪制新圖形
destination-in 原有内容中與新圖形重疊的部分會被保留,其它區域變成透明的
destination-out 原有内容中與新圖形不重疊的部分會被保留
destination-atop 原有内容中與新内容重疊的部分會被保留,并且在原有内容至下繪制新圖形
lighter 兩圖形中的重疊部分做加色處理
copy 隻有新圖形會被保留,其它都被清除掉