メイン・コンテンツまでスキップ

Canvas Widget(キャンバス・ウィジェット)

Canvas WidgetとCanvas Widget Rendererは、強力で用途の広いTouchGFXのアドオンで、相対的に少ないメモリ使用量で高パフォーマンスを維持しながら、幾何学形状に対して非常に滑らかでアンチエイリアスされた描画を実現します。 ただし、幾何学形状の描画は極めて負荷の高い操作ですので、注意して使用しないとマイクロコントローラのリソースが簡単にひっ迫することになります。

Canvas Widget Renderer(以下、CWR)は一般的なグラフィックAPIで、プリミティブな最適化された描画を実現し、ほとんどの不要な描画を自動的に除去してくれます。 TouchGFXは、複雑な幾何学形状の描画にCWRを使用します。 幾何学形状はCanvas Widgetによって定義されます。 TouchGFXには多くのCanvas Widgetがサポートされていますが、通常のウィジェットと同様に、ユーザのニーズに合わせて独自のカスタムCanvas Widgetを作成することができます。 CWRが描画する図の幾何学形状をCanvas Widgetが定義するとき、図の中の各ピクセルの実際の色が、関連付けられたPainterクラスによって定義されます。 この場合も、TouchGFXには多数のPainterが用意されていますが、ユーザのニーズに合わせて独自のカスタムPainterを作成することができます。

CanvasWidgetの使用

TouchGFXにある他のウィジェットは、サイズが自動的に設定されます。 たとえば、ビットマップ・ウィジェットは、含まれているビットマップの幅と高さを自動的に取得します。 このため、ビットマップ・ウィジェット上でsetXY()を使用して、ビットマップを画面上に配置するだけで十分です。

Canvas Widgetには、自動的に決められ初期設定されるデフォルト・サイズがありません。 ウィジェットを配置するだけでなく、正しくサイズ設定するように注意する必要があります。そうでないと、Canvas Widgetの幅と高さはゼロになり、画面上に何も描画されなくなります。

したがって、setXY()を使用する代わりにsetPosition()を使用してCanvas Widgetの配置とサイズ設定を行います。 カスタムCanvas Widgetを作成して使用する方法については、下にある例「カスタムCanvas Widget」を参照してください。

Canvas Widgetの位置とサイズを設定した後は、その中に幾何学形状を描画できます。 ウィジェット(画面ではない)の左上隅の座標が(0, 0)になり、X軸は右方向に、Y軸は下方向に伸びます。

Canvas WidgetはTouchGFX Designerでもサポートされ、使い勝手がよくなり、メモリの計算やメモリの割り当てが自動的に行われます。

TouchGFX Designerで使用可能なCanvasWidgetベースのウィジェットは以下のとおりです。

TouchGFX Designerでこれらのウィジェットを使用すると、実行時のウィジェットの見え方が示されるので、配置やサイズ調整がはるかに容易になります。

メモリ割当てと使用量

うまくアンチエイリアスされた複雑な幾何学形状を生成するには、追加のメモリが必要です。 このためCWRには、描画時に使用される特別に割り当てられたメモリ・バッファが必要です。 TouchGFXのその他の部分と同様に、CWRでは動的なメモリ割り当ては行われません。

TouchGFX Designerでのメモリ割当て

スクリーンのキャンバスにウィジェットを追加すると、メモリ・バッファが自動的に生成されます。 バッファのサイズは、スクリーンの幅に基づき、(幅 × 3)× 5という式を使用して決められます。 ただしこれは、すべてのシナリオに対して常に最適なバッファ・サイズではありません。 このため、下記の画像に示すように、バッファ・サイズをオーバーライドできます。

スクリーンのプロパティでオーバーライドされるキャンバスのバッファ・サイズ

ユーザ・コードでのメモリ割当て

メモリ・バッファはtarget/main.cppおよびsimulator/main.cppで割り当てて設定することも、画面ごとに設定して割り当てることもできます。

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

メモリ・バッファのサイズを定義するstatic const(static定数)と実際のメモリ・バッファは、main.cppまたはScreenView.hppの先頭で定義できます。

次にmain.cppメソッドのmain()メソッド、またはScreenView.cppsetupScreen()メソッドのいずれかでバッファを設定する次の行を追加できます。

CanvasWidgetRenderer::setupBuffer(canvasBuffer, CANVAS_BUFFER_SIZE);

CWRの必要なメモリ量は、アプリケーションで描画する形状の最大サイズに応じて異なります。 ただし、最も複雑な形状が必要とするメモリ量より少なく抑えることができます。 この状況に対処するため、CWRは形状の描画を小さいフレームバッファ・パーツに分割します。この結果、描画時間は少し長くなります。これは、ときには形状を描画するのに1回ではすまないことがあるからです。 シミュレータ・モードで実行すると、メモリ消費量を綿密に調べて微調整することができます。 main.cppに次の関数呼び出しを追加するだけです。

CanvasWidgetRenderer::setWriteMemoryUsageReport(true);

これで、描画操作が終了するたびに、必要とされたメモリ量をCWRがレポート(コンソールで出力)するようになります。 canvas_widget_exampleの場合は、“CWR requires 3604 bytes”(最初の描画操作時に)の後に、“CWR requires 7932 bytes (4328 bytes missing)”(2回目の描画操作時に)が続くことになります。 CWRのメモリ量は十分でないように見えますが(この例では4328バイト欠落)、アプリケーションは正常に実行されます。 これは、1回の実行で複雑な描画操作を完了するには、使用可能なメモリの量が少なすぎることをCWRが検出したからです。 代わりに、CWRは描画操作を2つの別々の描画操作に分割するので、形状の描画は問題なく行われますが、描画に時間がかかることになります。

したがって、正しいメモリ・バッファ・サイズの設定は、メモリとパフォーマンス(描画時間)の間のトレードオフなのです。 通常の適切な開始値は3000前後ですが、上記の技法を使用すると、もっと適した値を設定できることが多くなります。 形状が複雑すぎて割り当てられたメモリ・バッファが小さすぎる場合、形状の一部が描画されなくなり(一部の縦方向のピクセル・ラインがスキップされる)、何も描画されない可能性もあります。 いずれにせよ描画時間は非常に長くなります。

つまり、アプリケーションでCWR描画を最大速度で描画する場合には、要求されたメモリ量を割り当てる必要があるということです。 ただし、描画時間が長くかかっても構わない場合は、メモリ・バッファを減らしてもまったく問題ありません。

CWRの座標系

通常、TouchGFXの座標系は、画面上にビットマップを配置するためのピクセル処理に使用されます。 ビットマップ、テキスト、その他のグラフィック要素はすべて座標系に配置されます。ここでは(0,0)が左上のピクセルで、X軸は右方向に、Y軸は下方向に伸びます。 CWRでは、整数を使用してピクセルを十分に処理することはできません。特殊なケースでは十分なこともありますが、一般的にとても十分とは言えません。 この例を示すため、線幅が1の円について考えてみます。この円を5 x 5ピクセルのボックス内にぴったりと収める必要があるとします。 この円の中心は(2.5, 2.5)で、半径は2にする必要があります。このため中心座標に小数部が必要になります。 同様に、この円を6 x 6ピクセルのボックス内に収める場合には、中心を(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の同じ座標でピクセルの直前と上のギャップを処理します。

円という形状では、中心を正確に配置するために1/2ピクセル移動する必要が生じることが多いので、関数Circle::setPixelCenter()では、円の中心を所定のピクセルの中心(指定された座標と比べて右下に1/2ピクセル離れている位置)に配置します。

カスタムCanvas Widget

カスタムCanvas Widgetを実装するには、次の関数を使用して新しいクラスを実装する必要があります。

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

drawCanvasWidget()ではカスタム・ウィジェットが描画する必要のあるものすべてを描画する必要があり、getMinimalRect()は、幾何学形状を格納するウィジェット内に実際の長方形を返します。

Note
**分節不備** getMinimalRect() を使用する理由は、幾何学形状がそのウィジェット内を動き回ることができるからです。また、多くの場合、ごくわずかな領域を無効化するためだけに形状を変更するたびに、ウィジェットのサイズ変更や再配置を行うのは実用的ではありません。
<InlineCode>getMinimalRect()</InlineCode> のダミー実装では、単純に <InlineCode>return rect;</InlineCode>を使用すればよいのですが、返されるのはウィジェットのサイズです。ただし、幾何学形状を含む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を表示するには、Canvas Widgetから継承した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);

Painter(#painters)

Painterは、Canvas Widgetオブジェクトを塗りつぶすためのカラー・スキームを定義するもので、形状を表示するために必要になります。 Painterでは、すべてのピクセルに1つの色を提供したり(PainterRGB565など)、提供されたビットマップから各ピクセルをコピーしたりできます(PainterRGB565Bitmapなど)。 Painterはフレームバッファにピクセルを直接書き込むので、選択されたPainterが、ターゲットのディスプレイまたはダイナミック・ビットマップの仕様と一致する必要があります。 TouchGFXには、塗りつぶし色、またはビットマップの描画に特化したPainterなど、サポートされたすべてのディスプレイ用のPainterが用意されています。

ビットマップからピクセルを描画するPainterでは、タイル化されたビットマップも描画できるので、メモリ必要量の削減に役立ちます。

カスタムPainter

TouchGFXには、ほとんどのユースケース・シナリオに対応する一連の事前定義されたPainterクラスが含まれていますが、カスタムPainterも実装できます。

カスタムPainterを実装するときに、フレームバッファの外に記述しないように注意することが必要です(これはAbstractPainter基底クラスで処理されます)。 次に、オブジェクトを赤色にペイントするために使用するカスタム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がいくつか含まれています。