<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                #(28):坐標系統 在經歷過實際操作,以及前面一節中我們見到的那個`translate()`函數之后,我們可以詳細了解下 Qt 的坐標系統了。泛泛而談坐標系統,有時候會覺得枯燥無味,難以理解,好在現在我們已經有了基礎。 坐標系統是由`QPainter`控制的。我們前面說過,`QPaintDevice`、`QPaintEngine`和`QPainter`是 Qt 繪制系統的三個核心類。`QPainter`用于進行繪制的實際操作;`QPaintDevice`是那些能夠讓`QPainter`進行繪制的“東西”(準確的術語叫做,二維空間)的抽象層(其子類有`QWidget`、`QPixmap`、`QPicture`、`QImage`和`QPrinter`等);`QPaintEngine`提供供`QPainter`使用的用于在不同設備上繪制的統一的接口。 由于`QPaintDeice`是進行繪制的對象,因此,所謂坐標系統,也就是`QPaintDevice`上面的坐標。默認坐標系統位于設備的左上角,也就是坐標原點 (0, 0)。x 軸方向向右;y 軸方向向下。在基于像素的設備上(比如顯示器),坐標的默認單位是像素,在打印機上則是點(1/72 英寸)。 將`QPainter`的邏輯坐標與`QPaintDevice`的物理坐標進行映射的工作,是由`QPainter`的變換矩陣(transformation matrix)、視口(viewport)和窗口(window)完成的。如果你不理解這些術語,可以簡單了解下有關圖形學的內容。實際上,對圖形的操作,底層的數學都是進行的矩陣變換、相乘等運算。 在 Qt 的坐標系統中,每個像素占據 1×1 的空間。你可以把它想象成一張方格紙,每個小格都是1個像素。方格的焦點定義了坐標,也就是說,像素 (x, y) 的中心位置其實是在 (x + 0.5, y + 0.5) 的位置上。這個坐標系統實際上是一個“半像素坐標系”。我們可以通過下面的示意圖來理解這種坐標系: [![](https://box.kancloud.cn/2015-12-29_568232525183b.png)](http://files.devbean.net/images/2012/11/coordinate-system-demo.png) 我們使用一個像素的畫筆進行繪制,可以看到,每一個繪制像素都是以坐標點為中心的矩形。**注意,這是坐標的邏輯表示,實際繪制則與此不同。**因為在實際設備上,像素是最小單位,我們不能像上面一樣,在兩個像素之間進行繪制。所以在實際繪制時,Qt 的定義是,繪制點所在像素是邏輯定義點的右下方的像素。 我們前面已經介紹過,Qt 的繪制分為走樣和反走樣兩種。對此,我們必須分別對待。 一個像素的繪制最簡單,我們從這里開始: [![](https://box.kancloud.cn/2015-12-29_568232526a7a7.png)](http://files.devbean.net/images/2012/11/1px-painting.png) 從上圖可以看出,當我們繪制矩形左上角 (1, 2) 時,實際繪制的像素是在右下方。 當繪制大于1個像素時,情況比較復雜:如果繪制像素是偶數,則實際繪制會包裹住邏輯坐標值;如果是奇數,則是包裹住邏輯坐標值,再加上右下角一個像素的偏移。具體請看下面的圖示: [![](https://box.kancloud.cn/2015-12-29_5682325287e92.png)](http://files.devbean.net/images/2012/11/mutilpx-painting.png) 從上圖可以看出,如果實際繪制是偶數像素,則會將邏輯坐標值夾在相等的兩部分像素之間;如果是奇數,則會在右下方多出一個像素。 Qt 的這種處理,帶來的一個問題是,我們可能獲取不到真實的坐標值。由于歷史原因,`QRect::right()`和`QRect::bottom()`的返回值并不是矩形右下角點的真實坐標值:`QRect::right()`返回的是 left() + width() – 1;`QRect::bottom()`則返回 top() + height() – 1,上圖的綠色點指出了這兩個函數的返回點的坐標。 為避免這個問題,我們建議是使用`QRectF`。`QRectF`使用浮點值,而不是整數值,來描述坐標。這個類的兩個函數`QRectF::right()`和`QRectF::bottom()`是正確的。如果你不得不使用`QRect`,那么可以利用 x() + width() 和 y() + height() 來替代 right() 和 bottom() 函數。 對于反走樣,實際繪制會包裹住邏輯坐標值: [![](https://box.kancloud.cn/2015-12-29_56823252a0099.png)](http://files.devbean.net/images/2012/11/anti-aliasing-painting.png) 這里我們不去解釋為什么在反走樣是,像素顏色不是一致的,這是由于反走樣算法導致,已經超出本節的內容。 Qt 同樣提供了坐標變換。前面說,圖形學大部分算法依賴于矩陣計算,坐標變換便是其中的代表:每一種變換都對應著一個矩陣乘法(如果你想知道學的線性代數有什么用處,這就是應用之一了 ;-P)。我們會以一個實際的例子來了解坐標變換。在此之前,我們需要了解兩個函數:`QPainter::save()`和`QPainter::restore()`。 前面說過,`QPainter`是一個狀態機。那么,有時我想保存下當前的狀態:當我臨時繪制某些圖像時,就可能想這么做。當然,我們有最原始的辦法:將可能改變的狀態,比如畫筆顏色、粗細等,在臨時繪制結束之后再全部恢復。對此,`QPainter`提供了內置的函數:`save()`和`restore()`。`save()`就是保存下當前狀態;`restore()`則恢復上一次保存的結果。這兩個函數**必須**成對出現:`QPainter`使用棧來保存數據,每一次`save()`,將當前狀態壓入棧頂,`restore()`則彈出棧頂進行恢復。 在了解了這兩個函數之后,我們就可以進行示例代碼了: ~~~ void PaintDemo::paintEvent(QPaintEvent *) { QPainter painter(this); painter.fillRect(10, 10, 50, 100, Qt::red); painter.save(); painter.translate(100, 0); // 向右平移 100px painter.fillRect(10, 10, 50, 100, Qt::yellow); painter.restore(); painter.save(); painter.translate(300, 0); // 向右平移 300px painter.rotate(30); // 順時針旋轉 30 度 painter.fillRect(10, 10, 50, 100, Qt::green); painter.restore(); painter.save(); painter.translate(400, 0); // 向右平移 400px painter.scale(2, 3); // 橫坐標單位放大 2 倍,縱坐標放大 3 倍 painter.fillRect(10, 10, 50, 100, Qt::blue); painter.restore(); painter.save(); painter.translate(600, 0); // 向右平移 600px painter.shear(0, 1); // 橫向不變,縱向扭曲 1 倍 painter.fillRect(10, 10, 50, 100, Qt::cyan); painter.restore(); } ~~~ Qt 提供了四種坐標變換:平移 translate,旋轉 rotate,縮放 scale 和扭曲 shear。在這段代碼中,我們首先在 (10, 10) 點繪制一個紅色的 50×100 矩形。保存當前狀態,將坐標系平移到 (100, 0),繪制一個黃色的矩形。注意,`translate()`操作平移的是坐標系,不是矩形。因此,我們還是在 (10, 10) 點繪制一個 50×100 矩形,現在,它跑到了右側的位置。然后恢復先前狀態,也就是把坐標系重新設為默認坐標系(相當于進行`translate(-100, 0)`),再進行下面的操作。之后也是類似的。由于我們只是保存了默認坐標系的狀態,因此我們之后的`translate()`橫坐標值必須增加,否則就會覆蓋掉前面的圖形。所有這些操作都是針對坐標系的,因此在繪制時,我們提供的矩形的坐標參數都是不變的。 運行結果如下: [![](https://box.kancloud.cn/2015-12-29_56823252b9e4c.png)](http://files.devbean.net/images/2012/11/coordinate-transformations-demo.png) Qt 的坐標分為邏輯坐標和物理坐標。在我們繪制時,提供給`QPainter`的都是邏輯坐標。之前我們看到的坐標變換,也是針對邏輯坐標的。所謂物理坐標,就是繪制底層`QPaintDevice`的坐標。單單只有邏輯坐標,我們是不能在設備上進行繪制的。要想在設備上繪制,必須提供設備認識的物理坐標。Qt 使用 viewport-window 機制將我們提供的邏輯坐標轉換成繪制設備使用的物理坐標,方法是,在邏輯坐標和物理坐標之間提供一層“窗口”坐標。視口是由任意矩形指定的物理坐標;窗口則是該矩形的邏輯坐標表示。默認情況下,物理坐標和邏輯坐標是一致的,都等于設備矩形。 視口坐標(也就是物理坐標)和窗口坐標是一個簡單的線性變換。比如一個 400×400 的窗口,我們添加如下代碼: ~~~ void PaintDemo::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setWindow(0, 0, 200, 200); painter.fillRect(0, 0, 200, 200, Qt::red); } ~~~ 我們將窗口矩形設置為左上角坐標為 (0, 0),長和寬都是 200px。此時,坐標原點不變,還是左上角,但是,對于原來的 (400, 400) 點,新的窗口坐標是 (200, 200)。我們可以理解成,邏輯坐標被“重新分配”。這有點類似于`translate()`,但是,`translate()`函數只是簡單地將坐標原點重新設置,而`setWindow()`則是將整個坐標系進行了修改。這段代碼的運行結果是將整個窗口進行了填充。 試比較下面兩行代碼的區別(還是 400×400 的窗口): ~~~ painter.translate(200, 200); painter.setWindow(-160, -320, 320, 640); ~~~ 第一行代碼,我們將坐標原點設置到 (200, 200) 處,橫坐標范圍是 [-200, 200],縱坐標范圍是 [-200, 200]。第二行代碼,坐標原點也是在窗口正中心,但是,我們將物理寬 400px 映射成窗口寬 320px,物理高 400px 映射成窗口高 640px,此時,橫坐標范圍是 [-160, 160],縱坐標范圍是 [-320, 320]。這種變換是簡單的線性變換。假設原來有個點坐標是 (64, 60),那么新的窗口坐標下對應的坐標應該是 ((-160 + 64 * 320 / 400), (-320 + 60 * 640 / 400)) = (-108.8, -224)。 下面我們再來理解下視口的含義。還是以一段代碼為例: ~~~ void PaintDemo::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setViewport(0, 0, 200, 200); painter.fillRect(0, 0, 200, 200, Qt::red); } ~~~ 這段代碼和前面一樣,只是把`setWindow()`換成了`setViewport()`。前面我們說過,window 代表窗口坐標,viewport 代表物理坐標。也就是說,我們將物理坐標區域定義為左上角位于 (0, 0),長高都是 200px 的矩形。然后還是繪制和上面一樣的矩形。如果你認為運行結果是 1/4 窗口被填充,那就錯了。實際是只有 1/16 的窗口被填充。這是由于,我們修改了物理坐標,但是沒有修改相應的窗口坐標。默認的邏輯坐標范圍是左上角坐標為 (0, 0),長寬都是 400px 的矩形。當我們將物理坐標修改為左上角位于 (0, 0),長高都是 200px 的矩形時,窗口坐標范圍不變,也就是說,我們將物理寬 200px 映射成窗口寬 400px,物理高 200px 映射成窗口高 400px,所以,原始點 (200, 200) 的坐標變成了 ((0 + 200 * 200 / 400), (0 + 200 * 200 / 400)) = (100, 100)。 現在我們可以用一張圖示總結一下邏輯坐標、窗口坐標和物理坐標之間的關系: [![](https://box.kancloud.cn/2015-12-29_56823252d9548.png)](http://files.devbean.net/images/2012/11/coordinate-system-instruction.png) 我們傳給`QPainter`的是邏輯坐標(也稱為世界坐標),邏輯坐標可以通過變換矩陣轉換成窗口坐標,窗口坐標通過 window-viewport 轉換成物理坐標(也就是設備坐標)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看