
注記: Qt 6.12 において、Qt Canvas Painter の C++ API はもはや Technology Preview(技術プレビュー)ではありません。しかし、Canvas2D は依然として Technology Preview の段階にあります。実際に動作しますし、ぜひ皆さんに使用していただきたいと考えていますが、API は今後変更される可能性があります。また、現時点ではソース互換性およびバイナリ互換性は保証されていません。
Canvas2D とは何か?
Canvas2D は、JavaScript を使用して描画を行うための QML アイテムです。HTML Canvas や Qt Quick Canvas によって広く普及した、同じ命令型(imperative)描画モデルを利用できます。その内部では Qt Canvas Painter が使用されているため、描画処理は CPU 上で QImage に対して行われるのではなく、QRhi を介して GPU 上で実行されます。
始めるのは簡単です。以下にシンプルな QML の例を示します。
import QtQuick
import QtCanvas2D
Canvas2D {
width: 100
height: 200
onPaint: {
var ctx = getContext("2d");
// Paint a red rectangle
ctx.fillStyle = Qt.rgba(1, 0, 0, 1);
ctx.fillRect(0, 0, width, height);
}
}
もしこれまでに QML で Canvas { onPaint: ... } を記述したことがある、あるいはブラウザで <canvas> 要素を使用したことがあるのであれば、特に難しいことはありません。必要なのは QtCanvas2D モジュールをインポートすることだけです。そうすれば Canvas2D 要素を作成できるようになります。
HTML Canvas との互換性
Canvas2DContext API は、Web 開発者にとって馴染みのある W3C Canvas 2D Context API を実装しています。たとえば、beginPath()、moveTo()、lineTo()、bezierCurveTo()、fill()、stroke() といったメソッドに加え、fillStyle、strokeStyle、lineWidth、lineCap、lineJoin などの状態プロパティも利用できます。
これは、Web 上のサンプルコードやチュートリアル、Stack Overflow の回答、あるいは LLM が生成した Canvas コードを、そのまま実行できる場合が多いことを意味します。命令型の 2D 描画 API にとって、これは大きな生産性向上につながります。なぜなら、すでに膨大な知識やコードスニペットが存在しているからです。
ただし、Canvas2D の目標は HTML Canvas と 100% の互換性を実現することではありません。API をよりシンプルに保ち、GPU 上で高速に動作させるため、一部の機能は意図的に省かれています。現在、HTML Canvas と比較して未実装となっている機能は次のとおりです:
- パスへのクリッピング – すべてのクリッピングは、setClipRect() および resetClipping() を使用した(変換可能な)矩形クリッピングに限定されています
- 破線 – ストロークは常に実線として描画されます
- パス判定 – isPointInPath() および isPointInStroke() はサポートされていません
- テキストのストローク描画 – テキストの輪郭線を描画する機能はありません
- フィルター – Canvas の SVG フィルター効果はサポートされていません
- 合成モード(Composite modes) – 追加のレンダリングバッファを使用せずに実現できる、次の 3 種類のモードのみに限定されています: source-over, source-atop and destination-out.
- 組み込みシャドウプロパティ – shadowBlur などのシャドウ関連プロパティはサポートされていません
その代わりとして、調整可能なアンチエイリアシング機能およびボックスシャドウ機能が提供されています
これについては後ほど説明します
そして逆に、Canvas2D には HTML Canvas には存在しない機能もすでに追加されています:
- パスグループを備えた Path2D – createPath2D() は path2d コンテナを生成します
このコンテナは、オプションのパスグループを指定して塗りつぶしやストローク描画を行うことができます
同じグループ内のパスは GPU 側にキャッシュされた頂点バッファを共有します
これが、先の「パスキャッシュ」の記事で紹介した大幅な性能向上の理由です
- 調整可能なアンチエイリアシング – ctx.antialias および ctx.textAntialias を使用して、アンチエイリアシングの幅をピクセル単位で設定できます
これにより、単一の数値を変更するだけで、シャープな描画から柔らかいグロー効果まで表現できます。
- ボックスグラデーション – createBoxGradient() は、角丸矩形の形状に沿ったグラデーションを生成します
- ボックスシャドウ – createBoxShadow() および drawBoxShadow() は、CSS スタイルのボックスシャドウをレンダリングします
これは Qt Quick の RectangularShadow と同様の SDF(Signed Distance Field)アプローチを使用しており、ガウシアンブラーによるシャドウと比較して非常に低コストです
- グリッドパターン – createGridPattern() は、動的なグリッドパターンやバー(縞)パターンを一定のコストで生成できます
- カラーエフェクト – 標準的な globalAlpha に加えて、globalBrightness, globalContrast, globalSaturation といった追加のカラーエフェクトが提供されています
- 穴あきサブパス – beginHoleSubPath() / beginSolidSubPath() / setPathWinding() / windingEnforce を使用することで、巻き方向(winding direction)を手動で管理することなく、ドーナツ形状や切り抜き形状を容易に作成できます
- テキストの折り返し – fillText(text, x, y, width, height) は、指定された矩形領域内でテキストを折り返して描画します
折り返し動作は textWrapMode と textLineHeight によって制御されます
- 追加の図形プリミティブ – circle(), ellipse(), ellipseRect() などの図形がパスプリミティブとして直接サポートされています
- Transform2d – createTransform2D() は、本物の 3×3 行列オブジェクトを生成します
このオブジェクトは setTransform() や transform() に渡して利用できます
これにより、6 個の浮動小数点値を個別に管理する必要がなくなり、また完全な DOMMatrix を使用する必要もありません。
CanvasQt Quick Canvas との互換性
すでに Qt Quick Canvas を使用している場合、Canvas2D の API はそれとほぼ完全な互換性があります。必要な変更は、QtCanvas2D をインポートし、Canvas を Canvas2D に名前変更するだけです。それだけで、ほとんどの場合そのまま動作します。
以下は、その 2 行の変更を示すコード例です:
// Before
import QtQuick
Canvas {
onPaint: { var ctx = getContext("2d"); paintEverything(ctx); }
}
// After
import QtQuick
import QtCanvas2D
Canvas2D {
onPaint: { var ctx = getContext("2d"); paintEverything(ctx); }
}
Canvas 2D Tester のサンプル(後述)も、まさにこの互換性を利用しています。同じ JavaScript の描画関数が、Canvas アイテムと Canvas2D アイテムの両方に対して呼び出されます。
以下の表は、Qt Quick Canvas と Canvas2D の違いをまとめたものです。
| |
Qt Quick Canvas |
Canvas2D |
| 描画バックエンド |
QPainter(CPU)で QImage に描画し、それをテクスチャとしてアップロード |
QRhi 経由で GPU 上の Qt Canvas Painter を使用 |
| 描画対象 |
Canvas.Image(Qt 6.0 以降、FramebufferObject は無視される) |
QRhi を使用してシーングラフへ直接レンダリング |
| アニメーション用途/大規模キャンバス |
更新のたびにテクスチャをアップロードするため、ドキュメントでは使用を推奨していない |
主な用途として想定されている |
| フレーム間でのコンテンツ保持 |
内容を保持する。必要に応じて自分で clearRect() を呼び出す |
毎フレーム消去される。背景色は fillColor で指定する |
| パスキャッシュ |
なし |
あり path2d とパスグループによって実現 |
| クリッピング |
任意のパスに対する clip() が可能(コストが高くなる可能性あり) |
setClipRect() のみ。矩形クリッピング限定 |
| 破線ストローク |
あり (setLineDash) |
まだ利用不可 |
| シャドウ |
shadowBlur / shadowColor |
角丸矩形に対して高速なボックスシャドウ(SDF)を提供。その他の図形には調整可能なアンチエイリアシングを使用 |
| ピクセル操作 |
getImageData() / putImageData() (コストが高くなる可能性あり) |
利用不可 |
| アンチエイリアシング |
固定 |
ストローク、塗りつぶし、およびテキストごとに調整可能 |
| 追加ブラシ機能 |
- |
ボックスグラデーション、グリッドパターン、色付き画像(tinted images)
|
| カラーエフェクト |
globalAlpha |
globalAlpha, globalBrightness, globalContrast, globalSaturation
|
つまり、要点をまとめると次のようなトレードオフになります。あまり一般的には使用されないいくつかの機能を手放す代わりに、GPU による高速なレンダリング性能と、多数の新しいブラシ機能およびエフェクト機能を手に入れることができます。典型的な用途、例えばアニメーションを伴うカスタムビジュアライゼーション、ゲージ、チャート、波形表示、手描き風のカスタムコントロールなどにおいては、このトレードオフは十分に価値のあるものです。もし現在のコードが Qt Quick Canvas に存在する機能に依存しており、その機能が Canvas2D にはまだ実装されていない場合、そして現在の実装に満足しているのであれば、そのまま Qt Quick Canvas を使い続けてください。Qt Quick Canvas は引き続き利用可能です。ただし、皆さんのユースケースについてはぜひお聞かせいただきたいと考えています。そうしたフィードバックをもとに、今後どの機能を優先的に実装すべきか判断していきます。
また、動作上の違いとして重要な点をもう一度述べておきます:
Canvas2D はデフォルトでは毎フレーム、描画内容を最初から再描画します。そのため、HTML Canvas や Qt Quick Canvas のように onPaint の先頭で clearRect() を呼び出す必要はありません。
QCanvasPainter C++ API との互換性
QCanvasPainter と Canvas2DContext は、同じ描画エンジンを異なる形で利用するための 2 つのインターフェースです。C++ API ではセッターメソッドと Qt の値型(value types)を使用し、QML API ではプロパティと JavaScript に適した引数を使用します。しかし、メソッド名、引数の順序、そして動作の意味は共通しています。
そのため、どちらの方向への移植であっても、ほぼ機械的に行うことができます。スクリプトや LLM を利用して自動変換することも容易です。
以下は、QCanvasPainter のドキュメントに掲載されている丸いボタン(round button)のサンプルと、Canvas2DContext のドキュメントに掲載されている同じサンプルを並べて示したものです。
QCanvasPainter を使用した C++ 版:
QRectF rect(40, 70, 120, 60);
QRectF shadowRect = rect.translated(2, 4);
// Paint shadow
QCanvasBoxShadow shadow(shadowRect);
shadow.setRadius(30);
shadow.setBlur(15);
shadow.setColor("#60373F26");
p->drawBoxShadow(shadow);
// Paint rounded rect
p->beginPath();
p->roundRect(rect, 30);
p->setFillStyle("#DBEB00");
p->fill();
// Paint text
p->setTextAlign(QCanvasPainter::TextAlign::Center);
p->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
QFont font("Titillium Web", 18);
p->setFont(font);
p->setFillStyle("#373F26");
p->fillText("CLICK!", rect);
Canvas2DContext を使用したJavaScript 版:
let offsetX = 2;
let offsetY = 4;
// Paint shadow
let shadow = ctx.createBoxShadow(40 + offsetX, 70 + offsetY,
120, 60);
shadow.setRadius(30);
shadow.setBlur(15);
shadow.setColor("#60373F26");
ctx.drawBoxShadow(shadow);
// Paint rounded rect
ctx.beginPath();
ctx.roundRect(40, 70, 120, 60, 30);
ctx.fillStyle = "#DBEB00";
ctx.fill();
// Paint text
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "24px 'Titillium Web'";
ctx.fillStyle = "#373F26";
ctx.fillText("CLICK!", 100, 100);
移植のルールは非常にシンプルです:
| C++ QCanvasPainter |
QML Canvas2DContext |
| p->setFillStyle("#ff0000") |
ctx.fillStyle = "#ff0000" |
| p->setAntialias(10) |
ctx.antialias = 10 |
| p->setTextAlign(QCanvasPainter::TextAlign::Center) |
ctx.textAlign = "center" |
| QCanvasLinearGradient lg(...) |
let lg = ctx.createLinearGradient(...) |
| QCanvasPath |
path2d |
| QRectF rect の引数 |
x, y, width, height の引数 |
この API の統一は非常に重要であり、一見した以上に大きな利点をもたらします。例えば、まず QML 上でビジュアライゼーションを試作できます。QML であれば編集から実行までのサイクルは数秒程度で済むため、迅速に開発を進められます。その後、プロファイリングの結果として JavaScript がボトルネックになっていることが判明した場合や、C++ 側に存在する実際のデータへ直接アクセスする必要が生じた場合は、ほぼ検索・置換だけで同じ描画コードを C++ の QCanvasPainterItem へ移植できます。逆に、既存の C++ で実装された Canvas Painter のアイテムを、QML からスクリプトで制御できるバージョンとして公開することも可能です。このような移植を行う際、描画エンジンそのものを切り替えるわけではありません。切り替わるのは 言語(C++ と JavaScript/QML)だけ です。そのため、レンダリング結果は同一のまま維持されます。
Canvas 2D Tester サンプル
これらの機能を実際に体感する最良の方法は、モジュールに同梱されている Canvas 2D Tester サンプルを試すことです。このサンプルは、誰もが最初に抱く次の 2 つの疑問に答えるために作られました。自分の Canvas コードはそのまま動くのか?そして、本当に高速になっているのか?

このサンプルのポイントは、Canvas と Canvas2D の両方に対して、同じ QML JavaScript の描画コードを使用していることです。描画関数は 1 セットしか存在せず、ツールバーのスイッチによって、その描画関数を実行する対象を切り替えられるようになっています。ウィンドウの隅には FPS カウンターが表示されており、さらに「Animate」スイッチで連続再描画のオン/オフを切り替えられます。また、「Complexity」スライダーを 1 から 10 の範囲で調整することで、各テストが描画する内容量を増減できます。そのため、任意のテストを選択し、フレームレートを確認しながら Canvas と Canvas2D を切り替えることで、自分の環境や GPU ドライバー上で性能の違いを直接確認できます。
テストは 3 つのカテゴリに分けられています:
ベンチマーク — Rectangles, Lines, Circles, Clipping, Line Styles, Texts, State Handling, Images そして Transformations. これらは基本的な描画操作であり、数百〜数千回単位で描画されます。Rectangles テストでは、1 フレームあたり最大 5000 回の fillRect() および strokeRect() を交互に実行します。Lines テストでは、最大 1000 セグメントから構成されるポリラインを 50 本描画します。Circles テストでは、最大 2000 個の円弧から構成される単一のパスを生成します。Complexity を上げていくことで、各描画方式がどの時点で性能の限界に達するのかを観察できます。
サンプル — Text Align, Composite Modes, Gradients. こちらは性能比較ではなく、描画結果の正しさを比較するためのテストです。両方の要素で同じコードを実行し、テキストの基準位置/ 合成モード/ グラデーションのカラー停止位置 などが期待通りに動作しているか確認できます。
新機能 — Box Gradient, Box Shadow, Color Effects, Hole Subpaths, Adjusting Antialias, Grid Patterns そして Path2D. これらのテストは Canvas2D でのみ実行できます。このカテゴリでは、Canvas2D が新たに提供する機能を手軽に試すことができます。
さらに、このサンプルはコードの参考例としても有用です。サンプル内の CanvasView.qml には、数百行規模のシンプルな JavaScript 描画コードが含まれています。例えば Path2D テストでは、SVG パスからアイコンを生成し、それを変換しながら多数描画し、さらにキャッシュを活用してレンダリングする実装を見ることができます。
このサンプルプロジェクトは、example project on code.qt.io または、Qt Creator のサンプル一覧("canvas" で検索)から入手できます。
QCPainterBench を使用したベンチマーク
もし前述の Canvas 2D Tester サンプルだけでは Canvas2D の性能に納得できないのであれば、QCPainterBench が参考になるかもしれません。このベンチマークアプリケーションには、新たにQuick Canvas(従来版)とCanvas2D(新実装)のバックエンドが追加されました。以下の動画では、この 2 つの性能差を確認できます:
すべてのテストを 16 回ずつ描画する条件で、筆者のノート PC ではQuick Canvas: 4 FPS、Canvas2D: 165 FPSという結果になりました。つまり、Canvas2D は Canvas よりも 40 倍以上高速 という結果になります。
重要なのは、両者で使用している JavaScript コードが完全に同一であることです。したがって、この性能差はアプリケーションコードの違いによるものではありません。Canvas2D がアニメーションコンテンツ向けにより最適化されていることによって生じています。このように、QML の JavaScript から高性能な描画を実現できることで、多くの新しい可能性が開かれます。
現在の状況と今後の予定
Canvas2D は Qt 6.12 で新たに導入された機能であり、現在は Technology Preview(技術プレビュー) の段階にあります。そのため、API は今後、実際の利用状況やフィードバックを通じて得られた知見に基づいて変更される可能性があります。また、これまでのリリースと同様に、今後さらに性能が向上していく可能性も高いと考えられています。
ぜひ Canvas 2D Tester を試してみてください。そして、実際にターゲットハードウェア上で動作を確認し、既存の Canvas アイテムの一つを Canvas2D に移植してみてください。その結果について、ぜひQt bug tracker または forum からフィードバックをお寄せください。API がまだ Technology Preview の段階にある今だからこそ、皆さんからのフィードバックが将来の正式 API の形を決定する重要な材料になります。
次回のブログ記事では、Canvas2D を利用した具体的なアプリケーション例を紹介する予定です。その際には、Canvas2D だけでなく Qt Quick Effects や Quick3D も組み合わせて使用します。それでは、次回の記事でお会いしましょう。