Qt 2D绘图之二:抗锯齿渲染和坐标系统
一、抗鋸齒渲染
1.1 邏輯繪圖
圖形基元的大小(寬度和高度)始終與其數學模型相對應,下圖示意了忽略其渲染時使用的畫筆的寬度的樣子。
1.2 物理繪圖(默認情況)
在默認的情況下,繪制會產生鋸齒,并且使用這樣的規(guī)則進行繪制: 當使用寬度為一個像素的畫筆進行渲染時,像素會在數學定義的點的右邊和下邊進行渲染,如下圖1所示。當使用一個擁有偶數像素的畫筆進行渲染時,像素會在數學定義的點的周圍對稱渲染;而當使用一個擁有奇數像素的面筆進行渲染時,首先按照偶數對稱繪制,最后一個像素會被渲染到數學定義的點的右邊和下邊,如下圖2所示。
所以看起來圖像不是很平滑,像是有鋸齒,所以為了消鋸齒,就要用到抗鋸齒繪圖。
1.3 抗鋸齒繪圖
抗鋸齒( Anti-aliased)又稱為反鋸齒或者反走樣,就是對圖像的邊緣進行平滑處理,使其看起來更加柔和流暢的一種技術。QPaint er 進行繪制時可以使用QPainter ::RenderHint 渲染提示來指定是否要使用抗鋸齒功能,RenderHint 取值分為以下三種。
如果在繪制時使用了抗鋸齒渲染提示,即使用 QPainter:: setRenderHint(RenderHint hint, bool on = true) 函數,將參數 hint 設置為了 QPainter:: Antialiasing。那么像素就會在數學定義的點的兩側對稱的進行渲染,如下圖所示。
示例程序為:
QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); //抗鋸齒和使用平滑轉換算法二、坐標系統(tǒng)
2.1 坐標系統(tǒng)簡介
Qt的坐標系統(tǒng)是由QPainter類控制的,而QPainter是在繪圖設備上繪制的。一個繪圖設備的默認坐標系統(tǒng)中原點(0, 0)在其左上角,x坐標向右增長,y坐標向下增長。在基于像素的設備上,默認的單位是一個像素,而在打印機上默認的單位是一個點(1/72英寸)。
下面仍然在上一節(jié)的程序中進行代碼演示,更改paintEvent()的內容如下:
void Widget::paintEvent(QPaintEvent *) {QPainter painter(this);painter.setBrush(Qt::red);painter.drawRect(0, 0, 100, 100);painter.setBrush(Qt::yellow);painter.drawRect(-50, -50, 100, 100); }我們先在原點(0,0)繪制了一個長寬都是100像素的紅色矩形,又在(-50,-50)點繪制了一個同樣大小的黃色矩形。可以看到,我們只能看到黃色矩形的四分之一部分。運行程序,效果如下圖所示。
2.2 坐標系統(tǒng)變換
默認情況下,QPainter在指定設備的坐標系統(tǒng)上進行繪制,在進行繪圖時,使用QPainter::translate()函數平移坐標系統(tǒng);可以使用QPainter::scale()函數縮放坐標系統(tǒng);使用QPainter::rotate()函數順時針旋轉坐標系統(tǒng);還可以使用QPainter::shear()圍繞原點來扭曲坐標系統(tǒng)。如下圖所示。
我們可以使用前面提到的那些便捷函數進行坐標系統(tǒng)變換,也可以通過QTransform類實現。
(1)平移變換
將paintEvent()函數內容更改如下:
void Widget::paintEvent(QPaintEvent *) {//平移坐標系統(tǒng)QPainter painter(this);painter.setBrush(Qt::yellow);painter.drawRect(0, 0, 50, 50);//將坐標系原點向右、向下平移100像素點,即使原點坐標變?yōu)?#xff08;100,100)painter.translate(100, 100);painter.setBrush(Qt::red);painter.drawRect(0, 0, 50, 50);//將坐標系原點向左、向上平移100像素點,即重新使原點坐標變?yōu)?#xff08;0,0)painter.translate(-100, -100);painter.drawLine(0, 0, 20, 20); }這里先在原點(0, 0)繪制了一個寬、高均為50的正方形,然后使用translate()函數將坐標系統(tǒng)進行了平移,使(100, 100)點成為了新原點,所以我們再次進行繪制的時候,雖然drawRect()中的邏輯坐標還是(0, 0)點,但實際顯示出來的卻是在(100, 100)點的紅色正方形。可以再次使用translate()函數進行反向平移,使原點重新回到窗口左上角。運行程序,效果如下圖所示。
(2)縮放變換
將paintEvent()函數中的內容更改如下:
void Widget::paintEvent(QPaintEvent *) {//縮放坐標系統(tǒng)QPainter painter(this);painter.setBrush(Qt::yellow);painter.drawRect(0, 0, 100, 100);//將坐標系統(tǒng)的橫、縱坐標都放大兩倍painter.scale(2, 2);painter.setBrush(Qt::red);painter.drawRect(50, 50, 50, 50); }可以看到,當我們使用scale()函數將坐標系統(tǒng)的橫、縱坐標都放大兩倍以后,邏輯上的(50,50)點變成了窗口上的(100, 100)點,而邏輯上的長度50,繪制到窗口上的長度卻是100。運行程序,效果如下圖所示。
(3)旋轉變換
將paintEvent()函數更改如下:
void Widget::paintEvent(QPaintEvent *) {//旋轉坐標系統(tǒng)QPainter painter(this);painter.drawLine(0, 0, 100, 0);//以原點為中心,順時針旋轉30度painter.rotate(30);painter.drawLine(0, 0, 100, 0);painter.translate(100, 100);painter.rotate(30);painter.drawLine(0, 0, 100, 0); }這里先繪制了一條水平的直線,然后將坐標系統(tǒng)旋轉了30度,又繪制了一條直線。可以看到,默認是以原點(0, 0)為中心旋轉的。如果想改變旋轉中心,可以使用translate()函數,比如這里將中心移動到了(100, 100)點,然后旋轉了30度,又繪制了一條直線。運行程序,效果如下圖所示。
(4)扭曲變換
將paintEvent()函數更改如下:
void Widget::paintEvent(QPaintEvent *) {//扭曲坐標系統(tǒng)QPainter painter(this);painter.setBrush(Qt::yellow);painter.drawRect(0, 0, 50, 50);//縱向扭曲變形painter.shear(0, 1);painter.setBrush(Qt::red);painter.drawRect(50, 0, 50, 50); }shear()有兩個參數,第一個是對橫向進行扭曲,第二個是對縱向進行扭曲,而取值就是扭曲的程度。比如程序中對縱向扭曲值為1,那么就是紅色正方形左邊的邊下移一個單位,右邊的邊下移兩個單位,值為1就表明右邊的邊比左邊的邊多下移一個單位。大家可以更改取值,測試效果。運行程序,效果如下圖所示。
2.3 ”窗口-視口”轉換
在使用QPainter進行繪制時,會使用邏輯坐標進行繪制,然后再轉換為繪圖設備的物理坐標。邏輯坐標到物理坐標的映射由QPainter的worldTransform()函數和QPainter的viewport()以及window()函數進行處理。其中視口(viewport)表示物理坐標下指定的一個任意矩形,而窗口(window,與以前講的窗口部件的概念不同)表示邏輯坐標下的相同的矩形。默認的,邏輯坐標和物理坐標是重合的,它們都相當于繪圖設備上的矩形。
使用”窗口-視口”轉換可以使邏輯坐標系統(tǒng)適合應用的要求,這個機制也可以用來讓繪圖代碼獨立于繪圖設備。 下面來看一個例子。
創(chuàng)建一個Widget窗口應用(其默認寬400像素,高300像素,左上角為原點)。首先正常繪制一個正方形:
void Widget::paintEvent(QPaintEvent *) {QPainter p(this);p.setPen(Qt::blue);p.drawRect(0, 0, 100, 100); }這個是沒有問題的,因為現在的繪圖設備就是Widget,其左上角就是原點(0, 0)點。效果如下圖所示。
現在我們使用setWindow來設置邏輯坐標矩形:
void Widget::paintEvent(QPaintEvent *) {QPainter p(this);p.setPen(Qt::blue);p.drawRect(0, 0, 100, 100);p.setWindow(-50, -50, 100, 100);p.setPen(Qt::red);p.drawRect(0, 0, 100, 100); }這時,效果如下圖所示。
現在來說p.setWindow(-50, -50, 100, 100)的作用,它將邏輯坐標矩形(后面提到的術語window窗口)與我們現在的設備物理坐標矩形(后面提到的術語viewport視口)進行了線性映射,這里所說的設備物理坐標矩形就是我們可見的Widget的坐標,就是左上角為(0, 0)點,寬400,高300這樣的矩形,線性映射的示意圖如下:
也就是說,調用p.setWindow(-50, -50, 100, 100)之后,再次使用p進行繪制,那么坐標原點就不再是Widget的左上角了,而是到了其中心,以前繪制的寬100、高100的正方形,現在也會按比例變?yōu)閷?00, 高300,也就是我們看到的這個紅色矩形。
再來修改代碼:
void Widget::paintEvent(QPaintEvent *){QPainter p(this);p.setPen(Qt::blue);p.drawRect(0, 0, 100, 100);p.setWindow(-50, -50, 100, 100);p.setPen(Qt::red);p.drawRect(0, 0, 20, 20); }運行效果如下圖所示:
我們將繪制的紅色矩形變小,可以明顯看到,本應該是個正方形,現在卻變成了長方形。就是因為上面說的比例變換造成的,那么怎么才能讓它顯示應有的形狀呢,我們來設置視口:
void Widget::paintEvent(QPaintEvent *) {QPainter p(this);p.setPen(Qt::blue);p.drawRect(0, 0, 100, 100);int side = qMin(width(), height());p.setViewport((width() - side)/2, (height() - side) /2, side, side);p.setWindow(-50, -50, 100, 100);p.setPen(Qt::red);p.drawRect(0, 0, 20, 20); }現在使用setViewport設置視口為一個正方形,就是Widget可是區(qū)域上最大的正方形,這樣邏輯坐標和物理坐標進行比例變換的時候,紅色矩形的寬和高就不會因為縮放的比例不同而發(fā)生變形了,如下圖所示。
問題提出:那么為什么要修改這個邏輯坐標矩形?
這是為了便于我們繪圖,因為我們一般繪圖時只是想在標準的坐標系中應該繪制成什么樣子,不會考慮不同繪圖設備的具體坐標系(比如有的設備坐標原點在其左上角,有的在中心等等),也不會考慮窗口的大小不同而使用不同的代碼(比如我們只想在一個寬100、高100的繪圖區(qū)域的中心繪制一個高20、寬20的正方形,到底實際繪圖設備的單位是像素、還是英寸、還是厘米,我們不用考慮)。
引申術語:窗口、視口
這里說的我們想象中的寬100、高100、原點在中心的繪圖區(qū)域,就是邏輯坐標下的矩形,也就是使用setWindow設置的所謂的窗口;而實際的繪圖設備,比如這里的Widget部件,其可視化的區(qū)域上設置的一個矩形被稱為視口(英文為viewport),默認就是可視化區(qū)域的大小,但是可以通過setViewport來設置。窗口與視口相對應,可以進行線性變換,這樣,我們就可以通過先設置視口,再設置對應的窗口的方法,來確保我們的代碼在標準的想象中的坐標系中繪制的圖形,可以準確地顯示在不同的繪圖設備界面上。
參考:
67 2D繪圖(反走樣繪圖 / 抗鋸齒渲染)
Qt 2D繪圖部分窗口、視口的研究
轉載于:https://www.cnblogs.com/linuxAndMcu/p/11058857.html
總結
以上是生活随笔為你收集整理的Qt 2D绘图之二:抗锯齿渲染和坐标系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【翻译】QEMU内部机制:顶层概览
- 下一篇: d3.js 简易柱形图,入门demo