跳轉到主要內容

Canvas Widgets

Canvas Widget和Canvas Widget Renderer是強大的多功能TouchGFX外掛程式,在使用相對較小的存儲空間的同時維持高性能,可提供平滑、抗鋸齒效果良好的幾何圖形繪製。 但是,渲染幾何圖形必然是成本非常高的操作,如不小心使用,可能容易對微控制器的資源造成壓力。

Canvas Widget Renderer(以下簡稱CWR)是一種通用圖形API,為像素提供優化繪製,自動消除最多餘的繪製。 TouchGFX使用CWR繪製複雜的幾何圖形。 通過Canvas Widget定義幾何圖形。 TouchGFX支持許多Canvas Widget,但是就像普通小部件一樣,您可以創建自己的自訂Canvas Widget來滿足您的需求。 Canvas Widget定義要通過CWR繪製的圖形的幾何形狀,而圖形中每個像素的實際顏色則由相關Painter類定義。 同樣地,TouchGFX自帶許多Painter,但是您可以創建自己的自訂Painter來滿足您的需求。

使用CanvasWidget

TouchGFX中其他小部件的大小是自動設置的。 例如,點陣圖小部件將自動獲取包含的點陣圖的寬度和高度。 因此,對點陣圖小工具使用setXY()就足以將點陣圖放到顯示器上。

Canvas Widget沒有默認大小,其值既可以自動確定,也可以一開始就設置。 不僅要注意位置,還要正確地確定小部件的大小,否則Canvas Widget的寬度和高度將為零,並且不會在顯示器上繪製任何內容。

因此,不使用setXY(),而是用setPosition()來放置Canvas Widget並確定其大小。 另請參見下文Custom Canvas Widget部分關於如何創建和使用Custom Canvas Widget的範例。

在設置了Canvas Widget的位置和大小後,可以在其內部繪製幾何圖形。 坐標系統將使 (0, 0) 位於小部件(不是顯示器)的左上角,X軸向右延伸且Y軸向下延伸。

TouchGFX Designer中也支援Canvas Widget,可簡化使用,可自動計算記憶體需求和自動分配記憶體。

TouchGFX Designer中提供的小工具,基於CanvasWidget:

通過TouchGFX Designer使用這些小部件時,可通過顯示小部件在執行時間的狀態,並簡化放置和大小調整。

存儲空間分配和使用

為了生成反鋸齒效果良好的複雜幾何圖形,需要額外的存儲空間。 為此,CWR必須具有專門分配的記憶體緩衝區,以便在渲染過程中使用。 CWR與TouchGFX的其餘部分一樣,沒有動態記憶體空間分配。

TouchGFX Designer中的記憶體空間分配

在向螢幕的畫布添加小部件時,會自動生成記憶體緩衝區。 緩衝區大小基於螢幕寬度,計算公式為 (寬度 × 3) × 5。 但是,這並非是所有情況下的理想緩衝區大小。 因此,可以重寫緩衝區大小,如下圖所示。

在螢幕屬性中重寫畫布緩衝區大小

使用者程式碼中的記憶體空間分配

記憶體緩衝區可以在target/main.cppsimulator/main.cpp中進行分配和設置,或者按螢幕設置和分配。

static const uint16_t CANVAS_BUFFER_SIZE = 3600;
static uint8_t canvasBuffer[CANVAS_BUFFER_SIZE]

靜態常量定義記憶體緩衝區的大小,實際記憶體緩衝區可在main.cppScreenView.hpp檔的開頭進行定義

然後,可以在main.cppmain()方法或ScreenView.cppsetupScreen()方法中添加設置緩衝區的下列程式碼。

CanvasWidgetRenderer::setupBuffer(canvasBuffer, CANVAS_BUFFER_SIZE);

需要的CWR存儲空間的量取決於要在應用中繪製的最大圖形大小。 但是,您可以保留比最複雜形狀所需記憶體空間更少的記憶體。 為了應對這種情況,CWR將圖形繪製分割成較小的影像緩衝區,在這種情況下,由於有時需要不止一次地渲染圖像,因此渲染時間稍長。 在模擬器模式下運行時,可以更細微地調查存儲空間消耗並進行微調。 只需向main.cpp中添加以下函數:

CanvasWidgetRenderer::setWriteMemoryUsageReport(true);

現在,無論繪製操作何時結束,CWR都將報告(在控制台上列印)所需存儲空間的量。 對於canvas_widget_example,可以是“CWR requires 3604 bytes”(第一個繪製操作),然後是“CWR requires 7932 bytes(4328 bytes missing)”(第二個繪製操作)。 儘管顯示CWR沒有足夠存儲空間(本例中為4328個位元組缺失),應用程式仍正常運行。 這是因為CWR檢測到可用於完成一次複雜繪製操作的存儲空間太少。 為此,它將繪製操作分割成兩項獨立的繪製操作,可以很好地繪製圖形,但需要更多時間渲染。

因此,設置正確的記憶體緩衝區大小是在存儲空間與性能(渲染時間)之間取得平衡。 好的起始值通常約為3000,但使用上述技巧通常可以找到更優的值。 如果圖形過於複雜且分配的記憶體緩衝區過小,有部分圖形將不進行繪製(一些垂直像素線會被跳過),並且有可能不繪製任何內容。 在任何情況下,渲染時間都會增加許多。

這意味著如果您希望以最快速度渲染CWR繪圖,您需要請求分配全部的記憶體空間。 但是,如果能夠接受更長的渲染時間,完全可以縮小記憶體緩衝區。

CWR坐標系統

TouchGFX中的坐標系統通常用於定址像素,以便在顯示器上定位點陣圖。 點陣圖、文字和其他圖形元素都位於坐標系統中,其中 (0,0) 是左上角像素,X軸向右延伸,Y軸向下延伸。 在CWR中,能夠使用整數定址像素是不夠的,儘管在某些特殊情況下可能足夠,但在一般情況下遠遠不夠。 為了演示這一點,假設有一個線寬為1的圓,必須被精確地嵌入一個5x5像素的方塊中。 此圓的中心必須位於 (2.5, 2.5),半徑必須是2,因此中心座標需為小數。 類似地,如果圓應嵌入一個6x6像素的方塊,則中心必須位於 (3, 3),半徑必須是2.5,因此半徑需為小數。

這種新的圖形繪製座標定址方式意味著 (0,0) 處圖元的中心的CWR座標為 (0.5, 0.5)。 因此,包含螢幕左上角像素的方塊的輪廓如下:(0,0) -> (1,0) -> (1,1) -> (0,1) -> (0,0).

(0,0) 處像素的CWR坐標系

儘管起初看起來令人困惑,但很快會發現這是十分自然的。 點陣圖的坐標系統定址像素,Canvas Widget的同一坐標系統定址像素之前和之上的間隙。

因為圓形通常需要移動半個像素才能正確放置圓心,所以Circle::setPixelCenter()函數會將圓心放置在給定像素的中心,也就是說,從指定的座標再向右和向下移動半個像素.

Custom Canvas Widgets

實現Custom Canvas Widget需要用下列函數實現新的類:

virtual bool drawCanvasWidget(const Rect& invalidatedArea) const;
virtual Rect getMinimalRect() const;

drawCanvasWidget()必須繪製需要繪製的任何自訂小部件,並且getMinimalRect()應在包含幾何圖形的小部件中返回實際矩形。

Note
使用 getMinimalRect() 的原因在於可以在其小部件內部到處移動幾何圖形,並且當形狀變為僅使最小可能區域無效時,改變小部件的大小和重新定位小部件通常都不切實際。

函數 getMinimalRect() 的虛擬實現可能只 返回rect;,這是小部件的大小,但這會導致被Canvas Widget覆蓋的整個區域的重新繪製,而不只是包含幾何圖形的Canvas Widget的一部分。 幾何圖形通常只佔據Canvas Widget的一小部分。

Canvas Widget全部使用Canvas類,它如上文所述壓縮Canvas Widget Renderer。 CWR有許多自動應用的優化,儘管知道幾何圖形與無效區域有關,避免無效區域之外的不必要繪製,始終是一種提升性能的好方法。

在10x10方塊內部粗略實現一個菱形方塊,結果可能像這樣:

class Diamond10x10 : public CanvasWidget
{
public:
virtual Rect getMinimalRect() const
{
return Rect(0,0,10,10);
}
virtual bool drawCanvasWidget(const Rect& invalidatedArea) const
{
Canvas canvas(this, invalidatedArea);
canvas.moveTo(5,0);
canvas.lineTo(10,5);
canvas.lineTo(5,10);
canvas.lineTo(0,5);
return canvas.render(); // Shape is automatically closed
}
};
Note
同樣地,注意 getMinimalRect() 返回到正確矩形,否則螢幕上的圖形可能是錯誤的。

為了在顯示器上看到Diamond10x10,必須使用繼承自CanvasWidget的Diamond10x10::setPainter()設置顏色。 另外,必須正確地放置Diamond10x10並調整其大小。 結果可能像這樣:

在標頭檔中宣告

Diamond10x10 box;
PainterRGB565 myPainter;

並且程式碼中應有類似這樣的內容:

myPainter.setColor(Color::getColorFromRGB(0xFF, 0x0, 0x0));
box.setPosition(100,100,10,10);
box.setPainter(myPainter);
add(box);

繪圖器(#painters)

繪圖器定義一個配色方案,用於填充‘Canvas Widget’物件,因此繪圖器需要使形狀可見。 繪圖器可以為所有像素提供單一顏色(例如PainterRGB565),或者從提供的點陣圖中複製每個像素(例如PainterRGB565Bitmap)。 由於繪圖器直接將像素寫入影像緩衝區,因此所選的繪圖器必須匹配目標顯示器或動態點陣圖的規範。 TouchGFX提供的繪圖器面向所有支援的顯示器,專門用於純色或點陣圖繪製。

從點陣圖繪製像素的繪圖器也可以繪製平鋪的點陣圖,這有助於減少記憶體需求。

客製繪圖器

儘管TouchGFX提供一組預定義的繪圖器類,涵蓋了大多數的使用情境,但也可以實現客製繪圖器。

為了實現客製繪圖器,必須注意永遠不要在影像緩衝區之外進行寫操作(這是由抽象繪圖器基類處理的)。 下面是自訂Painter的範例,我們將用它畫一個紅色物件,只需實現函數renderNext()。 更多資訊見AbstractPainter。

class RedPainter : public AbstractPainterRGB565
{
public:
virtual bool renderNext(uint8_t &red, uint8_t &green, uint8_t &blue, uint8_t &alpha)
{
red = 0xFF;
green = 0x00;
blue = 0x00;
alpha = 0xFF;
}
};

為了在紅色上方畫一個方塊物件,將以下程式碼放入標頭檔:

Diamond10x10 box;
RedPainter redPaint;

並將以下內容放入程式碼:

box.setPosition(100,100,10,10);
box.setPainter(redPaint);
add(box);

請注意,可以重寫更多方法來創建特殊Painter,如renderInit(),但是TouchGFX有一些通用Painter,覆蓋了大多數應用。