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を作成することができます。
キャンバス・ウィジェット(Canvas Widget)の使用
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で使用可能なキャンバス・ウィジェット
TouchGFX Designerでこれらのウィジェットを使用すると、実行時のウィジェットの見え方が示されるので、配置やサイズ調整がはるかに容易になります。
メモリ割当てと使用量
うまくアンチエイリアスされた複雑な幾何学形状を生成するには、追加のメモリが必要です。 このためCWRには、描画時に使用される特別に割り当てられたメモリ・バッファが必要です。 TouchGFXのその他の部分と同様に、CWRでは動的なメモリ割り当ては行われません。
TouchGFX Designerでのメモリ割当て
スクリーンのキャンバスにウィジェットを追加すると、メモリ・バッファが自動的に生成されます。 バッファのサイズは、スクリーンの幅に基づき、(幅 × 3)× 5という式を使用して決められます。 ただしこれは、すべてのシナリオに対して常に最適なバッファ・サイズではありません。 このため、下記の画像に示すように、バッファ・サイズをオーバーライドできます。
ユーザ・コードでのメモリ割当て
TouchGFX Designerは、Designerでキャンバス・ウィジェットを使用する画面にのみメモリ・バッファを割り当てます。 Designer上でキャンバス・ウィジェットが組み込まれていない画面の、ユーザ・コードにキャンバス・ウィジェットを追加する場合は、手動でバッファを設定する必要があります。 設定には、Screen::setupScreen
メソッドの使用を推奨します。
以下をプライベート・メンバーとしてScreenクラスの定義に追加します。
private:
static const uint16_t CANVAS_BUFFER_SIZE = 3600;
static uint8_t canvasBuffer[CANVAS_BUFFER_SIZE]
次に、ScreenView.cpp
のsetupScreen()
メソッドに、バッファを設定する以下の行を追加します。
void ScreenView::setupScreen()
{
...
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にする必要があります(ラインは円周の両側に0.5幅で描画されます)。そのため、中心座標には小数部が必要です。 同様に、この円を6 x 6ピクセルのボックス内に収める場合には、中心を(3, 3)にして半径を2.5にする必要があります。今度は半径に小数部が必要になります。
グラフィックス描画のためのこの新しい座標処理方法は、(0,0)にあるピクセルの中心のCWR座標は(0.5, 0.5)になることを意味しています。 したがって、スクリーンの左上隅のピクセルを含むボックスのアウトライン座標は、(0,0) -> (1,0) -> (1,1) -> (0,1) -> (0,0)になります。
この処理方法に最初は戸惑うかもしれませんが、すぐに非常に自然なものになります。 ビットマップの座標系でピクセルを処理する場所では、Canvas Widgetの同じ座標でピクセルの直前と上のギャップを処理します。
円は、中心を正しく配置するために0.5ピクセル移動する必要がよく生じる形状なので、関数Circle::setPixelCenter()
は、円の中心を指定のピクセルの中心に配置、つまり、指定された座標よりも0.5ピクセル右および下に配置します。
カスタム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のボックス内に、ひし形の正方形を大まかに実装する方法は以下のようになります。
#include <touchgfx/widgets/canvas/CanvasWidget.hpp>
#include <touchgfx/widgets/canvas/Canvas.hpp>
using namespace touchgfx;
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をディスプレイに表示するために、Painterをひし形に渡して、色を設定する必要があります。 Painterの詳細については、次のセクションを参照してください。 さらに、Diamond10x10を正しく配置してサイズ設定する必要もあります。 これは次のような設定になります。
ヘッダ・ファイルで次のように宣言します。
Diamond10x10 box;
PainterRGB565 myPainter; // For 16bpp displays
また、コードでは、setupScreen()に次のような記述が必要です。
myPainter.setColor(Color::getColorFromRGB(0xFF, 0x0, 0x0));
box.setPosition(100,100,10,10);
box.setPainter(myPainter);
add(box);
Painter(ペインタ)
Painterは、Canvas Widgetオブジェクトを塗りつぶすためのカラー・スキームを定義するもので、形状を表示するために必要になります。 Painterでは、すべてのピクセルに1つの色を提供したり(PainterRGB565
など)、提供されたビットマップから各ピクセルをコピーしたりできます(PainterRGB565Bitmap
など)。 Painterはフレームバッファにピクセルを直接書き込むため、選択したPainterはフレームバッファまたはダイナミック・ビットマップのフォーマットに一致する必要があります。 TouchGFXには、塗りつぶし色、またはビットマップの描画に特化したPainterなど、サポートされたすべてのディスプレイ用のPainterが用意されています。
Painterクラス
次の表に、TouchGFXで利用可能なPainterを示します。 キャンバス・ウィジェットをTouchGFX Designer上で使用する場合は、Designerによって適切なPainterが選択されますが、キャンバス・ウィジェットを使用するコードを自分自身で記述する場合は、適切なPainterを選択する必要があります。
フレームバッファのフォーマット | カラーPainter | ビットマップPainter |
---|---|---|
BW | PainterBW | PainterBWBitmap |
GRAY2 | PainterGRAY2 | PainterGRAY2Bitmap |
GRAY4 | PainterGRAY4 | PainterGRAY4Bitmap |
ABGR2222 | PainterABGR2222 | PainterABGR2222Bitmap |
ARGB2222 | PainterARGB2222 | PainterARGB2222Bitmap |
BGRA2222 | PainterBGRA2222 | PainterBGRA2222Bitmap |
RGBA2222 | PainterRGBA2222 | PainterRGBA2222Bitmap |
RGB565 | PainterRGB565 | PainterRGB565Bitmap、PainterRGB565L8Bitmap |
RGB888 | PainterRGB888 | PainterRGB888Bitmap、PainterRGB888L8Bitmap |
ARGB8888 | PainterARGB8888 | PainterARGB8888Bitmap、PainterARGB8888L8Bitmap |
XRGB8888 | PainterXRGB8888 | PainterXRGB8888Bitmap、PainterXRGB8888L8Bitmap |
ビットマップPainterは各種ビットマップ・フォーマットに対応しています。
Painter | サポートされているビットマップ・フォーマット |
---|---|
PainterBWBitmap | BW、BW_RLE |
PainterGRAY2Bitmap | GRAY2 |
PainterGRAY4Bitmap | GRAY4 |
PainterABGR2222Bitmap | ABGR2222 |
PainterARGB2222Bitmap | ARGB2222 |
PainterBGRA2222Bitmap | BGRA2222 |
PainterRGBA2222Bitmap | RGBA2222 |
PainterRGB565Bitmap | RGB565、ARGB8888 |
PainterRGB565L8Bitmap | L8_RGB565、L8_RGB888、L8_ARGB8888 |
PainterRGB888Bitmap | RGB888、ARGB8888 |
PainterRGB888L8Bitmap | L8_RGB565、L8_RGB888、L8_ARGB8888 |
PainterARGB8888Bitmap | RGB565、RGB888、ARGB8888 |
PainterARGB8888L8Bitmap | L8_RGB565、L8_RGB888、L8_ARGB8888 |
PainterXRGB8888Bitmap | RGB565(透明度なし)、RGB888、ARGB8888 |
PainterXRGB8888L8Bitmap | L8_RGB565、L8_RGB888、L8_ARGB8888 |
タイル化されたビットマップ
ビットマップからピクセルを描画するPainterは、キャンバス・ウィジェットの左上隅にビットマップを配置します。 ビットマップ・ディメンション外にある形状のピクセルは描画されません。
ビットマップPainterは、形状全体を網羅するためにウィジェット(タイル化)を繰り返し実行するように設定できます。
タイル化は、Painter上でsetTiled(bool)
メソッドを呼び出すと、有効になります。
PainterRGB888Bitmap bitmapPainter;
...
bitmapPainter.setBitmap(touchgfx::Bitmap(BITMAP_BLUE_LOGO_TOUCHGFX_LOGO_ID));
bitmapPainter.setTiled(true);
タイル化は、Designerでは有効にできません。
カスタムPainter
TouchGFXには、ほとんどのユース・ケース・シナリオを網羅する一連のPainterクラスが事前に定義されていますが、カスタムPainterも実装できます。
このセクションでは、ヒントとして使用できる、いくつかの例を紹介します。 これらの例は16bpp RGB565用のみです。 それ以外のフレームバッファ・フォーマットには、少し変更を加える必要があります。
カスタムPainterはAbstractPainterクラスのサブクラスです。 16bpp(RGB565)フレームバッファ用Painterは、AbstractPainterRGB565クラスをスーパークラスとして使用できます。 24bpp(RGB888)フレームバッファ用Painterは、AbstractPainterRGB888クラスをスーパークラスとして使用できます。
これらのスーパークラスは抽象クラスです。 カスタムPainterクラスは次のメソッドを実装する必要があります。
virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const = 0;
destinationは、フレームバッファの開始位置(ウィジェットの左端)を示しています。
offsetは、この開始位置から最初のピクセルを配置するまでのピクセル数です。
widgetX、widgetYは、ウィジェットを基準にした最初のピクセルの座標です(フレームバッファ座標系で指定)。
countは、指定のalphaで描画されるピクセル数です。
キャンバス・ウィジェットはこのメソッドを何度も呼び出すため、Paintの実装に時間がかからないことが大変重要です。 キャンバス・ウィジェットが頻繁に更新されない場合は、これはさほど重要ではありません。
カラーPainter
最も単純なPainterは、フレームバッファに固定カラーを書き込むだけです。 その実装方法は次のとおりです。
#include <touchgfx/widgets/canvas/AbstractPainterRGB565.hpp>
using namespace touchgfx;
class RedPainter : public AbstractPainterRGB565
{
public:
virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
{
uint16_t* framebuffer = reinterpret_cast<uint16_t*>(destination) + offset; // Address of first pixel to paint
const uint16_t* const lineend = framebuffer + count; // Address of last pixel to paint
const uint16_t redColor565 = 0xF800; // Full red in RGB565
do
{
*framebuffer = redColor565;
} while (++framebuffer < lineend);
}
};
Painterのインスタンスを作成し、それをキャンバス・ウィジェットに割り当てることを忘れないでください。 Painterタイプのメンバーをクラスに追加します
Circle myCircle;
RedPainter myPainter;
また、コードでは、setupScreen()に次のような記述が必要です。
...
myCircle.setPainter(myPainter);
...
上記のRedPainterクラスはアルファ・パラメータを無視します。 これにより、 すべてのピクセルが完全に赤色になるため、エッジが粗くなります(アルファブレンディングなし)。 この状態を、必要に応じて若干のコードを更新し、アルファ・パラメータを使用してブレンディングを行なうよう改善できます。
#include <touchgfx/widgets/canvas/AbstractPainterRGB565.hpp>
using namespace touchgfx;
class AlphaRedPainter : public AbstractPainterRGB565
{
public:
virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
{
uint16_t* framebuffer = reinterpret_cast<uint16_t*>(destination) + offset; // Address of first pixel to paint
const uint16_t* const lineend = framebuffer + count;
const uint16_t redColor565 = 0xF800; // Full red in RGB565
do
{
if (alpha == 0xFF)
{
*framebuffer = redColor565; // Write red to framebuffer
}
else
{
*framebuffer = alphaBlend(redColor565, *framebuffer, alpha); // Blend red with the framebuffer color
}
} while (++framebuffer < lineend);
}
};
AlphaBlend関数は、2つのRGB565ピクセルを、最初のピクセルに指定されたアルファ係数を使用してブレンディングします。 この関数は、スーパークラスAbstractPainterRGB565によって提供されます。 円のエッジはこのコードで滑らかになります。
WidgetXおよびWidgetYパラメータは、特定の領域への描画を制限するために使用できます。 ここに、1行おきの水平ラインにのみペイントするPainterの例を示します。 WidgetYはその制御に使用されます。
#include <touchgfx/widgets/canvas/AbstractPainterRGB565.hpp>
using namespace touchgfx;
class StripePainter : public AbstractPainterRGB565
{
public:
virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
{
if ((widgetY & 2) == 0)
{
return; // Do not draw anything on line 0,1, 4,5, 8,9, etc.
}
uint16_t* framebuffer = reinterpret_cast<uint16_t*>(destination) + offset;
const uint16_t* const lineend = framebuffer + count;
if (alpha == 0xFF)
{
do
{
*framebuffer = 0xF800;
} while (++framebuffer < lineend);
}
else
{
do
{
*framebuffer = alphaBlend(0xF800, *framebuffer, alpha);
} while (++framebuffer < lineend);
}
}
};
フレームバッファの変更
このセクションのPainterは、フレームバッファに対して特定のコンテンツをペイントすることはありませんが、フレームバッファをグレースケールに変更します。 これは、フレームバッファのピクセル値(円のバックグラウンドにあるウィジェットによって書き込まれた)を読み取ることで実行され、緑のコンポーネントを抽出し、これを使用して灰色(赤、緑、青の値が同じ)を作成し、フレームバッファに書き戻します。
フレームバッファの読み取りと変更に関するこの原則を使用して、多くの類似する技法を開発できます。
#include <touchgfx/widgets/canvas/AbstractPainterRGB565.hpp>
#include <touchgfx/Color.hpp>
using namespace touchgfx;
class GrayscalePainter : public AbstractPainterRGB565
{
public:
virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
{
uint16_t* framebuffer = reinterpret_cast<uint16_t*>(destination) + offset;
const uint16_t* const lineend = framebuffer + count;
do
{
const uint8_t green = Color::getGreenFromRGB565(*framebuffer) & 0xF8; // Keep only 5 bits of the green
const uint16_t color565 = LCD16bpp::getNativeColorFromRGB(green, green, green);
if (alpha == 0xFF)
{
*framebuffer = color565;
}
else
{
*framebuffer = alphaBlend(color565, *framebuffer, alpha);
}
} while (++framebuffer < lineend);
}
};
回転したディスプレイ上のカスタム・コンテナ
アプリケーションで回転したディスプレイを使用している場合、カスタム・コンテナのコードでは、これを考慮する必要があります(ただし、ペイントで座標が使用されている場合)。
次に、回転したディスプレイで使用されるStripePainterを示します。
画像、テキスト、ボタンはTouchGFXエンジンによって回転されましたが、ストライプはテキストに対して垂直になっていることがわかります。これは平行になっているべきものです。 つまり、ラインは回転されませんでした。
問題はフレームバッファが回転されないことです。そのため、Painterが連続したアドレス(フレームバッファのピクセル)にペイントすると、ラインは以前と同じ向きになります(回転しません)。
これは、WidgetXを使用してペイントしているかどうかを判断することで修正できます。 widgetXおよびwidgetYパラメータは、フレームバッファ座標系で指定されます。 つまり、widgetXは、ディスプレイを下方に移動すると増加し、ディスプレイの座標系ではy座標に一致することを意味します。★
#include <touchgfx/widgets/canvas/AbstractPainterRGB565.hpp>
#include <touchgfx/Color.hpp>
using namespace touchgfx;
class StripePainterRotate90 : public AbstractPainterRGB565
{
public:
virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
{
uint16_t* framebuffer = reinterpret_cast<uint16_t*>(destination) + offset;
const uint16_t* const lineend = framebuffer + count;
if (alpha == 0xFF)
{
do
{
if (widgetX++ & 2)
{
*framebuffer = 0xF800;
}
} while (++framebuffer < lineend);
}
else
{
do
{
if (widgetX++ & 2)
{
*framebuffer = alphaBlend(0xF800, *framebuffer, alpha);
}
} while (++framebuffer < lineend);
}
}
};
これで、ストライプが正しい向きになりました。