canvas要素の基本的な使い方まとめ

written by DEFGHI1977

本文書はsvg要素の基本的な使い方まとめの姉妹版として作成を開始しました. canvas仕様そのものの解説のみならず, その使い途についていろいろ言及しています. なお, 筆者の思い込みが混入していたり, まだ使えない機能等満載(そのためスクリプトエラーが発生する場合もあります)だったりと, 内容に間違いがあっても中々検証することができません. またいつの間にか随分なコード量になってしまってえらくページロードに時間がかかります. その部分を了承した上でご利用下さい…

更新履歴

canvas要素の概観

canvas要素とは

canvas要素はWEBブラウザ上(クライアントサイド)で動的にグラフィックを描くための仕組みです. もともとApple社が自社製品の機能向上を目的に, WEBブラウザSafariの独自拡張として策定したものでした. が, その利便性が認識されるとMozilla(FireFox)やOpera等のブラウザベンダーも追随するようになり, HTMLにおける事実上の標準仕様として認識されるようになりました. この流れから, HTML5ではWHATWG/W3Cによって正式に仕様として取り入れられ, 現在Internet Explorerを含むほとんどのブラウザ上でcanvas要素が利用可能です.

canvas要素の役割

canvas要素はグラフィックの入出力及び分析を可能とするノードです. canvas要素に描かれたグラフィックはimg要素で表示している画像と同等に扱われるほか, 画像ファイルとして外部に取り出すことも出来ます. 一方canvas要素それ自身はグラフィック描画機能を持ちません. 実際に描画を行うのはcanvas描画コンテキスト(CanvasRenderingContext)オブジェクトであり, このオブジェクトが提供するAPIをJavaScriptから操作することでcanvas要素にグラフィックが描かれます. なおコンテキストオブジェクトは描画したい内容(2D/3D)に応じて選択可能です.

canvas要素を取り巻く環境

img要素との違い

その仕組みから「動的なimg要素」としての側面を持つcanvas要素ですが, 完全に置き換え可能なわけではありません. 例えばクリッカブルマップを定めるusemap属性はcanvas要素には存在しません. 同様にWEBブラウザの設定が「画像の表示が無効」となっていても, canvas要素の表示には影響しません.

canvasの歴史

canvas要素の歴史は意外に古く, WebKit(Safari)では2004年に, Gecko(FireFox)では2005年に導入されています. そこから6年後の2011年にはTrident(Internet Explorer)でも利用可能となりました. また徐々にAPIが拡張されてきたことから, W3C仕様を遡ることでAPIの変遷が見て取れます.

このようにcanvas要素に関わる文献はWEB上で沢山見つかります. が, 旧世代ブラウザ向けのノウハウや, パフォーマンスの改善・バグの修正, APIセットの名称変更など様々な面で変化が起こっており, (本文書を含め)大抵の場合記事の内容が古くなっています. 参考とする場合はその記事が掲載された時期に注意して下さい.

canvas要素と周辺技術

canvas要素はWEB環境における標準的なグラフィック加工インターフェースとしての役割を担っていて, それ自身使い途がありますが, HTML5を取り巻く様々なWEB周辺技術と組み合わせることでよりその価値が高まります. 例えばWEBカメラで撮った映像をcanvas要素で加工し, それを動画として出力すると言ったことすら可能ですし, WebVRにおける立体映像の出力にはcanvas要素(WebGL)を要します.

canvas要素を取り巻く技術

canvas要素のすすめ

canvas要素はOSやWEBブラウザの種類に依らず動作することから, 無償で得られる画像編集環境としては最も導入障壁の低いものです. また, スクリプトによる処理結果が見た目に直接反映される点や, 画像処理に必要となる(I/Oやグラフィック)機能セットが一通り揃っていることから, JavaScriptや画像処理の入門に最適と言えます. また, 作成したアプリケーションをWEBサーバー上に配置すればそのままWEBアプリケーションとして公開できるなど, その応用範囲はかなりのものです. まずは試してみて, その懐の広さを確かめて下さい.

canvas要素の利用が可能な環境

canvas要素は現在ほとんどのブラウザ(FireFox, Chrome, Opera, Safari, Edge, Internet Explorer9以降)で利用可能です. とは言えHTML5と同様にcanvas仕様も日々変化しており, 仕様にあるからと言っても全ての機能が使えるわけではありません.

canvas要素とレンダリングエンジン

canvas要素によるグラフィックはレンダリング(レイアウト)エンジンの特性により描画の差が発生します. そのためブラウザ横断的に(データレベルでの)完全な互換動作を求めるのは得策ではありません. また, 利用できるAPIにも大きく異なる部分があるため, スクリプトの動作検証は注意深く行う必要があります.

ブラウザごとに内部で利用しているレンダリングエンジンについてまとめます.

ブラウザとレンダリングエンジンの相関
主要ブラウザFireFoxChromeInternet
Explorer
Edge
(Windows)
SafariOpera
(12以前)
関連ブラウザicecat
cyberfox
seamonkey…
chromium
opera
vivaldi
Edge(android)…
epiphany(Web)
Edge(iOS)
レンダリング(レイアウト)エンジンGecko†1Blink
(WebKit後継†2)
TridentEdgeHTML†3
(Trident後継†4)
WebKit2
(WebKit後継†5)
Presto
参考)スクリプトエンジンspidermonkeyV8chakrajavascriptcorecarakan
†1 FireFox53以降はServo(実験的なレンダリングエンジン)における成果を取り込んでおり(Project Quantum), 本バージョンを境にプロセス分離や旧アドオン向けAPIの廃止等の多くの機能が順次追加・変更されています. そのため細かい部分でcanvas要素の動作に影響を及ぼしている可能性があります.
†2 BlinkエンジンはWebKitエンジンのブランチですが, 現在のWebKit2とはほぼ別の内容になっています.
†3 Windows版. iOS版ではWebKit2, android版ではBlinkエンジンを採用しています.
†4 Tridentエンジンを基に挙動を原則Safari(WebKit2エンジン)と同等とすることを目標としています.
†5 WebKitの採用を謳ったブラウザには様々なもの(midori等)がありますが, プロセス分離が為されていない所謂WebKit1エンジンはメンテナンスが滞っており機能の追加・改善は見込めません.

canvas要素のポリフィルライブラリ

また, レガシーIE(6〜8)についても, 擬似的にcanvas要素に対応させるJavaScriptライブラリが公開されています.

なおベンダーによるブラウザサポートはIE11を除き全て終了したため, これらのライブラリもその役割を終えました.

Node.js環境でのcanvas操作

WEBブラウザを介さず直接JavaScriptを実行可能とするNode.jsでは, node-canvasモジュールを導入することでcanvas要素とほぼ同等の機能をもつCanvasオブジェクトが利用可能になります. これによりサーバーサイドでの画像生成やローカルにおけるグラフ作画においてもcanvas APIを利用できます.

canvas要素に対応したスクリプトライブラリ

canvas要素に対してはその柔軟性故に多くの個性的なJavaScriptライブラリが公開されています. これらを適切に選択することでcanvas要素に対する作業を大幅に省くことが出来ます. なお, ライブラリを利用する場合にもcanvas APIについて目を通しておくと, 自ずと内部でどのようなことを行っているかが判るため理解が深まります.

ライブラリを大まかに分類すると次の3つにわけられます.

複数のライブラリを組み合わせて利用する場合は, その相性を鑑みて動作検証は注意深く行って下さい.

canvas要素に関連するライブラリ

なお, canvas APIを参考としたライブラリも存在します.

また, canvas要素の持つ画像処理機能と競合するライブラリもあります.

動作検証に関わる準備

ブラウザによっては, ローカル環境上ではcanvas要素の一部の機能を利用することができません. 例えばローカル画像を描画した後にtoDataURLメソッドを呼び出すとセキュリティエラーが発生することがあります. 最も確実な対処策は動作検証用のWEBサーバーを構築し, そこにcanvasスクリプトを配置することですが, それが出来ない場合はWEBブラウザの設定を変更することで対処します.

上記設定を施した環境ではセキュリティリスクが増加しています. その為, ローカルに保存したHTML文書を開く際はその配布元や内容が信頼に値するかについて注意して下さい.

検証中機能の有効化

また, APIによっては専用のフラグを有効化しないと動作しないものがあります. これらは仕様や動作の検証中の機能なので, テストの目的以外では利用しないで下さい. うっかりフラグ値を変更していることを忘れていると, 一般使用不可なAPIに依存したスクリプトを記述してしまうかもしれません.

SVGとcanvas要素

canvas要素と同様にHTML5において動的なグラフィックを扱う技術としてSVG(Scalable Vector Graphics)が挙げられます. SVGはXMLによる二次元ベクタグラフィック記述言語であり, SVGで記述された内容は現在ほとんどのWEBブラウザで表示できます. 例を示します.

svgによるグラフィック
SVGグラフィックの例

SVGとcanvas要素の使い分け

SVGとcanvas要素とは明確な使い分けが可能です. 以下は「グラフィック上での動作に応じて内容を書き換える」ことをSVGとcanvas要素とで表現したものです.

SVGの適用場面

was clicked.

SVGではグラフィックの構造をDOMツリーとして記述することが可能で, 既存のイベントモデルやCSS等の仕組みを活用できます. このことから構造がしっかりとしているユーザーインターフェースやグラフ等の用途に向きます. また, 印刷を前提としたグラフィックについては出力結果が解像度に依存しないSVGの利用が無難です. 他方見た目が複雑なものや手続き的な処理が必要なものをSVG単体で表現しようとすると途端に実現が難しくなります.

canvas要素の適用場面

canvas要素にはグラフィックに構造を与えるものが少なく, 全ての処理を自分で記述する必要があります. 結果, 先ほどのSVGの例と比べてコードの記述量が増加しています.

その一方でcanvas要素には画像を操作するための低レベルなAPIが提供されており, 機能的な制約がほとんど存在せず柔軟性に富みます. そのためcanvas要素は, 派手な演出を要するゲームや画像の生成・加工等に最適です. また, 関数グラフ等の静的な表現が困難なケースにおいてはcanvas要素による描画が有効なことがあります.

canvas要素とSVGの連携

以上まとめるとSVGは「画像描画フレームワーク」であり, canvas要素は「画像描画API」であると言えます. このようにSVGとcanvas要素とは抽象化のレベルが異なるため, 組み合わせて利用することが可能です. 例えばcanvas要素で描いた内容をSVGに挿入すると言った工夫をすることで両者の恩恵に同時に与ることが出来ます.

補足)SVGとの違い

実際の処, SVGとcanvas要素の違いはベクタグラフィックを書き下す際の記法の違い(XML:宣言的な記法とJavaScript:続き的な記法)に過ぎず, 出力結果に明確な違いはありません.

このことから, かつてHTML5にグラフィカルな要素を導入する際にSVGとcanvas要素のどちらを採用するかの論争がありました. 以下は(蛇足ですが)当時(2010年前後)のベンダ毎の立ち位置です.

canvas推進派中立SVG推進派
AppleMozilla, GoogleMicrosoft, Opera

結局両方採用することで落ち着きましたが, 後々のユースケースの分化・連携を鑑みるに最善の選択が為されたと言えるでしょう.

補足)パフォーマンスの観点から見た比較

同様にパフォーマンスの面からcanvas要素とSVGとを比較する議論が見受けられますが, これは全く無意味です. なぜなら, canvas要素もSVGも結局はブラウザのレンダリングエンジンを介してグラフィックを描くため, 本質的な描画パフォーマンスに違いがないからです. ですから, APIセット等の特性を鑑みてどちらがより要件に適しているかを検討すべきです.

とは言えパフォーマンスに関わる傾向はあります. canvas要素ではスクリプトコードの品質がそのまま動作パフォーマンスに結びつくため, 適切に処理を吟味することで大きく処理効率を改善できる余地があります. SVGではグラフィック描画に伴う一連の処理をブラウザ側に任せるため, 常に最適に近いパフォーマンスが得られます. その一方でひとたび劣化したパフォーマンスを改善するには, ブラックボックス化しているブラウザ側の描画処理を意識せねばならず, 自ずとその範囲は限られます.

canvas要素によるラスタ画像の描画

canvas要素を取り巻くオブジェクト群

canvas要素はJavaScriptに対する画像描画APIと考えられます. 以下はcanvas要素を取り巻くオブジェクトの相関を表しており, 黄色いものはcanvas要素仕様に直接関わるものです. グラフィックの入出力まで含めると, 非常に多くのオブジェクトと関わりを持つことが判ります.

canvas要素を取り巻くオブジェクトの相関

canvas要素の基本構造部分

CSSに関わる部分

グラフィック描画を扱う部分

画像入出力に関わる部分

バイナリデータ操作に関わる部分

バックグラウンド動作に関わる部分

ストリーミング出力(WebRTC)に関わる部分

補足)利用可能なAPIについて

なお, 環境によってはここに記載されているAPIの全てが利用可能というわけではありません とりわけInternet Explorerでは(ライフサイクルの観点から)利用可能なオブジェクトやAPIに大きな制約が発生しています. 以下のテーブルはこのブラウザで利用可能なオブジェクトを示しており, ピンク色の機能は利用できないことを表しています.

オブジェクト・APIのサポート状況
CanvasPixelArrayUint8ClampedArrayPath2D
ImageBitmapImageBitmapRenderingContext
atobbtoarequestAnimationFrame
WorkerBlobURL
メソッドのサポート状況(HTMLCanvasElement)
toBlobmsToBlobtransferControlToOffscreen
メソッドのサポート状況(CanvasRenderingContext2D)
drawFocusIfNeededaddHitRegionscrollPathIntoView

グラフィック描画の流れ

canvas要素によるグラフィックの描画は単体では動作せず, JavaScriptから先程示したオブジェクトを操作することで行われます. 以下にその処理の流れについて示します.

  1. canvas要素を記述する(事前準備)
    HTMLソースコードのグラフィックを描きたい箇所にcanvas要素を記述します. img要素と同じようにレイアウトして構いません. なお直接スクリーンにしないのであれば必要ありません.
  2. canvas要素を入手する
    document.createElement("canvas")を用いてカンバス要素を生成するか, 既存のカンバス要素をdocument.getElementById / getElementsByTagName / querySelector / querySelectorAllメソッド等で取得します. 得られるオブジェクトはHTMLCanvasElementオブジェクトです. なお, createElementメソッドによってcanvas要素を生成した場合はその要素をDOMツリーに挿入せず, そのままメモリ上でグラフィックを描く事もできます.
  3. canvas要素のサイズを設定する
    widthプロパティとheightプロパティを使って画像サイズを設定します. canvas要素そのものに属性値が設定されている場合はこの処理は要りません.
  4. コンテキストオブジェクトを取得する
    getContextメソッドを使ってコンテキストオブジェクトを取得します. コンテキストは一般に「文脈」と訳されますが, canvas要素においては絵を描くための「道具」を表します.
  5. コンテキストオブジェクトを介してグラフィックを操作する.

canvas要素を記述する

HTML文書中で動的にグラフィックを描きたい箇所にcanvas要素を記述します. canvas要素に指定可能な属性は以下のとおりです.

width
canvas内部のグラフィック幅を指定する.
height
canvas内部のグラフィック高を指定する.
id,class,style,title,tabindex,dir,contenteditable…
HTMLの要素共通の属性. idはHTML文書内で一意の値(重複を許さない)とする.

canvas要素はレイアウト上img要素と同様に振る舞う(フローコンテンツ)ため, CSSを用いて自由に配置・装飾することが出来ます. その際, 後でスクリプトが目的のcanvas要素を検索できるようにid属性やclass属性を定義しておくと良いでしょう.

canvas要素はグラフィックを表すので, JavaScriptが無効化されている場合やテキストブラウザ, スクリーンリーダー環境などのためにフォールバック(代替)コンテンツ指定しておきましょう.

canvasタグ記述上の制限

canvas要素はフォールバックコンテンツを必要とするので, img要素のように単一タグで記述することは出来ません. さもないとcanvasタグ以降が全て非表示となる現象が発生します. (なおXHTMLの場合は問題ありません)

またid値によるcanvas要素検索に失敗する場合, 文書内に同一のid値を持つノードが他に存在していないか確認して下さい. HTMLコードをコピー・ペーストした場合にしばしばこの問題が発生します.

スクリプトの記述

canvas要素にはスクリプトが必要です. スクリプトコードを記述・配置する場所に特に制限はありませんが, canvasの描画処理はHTMLCanvasElementのインスタンスが入手可能となるDOMの解析が終了したあと(つまりdocumentのDOMContentLoadedもしくはwindowのloadイベント)に登録するのが一般的です.

HTMLCanvasElementオブジェクトの入手とサイズの決定

グラフィック描画処理を開始するには, まず描画対象のcanvas要素に対応するHTMLCanvasElementオブジェクトを入手します. これはgetElementByIdメソッドなどを使ってDOMツリー内のものを参照しても, createElementメソッドで新たに生成しても構いません.

続けて, 得られたオブジェクトに対しグラフィックのサイズを指定します. この時canvas要素にwidth,height属性が含まれている場合はこの処理を省くことが可能です.

HTMLCanvasElement.width
canvasのグラフィック幅. width属性値に対応する(DOMに反映される). グラフィック描画後に値を変更すると, グラフィックやスタイル設定が初期化される. width属性未設定の場合300(ピクセル)として扱われる.
HTMLCanvasElement.height
canvasのグラフィック高さ. height属性値に対応する(DOMに反映される). グラフィック描画後に値を変更すると, グラフィックやスタイル設定が初期化される. height属性未設定の場合150(ピクセル)として扱われる.

なお, この値はCSSによる表示上のサイズとは異なるものです. 詳しくは後述します.

なお, 全ての画像処理を手続き的に記述することとなるので, 共通機能をサブファンクション化したり外部ライブラリ化するなどしておくと, コードの管理がやりやすくなります. 特にサーバー側で取得したパラメータを元にクライアントサイドでcanvasグラフィックを描く場合は, その受け渡し方法をよく検討して下さい.

canvas要素とscript要素をまとめる

canvas要素とscript要素とを組み合わせて管理したい場合, canvas要素直下にscript要素を配置する方法があります. この場合, document.currentScriptプロパティからスクリプトが記述されているscript要素を取得し, そこから操作対象のcanvas要素を入手します.

これはSVGにきわめて近い構成であり, canvasグラフィックの再利用が容易になります.

補足)loadイベントと履歴操作

参考:suikawiki:PageTransitionEvent

WEBブラウザの中にはページを移動した際にWEBページの出力結果をセッション履歴としてキャッシュするものがあります. このキャッシュは履歴を遡る操作(例えば戻るボタンの押下)を行った際に再レンダリング処理を高速化する目的で用いられます. その際, windowオブジェクトはloadイベントを発生させない筈ですが, 特定の条件下ではキャッシュ機構が上手く働かずにloadイベントを発生させてしまうことがあります.

この動作がcanvasグラフィックに悪影響を及ぼす場合は, 次のように対処します.

補足)canvas要素の最大サイズ

出典:Maximum size of a <canvas> element※2014年当時の調査結果

ブラウザ毎にcanvas要素がとりうる最大サイズを下記に示します.

ブラウザ別canvas最大サイズ
最大幅/高最大画素数メモリ換算(1画素あたり4byteと仮定)
Chrome32,767268,435,456 (16,384 × 16,384)1GB
FireFox32,767472,907,776 (22,528 × 20,992)2GB
Internet Explorer8,19267,108,864 (8,192 × 8,192)256MB
なお実際の値は環境・ページの動作状況毎に異なります.

現在考えられうる最大のスクリーンサイズは8K(7680 × 4320)であるため, 理論上はどのブラウザにおいてもフルスクリーンでのcanvasグラフィックの描画は可能ということになります. ですが, 動作パフォーマンスを考えると出来る限りスクリーンに表示するカンバスのサイズは小さいほうが良いでしょう. またアニメーションのコマ送り画像やスプライト画像をcanvas要素に展開する場合は, 上記のサイズに収まるように工夫します.

canvas要素の描画モードとコンテキストオブジェクト

canvasサイズが決まったら, 次に描画モード(グラフィックの描画手段)を決定します. 描画モードには主に2dwebglwebgl2bitmaprendererplaceholderの5つがあり, getContextメソッドで選択します.

getContextメソッドを実行すると, 与えた引数の内容からcanvas要素の描画モードが定まり, 対応するコンテキストオブジェクトが返されます. このコンテキストオブジェクトが提供するAPIを使ってグラフィックを描いていきます. なお, HTMLCanvasElementが実装している機能(例えばtoBlobメソッド)はコンテキストオブジェクトの種類に依らず利用可能です.

HTMLCanvasElement.getContext(type [,arguments])
canvas要素の描画モードを決定し, グラフィック描画を行うためのコンテキストオブジェクトを取得する.
第1引数でグラフィックを描く際の手段(APIセット)を選択する. なお, コンテキストオブジェクトはcanvas要素につき唯一である. また, 誤ったキーワードを指定した場合, ブラウザが対応する機能を持たない場合はnullが返される.
  • 2d…2dモード/CanvasRenderingContxt2D
    2次元ベクタ(ラスタ)グラフィックを描くための標準的なAPIセットを提供する. グラフィックを描くための必要最低限度の機能を提供しており, 通常canvas要素によるグラフィックといえばこれを指す. 多くのマイナーチェンジを経るもcanvas要素の登場時から基本の部分は変化していない. 本ドキュメントの解説対象.
  • webgl(experimental-webgl)…webglモード/WebGLRenderingContext
    OpenGL ES 2.0による3Dグラフィックを描くためのAPIセットを提供する. experimental-webglはWebGLの技術検証中であることを示す. GPUにアクセスするためかつては動作環境が限定されていたが, 多くのブラウザがサポートを表明したことから現在では幅広く利用されている.
  • webgl2(experimental-webgl2)…webgl2モード/WebGL2RenderingContext
    OpenGL ES 3.0による3Dグラフィックを描くためのAPIセットを提供する. webglモードの上位互換であり, 条件付きながら動作する環境が増えている.
  • bitmaprenderer…bitmaprendererモード/ImageBitmapRenderingContext
    ImageBitmapオブジェクトを直接canvas要素に描画するためのAPIセットを提供する. ビットマップデータの描画・削除のみをサポートする.
  • この他, 独自のコンテキストオブジェクトを定義しても良い. 従ってgetContextメソッドをオーバーライドすることで, 独自のAPIを備えたコンテキストオブジェクトを自作することも可能.
第2引数にはコンテキストオブジェクトに対する設定値(オブジェクト形式)を指定する.
getContextメソッドに渡すパラメータ一覧
種類キー意味
2dalphatrue/falseアルファチャンネルの有効/無効
storagepersistent/discardable(Blinkのみ)コンテキストの強制開放の有無→ブラウザ環境の保護
willReadFrequentlytrue/false(Geckoのみ)ImageDataの読み書きを多量に行う際の省メモリ化の有無を指定.
※要フラグgfx.canvas.willReadFrequently.enable設定
colorSpacesrgb/rec2020/p3canvas要素を扱う上での色空間を指定する. 未指定の場合srgbが選択されたものとされる. コンテキストオブジェクトの動作に横断的な影響を及ぼす.
pixelFormat8-8-8-8/10-10-10-2/12-12-12-12/float16色成分毎に割り当てるbit数
linearPixelMathtrue/falsetrueを設定するとガンマ補正を行わない. 規定値はfalse.
webglalpha/ depth/ stencil/ antialias/ premultipliedAlpha/ preserveDrawingBuffer/ failIfMajorPerformanceCaveat
HTMLCanvasElement.probablySupportsContext(type [,arguments])
指定した描画モードをブラウザがサポートしているかを判定する. whatwg仕様からは一旦削除されたが, canvas要素の機能拡張に伴い再検討の可能性がある.

コンテキストオブジェクトからcanvas要素を参照する

コンテキストオブジェクトのcanvasプロパティにはこのコンテキストを生成したcanvas要素が設定されています.

ctx.canvas
このコンテキストを生成したcanvas要素.

描画モードの確定

一度確定したcanvas要素の描画モードを後から変更することはできません. getContextメソッド呼び出しに初回と別のキーワードを指定した場合, nullが返されます.

コンテキストオブジェクトの唯一性

getContextメソッドで得られるコンテキストオブジェクトはcanvas要素につき唯ひとつです. 下のようにコード上は2つのオブジェクトが存在しているように見えても実体は全く同じものとなります.

アルファチャンネルの無効化

canvas要素は当初透明な黒(rgba(0, 0, 0, 0))で塗りつぶされており, そこへアルファチャンネル(不透明度)を含めたグラフィックの合成が行われます. ここでコンテキスト取得時に{alpha: false}を指定すると自動的にアルファ値が1(255)に固定され, ピクセルデータは実体のある黒(rgba(0, 0, 0, 1))で充填されます.

生成する画像に透明部が必要ない場合は, このパラメータを明示することで(僅かながら)描画処理速度の向上が見込めます.

このように2dモードには更にアルファチャンネルの有効/無効という2つの描画モードがあると言えます. が, 単一のcanvas要素に対して何度もgetContextメソッドを呼び出し, モードを切り替えてはなりません. コンテキストオブジェクトを見ただけでは現在の描画モードが判らないだけでなく†, ブラウザごとに動作が異なるからです. 例えば次のコードはグラフィックのクリア処理においてFireFoxとChromeとで動作が異なります.

色空間の指定

コンテキスト設定のcolorSpaceパラメータには, canvas要素がグラフィック描画を行う際に用いる色空間を指定します. なお指定した内容によってはグラフィックを構成するサブピクセル値が8bitの範囲に収まらなかったり, 出力した画像データにカラープロファイル情報が付与されると言ったようにCanvasRenderingContext2Dオブジェクトの機能に横断的な影響を及ぼします. 詳しくはcanvas要素と色空間の項で解説します.

コンテキスト設定の確認

コンテキストオブジェクトを取得した際のパラメータはgetContextAttributesメソッドを使って取得します.

CanvasRenderingContext2D.getContextAttributes()
コンテキストを取得した際のパラメータ設定情報を取得する. alpha, colorSpace, linearPixelMath, pixelFormat値を取得可能. (現在Blinkのみ)

canvas要素における2つのサイズ

canvas要素にはその仕組み上, グラフィックそのもののサイズとグラフィックの見た目のサイズの2つのサイズが定義されます.

canvas要素における2つのサイズ
サイズの記法
 グラフィックそのものサイズグラフィックの見た目のサイズ
対応する記述width/height属性CSSのwidth/heightプロパティ
記述例
未設定時の扱い300×150グラフィックそのものサイズ
備考img要素ではnaturalWidth / naturalHeightとして取得

無限の広がりをcanvas要素で表現する

地図のようにcanvas要素上に描きたい対象が無限の(もしくは非常に大きな)広がりを持つ場合は, 全体をcanvas要素に描くのではなく, スクリーンに表示されうる範囲だけをcanvas要素に描くようにし, 適宜描画基準・スケールを変更することで無限の広がりを「表現」します. 具体例としてはGoogle Mapsがこの方針に沿い, 有限のcanvas要素上に逐次情報を描画することで広大な地図を表現しています.

width/height属性を指定しなかった場合の動作

canvas要素にwidth属性もしくはheight属性が指定されていなかった場合, 往々にして意図した出力結果となりません. 例えば次の例ではCSSで見た目のサイズを200px×200pxに設定しており, そのサイズでグラフィックを青色で塗りつぶそうとしていますが, 実際には300×150のカンバスに描いているため描画結果が狂っています.

見た目からcanvasサイズを算出する

画像サイズを固定出来ない場合, window.getComputedStyleメソッドを利用すると, グラフィックの大きさをcanvas要素の見た目の大きさに合わせることが出来ます.

これを応用すると, canvasグラフィックの大きさに50%と言った比率値を指定することもできます. 但しウインドウのリサイズ時にグラフィックの再描画を行うなどの工夫が必要となるでしょう.

DOM操作とcanvas要素

canvas要素に描画した内容は, canvas要素そのものの変更を伴うDOM操作の影響を受けます. なお, canvas要素の配置の変更, 子要素の編集と言った操作の影響は受けません.

widthプロパティ, heightプロパティに対する操作

canvas要素に対してwidth / heightプロパティを操作すると, グラフィックを含むcanvas要素に関わる設定全体が初期化されます.

この挙動はしばしばグラフィックのクリアに用いられます. また, 意図せずグラフィックがクリアされてしまう場合はこのサイズ変更操作が原因の場合があります. なお, canvas要素のサイズ変更操作はMutationObserverオブジェクトで検出可能です.

cloneNodeによるcanvas要素の複製

canvas要素はcloneNodeメソッドにより複製できますが, canvas要素に描いた内容までは引き継ぎません.

グラフィックの内容まで複製したい場合は後述するdrawImageメソッドを用います.

canvas要素を含む要素のinnerHTMLプロパティを操作する

この場合暗黙的にcanvas要素が再構成されるため, 内容がクリアされます.

出典:innerHTML clears the drawing canvas pixels.

この場合, 代替としてinsertAdjacentHTMLメソッドを用いましょう. 既存のDOM構成が維持されるため, canvasグラフィックに影響を及ぼしません.

描画処理の完了

canvas要素に対する操作は(一部を除き)全て同期的に行われるため, 通常はいつグラフィックが書き換わったかを意識する必要はありません. が, モーダルダイアログの表示(alertconfirm関数の実行)を行った際に描画処理が遅延しているように見えることがあります. これはスクリーンリフレッシュのにダイアログが表示されたことで, canvasグラフィックのスクリーンへの反映が待ちの状態になっているからです.

canvasグラフィックがスクリーンに表示されてからダイアログを表示させたい場合は, alert関数等を非同期で呼び出すようにします.

環境の確認

現在では少なくなりましたが, canvas要素に対応していないブラウザに対処するために処理の振替を行うトラップコードを用意しておくと良いでしょう. この場合CanvasRenderingContext2Dの実装状況を確認します.

CanvasRenderingContext2Dの拡張

canvas要素を利用するにあたり, よく使う処理は共通ライブラリとして使い回したいものです. その際, 事前にCanvasRenderingContext2D.prototypeオブジェクトに関数を定義しておくことで, getContextメソッドで取得したコンテキストオブジェクト全てで利用可能となります.

既存APIの拡張

同様にCanvasRenderingContext2Dがもつメソッドそのものを拡張することも可能です. 下の例ではarcメソッドの角度の指定を省略した場合に円を引くように機能を拡張しています.

Object.getOwnPropertyDescriptorメソッドをサポートする環境ではプロパティを拡張可能です. 下の例ではfillStyleプロパティを拡張しています.

メソッドチェーンの実現

canvas要素の操作は往々にして冗長になりがちですが, メソッドが自分自身を返すように機能を拡張しておくと, jQueryのようにメソッドを連続して呼び出すこと(メソッドチェーン)が可能となります.

図形の描画

canvas要素における座標系

canvas要素における座標系は通常のCSSにおける座標系と同じです. つまり単位をpx(ピクセル)にとり, 左上を(0, 0), 右下を(width, height)とする座標系です. canvas APIは全てこの座標系を元にグラフィックを描きます.

また座標値は単精度(32bit)浮動小数点数で管理されており, 極端に大きな値や小さな値を指定した場合に誤差を生じます.

基本的な図形描画処理の流れ

CanvasRenderingContext2Dオブジェクトを用いた図形描画の流れは次の通りです.

  1. beginPathメソッドにより描画範囲の開始を宣言します. なお省略すると暗黙的に実行されたことになります.
  2. moveToメソッドによりパスの起点を指定します.
  3. lineTo, arcTo, quadraticCurveTo, bezierCurveToメソッドによりパス切片を追加して行きます.
  4. 必要に応じてclosePathメソッドを実行し, パス図形を閉じます.
    これらのメソッドを必要な回数繰り返します. これらの図形は次にbeginPathメソッドが呼び出されるまで内部に保持されます.
  5. コンテキストオブジェクトにスタイルを設定します.
    これは下記のメソッドにおける色や形の指定, 画像合成の方法を指示するもので, パス図形の定義処理と順番は問いません.
  6. strokeメソッド, fillメソッドを用いてパス図形を描画します.
    現在コンテキストが内部に保持しているパス図形に沿って線の描画, 領域の塗り潰しが行われます. なおこれらの描画処理は画像領域に上書き(合成)していくため, 処理を取り消すことはできません.
図形描画の流れ

この処理セット必要な分だけ繰り返し目的のグラフィックを描いていきます. 例を示します.

図形描画のやり直し

このようにcanvasでの描画は基本的に図形を上に逐次重ねがけしていきます. そのため一部の描画の内容を変更する(例えばグラフィックを動かす)と言った場合は, 図形定義を含め処理全体を最初からやり直す必要があります. つまり, HTMLやSVGではブラウザが暗黙的に行っている処理をcanvas要素では自分で実装しなければなりません. これは一見面倒ですが, グラフィック描画処理の全てをスクリプトの制御下におけるというメリットでもあります.

図形の開始宣言

beginPathメソッド実行すると, それまでのパス図形(後述)を破棄し新たなパス図形を定義します.

CanvasRenderingContext2D.beginPath()
パス図形を初期化し, パス図形の開始を宣言する.

このメソッドを実行せず図形の描画処理を行った場合, 暗黙的に呼び出したものとして扱われるため, どこから新しいパス図形かがわからなくなります. 下記は途中のbeginPathメソッドの実行を忘れてしまった例で, 先ほどと異なり一連の図形が一つのものとして扱われています.

パス図形に対する描画

fill及びstrokeメソッドはパス図形に沿ったグラフィックを描きます.

CanvasRenderingContext2D.fill([path2d],[fillRule])
現在のパス領域を塗りつぶす. Path2Dをサポートしている場合, 引数に渡したパスデータの領域を塗りつぶす. fillRule…塗り潰し方法の指定 nonzero/evenodd
CanvasRenderingContext2D.stroke([path2d])
現在の領域の境界に線を引く. Path2Dをサポートしている場合, 引数に渡したパスデータの領域を塗りつぶす.

この操作はスクリプト側で自由に順番を変更でき, 様々な表現が可能となっています. 例えばstrokeによる境界線の上に塗り潰しを施すことでHTMLにおけるborderのような描画を行うこともできます.

パス図形の定義

パス図形とは, 直線や曲線をつなぎあわせたもので, 図形を描く(stroke, fillメソッドを実行する)際の基準を定めます. また, パス図形を構成する直線や曲線をパス切片と呼びます. 下記にcanvas要素におけるパス切片を定義するメソッドを示します.

パス切片・図形の定義メソッド群

canvas要素ではこれらのメソッドを数珠つなぎで実行することで一連なりのパス図形を表現します. 予めmoveToメソッドで起点を指定し, 各メソッドを実行すると引数として与えた終点座標まで線が引かれます. この終点が次のメソッドにおける始点となるのです. なおパス図形はとび石状に複数の図形(複合パス)から構成されることもあります.

図形の近似と精度

パス図形はベクタデータです. 従ってピクセル画素の境界にまたがるような図形に対してペイント処理が行われると, アンチエイリアス処理によって見た目が最適になるよう境界部の色味が調整されます. またパス図形は理論上いくらでも細分化できますが, canvas要素においては1ピクセル以下の誤差は目に見えませんからそこそこの粒度・精度があれば十分です.

始点の指定:moveTo

各種パス切片を定義する際の始点を指定するメソッドです. 直線にしろベジェ曲線にしろ, まずはこのメソッドで始点を指定します. なお, 省略すると暗黙的に呼びだされたものとして扱われます.

CanvasRenderingContext2D.moveTo(x, y)
パス切片の起点を指定する. x,y…起点の座標

直線:lineTo

前回の終点から引数に指定した終点まで直線(有向線分)を引きます.

CanvasRenderingContext2D.lineTo(x, y)
現在位置から指定した座標まで直線(線分)を引く.

繰り返し実行することで多角形を表現出来ます.

2次ベジェ曲線:quadraticCurveTo

方向点と終点を与え現在位置から2次のベジェ曲線を引きます. ベジェ曲線はざっくりといえば方向点の向きに線を引っ張ることで曲線を表すもので, 方向点とアンカー(始点や終点)とを結んだ直線(方向線)がベジェ曲線に接します. 媒介変数を用いることで簡潔に記述できることから, コンピュータグラフィックでは環境を問わず標準的に用いられています.

CanvasRenderingContext2D.quadraticCurveTo(x1, y1, x, y)
2次ベジェ曲線を引く. x1,x2…ベジェ曲線の方向点座標, x,y…終点の座標

2次ベジェ曲線の媒介変数表現

2次ベジェ曲線は媒介変数tを用いると次のように記述できます.

\begin{cases} x(t) = (x_3-2x_2+x_1)t^2+2(x_2-x_1)t+x_1\\ y(t) = (y_3-2y_2+x_1)t^2+2(y_2-y_1)t+y_1 \end{cases}

上記の関係式のもと, tの値を徐々に変化させた際に得られた点の軌跡が求めるべきベジェ曲線になります. また, この関係式はベジェ曲線に接する直線の傾きを求める際に便利です.

3次ベジェ曲線:bezierCurveTo

始点と終点に対する方向点2つを与え3次のベジェ曲線を引くものです.

CanvasRenderingContext2D.bezierCurveTo(x1, y1, x2, y2, x, y)
3次ベジェ曲線を引く. x1,y1…始点に対する方向点, x2,y2…終点に対する方向点座標, x,y…終点の座標

3次ベジェ曲線の媒介変数表現

3次ベジェ曲線は媒介変数tを用いると次のように記述できます.

\begin{cases} x(t) = (x_4-3(x_3+x_2)-x_1)t^3 + 3(x_3-2x_2+x_1)t^2 + 3(x_2-x_1)t+x_1\\ y(t) = (y_4-3(y_3+y_2)-y_1)t^3 + 3(y_3-2y_2+y_1)t^2 + 3(y_2-y_1)t+y_1 \end{cases}

角の丸め:arcTo

2直線に接する円弧を描画します. 直感的には角の丸めであり, 引数としては座標(交点と方向点)と円の半径を与えます. すると始点-交点と交点-方向点という2直線が定まるので, これらに接する円を求めます. そこで, arcToは現在位置からまず円の接点まで線を引き, 続いてもう片方の接点まで円弧を引き処理を終了します. なお円弧は短い方が選択されます.

arcToメソッドの図解
CanvasRenderingContext2D.arcTo(x1, y1, x2, y2, radius)
角を丸める. x1,y1…交点の座標, x2,y2…方向点の座標, radius…円弧の半径
CanvasRenderingContext2D.arcTo(x1, y1, x2, y2, rx, ry, rotate)
arcToメソッドの拡張. 楕円弧で角を丸める. 動作する環境はまだない.

このメソッドは角丸四角形を定義する場合に便利です.

arcToメソッドが失敗する例

逆に角丸四角形以外の図形定義にarcToメソッドは使わないで下さい. 本メソッドは当初から角丸四角形への応用のみが念頭に置かれ, 仕様の詳細化を蔑ろとしたことでレンダリングエンジン毎に深刻な動作の非互換をもたらしました. その反省から現在ではバグをも含めWebKitでの動作を事実上の標準(仕様)としており, 使い方によっては何れの環境でも等しく描画処理に失敗します.

パスを閉じる:closePath

closePathを実行すると直近のmoveToメソッドで指定した座標まで直線が引かれ, 図形が閉じられます.

CanvasRenderingContext2D.closePath()
パス切片を閉じる.

逆にclosePathメソッドを指定しなかった場合, 開いた図形となります. fillメソッドはclosePathを実行した際の領域を塗りつぶします.

名称が対照的であるため勘違いしやすいのですが, beginPathメソッドとclosePathとの間に直接的な関連は存在しません.

直線に対するclosePath

単一のlineToメソッド実行直後にclosePathメソッドを実行すると, パスの終了位置を初期座標に戻すことになります.

矩形と円弧の定義

先程の4つのメソッドでほとんどの図形は定義可能ですが, よく使う矩形(四角形)と(楕)円弧の定義については専用のメソッドが提供されています. いずれも, (beginPath〜)moveTo〜closePathまでの一連の操作を一括で指定できるようにしたマクロ的な動作をするため, パス図形の中に組み入れて利用することが出来ます.

矩形:rect

rectメソッドでは四角形を左上の頂点と幅と高さの4つの値で指定します. rectメソッドの終点は左上の頂点となるので, 後続のlineToメソッド等はこの頂点を基準に線を描画します.

CanvasRenderingContext2D.rect(x, y, w, h)
矩形の生成. x,y…矩形の左上頂点の座標, w…矩形の幅, h…矩形の高さ

パラメータの符号とパスの向き

rectメソッドで引かれるパスは時計回りとなります.

width値とheight値の何れかに負の値を指定した場合は反時計回りとなります.

円弧:arc

arcメソッドでは円/円弧を定義します. 中心の座標と半径, 円弧の開始角と終了角(単位ラジアン)を指定します. 角度の基準は中心から右のラインを0度とし, 時計回りに角度を算出します. anticlockwiseフラグにtrueを指定すると, 円弧の定義を反時計回りに行います. なお, 既にパス定義が存在している場合は, 弧の始点まで直線が引かれます.

arcメソッドの動作
CanvasRenderingContext2D.arc(cx, cy, radius, startAngle, endAngle, anticlockwise)
円弧の生成. cx,cy…円弧の中心座標, radius…円弧の半径, startAngle,endAngle…円弧の開始/終了ラジアン, anticlickwise…反時計回りフラグ(trueで半時計回り)

扇型の定義

扇形を定義する場合は, 一旦円弧の中心にmoveToメソッドで起点を移し, arcメソッド実行後closePathメソッドを実行します.

円の定義

真円を描く場合は角度に0〜Math.PI*2を指定し, closePathメソッドでパスを閉じます. なおパスを閉じないと厳密な意味での円とはなりません. (lineCapのスタイルに影響します)

なお, 開始角と終了角との差の絶対値が2πを超えると円弧の向きによって動作が変化します.

円弧の追加と暗黙のlineTo

明示的にbeginPathメソッドが指定されていない場合, arcメソッドは暗黙的にbeginPathを実行し, 始点を円弧の開始点としますが, 他のサブパス生成処理の後にarcメソッドを実行すると円弧の始点まで勝手に直線を定義してしまいます.

楕円弧:ellipse

ellipseメソッドは楕円・楕円弧を定めます. 使い方はarcメソッドと同様ですが, パラメータが追加されています.

CanvasRenderingContext2D.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
楕円弧の生成. cx,cy…楕円弧の中心座標, radiusX,radiusY…楕円弧のx軸/y軸方向半径, rotation…楕円弧の回転角, startAngle/endAngle…楕円弧の開始/終了ラジアン, anticlockwise…反時計回りフラグ

楕円弧の制御

ellipseメソッドで得られる楕円弧はarcメソッドで得られた単位円弧を半径値(x軸方向, y軸方向)で拡大・縮小し, 傾き値で回転したものです.

例からもわかるように, 円弧を拡大する際に楕円弧の見た目の開始・終了角がパラメータで指定した角度から変化します. 見た目の角度に合わせるにはパラメータに渡す角度を逆算します.

パスの中抜きルール

通常, パス図形で囲まれた領域がfillメソッドによる塗りつぶしの対象となります. が, パス図形の定義の仕方によっては, 複数のパスで囲まれた領域が発生します. 従ってこのような領域を塗りつぶしの対象とするかどうかについてのルールを決めておく必要があります. canvas要素ではこのルールをfillRuleとして定めていて, その基準として領域とパス図形から求まる交差数を用います.

交差数とは, カンバス内の特定の位置から遠くの点(どこでも良い)に線を引き, パスと何回交差したかを表す値です. 但しパスには始点から終点への向きが定まっているので, 向かって左から右に交わった場合に交差数に1を加算し, 逆に右から左に交わった場合に1を減算します. すると, カンバス上の領域は全てこの交差数で分類できるのです.

交差数の算出
交差数の例

fillメソッドはこの交差数を元に塗りつぶす領域を判定します.

nonzero
交差数が0以外の領域を塗りつぶします. 直感的にはパスの向きが打ち消し合っている部分が中抜きされます. fillメソッド規定の動作です.
evenodd
交差数が奇数の領域を塗りつぶします. 直感的には偶数回重なっている部分は中抜きされます. SVGから輸入された仕組みで, 古い環境では動作しないことがあります.

nonzeroルールでの中抜き

単一のパス図形のパス切片が重なっていて, そのパス切片の向きが互いに逆向きであった場合, 交差数が0となるため塗り潰しの対象外となります. 例を示します.

evenoddルールでの中抜き

fillメソッドの引数として"evenodd"を指定することで交差数が偶数の領域が中抜きされます.

Path2Dオブジェクトによるパス図形の管理

canvas要素においてパス図形を管理することはこれまで面倒でしたが, 新たに追加されたPath2Dオブジェクトを用いることでこの問題が解決します.

Path2D()
Path2D(path)
Path2D([path], fillRule)
Path2D(pathString)
パス図形を表すオブジェクト. 引数として既存のPath2DオブジェクトPath2Dオブジェクトの配列,fillruleSVGパスデータ文字列の何れかを指定できます.

下の例では, Path2Dオブジェクトを使って図形定義を使いまわしています.

このように図形そのものをオブジェクトとして扱うことが出来るため, 図形の生成や再利用が容易に行えます. 以下はPath2Dオブジェクトを引数に渡すことが可能なメソッドの一覧です.

Path2Dを引数に取るメソッド群
fill(path, fillRule)
stroke(path)
clip(path, fillRule)
isPointInPath(path, x, y, fillRule)
isPointInStroke(path, x, y)
drawFocusIfNeeded(path, element)
scrollPathIntoView(path)
addHitRegion({path: path})

なお残念ながら, Path2Dオブジェクトの中身を操作する方法はありません. そのため, 図形の一部をずらすと言った編集用途には向きません.

パス図形の逐次定義

Path2DオブジェクトにはCanvasDrawingContext2Dオブジェクトのもつパス図形定義関数「closePath, moveTo, quadraticCurveTo, besierCurveTo, arcTo, rect, arc, ellipse」の全てが定義されています. 従ってコンテキストオブジェクトと同様にパス図形を逐次定義することが出来ます.

このようにコンテキストオブジェクトに対して発行していたメソッドの対象がpathに変更された点とfillメソッドの引数にPath2Dオブジェクトが追加されている他は全く変わりません.

なおPath2DオブジェクトにはbeginPathメソッドに相当するAPIを備えていません. 従って, パス図形を再定義する場合は新たにPath2Dオブジェクトを生成する必要があります.

SVGのパス文字列を用いたPath2Dの生成

SVGではパス図形をパスデータ文字列と呼ばれる形式で定義します. Path2Dオブジェクトの生成にはこの文字列を利用することが出来ます. パスデータ文字列を構成するコマンドの意味合いは次の通りです.

SVGのパスコマンド一覧
コマンド内容記述対応するコード
M/m始点に移動するM[x],[y]ctx.moveTo(x,y)
L/l直線を引くL[x],[y]ctx.lineTo(x,y)
H/h水平線を引くH[x]ctx.lineTo(x,[現時点のy座標])
V/v垂直線を引くV[y]ctx.lineTo([現時点のx座標],y)
C/c3次ベジェ曲線を引くC[x1],[y1] [x2],[y2] [x],[y]ctx.bezierCurveTo(x1,y1,x2,y2,x,y)
S/s3次ベジェ曲線を引くS[x2],[y2] [x],[y]ctx.bezierCurveTo([自動計算],x2,y2,x,y)
Q/q2次ベジェ曲線を引くQ[x1],[y1] [x],[y]ctx.quadraticCurveTo(x1,y1,x,y)
T/t2次ベジェ曲線を引くT[x],[y]ctx.quadraticCurveTo([自動計算],x,y)
A/a楕円弧を引くA[rx],[ry] [angle] [largeArcFlag] [sweepFlag] [x],[y]対応する関数なし
Z/zパスを閉じるZctx.closePath()
小文字のコマンドは現在位置からの相対座標として扱います.

先程の例を今度はパスデータ文字列を使って書き直してみましょう.

このように上手く使いこなせれば大幅にパス定義処理の記述を減らすことができます.

パス図形の複製

Path2Dオブジェクトの複製は複製したいパス図形をコンストラクタに渡します.

パス図形の合成

Path2Dオブジェクトは既存のPath2Dオブジェクトのリストを元に生成することも出来ます.

Path2D.addPathメソッドを用いると既存の図形に新たなパス図形を追加することが可能です.

Path2D.addPath(path, transform)
パス図形に他のパス図形を追加します. transformには行列データ(SVGMatrix, DOMMatrix及びそれに類するもの)を渡します.

またaddPathメソッドにはオプションとして変換行列を渡すことが出来ます. 引数に渡したパス図形はこの変換行列で変形されたうえで大本の図形に追加されます.

座標軸変換とPath2D

Path2Dは座標軸から独立した抽象的な図形を表すオブジェクトです. 従って, fill・strokeメソッド等による描画結果はcanvas要素内部の座標軸に応じて変形されます.

補足)棄却された機能群

下記はPath2Dの初期草案に存在したメソッド群で, 現在は仕様から省かれています.

Path2D.addPathByStrokingPath()
パス図形に他のパス図形のストローク図形を追加する.
Path2D.addText()
パス図形にテキスト外形を追加する.
Path2D.addPathByStrokingText()
パス図形にテキスト外形のストローク図形を追加する.

より高度な図形定義

基本APIによる図形定義だけでも大抵のグラフィックは描けますが, その特性を理解するとより高度な図形を簡単に描けるようになります.

スプレッド演算子の活用

図形定義に関わるメソッドの実行には多くの引数を要しますが, スプレッド演算子「...」を用いると引数の配列を直接メソッドに渡すことが出来ます. また, Object.valuesメソッドと組み合わせると引数オブジェクトを渡すことも可能です.

これはパス図形をオブジェクト化する上で有効なテクニックです.

JSONを使ったグラフィック表現

パス文字列による図形定義をJSONにまとめることでグラフィックとグラフィック描画スクリプトとを分離できます. こうすることでcanvas要素のSVG的な運用が可能となります.

この手法はとりわけサーバーサイドで生成したグラフィックをcanvas要素に描く用途に於いて有効です.

図形データ・オブジェクトのパス文字列化

図形データは様々な形態を取りえますが, パス文字列を介してPath2Dオブジェクト化すると図形の描画処理を大幅に簡略化できます.

頂点座標のリスト

図形が頂点の座標データのリストとして表現されるケースです. 頂点座標の配列(及びその文字列表現), HTMLのarea要素(shape=poly), SVGのpolygon要素, polyline要素, CSS Shapesのpolygon等がこれにあたります. DOMQuadオブジェクトは頂点配列に変換してから適用します.

矩形

図形が左上座標と幅・高さ値として表現されるケースです. HTMLのarea要素(shape=rect), HTMLDOMのDOMRectオブジェクト(getBoundingClientRectメソッドの実行結果), SVGのrect要素(rx,ry属性を持たないもの), viewBox属性, SVGRectオブジェクト(bboxメソッドの実行結果), CSS Shapesのrect等がこれにあたります.

円・楕円

図形が中心座標と半径値として表現されるケースです. HTMLのarea要素(shape=circle), SVGのcircle・ellipse要素, CSS Shapesのcircle・ellipse等がこれにあたります.

マーカーの描画

パスの端点に配置される図形(例えば矢印の矢じりの部分)をマーカーと呼びます. マーカーを描くにはMath.atan2関数等を使ってパス図形の端点の向きを求め, この向きに沿って図形を描きます. なお, パス図形定義の仕組み上予めパラメータとなる座標等が判っていないとマーカーは描けません.

直線(線分)の場合

直線分に対しては傾きの基準として線分の始点と終点を用います.

ベジェ曲線の場合

ベジェ曲線に対しては傾きの基準として端点と方向点を用います.

円弧の場合

円弧の場合はパラメータから端点の座標を求め, 角度値に\(\pi/2\)を減算・加算したものが端点の向きとなります. なお, arcメソッドの第6引数(anticlockwise値)で向きが反転します.

楕円弧の場合

楕円弧の場合は角度値を微細に増減した際の座標の変化から端点の向きを求めます.

これは三角関数の導関数(微分)\(cos'(\theta) = -sin(\theta), sin'(\theta) = cos(\theta)\)に相当するため, 次のように書き換えられます.

arcToメソッドの場合

arcToメソッドを用いた場合は始点に対する向きと終点の座標を求めます. これらは簡単な幾何計算で求められます.

arcTo図形に対するマーカーの描画

ベジェ曲線の活用

ベジェ曲線は古くからその数学的な特徴が研究されており, それらをcanvas要素に応用することが可能です. 頻繁に使うものは別途ライブラリ化しておくと良いでしょう.

円弧の近似

円弧や楕円弧を変形する際, そのまま扱うより一旦ベジェ曲線で近似したものを用いた方が処理が単純になります. 経験上, 8分割程度で元の図形と見分けがつかなくなります.

Catmull-Rom曲線による曲線補間

Catmull-Rom曲線は複数の点を滑らかにつなぐもので, 簡単な演算で3次ベジェ曲線に変換(方向点を算出)することが可能です. この技法はベジェ曲線で表現できない関数グラフを滑らかに描く際に重宝します.

例を示します.

塗りと線のスタイル

スタイル設定

パス図形を定義した後, strokeメソッドで線を, fillメソッドで塗り潰しが行われることはここまで見た通りです. その際に線の太さや色等の様々な設定を行うためのプロパティが提供されています.

グラフィックの色:strokeStyle, fillStyle

strokeStyleプロパティは線の色を, fillStyleプロパティはパス図形の塗り潰しの色を表します. 色の指定にはCSSにおけるものを用います. つまりHTML色rgb関数/rgba関数#000(RGB)/#000000(RRGGBB) 色のHEX(RGB16進)指定hsl関数/hsla関数currentcolorの何れかを指定します(初期値は黒). 不透明度(rgbaにおける4つめの値, alpha. 0で透明, 1で不透明となる. )が指定されている場合は, その内容に応じて色が合成され(重ねられ)ます. なお, プロパティにはこの他にグラデーションやパターンを指定することも出来ます.

CanvasRenderingContext2D.strokeStyle
線のスタイル. 色の他, グラデーション・パターンを指定する.
CanvasRenderingContext2D.fillStyle
塗り潰しのスタイル. 色の他, グラデーション・パターンを指定する.

fillStyle及びstrokeStyleプロパティは値を参照することも出来ます. 値を参照した場合, HEX値(不透明度が指定されていた場合はrgba形式)での色文字列が返されます.

拡張HEX形式による色の指定

CSS4では#0000(RGBA)/#00000000(RRGGBBAA)形式での色の指定が可能となります.

currentcolorの効能

currentcolorfillStyle/strokeStyleプロパティに指定すると, 値を設定したその時点でのcanvas要素におけるcolorプロパティの色が選択されます.

colorプロパティは祖先要素から継承されるため, canvas要素のプロパティが未指定であっても値をもつ場合があります.

パス図形を固定して異なる描画スタイルを重ねる

単一のパス図形に対してスタイルを変更し, stroke/fillメソッドを続けて実行することで図形を重ねることが出来ます.

同様に, パス図形に関わる処理を連続実行することも出来ます.

線の太さ:lineWidth

lineWidthプロパティには線の太さをピクセル単位で指定します. canvas要素では線の中央がパス図形と重なるように線が描かれます.

CanvasRenderingContext2D.lineWidth
線の太さ.

なお, 線の境界がピクセル境界に合っていない場合や幅が1ピクセルに満たない場合は, 自動的にアンチエイリアスが掛かります. そのため, 出力結果の色が期待していたものと異なると言った現象が起こります.

端点のスタイル:lineCap

lineCapプロパティは端点のスタイルを表します. butt(なし)/round(円)/square(四角形)の何れかから選択します.

CanvasRenderingContext2D.lineCap
線の端点のスタイル. butt(無し)/round(丸め)/square(四角)

長さを持たない線分の端点

パス切片内に始点と終点が同じ座標となる線分が含まれていた場合, lineCapプロパティにround/squareを指定しておくとstrokeメソッドにより円/矩形が出力されます.

頂点のスタイル:lineJoin

lineJoinプロパティは頂点のスタイルを表します. bevel(なし)/round(丸め)/miter(留継ぎ)の何れかから選択します.

CanvasRenderingContext2D.lineJoin
線の頂点のスタイル. bevel(面取り)/round(丸め)/miter(留継ぎ)

miterLimit値の指定

パスの頂点における線の厚み(最大幅)をmiter(留継ぎ)幅と呼び, miter幅とstroke幅との比率をmiter比と呼びます.

CanvasRenderingContext2D.miterLimit
lineJoinがmiterの場合のmiterスタイルをとるのmiter比の限界値. 初期値は10

これらの値は2線分が為す角度\(\theta\)を使って次のように表せます.

\begin{equation} {\it miterRatio} = \frac{1}{{\it sin}(\theta/2)} = \frac{{\it miterWidth}}{{\it lineWidth}} \end{equation}
miter比の計算式

図に表すと次のようになります.

strokeWidthとmiterWidthとの関係

lineJoinプロパティがmiterの場合, miterLimitプロパティでmiter比の上限値を設定することが出来ます. つまりmiter幅がmimterLimit×stroke幅値を超えると自動的にbevel(角なし)として扱われます.

破線の描画

stroke/strokeTextメソッドで描く線を破線とすることができます. setLineDashメソッドには引数として破線のパターンを配列として渡します. 破線のパターンは[実線(, 間隔, 実線, 間隔...)]として記述します. 空の配列を渡すと破線パターンが破棄されます. lineDashOffsetは破線の開始位置をマイナス方向にずらします.

CanvasRenderingContext2D.setLineDash(segments)
破線の設定をする. segments…破線設定の配列, 空の配列を渡すと初期化される.
CanvasRenderingContext2D.getLineDash()
破線の設定を配列として取得する.
CanvasRenderingContext2D.lineDashOffset
破線のオフセット値を設定する.

複合パスの取り扱い

複合パス(連結部の集合)に対しては, 連結したパスそれぞれで破線の開始位置が決定されます.

部分パス図形の算出

破線設定の値を大きく取ることでパス図形に対する部分(サブ)パス図形が得られます.

パス長から最適な破線を求める

破線の出力結果をより美しいものとする場合は, パス図形の周囲長を等分するように破線のパターンを算出します. なお図形の長さを求めるためにSVGPathElementを使うため, 図形の定義にパス文字列を用います.

破線描画の応用

破線パラメータの設定によっては, 興味深い出力結果が得られます. 幾つか例を示します

この他にも, 破線を重ねあわせることでパスに沿った図形を表現可能です.

上記を発展させ, 線の描画をパターン化してみました.

破線によるストロークのパターン化

とは言え相応の描画コストが掛かるため, 頻繁に書き換える用途には適しません.

矩形領域に関わる専用メソッド

矩形範囲に対する操作は頻出するため, 専用のメソッドが用意されています. これらの操作はパス図形の定義とは関連せず, 独立して実行することができます.

矩形領域の塗り潰し:fillRect

CanvasRenderingContext2D.fillRect(x,y,width,height)
現在のコンテキストの設定を使って矩形領域を塗りつぶす. x,y…矩形の左上頂点の座標, width…矩形の幅, height…矩形の高さ

矩形領域の線:strokeRect

CanvasRenderingContext2D.strokeRect(x,y,width,height)
現在のコンテキストの設定を使って矩形の線を描画する. x,y…矩形の左上頂点の座標, width…矩形の幅, height…矩形の高さ

矩形領域のクリア:clearRect

canvasは初期の色として全面が「黒の透明(rgba(0,0,0,0))」で初期化されています. clearRectメソッドは指定した矩形範囲の描画の内容をこの黒の透明に初期化します. 下の例では分かりやすくするためcanvas要素のbackground-colorをyellowに指定しています. clearRectメソッドを実行したことで, グラフィックをクリアした部分の背景の色が透けて見えています.

CanvasRenderingContext2D.clearRect(x,y,width,height)
指定した矩形をクリアする. x,y…矩形の左上頂点の座標, width…矩形の幅, height…矩形の高さ

図形描画とアンチエイリアス処理

canvas要素を構成する画素の数は有限であるため, そこに(無限の解像度を持つ)ベクタ図形を描画する上で自ずと近似(アンチエイリアス)処理が発生します. Retinaディスプレイ等の高解像度環境では目立ちにくいものの, 時に厄介な問題を引き起こします.

線の境界のアンチエイリアス処理を回避する

strokeによる線の描画は図形の境界が線の中央となるように描画されますが, 設定によっては美しい結果が得られません. 例えば下の図においてパス図形が黄色の線だとすると, 1px幅のストロークは2ピクセルにまたがってしまうためアンチエイリアスが発生します.

何も対処しなかった場合 何も対処しなかった場合 幅を2の倍数とする場合 幅を2の倍数とする場合 描画位置を0.5ずらす場合 描画位置を0.5ずらす場合
線描画時のアンチエイリアスの発生原理と回避法

このような場合は, lineWidth値を2の倍数としたり, 線の位置を0.5ピクセルずらすといった作業を行うとアンチエイリアス処理を回避できます. とは言えこれはcanvas要素にスタイルが設定されていたり, ディスプレイの解像度によって変化するため制御が難しい面もあります.

予めアンチエイリアス処理が発生しないようにグラフィックを制御することは, グラフィックの描画パフォーマンスを向上する上でも重要です.

SVGフィルタを用いたアンチエイリアス回避

SVGフィルタを用いると, アンチエイリアス処理に伴う透明度の情報を除去でき, 擬似的にアンチエイリアスが発生していないように見せかけられます. また斜め線や曲線においても効果があります.

但しこの方法も万能ではなく, フィルタの動作原理上描画パフォーマンスに劣る他, 細いラインが部分的に千切れると言った現象が発生します. また, もともと透明度を持つ図形についてはフィルタを通すことで不透明になるため, このテクニックは使えません.

図形の一括描画と逐次描画の違い

同様の問題はfillメソッドに依る塗り潰しにおいても発生します. 同じ領域に同じ色を重ねた場合, 論理的には全く同じ結果となるはずですが, パス図形の境界においてアンチエイリアス処理が発生すると, 塗り潰し処理を一括で行うか否かで得られる結果が異なります. 逐次塗り潰しを行った場合, 不透明部が重ね合わされる(アルファマルチプリケーション)ことで, 想定した色よりも濃い部分が発生するのです.

下の例では何度も塗りつぶされている四角形の左上の部分の色が濃くなっています.

グラデーションとパターン

グラデーションの生成

CanvasGradientオブジェクトを用いることで, 塗り潰しや線にグラデーションを設定することが出来ます. CanvasGradientを生成するメソッドとしてはcreateLinearGradientとcreateRadialGradientの2つが提供されています.

線形グラデーション:createLinearGradient

createLinearGradientメソッドには引数としてグラデーションの基準となる線分の始点と終点の座標を渡します. 得られたオブジェクトに対してaddColorStopメソッドを実行することでグラデーションの基準となる色(ストップカラー)を設定します. この指定方法はSVGにおけるグラデーションの指定方法に近く, CSS3での角度による指定方法とは使い勝手が異なります.

CanvasRenderingContext2D.createLinearGradient(x0, y0, x1, y1)
線形グラデーションオブジェクトを生成する. x0,y0…グラデーションの開始座標, x1,y1…グラデーションの終了座標
CanvasGradient.addColorStop(offset, color)
グラデーションの色の起点を設定する. offset値は0〜1の間で指定する. この範囲外の値を指定した場合エラーとなる.

得られたCanvasGradientオブジェクトは通常の色文字列と同様にfillStyleプロパティ, strokeStyleプロパティに設定することが出来ます.

ストップカラーを設定する順番に特に決まりはありませんが, 同じoffset値が色の境界を表すという特徴からなるべくoffset値が小さいものから記述するようにすると見通しがよいでしょう.

このようにストップカラーの追加は既存の設定を上書きするわけではありません. 従ってグラデーション色を変更したつもりで設定を追加し続ける操作はメモリリークを引き起こします. この場合面倒でもCanvasGradientオブジェクトを再生成するようにします.

放射状グラデーション:createRadialGradient

createRadialGradientメソッドは引数として2つの円(中心座標と半径)を取ります. colorStop値の設定は線形グラデーションと同様です.

CanvasRenderingContext2D.createRadialGradient(x0, y0, r0, x1, y1, r1)
放射状グラデーションオブジェクトを生成する. x0,y0,r0…グラデーションの開始円, x1,y1,r1…グラデーションの終了円

円の中心をずらすことで様々な表情のグラデーションを表現することが可能です. なお, 色の境界がギザギザしていて気になる場合は, offset値をほんの少しずらすことで気になりにくくなります.

グラデーションの繰り返し

SVGやCSSのような繰り返すグラデーションはストップカラーを繰り返し設定することで表現します.

一見面倒ですが, この方法はストップカラーの位置を自由に設定できる点で他のグラデーション定義よりも優れています.

グラデーションの変形

カンバスの座標軸が変換されていた場合, グラデーションもその内容に従います.

パターンの生成

グラデーションと同じような役割を果たすものとしてpatternがあります. これは図形の塗り潰しや線の描画といったものに任意の図案を指定可能とするものです.

CanvasRenderingContext2D.createPattern(image, repetition)
パターンオブジェクトを生成する. image…パターン画像, repetition…パターン繰り返しの方法.
repeat(両方向(初期値))/repeat-x(水平方向のみ)/repeat-y(垂直方向のみ)/no-repeat(なし)

得られたCanvasPatternオブジェクトはグラデーションと同様にfillStyleプロパティ, strokeStyleプロパティに設定することが出来ます.

パターンの生成:creataPattern

元となるパターン画像としてはimg要素canvas要素video要素ImageBitmapオブジェクトの何れかを利用します. 例えばパターン画像としてを利用する場合, 次のような結果が得られます.

また動的にcanvas要素を生成し, それをパターンとして利用することも可能です.

自分自身に描いたパターン画像を参照することもできます.

なお, strokeStyleに設定したパターンは所謂ブラシと呼ばれる線の方向に沿ったものではなく, あくまで線領域の塗りつぶし用のパターンになります.

パターン模様の基準と繰り返し方向の指定

パターン画像はカンバスの原点座標を基準に並べられます. その際, 座標軸が変換されていた場合はパターン模様もその内容に従います.

createPatternメソッドの第2引数にはパターン模様を繰り返す方向(repetition)を指定します.

なおrepetition値のとり方によってはパス図形の範囲がパターン模様の範囲から外れることがあります. すると, fillなどのメソッドを実行しているにも関わらず, 何も起こらない場合があります.

パターンに対する変形

パターン模様は座標軸変換の内容によって変形されますが, パターン模様に対してのみ有効な変形を指定することが可能です.

CanvasPattern.setTransform(matrix)
パターンに変形を施す. matrixには行列情報を格納したdictionary(つまり, DOMMatrixやSVGMatrixでも良い)を渡す.

この機能はパターン模様の基準やサイズを変える場合に利用します. 例を示します(現在引数の内容において仕様と実行との間にブレがあります).

setTransformメソッドをサポートしない環境では, 次のように記述できます.

パターンの破棄

fillStyle, strokeStyleプロパティに設定されたCanvasPatternオブジェクトは, コンテキストオブジェクトが破棄(≒canvasサイズが変更)されるまでメモリから開放されません. CanvasPatternオブジェクトはビットマップデータを内包しているためメモリを占有しやすく, saveメソッドによって参照履歴が残ります. そのため, (巨大なパターン画像を用いる場合は)出来る限り描画処理が完了した後で明示的にスタイル値を別の色で上書きしましょう.

画像効果・合成

クリップ領域による描画範囲の制限

clipメソッドを実行すると, コンテキストオブジェクトに保持していたパス図形をクリップ領域に変換し, それ以降のカンバスへの描画をこの範囲に制限します.

CanvasRenderingContext2D.clip([path2d],[fillRule])
現在のパス図形の範囲をクリップ領域に変換し, fill, stroke, drawImage, fillRect, strokeRect, clearRectメソッドによるこの範囲外への図形・画像の描画を無効とする. Path2Dオブジェクトをサポートしている場合は引数渡したパス図形オブジェクトでクリップする. fillRule…クリップの方法

clipメソッドは重ねがけすることが出来ます. 重ねる毎にクリップ領域が狭まっていきます.

任意形状の繰り抜き

clip領域はclearRectによるグラフィッククリアにも適用されます.

グラフィックの固定化

パス図形が存在しない時にclipメソッドを実行することでcanvasグラフィックを固定し, 以降ピクセル操作を除く一切の操作を受け付けないように出来ます.

クリップ領域の解除

一度クリップ領域を定義した場合原則解除することはできませんが, 事前にsaveメソッドを実行しておくとrestoreメソッドを実行することでsaveメソッドを実行した時点でのクリップ領域に戻すことが出来ます.

補足)resetClipメソッドに係る議論

resetClipメソッドによるクリップ領域の初期化機能の導入は歴史的に何度も議題に上がっており, その都度スクリプトの構造化の観点から棄却されています. メイン処理内のクリップ領域を呼び出し先のサブ関数が変更出来てはならないという見解です. しかし, そのような配慮はスクリプトの実装者側で行うべきであり, resetClipメソッドは必要であるとの異論もあります. 実際似た役割を持つresetTransformメソッドが存在していることから, 現行のWHATWGのHTML仕様ではresetClipメソッドが復活しています.

CanvasRenderingContext2D.resetClip()
クリップ領域を初期化する. なお, 実装しているブラウザは存在しない.

ドロップシャドウ効果

shadowプロパティに値を設定しておくことで, 描画処理(つまり, fill, stroke, drawImage, fillText, strokeTextメソッドの実行)時に影を追加することができます. 影は塗り潰し, 線の描画の両方に有効です. その際のスタイルに不透明度が設定されている場合は, 背後に影が描画されているものとして図形がその上に合成されます. つまり2つの描画を一度のメソッドで実行することになります.

CanvasRenderingContext2D.shadowColor
影の色.
CanvasRenderingContext2D.shadowBlur
影のぼかし幅.
CanvasRenderingContext2D.shadowOffsetX
影のx軸方向のずらす量.
CanvasRenderingContext2D.shadowOffsetY
影のy軸方向のずらす量.

なおshadowColorには単色のみ設定可能で, グラデーションを指定することはできません.

shadowBlur値とCSS/SVGとの関係

HTML環境における影のサイズ指定には, canvas要素のshadowBlurの他にCSSのbox-shadow(blur-radius)とSVGのfeGaussianBlur(stdDeviation)が挙げられ, \(blurRadius = shadowBlur = 2stdDeviation\)の関係が成り立ちます. つまりこれらを用いて等価な影を得る場合は, stdDeviationに対してはshadowBlur, blur-radiusの半分の値を指定します.

CSS
SVG

影設定の初期化

WebKit環境では全ての影設定を初期化するclearShadowメソッドを利用できます.

CanvasRenderingContext2D.clearShadow()
影設定の初期化を行う. (WebKitのみ)

影のみを描画する

影のみを描画するには, 元となるパス図形をカンバスの範囲外に定義し, offset値を使って影をグラフィック範囲内に引き込むようにします. これは単色の図形に対するぼかし処理を単純化します.

パス図形の内側への影を定義する

出典:Inset shadows with HTML5 Canvas

canvasでの影は通常図形の外側に広がるように定義されますが, CSSでのbox-shadowプロパティではinsetオプションを設定することで図形範囲の内側に広がる影を描くことが出来ます. この動作をcanvasで再現する場合は, 次のように元となる図形を逆方向の矩形(できれば必要最低限の広さとなるもの)で囲み, 図形を反転させてから影をつけるようにします.

不透明度とグローバルアルファ

図形の不透明度を指定するには2つの方法があって, 色の指定をrgba関数で行うglobalAlphaプロパティで透明度を指定するの2つの方法があります.

CanvasRenderingContext2D.globalAlpha
共通不透明度. 描画処理にたいする不透明度を表す. 0で完全に透明. 1で不透明. なお, 透明であっても色の情報は存在している点に注意する.

色の指定にrgba関数を用いた例を示します.

これをglobalAlphaプロパティで書き換えると次のようになります.

globalAlphaの値は何も単色の指定にのみ有効というわけではありません. 例えば外部画像をカンバスに出力する場合の不透明度としても利用可能です.

画像の合成

複数の図形・画像を合成する(重ねる)際, その合成方法をglobalCompositeOperationで指定することが出来ます. この値は既存の色に新たな色を付け加える際の計算式を表し, clipメソッドによるクリップ領域を設定せずとも画像のくり貫き等を実現することができます.

設定可能な値は大きく分けてcomposite-modeblend-modeの2つに分類でき, 後者は比較的新しい環境でのみ動作します.

CanvasRenderingContext2D.globalCompositeOperation
画像の合成方法. 下記のcomposite-modeかblend-modeの何れかを指定する.
composite-mode(Porter Duff Compositing Operators)
source-atop
source-in
source-out
source-over
destination-atop
destination-in
destination-out
destination-over
lighter
copy
xor
blend-mode(Compositing and Blending Level 1)
normal
通常(source-overに同じ)
multiply
乗算
screen
スクリーン合成
overlay
オーバーレイ合成
darken
比較暗
lighten
比較明
color-dodge
覆い焼き
color-burn
焼きこみカラー
hard-light
ハードライト
soft-light
ソフトライト
difference
差の絶対値
exclusion
除外
hue
色相(色相値の注入)
saturation
彩度(彩度値の注入)
color
カラー(色相・彩度値の注入)
luminosity
輝度(輝度値の注入)
 
 

globalCompositeOperationプロパティとドロップシャドウ

globalCompositeOperationプロパティとドロップシャドウ設定とを混在させた場合, レンダリングエンジン毎に異なる結果となります.

copy合成とドロップシャドウ設定の混在(左からGecko,Blink,WebKit)

composite-modeの指定

composite-mode値を指定すると, Poter Duff合成演算に基づいた処理を行います. 値を適切に設定することでグラフィックを繰り抜いたり, 既存グラフィックの下に新たな図形を挿入すると言った効果が得られます.

例を示します. 先に赤い円を描画し, その後に青い矩形を描画しています. その際の描画結果の違いについて注意して下さい.

composite-modeの動作サンプル

合成対象にアルファ値が含まれていた場合は次のようになります. ここでは透明度を含む赤・rgba(255,0,0,0.25)に青・rgba(0,0,255,0.75)を重ねています.

composite-modeの動作サンプル(アルファ値を含むケース)

blend-modeの指定

Compositing and Blending Level 1をサポートしている環境ではglobalCompositeOperationにblend-mode値を指定することが出来ます.

blend-modeの動作サンプル

画像合成処理の応用

globalCompositeOperationプロパティは上手く使うことで様々な用途に応用可能です.

重なりの表現

「黄色い背景に中身を繰り抜いた四角形を重ねる」ことを考えた場合, この通りに処理を行うのであれば次のようになります.

一旦別のcanvas要素に内容を書き出すのが面倒ですが, こうしないとclearRectメソッドが背景の色まで削除してしまいます. これを「中身を繰り抜いた四角形を描いた後ろに黄色い背景を描く」と次のようになります.

copy合成の応用

一見役に立ちそうにないcopy合成ですが, canvas要素の自分自身への書き込みと組み合わせることで既存のグラフィックに対する編集操作が可能です.

画像の平行移動

自分自身の描画位置をずらすことで画像の平行移動を表現できます.

フィルタの書き戻し

描画済みの内容にフィルタを適用する際に利用します.

ピクセルデータの入れ替え

入れ替えたい範囲をclip領域で囲み, その上に描画を行います. アルファブレンディングによる合成処理を行いたくない場合に利用します.

クロスフェード画像の生成

2つの画像を一つに合成する際にglobalAlphaプロパティを使うとクロスフェード画像が得られます.

これはglobalCompositeOperationプロパティを使って書き換えることが出来ます.

この方法を用いると画像の透明度をグラデーション化することが可能で, より高度なクロスフェード画像が得られます.

動きの検知

difference合成は2つの画像の違いを出力します. これは例えばカメラによる動きの検知に応用できます. 時間を空けた2枚の画像を合成した際, canvasに出力される内容が黒(もしくはそれに近い色)一色であればその間何も動かなかった事になります.

色成分への分解と再構成

globalCompositeOperationを用いると画像を成分毎に分解できます. また, 分解した色情報を再度合成することで元の画像を復元することも可能です.

RGB

光の三原色による分解・合成.

A

不透明度の抽出.

CMY

色の三原色による分解・合成.

HSL

blend-modeのうち, hue, saturation, luminosity, colorはいずれもHSL色空間に対する操作を表します. 操作毎の色相, 彩度, 輝度値の対応を図にすると次のようになります.

HSL値への分解と合成
この値の対応を元に画像をh,s,l値に分解し, 再度合成し直すことができます.

HSL合成の応用

HSLを用いた合成では出力結果が同じでもその描画順によって必要となるブレンドモードが変化します.

グレイスケール化

画像をグレイスケール化する場合は, 輝度のみが残るように黒色と合成します.

単色相化

luminocity/color合成は画像の色を単一の色相値に統一する際に便利です.

フィルタ

フィルタとは画像に対して何らかの効果(画像の加工)をもたらすもので, 代表的なものとしてはグレイスケール化やセピア化と言ったものが挙げられます. WEBブラウザ環境ではFilter Effects Module Level 1仕様に則ったフィルタ処理を利用でき, canvas要素が描くグラフィックにおいても様々なフィルタを適用可能です.

フィルタの適用手法とタイミング

canvas要素に対するフィルタの適用にはcanvas APIによるものCSSによるものの2つがあります. 前者はcanvasグラフィックそのものを加工するため, フィルタ適用前の画像データが失われます. 後者はcanvasグラフィックをスクリーンに表示する際にフィルタを適用するため, 画像データとしてはフィルタを適用する前の状態が維持されます. このようにそれぞれフィルタを適用するタイミングが異なるため用途に応じて使い分けます.

canvas APIにおけるフィルタ

コンテキストオブジェクトのfilterプロパティにフィルタ関数を指定すると, 以降のグラフィック描画対象にフィルタ効果が適用されます. これはfilterプロパティの内容が変更されるまで有効となります. なお, canvas要素に描画されている内容に対してフィルタを掛けているわけではありません. なお, 先ほど見たとおりcopy合成と組み合わせることで既存の描画内容にフィルタを掛けることが可能です.

CanvasRenderingContext2D.filter
これから描画する内容に対するフィルタのフィルタ関数を指定する.

指定可能なフィルタ関数には次のものがあります. なおフィルタ関数を列挙することで複数のフィルタを重ねがけすることも可能です.

filterプロパティの動作サンプル

フィルタと合成の適用順

filterプロパティとglobalCompositeOperationプロパティの両方が設定されていた場合, まずカンバスに描画しようとしているグラフィックにフィルタが掛けられ, その内容がカンバス上のグラフィックに合成されます. 下ではフィルタと合成の組み合わせを数回繰り返して画像の二値化を実現しています.

SVGフィルタの利用

上記に示したフィルタはいずれも簡易的なものですが, SVGによるフィルタ定義を用いるとより本格的な画像加工が可能です. SVGフィルタを使うにはDOMツリーにおけるfilter要素のidにurl関数を適用したものをfilterプロパティに設定します.

幾つか例を示します.


クロマキー合成に用いたJPEG画像(背景/前景)

このようにcanvas要素単体では実現が困難な画像処理を簡潔に記述できます. とは言え, 余りに複雑な内容の場合グラフィックの描画速度に影響します. フィルタ画像を動的に生成する必要が無いのであれば, フィルタ結果をPNG形式などの静的な画像として保存しておき, drawImageメソッドでcanvasに描画するようにして下さい.

SVGフィルタを用いる際の注意点

CSSフィルタとの連携

前に見たとおり, canvas要素に対するフィルタはAPIによるものとCSSによるものの2つがありましたが, これらを組み合わせて利用することも出来ます. 例えば効果を互いに打ち消し合うフィルタの組を用意しておき, APIとCSSから同時にフィルタを掛けると興味深い動作が得られます. 下の例では一見何の仕掛けもしていないように見えますが, canvasグラフィックを(右クリックメニュー等を用いて)保存・表示した際にフィルタ済みの画像が得られます.

互いに打ち消し合うフィルタの適用

これは広く応用可能な動作です. 例えばノイズの追加・除去を行うフィルタを用いればcanvasグラフィックの再利用を制限する上で一定の効果が期待できます. 例を示します.

SVGフィルタで生成したノイズ画像を添加することで, canvasグラフィック単体での再利用を困難にしています.

テキストの描画

canvas要素とテキスト処理

canvas要素に対して文字列を描画する場合は, 幾つか留意すべき点があります.

意外かもしれませんが, canvas要素が提供するテキスト描画機構は限られた機能しか持ち合わせていません. そのため凝った文字レイアウトを実現する場合は相応の設計や工夫を要します. また, ブラウザ毎の動作差異が大きいため, 検証は十分に行って下さい. 特にcanvas要素に対するスタイル指定がテキスト描画に影響する場合(本記事でも解説しています)がありますが, この動作は標準ではありません. 将来的に適切なAPIが追加されるかもしれません.

この他, canvas要素に描いた文字からは文字列の内容が欠落します. これは使い方によってはアクセシビリティが損なわれるということを意味します.

fillTextメソッドとstrokeTextメソッド

canvas要素においてテキストを描画するにはstrokeTextメソッドもしくはfillTextメソッドを用います. strokeStyleやfillStyleはテキスト描画にも適用されます. また, テキスト描画の最大幅を設定することで文字列が描画範囲からはみ出さないようにすることが可能です. なお描画は一行のみで, 複数行に渡る描画は行えません. また, テキスト内の空白文字はそのまま出力されます.

CanvasRenderingContext2D.strokeText(text,x,y[,maxWidth])
文字列のアウトラインを描画する. テキストと描画位置, 最大の幅を指定する.
CanvasRenderingContext2D.fillText(text,x,y[,maxWidth])
文字列を塗りつぶして描画する. テキストと描画位置, 最大の幅を指定する.

例を示します. メソッドの第1引数に描画対象の文字列を, 第2・3引数に描画の基準となる座標を指定します.

袋文字の描画

袋文字を描画する場合は, まずstrokeTextで文字の縁取りをした後にfillTextで文字を塗りつぶします.

フォントの指定

フォントの指定はfontプロパティに対して行います. 書式はCSSのfontプロパティと同等で, font-style, font-variant, font-weight, font-size, font-family(うち, font-sizeとfont-familyは必須)の順に並べた文字列としてプロパティに設定します. なお, 間違った書式のパラメータは無視されます. また, 既存のフォント設定の一部を変更することは出来ません.

CanvasRenderingContext2D.font
フォントの設定. 書式はCSSにおける記述に準ずる. つまり, 下記の値を順に記述する. (font-style, font-variant, font-weightは順不同) 規定はnormal 10px sans-serif
font-style
フォントのスタイル. [normal|italic|oblique].
font-variant
フォントの見た目. [normal|small-caps].
font-weight
フォントの太さ. [normal|blod|100〜900].
font-size
(必須)フォントの大きさ. CSSでの単位系を利用可能.
font-family
(必須)フォントの種類. 名称に空白が含まれる場合は引用符で囲む. カンマで区切ることで代替フォント種を指定可能.

フォントサイズの上限と下限

一部のブラウザでは有効なフォントサイズに制限があります.

そのため, この範囲から外れたフォントサイズを利用したい場合はscaleメソッドを用いて文字を拡大・縮小します.

ビットマップフォントを含むフォントの取り扱い

一般的なフォントはグリフ(文字の形)をベクタデータとして扱いますが, MSゴシックを代表とする一部のフォントでは一定の条件の元で(ベクタデータとは別に埋め込まれた)ビットマップ形式のグリフをスクリーンに表示します. このようなフォントをcanvas要素に描画する場合は注意が必要です.

strokeTextメソッドはベクタ形式のグリフを参照する一方, fillTextメソッドはビットマップ形式のグリフを参照します. そのため, 先ほどの袋文字を描画しようとした際に文字の形状や幅が異なることによって意図した結果が得られません. 例を示します. MSPゴシックフォントではフォントサイズが22px(16pt)以下の場合にビットマップフォントが適用されます. その結果, strokeTextとfillTextメソッドの結果を重ねた際にフォントサイズによっては上手く重なりません.

袋文字の描画に失敗するケース(PNG画像)

この問題を回避するには, scaleメソッドを使ってx軸(y軸)方向のスケールを判らない程度に縮める(もしくは広げる)ようにします. 但しスケールが変更されたことによるテキスト描画位置の微調整を要します.

参考:“MS Pゴシック” をメイリオみたいにアンチエイリアスさせるCSS3ハック

テキスト描画の上限幅の指定

fillText,strokeTextメソッドの第4引数にはテキストの描画幅の上限を指定します. テキストはこの値に収まるように自動的に描画幅が狭められます(presto・旧Opera環境では文字の大きさが変化しました). なお, 幅広の文字となることはありません.

テキスト描画を指定幅に合わせる

特定の幅に文字列を引き延ばす場合は後述するmeasureTextメソッドとscaleメソッドとを組み合わせることで実現します.

テキストの描画位置の制御

指定した座標に対してどのようにテキストを描画するかは次に掲げるtextAlign, textBaseline, directionの3プロパティで制御します.

CSS,SVGとの比較

テキストの配置に関わるプロパティはCSS, SVG, canvasとで名称が異なります. 以下にその関連を示します.

テキスト配置に関わるプロパティ相関
横位置の基準縦位置の基準文章の方向
CSStext-align
left|center|right
vertical-align
baseline|top|middle|bottom|text-top|text-bottom| super|sub|%値|length値
direction
ltr|rtl|inherit
SVGtext-anchor
start|middle|end
dominant-baseline/alignment-baseline
auto|use-script|no-change|reset-size| ideographic|alphabetic|hanging|mathematical|central|middle| text-after-edge|text-before-edge|inherit
canvas要素textAlign
start|center|end|left|right
textBaseline
top|hanging|middle|alphabetic|ideographic|bottom

横方向におけるテキスト描画の基準設定

textAlignプロパティは基準となる座標に文字列のどこを揃えるかを指定します. 値としては, 文字列の見た目の左端(left)/右端(right), 文章(文字列)としての開始位置(start)/終了位置(end)及び中心(center)のいずれかを指定します.

CanvasRenderingContext2D.textAlign
文字列の横の描画基準を指定する. start,end値についてはdirectionプロパティがltrかrtlかで変化する.
left
文字列の左端を基準とする.
right
文字列の右端を基準とする.
start
文章の始点を基準とする.
center
文字列の中央を基準とする.
end
文章の終点を基準とする.

文章の方向による動作の変化

WEB環境は多言語環境に対応しているため, 言語毎に異なる文章の方向についても考慮されています. canvas要素ではdirectionプロパティに文章の方向(左から右:ltr, 右から左:rtl)を指定します. なお, 先ほどのtextAlignプロパティのstart/end値はこの文章の方向によって意味合いが変化します.

CanvasRenderingContext2D.direction
文章の方向を指定する. textAlignプロパティのstart/end値の動作に影響を及ぼす.
ltr
左から右(left to right)
rtl
右から左(right to left)アラビア語等
inherit
canvas要素及びその祖先から継承(規定値)

direction値を指定しなかった場合はCSSスタイルから値が決定され, 日本語環境では通常ltrが設定されているものとして扱われます.

文字列の縦位置設定

日本語環境では気付きにくいのですが, 文字の形状を決定するグリフデータには基底線(baseline)と呼ばれる文字揃えの基準となる「縦位置」が設定されており, HTMLに記述された文字列はこの位置を基準に一文字ずつスクリーンに描画されます. canvas要素ではこの基底線を選択することで縦位置での文字揃えが可能です.

基底線とテキストの描画位置
CanvasRenderingContext2D.textBaseline
文字列の縦の描画基準を指定する.
top
文字列の上端.
hanging
hanging基底線(フォントが持つグリフ整列の基準ラインの一つ)を基準とする. ヒンディー語等.
middle
文字列の垂直位置の中心.
alpabetic
alpabetic基底線を基準とする.
ideographic
ideographic基底線を基準とする. かな漢字など.
bottom
文字列の下端.

属性値・スタイル値の影響

指定した値や環境によってはcanvas要素に対する属性やスタイルがテキストの描画に影響を与えます.

fontプロパティ

フォントサイズにem%等の単位を指定した場合はcanvas要素のスタイル値から計算されたサイズとして解釈されます.

また, getComputedStyleメソッドと組み合わせることで, canvas要素に対するフォントスタイルを使ったテキストの出力が可能です.

dir属性・directionプロパティ・unicode-bidiプロパティ

HTML要素におけるdir属性, directionプロパティは何れも文章の方向を表すもので, コンテキストオブジェクトのdirectionプロパティと意味合いは同じです. canvas要素にこれらの値が設定されているとfillText, strokeTextメソッドの動作に影響します.

スタイルの継承

direction値は祖先要素から継承します.

rtlを設定したノード

文字並び方向の強制

direction値を変更しても必ずしも文字の並びが逆となるわけではありません. 一見奇妙ですが, 複数の言語から構成される文字列を扱うためにこのようになっています. 例えばアラビア語では右から左へ文章を記述する一方で数字や英語や日本語の単語は左から右に記述します. その為, 文章の方向とは別に文字毎の並ぶ方向が定められており, このルールに沿って文字の描画位置が決定されるのです. 文字の並ぶ方向をもdirection値で強制したい場合は, canvas要素のunicode-bidiプロパティにbidi-overrideを指定します. なお, unicode-bidi値は継承しません.

それ以外のプロパティ

この他にもテキスト描画に影響するものはありますが, いずれも標準の動作かどうかは疑わしく, 互換性を考慮するなら利用は避けましょう.

テキストの描画幅の取得

テキストの描画幅はフォントやグリフの形状によって変化しますが, measureTextメソッドを使うと文字列グラフィックを矩形で囲んだり, 文字列の一部に下線を引くと言った処理を実現出来ます.

CanvasRenderingContext2D.measureText()
テキスト幅を算出するTextMetricsオブジェクトを取得する. ※斜体はtobe
TextMetrics.width
テキストの描画幅
TextMetrics.actualBoundingBoxLeft
テキストの境界ボックスの左辺.
TextMetrics.actualBoundingBoxRight
テキストの境界ボックスの右辺.
TextMetrics.fontBoundingBoxAscent
フォントの描画上限位置.
TextMetrics.fontBoundingBoxDescent
フォントの描画下限位置.
TextMetrics.actualBoundingBoxAscent
テキストの境界ボックスの上辺.
TextMetrics.actualBoundingBoxDescent
テキストの境界ボックスの下辺.
TextMetrics.emHeightAscent
ベースラインから文字の上端までの長さ.
TextMetrics.emHeightDescent
ベースラインから文字の下端までの長さ.
TextMetrics.hangingBaseline
hanging基底線の位置.
TextMetrics.alphabeticBaseline
alphabetic基底線の位置.
TextMetrics.ideographicBaseline
ideographic基底線の位置.

以下はTextMetricsの中身を列挙してみたものです. 現状ではテキスト幅しか取得できませんがが, 将来的にはテキストレイアウトに関わる様々な値が取得可能となります.

欧文フォントにおけるカーニングと文字列幅

アルファベットを扱う欧文フォントにおいては, 文字間の隙間を自動的に詰め見た目を調整する仕組み(カーニング)を備えたものがあります. 以下に例を示します.

AVAVAVA
AVAVAVA
カーニング有無(font-kerning)による文字配置の違い(上カーニングなし, 下あり)

canvas要素では原則このカーニングを無効とする手段が無いため, 文字列全体の長さが文字個別の幅の合計よりも短くなる場合があります.

この動作からcanvas要素で文字列に対するカーニングを無効とするには一文字ずつ幅を求めながら描画すれば良いことになります.

文字幅の数え上げにまつわる問題

一文字ずつ文字幅を計算する場合, 数え上げ対象がユニコード文字であることを意識する必要があります. つまり, 次のようなケースを想定せねばなりません.

日々進化するユニコード仕様を厳密に追い続けることはほぼ不可能ですから,「個別の文字幅を計算せずとも良い」設計・運用をしたほうが無難です.

ユニコード文字配列の利用

参考:JavaScript における文字コードと「文字数」の数え方

ES2016ではユニコード文字列を扱うための仕組み, 例えば文字列をUTF-16ではなくユニコード文字(コードポイント)配列化する機構が強化されています. そのため, UTF-16エンコーディングや結合文字に関わる問題はこのユニコード文字配列を適用することである程度解決します.

テキストのレイアウト

canvas要素のテキストレイアウト機能は必要最低限度の簡易的なもので, 必要に応じて何らかの処理を自作する必要があります.

テキストの改行

改行等を含む文字列を直接複数行に亘って描画する場合, 最も素朴にはfillText/strokeTextメソッドを繰り返します. 改行コード毎にテキストの描画位置を下にずらしていくのです. いささか面倒ですが, 確実に文字を配置できます.

この他CSSでcavans要素にdiv要素を重ねる方法もあります. 文字を画像として扱う用途には不向きですが, 実装が素直であることからおすすめです.

SVGを介したCSSレイアウト

より複雑なレイアウト処理が必要な場合は, SVGを用いると良いでしょう. 詳しくはSVG連携の項で解説しますが, CSSがサポートする多くの機能をテキスト描画処理に適用可能です. 但しフォントの参照に制約もあります. 下記はCSSの縦書き機能をcanvas要素に適用したものです.

HTML文字実体参照による文字の描画

HTMLには文字実体(エンティティ)参照と呼ばれる仕組みが備わっており, 文字を&#[(10進)ユニコード];/&[キーワード];と言った記述(文字列)で指定することが出来ます. 文字実体参照の内容をcanvas要素に描きたい場合は, HTML要素(何でも良い)のinnerHTMLプロパティとtextContentプロパティを使って文字実体参照を実際の文字に変換します.

canvas要素におけるサブピクセルレンダリング

WEBブラウザ等のテキストを扱うアプリケーションでは, 文字の判読性を高める目的でフォントの描画にしばしばサブピクセル(もしくはLCD)レンダリングと呼ばれる仕組みを用います. これは現在主流のスクリーンにおいて, 1画素が実際には色成分(RGB)毎の副画素(サブピクセル)の並びから構成されていることを利用し, 横方向の解像度を擬似的に3倍とすることでピクセル単位で行われるアンチエイリアス処理よりも線の境界を鮮明に見せるものです.

例を示します. 左の画像はサブピクセルレンダリングによる文字の描画結果「画像あ」をピクセルごと拡大したものです.

サブピクセルレンダリングの出力結果例(RGB順)

この内容を画像として直接拡大すると, 右のように黒色の文字の周囲にサブピクセルレンダリングに起因する色のにじみが発生します. この色にじみはスクリーンを構成するサブピクセルの順番(環境)に依存します.

canvas要素はテキスト描画機構を含め原則サブピクセルレンダリングをサポートしません. そのためcanvas要素上の文字列はCSSによるものと比べて細部が潰れ読みにくくなります.

サブピクセルレンダリングの有効化

サブピクセルレンダリングを直接有効化するAPIは存在しないものの, 特定の条件下(例えばアルファチャンネルを無効化した状態でfillTextメソッドを実行した場合やSVGに含まれる文字列を描画した場合)においては有効となることがあります.

参考:Subpixel anti-aliased text on HTML5's canvas element

サブピクセルレンダリングが有効化されると文字が読みやすくなる一方, 拡大や回転を伴う画像の加工を行った際に文字周囲部のにじみが強調されかえって画像の品質を損ないます.

WEBフォントの描画

CSSの@font-face規則を利用するとcanvas要素に対しても文字列の描画にWEBフォントを用いることができます. 例えば次のコードをスタイルシートとして宣言しましょう.

こうすることでGoogleが提供しているWEBフォント(Google Fonts)が利用可能となります. しかしWEBフォントの準備が完了する前にこのフォントを呼び出した場合, 意図した結果が得られません. 例えば次の二つのコードでは描画結果が必ずしもWEBフォントの内容を反映しません. 一般に言われているHTML内部でWEBフォントを呼び出しつつ, window.loadイベントでのスクリプトを実行する方法でも不十分な場合があります.

この問題はWEBフォントの分析がwindowオブジェクトのloadイベント発生したタイミングでも完了しない場合に発生します. このように元来確実に読み込まれるとは限らないWEBフォントを正しくcanvas要素に描画するには, 少々追加のスクリプトを記述する必要があります.

素朴なWEBフォントの描画

原理的にはWEBフォントの準備が完了したタイミングを検知し, そこを起点として描画処理を開始すれば良いことになります. 従って前項で示したmeasureTextメソッドを利用して処理を自作します.

measureTextで得られるテキスト幅はフォントの種類に依存するため, この値を観察することでWEBフォントのロード状況を確認出来ます. 予め代替フォントを併記したものと代替フォントのみを記述したコンテキストを用意しておき, それぞれで得られる文字列の幅を一定時間毎に比較します. WEBフォントが利用可能となると文字列の幅が変化し比較結果がfalseとして判定されるので, このタイミングを起点として描画処理を開始するのです.

FontFaceSet/FontFaceオブジェクトの利用

CSS Font Loading Module Level 3をサポートしている環境であれば, よりスマートに対処することが可能です.

loadingdoneイベントの利用

単純にドキュメントロード時にWEBフォントを描画するのであれば, document.fonts(FontFaceSet)オブジェクトがフォントのロード完了時に発するloadingdoneイベントを起点に文字列を描画します.

但しこの方法ではイベントが発生する前にリスナ関数を登録しなければなりません. そのため上手く行かない場合は次のloadメソッドを使う方法を試します.

loadメソッドの利用

document.fonts.loadメソッドを用いると, 特定のWEBフォントが有効となるまでテキストの描画を待つことが可能です.

FontFaceオブジェクトの利用

FontFaceオブジェクトを用いるとCSSを介さずに直接WEBフォントを読み込むことが可能です.

WEBフォントの活用

WEBフォントはそれ自体便利な仕組みですが, 使い方次第でより高度な表現が可能となります.

SVGのテキストレイアウトとWEBフォントを同時に利用する

この場合, SVGにWEBフォントそのものを埋め込む必要があるため実装にコツがあります. 詳しくはSVG連携の項で解説します.

図形コンテナとしてのフォント

文字形状(グリフ)を集めたフォントは見方によればベクタ図形の集合とも言えます. 従って, WEBフォントを頻繁に描画する図形のコンテナとしての利用が可能です.

opentype.jsによるグリフデータの抽出

opentype.jsを用いるとWEBフォントの内容を解析し, グリフ形状をSVGパス文字列として取り出せます. フォント形式としてTTF,OTF,WOFF(WOFF2を除く)を利用出来ますが, その仕組み上システムフォントを解析対象とすることは出来ません.

カラーフォントの描画

参考:OpenType-SVG color fonts

絵文字文化の拡大に伴い, 今後グリフデータに色情報をもたせたカラーフォントの普及が見込まれています. FireFox等の現在一部のブラウザでその動作を確認出来ます.

😀😁😂😃😄😅😆😇😈😉
カラーフォントの表示例(U+1F600〜U+1F609)
フォント公開元:Emoji One Fonts

以下はこのカラーフォントをcanvas要素に描画したものです. FireFoxでは各種スタイル設定が無視されているほか, strokeTextメソッドの実行結果がfillTextメソッドと同じになっていることが見て取れます. (カラーフォントをサポートしない環境では一般のフォントと同様の結果になります. )

フォント描画処理の自作

WEBブラウザが直接サポートせずとも, 次の要素を備えたデータは(広義の)WEBフォントとしてcanvas要素に描くことが可能です.

このようなフォントには次のようなものがあります.

何れにせよグリフデータをcanvas要素に転写する際に多少のスクリプトを記述する必要があります. なお後述するOffScreenCanvasにおける文字列描画処理に応用可能です.

SVGフォントの描画

SVGフォントはフォント構造をXMLで書き下したものであり, 人間の目からその構造が判りやすいというメリットがあります. 一方データサイズが肥大する傾向があるため現在SVGフォントをサポートするWEBブラウザは存在しません.

SVGフォントの内容をcanvas要素に描画するにはglyph要素からグリフ形状を表すパス文字列を取り出し, そこから生成したPath2Dオブジェクトをfill/strokeメソッドに渡します. その際はSVGフォントの仕様からグリフ形状が上下反転している点に注意します.

座標軸変換

座標軸の変形と図形の描画

CSSやSVG, canvas要素における通常の座標軸は原点をcanvasの左上にとり, x軸を水平方向に, y軸を垂直方向にとります. 図形はこの座標軸を基準に描かれるため, 座標軸そのものを変換すると描かれる図形もそれに従い変形されます. 以下に代表的な変換を挙げます.

これらのなかでcanvas要素が標準的に提供しているものはアフィン変換のみです. それ以外の変換は後述するメッシュ変形による近似やピクセル操作, SVG, WebGL等を用いて実現します.

canvas上のアフィン変換

CanvasRenderingContext2Dオブジェクトではこのうちアフィン変換によるものをサポートしており, 図にすると次のようになります.

アフィン変換による図形の変形

ここでアフィン変換は3×3行列として表すことができ, 変換後の座標\((x,y)\)が変換前の座標において\((x',y')\)に相当すると考えた場合, 次のように記述できます.

\begin{matrix} \begin{pmatrix} x' \\ y' \\ 1 \end{pmatrix}& =& \begin{pmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} x \\ y \\ 1 \end{pmatrix} \end{matrix} もしくは \begin{cases} x' &=& ax + cy + e\\ y' &=& bx + dy + f \end{cases}
アフィン変換における座標の関係式

CanvasRenderingContext2Dオブジェクトは内部にこの変換行列を保持しており, moveTo・lineTo等のメソッドが呼び出された際, 引数として渡された座標を変換したうえでパス図形を定義します.

以下はこの変換行列を操作するためのメソッドです.

CanvasRenderingContext2D.setTransform(a,b,c,d,e,f)
現在の変換行列を指定した内容で置き換える.
CanvasRenderingContext2D.transform (a,b,c,d,e,f)
現在の変換行列に指定した変換行列を右から掛ける.
CanvasRenderingContext2D.resetTransform()
現在の変換行列を恒等変換(初期値)に戻す.
CanvasRenderingContext2D.scale(sx,sy)
現在の変換行列に指定したスケール変換行列を右から掛ける.
CanvasRenderingContext2D.rotate(angle)
現在の変換行列に指定した回転行列を右から掛ける.
CanvasRenderingContext2D.translate(dx,dy)
現在の変換行列に指定した平行移動行列を右から掛ける.

例を示します. 何れも同じ図形を描画していますが, 座標軸が平行移動されていることから, 結果として得られたグラフィックでは描画位置がずれている事が判ります.

これらのメソッドは重ねがけすることができ, 実行する毎に基準となる座標軸が変化していきます.

なお変換メソッドの呼び出し順は, 行列の掛け算の順番に相当することから変更することが出来ません.

座標軸変換をまたいだパス図形の定義

座標軸の変換処理は既存の座標定義・パス図形には影響しません. この挙動を用いると, 紙上で定規をずらしていくように座標軸を変換しながら一つのパス図形を定義することが出来ます. これはcanvas要素特有の仕組みで, 面倒な座標計算処理を省くことが出来ます.

変換の種類

以下に基本的な変換及び, それに関わる変換メソッドについて見ていきます.

行列による変換:transform/setTransform

transformメソッドは現在の変換行列に別の変換行列を(右から)掛け合わせます. setTransformメソッドは現在の変換行列を指定した行列で入れ替えます. translate, scale, rotateは全てこのメソッドに置き換えることが出来ます.

拡大縮小:scale

x軸方向とy軸方向の倍率を指定します. 何れかの値が0の場合は図形の定義が無視されます. setTransformメソッドで書き換えると次のようになります.

回転:rotate

原点を中心とした回転角を指定します. 引数はラジアンで指定します. setTransformメソッドで書き換えると次のようになります.

平行移動:translate

水平方向の移動量と垂直方向の移動量を指定します. setTransformメソッドで書き換えると次のようになります.

せん断:skew

図形を傾ける効果を得ます. CSSやSVGに存在する変換ですがcanvas要素には専用のメソッドが存在しないため, transformメソッドで代用します.

変形の基準を変更する

変換メソッドによる図形の変形は原点\((0,0)\)を基準に行われます. これを特定の座標を基準とするには一旦原点を基準座標に平行移動してから変換メソッドを実行し, その後原点を元の座標に戻すようにします.

変換行列の初期化

現在の変換行列を初期化するにはresetTransformメソッドを実行するかsetTransform(1, 0, 0, 1, 0, 0)として恒等変換行列に入れ替えるようにします.

座標軸変換の限界

canvas要素に関わる座標は内部で単精度(32bit)浮動小数点数として管理されています. そのため, 極端に大きい・小さいパラメータを渡すと, 図形定義処理に伴う座標演算に於いて近似による誤差が発生します. この誤差が極端な座標軸変換(拡大・縮小・平行移動)によって増幅されると, 正しい描画結果が得られないことがあります.

例を挙げます. 一般にストローク図形の描画にはベジェ曲線による近似を要しますが, 下ではこの処理に伴う誤差を座標軸変換によって増幅しています.

なお(楕)円弧の定義には3次ベジェ曲線による近似を伴うため, fillメソッドにおいても同様の問題が発生します.

この問題は図形定義メソッドに渡す座標パラメータを座標軸変換に頼らずに自力で計算することで解決します.

変換行列オブジェクトの取得

現在の座標変換状況はDOMMatrixオブジェクトを介してgetTransform/setTransformメソッドとして取得・設定が可能です.

CanvasRenderingContext2D.getTrasnform()
現在の変換行列をDOMMatrixオブジェクトとして取得する.
CanvasRenderingContext2D.setTransform(matrix)
現在の変換行列を指定した行列データ(a,b,c,d,e,fプロパティを含むオブジェクト)で上書きする.

しかし, このAPIは定義されてから日が浅いため, 実装しているブラウザはまだ無いようです.

SVGMatrixオブジェクトの利用

その為getTransformメソッドの代替としてSVGMatrixオブジェクトを使う方法を示します. SVGMatrixオブジェクトを使って得た行列の計算結果をsetTransformメソッドでcanvasに適用するのです. 例を示します. scaleメソッドの名称, rotateメソッドの引数の単位に若干の相違があるものの置き換え可能です.

以下に大体のメソッド相関について示します.

座標変換メソッドの対応
座標変換CanvasRenderingContext2DSVGMatrixCSS備考
拡大縮小ctx.scale(sx, sy)m.scaleNonUniform(sx, sy)
m.scale(s)
scaleSVGMatrix.scaleはsx=sy時に置き換え可能.
回転ctx.rotate(rad)m.rotate(deg)rotatecanvasではラジアン値, SVGMatrixでは角度値
平行移動ctx.translate(dx, dy)m.translate(dx, dy)translate
せん断-m.skewX(deg)
m.skewY(deg)
skew
skewX
skewY
transformメソッドで代替
初期化ctx.resetTransform()
ctx.setTransform(1, 0, 0, 1, 0, 0)
m.a = m.d = 1;
m.b = m.c = m.e = m.f = 0;
-
変換行列ctx.transform(a, b, c, d, e, f)m.multiply([matrix])matrix
行列指定ctx.setTransform(a, b, c, d, e, f)m.a = a; m.b = b; m.c = c;
m.d = d; m.e = e; m.f = f;
-
逆行列-m.inverse()-カーソル位置の算出に利用. FireFoxではmozCurrentTransformInverseで取得可能.

CSSのtransformプロパティとの相関

座標変換メソッドの実行は現在の座標変換行列に右から行列を掛けあわせることに相当します. そのため, 同等の座標変換をCSSで記述すると次のようになります.

補足)廃止となったプロパティ群を使ったgetTransform/setTransformポリフィル

getTransformメソッドが定義される以前では同等の機能をcurrentTransformプロパティが担うこととされており, 環境によってはまだ利用可能です. またFireFoxでは同等の機能をmosCurrentTransformプロパティとして定義しています.

CanvasRenderingContext2D.currentTransform
現在の変換行列をSVGMatrixオブジェクトとして取得・設定する.
CanvasRenderingContext2D.mozCurrentTransform
(FireFoxのみ)現在の変換行列を配列で取得する.
CanvasRenderingContext2D.mozCurrentTransformInverse
(FireFoxのみ)現在の変換行列の逆行列を配列で取得する.

その為次のポリフィルコードによりgetTransformメソッドの動作を再現可能です. (SVGMatrixは将来的にDOMMatrixのエイリアスとなるため, SVGMatrixを返すようにします.)

図形描画への応用

以下, 座標軸変換の応用について示します.

楕円の描画

円を何れかの方向に引き伸ばすと楕円が得られます. しかし楕円の境界に線を引く際に座標軸の引き伸ばしをしたままstrokeメソッドを実行すると, 線の幅まで引き伸ばされてしまって見た目が良くありません. この問題は一度座標軸を戻すことで解決します. このことからstrokeによる線の描画にも座標軸が用いられていることが判ります.

厚みをもったstroke(カリグラフィー)

上とは逆にstrokeによる線の幅を縦横不均衡とすることで, 線に厚みを持たせることが出来ます.

座標軸を回転させることによる図形の定義

カンバスを逐次回転させ, 点の位置を少しづつずらして行くことで様々な図形を描画することができます. 三角関数の記述が要らないため構造が単純になります.

螺旋

集中線

多角形・星型

タートルグラフィックの実現

カンバスを回転させたり基準点の位置をずらしていくことで図形を定義する手法を一般にタートルグラフィックと呼びます. タートルグラフィックは本質的に初期位置の指定回転角の指定前進距離の指定の3操作の繰り返し(プログラム)で表現されます. このプログラムによりタートル(カーソル)が動いた軌跡を元に図形を定義するのです.

タートルグラフィックによる図形定義

これらの操作はcanvas要素では座標軸変換として表現できます.

タートルグラフィックコマンドの対応
コマンドcanvasでの操作
起点の指定setpos(x, y)ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.translate(x, y);
ctx.beginPath();
ctx.moveTo(0, 0);
回転角の指定right(rad)ctx.rotate(rad)
前進距離の指定forward(d)ctx.translate(d, 0);
ctx.lineTo(0, 0);

以下はCanvasRenderingContext2DオブジェクトにタートルグラフィックのためのAPIを定義するスクリプトです.

これらを用いると一般のタートルグラフィック描画アルゴリズムをcanvas上に再現できます.

図形の再帰定義とフラクタル図形

参考:タートルグラフィックスで亀と戯れる

タートルグラフィックは再帰処理と相性がよく, 複雑な図形を単純なアルゴリズムで記述できることからフラクタル(自己相似)図形の描画によく用いられます. 下記はタートルグラフィックの描画手法を用いてコッホ曲線を描画しています.

グラデーション・パターンへの適用

パス図形を定義した後, 座標軸変換を施すことで塗り潰しの内容, 線の内容のみに変形を施すことができます.

座標軸設定と描画処理の分離

座標軸の管理とグラフィックの描画とを分離することで, canvasサイズ(解像度)や基準となる座標に依存しないグラフィックが得られます. これらは何れもベクタ画像の特徴ですが, canvas要素では状況によって画像の内容を書き換えることで実現します. これは後述するスクリーン解像度(密度)に最適なグラフィックを描く上でも役に立ちます.

ズーム・パン機能の導入

座標軸変換を用いてcanvasグラフィックにズーム(拡大・縮小)とパン(平行移動)機能を追加します. 下の例ではマウスによるドラッグ操作でグラフィックの描画位置を変更可能としています.

グラフィックのレスポンシブ化

静的なラスタ画像は一般に解像度が確定しているため, 描画サイズが変更されるとアンチエイリアシングと言った近似処理が発生しますが, canvas要素では座標軸変換を用いることで, 解像度毎に最適なグラフィックを描くことが出来ます. 具体的な手順を示します.

  1. グラフィックの内部サイズを決定する.
  2. canvas要素の見た目のサイズ(CSSサイズ)を取得する.
  3. ctx.scaleメソッドを使ってグラフィックの描画スケールを「見た目サイズ/内部サイズ」する.
  4. ctx.saveを実行し, 現在のスケール設定を保存する.
  5. 以降はこれまでと同様のサイズ200×200のスクリプトを実行する.
  6. canvas要素のサイズを変更しうるイベントに再描画処理を登録する.

次の例では, input[type=range]の入力内容によってcanvas要素のサイズを変更しています. 文字グラフィックが滑らかに拡大/縮小される部分に注目して下さい.

種々の変形のアフィン変換への帰着

射影変換等のcanvas要素が直接サポートしない変換も, 適宜パラメータを求めたり適用範囲を細分し近似することでアフィン変換に帰着出来ます.

3点対応による変換

平面におけるアフィン変換は3点の対応によって決定されます. 例えば次のような座標の対応があったとします.

3点の対応によるアフィン変換

するとこの対応から定まるアフィン行列の成分\((a,b,c,d,e,f)\)は次の式で求められます.

\begin{matrix} \begin{pmatrix} a \\ b \\ c \\ d \\ e \\ f \end{pmatrix}& =& \begin{pmatrix} x_1 & 0 & y_1 & 0 & 1 & 0 \\ x_2 & 0 & y_2 & 0 & 1 & 0 \\ x_3 & 0 & y_3 & 0 & 1 & 0 \\ 0 & x_1 & 0 & y_1 & 0 & 1 \\ 0 & x_2 & 0 & y_2 & 0 & 1 \\ 0 & x_3 & 0 & y_3 & 0 & 1 \\ \end{pmatrix}^{-1} \begin{pmatrix} X_1 \\ X_2 \\ X_3 \\ Y_1 \\ Y_2 \\ Y_3 \end{pmatrix} \end{matrix}
3点の対応によるアフィン変換の成分

例を示します.

逆行列の計算に失敗するケースへの対策

上記関係式において指定した座標に0もしくは0に近い値が含まれていると, 浮動小数点演算に伴う計算誤差が強調され逆行列の値が正しく求められないことがあります. この場合, 平行移動処理と組み合わせ0値から十分に離れた位置で3点対応を求め, 元の位置に引き戻すようにします.

メッシュ変形

元の画像を網目状に細分し, その頂点位置をずらすことでグラフィックを変形する手法をメッシュ変形と呼びます. canvas要素では領域を三角形のメッシュに分解し, 変換前後の頂点の対応から三角形領域毎にアフィン変換行列を求めることで目的のメッシュ変形を得ます.

メッシュ変形における対応

処理フローを示します.

  1. 変形したい画像(img/canvas要素)を用意します.
  2. コンテキストの状態を保存します.
  3. 変形先の三角形の範囲でcanvas要素をクリップします.
  4. 三角形頂点の対応を元に座標軸を変換します.
  5. 1の画像を描画します
  6. コンテキストの状態を復元します.
  7. 2〜6の作業を三角形の数だけ繰り返します.
メッシュ変形の実装例

なおメッシュ変形を施すにあたり, 描画範囲をクリップする過程でどうしても不要なアルファ値が発生します. そのままでは見苦しいため, SVGフィルタやピクセル操作を使ってアルファ値を除去します.

射影変換

平面上の4点を遠近法のもとで別の4点に移す変換を射影変換と呼びます. 例えば次のような座標の対応があったとします.

4点の対応による射影変換

すると, 元の座標\((x,y)\)と変換先の座標\((X,Y)\)との間には次の関係が成り立ちます.

\begin{cases} X = \cfrac{c_1x + c_2y + c_3}{c_7x + c_8y + 1} \\ Y = \cfrac{c_4x + c_5y + c_6}{c_7x + c_8y + 1} \end{cases}
\begin{matrix} \begin{pmatrix} X \\ Y \\ 1 \end{pmatrix}& =& \cfrac{1}{c_7x+c_8y+1} \begin{pmatrix} c_1 & c_2 & c_3 \\ c_4 & c_5 & c_6 \\ c_7 & c_8 & 1 \end{pmatrix} \begin{pmatrix} x \\ y \\ 1 \end{pmatrix} \end{matrix}
射影変換を行う関係式と射影変換行列

ここで\(c_1\)〜\(c_8\)は次の式を充たす値です.

\begin{matrix} \begin{pmatrix} c_1 \\ c_2 \\ c_3 \\ c_4 \\ c_5 \\ c_6 \\ c_7 \\ c_8 \end{pmatrix}& =& \begin{pmatrix} x_1 & y_1 & 1 & 0 & 0 & 0 & -X_1x_1 & -X_1y_1 \\ x_2 & y_2 & 1 & 0 & 0 & 0 & -X_2x_2 & -X_2y_2 \\ x_3 & y_3 & 1 & 0 & 0 & 0 & -X_3x_3 & -X_3y_3 \\ x_4 & y_4 & 1 & 0 & 0 & 0 & -X_4x_4 & -X_4y_4 \\ 0 & 0 & 0 & x_1 & y_1 & 1 & -Y_1x_1 & -Y_1y_1 \\ 0 & 0 & 0 & x_2 & y_2 & 1 & -Y_2x_2 & -Y_2y_2 \\ 0 & 0 & 0 & x_3 & y_3 & 1 & -Y_3x_3 & -Y_3y_3 \\ 0 & 0 & 0 & x_4 & y_4 & 1 & -Y_4x_4 & -Y_4y_4 \end{pmatrix}^{-1} \begin{pmatrix} X_1 \\ X_2 \\ X_3 \\ X_4 \\ Y_1 \\ Y_2 \\ Y_3 \\ Y_4 \end{pmatrix} \end{matrix}
射影変換行列の成分

この関係をメッシュ変形時の頂点対応に適用することで射影変換を近似することができます. メッシュの細かさを上げるにつれ, 本来の射影変換による出力結果に近づいていきます.

メッシュ変形による射影変換の近似

補足)射影変換のCSS表現

上記で得られた射影変換行列は, CSSのmatrix3d関数で書き下すことでtransformプロパティから利用することも出来ます. なお, 変換の基準座標は\((0,0)\)であるため, transform-origin:0 0と組み合わせます.

同様にWebGLのシェーダに渡すことも可能です.

行列を使った3次元図形の描画

3次元空間内の図形は4次行列による座標変換を使って平面上に投影できます. 例えば点\((x, y, z)\)が視点\((p_x, p_y, d)\)により平面上の点\((X', Y')\)に投影されるとすると次の関係が成り立ちます.

\begin{matrix} W \begin{pmatrix} X' \\ Y' \\ Z' \\ 1 \end{pmatrix}&=& W \begin{pmatrix} X/W \\ Y/W \\ Z/W \\ 1 \end{pmatrix}&=& \begin{pmatrix} X \\ Y \\ Z \\ W \end{pmatrix}&=& PT \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} \end{matrix}
ここで,
\(P=\begin{pmatrix} 1 & 0 & 0 & p_x \\ 0 & 1 & 0 & p_y \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & -1/d & 1 \end{pmatrix} \begin{pmatrix} 1 & 0 & 0 & -p_x \\ 0 & 1 & 0 & -p_y \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}, T=\begin{pmatrix} m_{11} & m_{21} & m_{31} & m_{41} \\ m_{12} & m_{22} & m_{32} & m_{42} \\ m_{13} & m_{23} & m_{32} & m_{43} \\ m_{14} & m_{24} & m_{32} & m_{44} \\ \end{pmatrix} \)
3次元空間の平面への変換

ここで\(T\)は3次元空間における変換を表すtransform行列, \(P\)は視点から求まるperspective行列です. この対応を用いると空間内の単純な図形(線分・ベジェ曲線・多角形)及びそれらで近似可能な図形(例えば円弧)を2Dcanvas上に描くことが可能です.

これらの行列演算を提供するオブジェクトとしてはDOMMatrixとWebKitCSSMatrixの2つがあり, 環境によって利用できるものが変わります. なお, 別途行列演算を行うライブラリを導入しても良いでしょう. を示します.

2Dカンバスにおける3D図形の描画

このように素朴なグラフィックを描く場合や, 遠近法変換の学習用途においてはCanvasRenderingContext2Dで十分その役割を果たせます.

補足)遠近法変換のメッシュ変形による近似

遠近法変換は射影変換と同じ手法でメッシュ変形で近似出来ます.

補足)遠近法変換のCSS表現

先ほどの変換行列の関係をCSS形式で書き下すと次のようになります.

アフィン変換に頼らない変形

座標軸変換等の行列として表すことが難しいものについてはcanvas標準の座標軸変換機構を用いず自力で座標対応を求めてグラフィックを変形します. その手法としては後述するdrawImageメソッドによるものとピクセル操作によるものの2つがあり, 何れも変形後のピクセルの位置から変形前のピクセルデータをを求めピクセル毎に色を決定していきます. 変形前のグラフィックを要する他, アフィン変換によるものと比較しパフォーマンスや出力結果の品質が劣化しやすいため実装に工夫を要します.

drawImageメソッドによるラスタ変形・フリー変形

drawImageメソッドを用いると画像の一部を切り出すことができます. これを切り貼りすることで図形を自由に変形することが可能です. この方法ではクロスオリジン画像の変形が可能です.

ラスタ変形

トリミング指定による画像描画を横(縦)ラインごとに行うと, 画像をラスタ変形することが出来ます.

フリー変形

トリミング指定による画像描画をピクセル毎に行うと, 画像を任意形状に変形することが出来ます.

ピクセル操作による極座標変換

getImageDataメソッドを使って取得したピクセル情報をもとに画像を変形する方法です. 処理をWorker環境で行わせることが可能です.

極座標変換

極座標変換はピクセル位置\((x, y)\)(直行座標)と中心からの半径\(r\)・偏角\(\theta\)(極座標)とを対応付けるものを指します.

\begin{cases} r &=& \sqrt{x^2 + y^2}\\ \theta &=& atan2(y, x) \end{cases} \begin{cases} x &=& r \times {\it cos}(\theta)& (r \geq 0)\\ y &=& r \times {\it sin}(\theta)& (0 \leq \theta < 2\pi) \end{cases}
直行座標\((x, y)\)と極座標\((r, \theta)\)との関係
極座標変換の適用結果画像

ピクセル補間処理による品質の改善

変換の過程で算出された座標に対応するピクセルデータを取得する際, 先程の例では位置的に最も近いものを採用していました. この方法はニアレストネイバー法と呼ばれ高速に処理できる反面, 変換した画像の出力品質がよくありません. この場合, 算出された座標を囲むピクセル群から何らかの補間処理を施したものを用いることで品質が改善します. ここでは最もシンプルなバイリニア法による補間処理を適用した例を示します. バイリニア法は得られた座標を囲む4ピクセルの値から重み付け平均をとるもので, シンプルながら良好な出力結果が得られます.

バイリニア法の概念図
バイリニア補間を適用した出力結果

カンバス状態の管理

コンテキスト状態の一時保存と復元

カンバスでの描画処理はコンテキストオブジェクトを何度も使い回して行われます. この時, 図形を描画する都度その色や座標系, クリップ領域を再定義するといったことでは非常に面倒です. このような問題を解決するため, コンテキストオブジェクトには現在の各種設定を一時的に保存するsaveメソッドと元に戻すrestoreメソッドが定義されています. この機能を用いることで一般に難しいクリップ領域の削除や, 座標軸変換状況の復元などの操作が行えます.

CanvasRenderingContext2D.save()
現在のコンテキストの内容をスタックに保存する. 保存されるプロパティ値は次の通り.
座標軸の変形状況, クリップ領域, strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, filter, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled, imageSmoothingQuality, 破線設定
CanvasRenderingContext2D.restore()
前回の状態をスタックから取り出す.

この状態の保存機能はスタックとして実装されているため, 一度restoreメソッドを実行してしまうと先ほどsaveメソッドで実行した内容はなくなってしまいます. 現在の状況をまた後で用いたい場合は再度saveメソッドを実行する必要があります. また, 際限なくsaveメソッドを実行した場合スタックが溢れてしまう可能性があるので, saveメソッドとrestoreメソッドとはセットで用いるようにしましょう. 特にtry/catch構文が含まれる場合は, 必ずfinally句でrestoreメソッドを呼ぶようにします.

スクリプトライブラリの利用と独自描画

導入したスクリプトライブラリに目的とする機能が含まれていない場合は, canvas APIを直接操作することになります. この場合, ライブラリによる描画を行った後で当該canvas要素からコンテキストオブジェクトを取得し, 直ぐにsaveメソッドを発行します. これはライブラリが内部で利用しているコンテキスト状態を維持するためです. その後独自の描画処理を行い, restoreメソッドを実行してコンテキスト状態を元に戻すようにします.

コンテキスト状態の破棄

コンテキスト状態だけを破棄するAPIは存在しませんが, 次のようにすると現在の画像を維持しつつコンテキスト設定のみをリセットすることが出来ます.

  1. getImageDataメソッドで現在の画像データを取得する.
  2. ctx.beginPath();ctx.closePath();を実行する.
    現在のパス図形をクリアする. (Trident系ブラウザでは下の操作だけではパス図形が残る.)
  3. canvas.width = canva.widthを実行する.
    コンテキスト状態及びグラフィックが破棄される.
  4. (1)で保存しておいた画像データをputImageDataメソッドで書き戻す.

コンテキスト状態の列挙

コンテキスト状態の全体を取得するAPIは存在しませんがfor-in構文を使って列挙することができます. 上手く設計すればコンテキストの状態を外部で管理することも可能です. 但しFunctionやObjectと言ったものも含まれてしまうので, 必要となるものを適宜取捨選択する必要があります.

描画状態の一時保存と復元

処理の都合上グラフィックの内容を保存するケースはよくあります. canvas要素に描いた内容をスナップショットとして外部に取り出す方法には幾つかあり, それぞれ用途や特性が異なります. なお, 細かいAPIの説明についてはそれぞれの項目を参照して下さい.

  1. canvas要素によるもの
    最も素朴な方法で, 別のcanvas要素にグラフィックを描画して保持します. 書き戻しはdrawImageメソッドで行います. シンプルで使い勝手が良いものの, 用途に対して消費するリソースが大きすぎる点が気になります.
  2. CanvasPatternオブジェクトによるもの
    現在の描画内容をcreatePatternメソッドでオブジェクト化します. グラフィックの書き戻しにはfillメソッドを用いるため, 同期的に処理出来るメリットがあります. 一方, 画像データの永続化(クッキーやローカルストレージへの格納)には用いることが出来ず, 専ら一時的なデータ保存に利用します.
  3. toDataURLメソッドによるもの
    画像データを文字列として扱うもので, データの永続化(localStorageやsessionStorageへの格納)が簡単に行えるメリットがあります. 一方グラフィックの書き戻しには一旦Imageオブジェクトを介した非同期処理を介する必要があるため, 扱いが面倒です.
  4. toBlobメソッドによるもの
    使い勝手はtoDataURLメソッドと同様ですが, 画像データの保存先としてIndexedDBやローカル環境を用いる場合に利用します.
  5. ImageBitmapオブジェクトによるもの
    ImageBitmapオブジェクトはtransfarableインターフェースを実装するため, messageイベントを介して異なる環境(window環境/worker環境やフレーム間)でのcanvas要素/OffscreenCanvasオブジェクト間でグラフィックをやり取りすることが可能です. 一方オブジェクトの生成にはPromiseオブジェクトが介在するため, 非同期処理として記述することになります. またFetch APIと組み合わせることで画像の読み込み処理が単純化されます.
  6. ImageDataオブジェクトによるもの
    グラフィックをバイトデータとして取得するもので, ImageBitmapオブジェクトと同様に異なる環境間でのデータの授受が容易です. また, コンテキストの状態(transform等)に依らずに画像を復元できます.

それぞれの方法の特徴をまとめると次のようになります.

スナップショット取得別の特徴まとめ
場面\方法canvasCanvasPatterntoDataURLtoBlobImageBitmapImageData
SSの保存先(任意)変数変数・storage
・ファイル・IndexedDB
変数・ファイル
・IndexedDB
変数変数・IndexedDB
書き出し同期同期同期非同期非同期同期
書き戻し同期同期非同期非同期同期同期
動作環境windowwindowwindow/?window/?window/workerwindow/worker
備考一時保管WebStorage保存ファイル保存ファイル読み込みバイナリ編集
worker/frame間通信
†ImageDataオブジェクトをUint8ClampedArrayとサイズに分解した上で格納

いずれの方法を採用したとしても, 1履歴分画像のスナップショットを撮ると最低でもcanvasグラフィックの分だけ記憶領域(メモリ/ディスク)が消費されていきます. その為, 無用なグラフィック保存はシステムに負担をかける事になります.

IndexedDBへのグラフィック格納

IndexedDBはWEBブラウザ(worker環境を含む)上で動作するオブジェクト指向データベースで, Blobや型付き配列などのデータをそのまま挿入できることからcanvasグラフィックの格納先として利用できます. またデータはディスク上に格納されるため, 複数の描画履歴を管理する場合や描画処理を中断・再開する用途に適しています. その一方で殆どの処理が非同期(イベントモデル)で行われることからコードが煩雑化しやすく, 慣れるまでは使いにくく感じるかもしれません.

例を示します.

以下にポイントをまとめます.

ページ遷移時のcanvasグラフィックの受け渡し

canvasグラフィックの受け渡しにページ遷移を挟む場合, その内容を同一オリジン内で共有可能な場所に一時的に保管することで無用なサーバー間通信を省くことが可能です. その保管場所の候補としては次のものがあります.

なお, 何れの場所を選択した場合も(特に明示しない限り)保管したグラフィックデータがディスク領域を専有し続けるため, 不必要となった時点でその内容を削除するようにしましょう.

Web Storage APIの利用

Web Storage APIは同一オリジン内でのデータ共有を目的としたもので, キー-バリュー型(文字列等)の単純なデータを格納するのに適しています. しかし, ストレージサイズの上限を鑑みるとサイズの大きなcanvasグラフィックの授受には向いていません. IndexedDBを用いるか, messageイベントを介したwindow間通信で代替出来ないか検討して下さい.

localStorage/sessionStorage.setItem(key, value)
キーに対する値(文字列)を格納する. オリジン毎に2〜10MB(ブラウザ依存)の上限あり
localStorage/sessionStorage.getItem(key)
キーに対する値(文字列)を取得する.
localStorage/sessionStorage.removeItem(key)
キーに対する値を削除する.

描画状態の破棄

カンバスグラフィックの内容をクリアするには主に次の4つの方法が考えられます.

  1. ctx.clearRect(0,0,canvas.width,canvas.height);
    (推奨)コストの低い操作であるため, 特に問題がない限りこの方法を選択して下さい. 最も単純なクリアの方法ですが, 座標軸変換が存在している場合に正しく動作しません.
    この問題はステートのセーブで解決します.
  2. canvas.width = canvas.width;
    唯一canvas要素に対する操作で中身をクリアする方法です. 簡単に記述できる反面, グラフィックの他スタイル履歴情報等も全てクリアされてしまいます. また明示的に何を意図しているのか判りにくいのが欠点です.
    ※Trident(Internet Explirer, Edge)ではパス図形のみ維持されるといったようにブラウザ間で動作に違いがあります.
    スクリプトライブラリがcanvas要素のグラフィックを消してしまう場合, 冗長な属性値操作が発生していないか(MutationObserver等を使って)確認します.
  3. ctx.globalCompositeOperation = "copy"; ctx.fillRect(0,0,0,0);
    画像合成を逆手に取った画像のクリア. 座標軸変換等の状態などを気にせずともよく意外に使いやすいです. 但しcanvas全体に演算が走ることがあり, FireFoxではコストの高い操作となります.
  4. ctx.putImageData(ctx.createImageData(canvas.width, canvas.height), 0, 0);
    (非推奨)グラフィックデータのみをクリアできます. origin-cleanフラグがfalseとなっていた場合にエラーが発生します. なお生成したImageDataオブジェクトは使い捨てにせず変数にとっておき, クリアの都度再利用するとよい.でしょう

上記4つの方法についてそれぞれ動作コストが異なります. 特に(4)のputImageDataメソッドによる方法は画像サイズに比例し極端なパフォーマンス劣化が見られます. また(2)のcanvas.widthプロパティによる方法はcanvas要素の内部構造のクリアを伴うため(1)のclearRectメソッドによる方法よりも若干コストが高くなります. 概ね(1)≦(2)<(3)<<<(4)の順で軽快に動作します.

画像の挿入

drawImageによる画像の挿入

drawImageメソッドを用いると, canvas要素に任意のラスタ(ベクタ)画像を描画することができます.

CanvasRenderingContext2D.drawImage()
既存の画像を描画する. HTMLImageElement/HTMLVideoElement/HTMLCanvasElement/ImageBitmapが描画対象. なお, GIFアニメーションや動画については, 特定のフレーム(大抵は先頭フレーム)がスナップショット的に描画される. なお, WebKitでは, アニメーションGIF/PNG画像を書き込むタイミングによって出力されるフレームが変化する.
CanvasRenderingContext2D.drawImageFromRect()
互換性のために残されているdrawImageメソッドのエイリアス(WebKit環境のみ).

なお, CSS背景画像等をcanvas要素に直接描画することはできません.

描画可能な画像のフォーマット

基本的にブラウザがサポートしている(img要素で表示可能な)画像・動画形式であればcanvas要素に描画できますが, ブラウザ互換性を鑑みると, PNG, JPEGのいずれかを利用するのが無難です.

エンジン別画像フォーマット対応状況(wikipediaより抜粋)
formatmime typeGeckoBlinkEdgeHTMLWebKit備考
JPEG image/jpeg 非可逆
GIF image/gif 可逆, 色数限定, 単色透明可, アニメーション可
PNG image/png 可逆, 透明可
APNG Blink/EdgeHTML環境ではアニメーション不可
SVG image/svg+xml ベクタ形式, スクリプトによる生成容易
BMP image/bmp 未/低圧縮, 透明可, スクリプトによる生成容易
WEBP image/webp ××可逆・非可逆, 透明可, アニメーション可
JPEG XR image/vnd.ms-photo ×××
JPEG2000image/jp2 ×××Safariのみ?
TIFF image/tiff ××マルチページ
†将来的なサポートを表明.

画像形式のサポート判定

ブラウザ環境が指定した画像形式の表示をサポートするかどうかは, img要素に実際に画像を読み込ませてエラーとならないかどうかで判定します. そのために, 1px平方の画像をデータURIスキーム形式に変換したものを用いると便利です.

このブラウザはWEBP形式をサポート.

画像挿入の3パターン

画像挿入に用いるdrawImageメソッドは渡した位置パラメータの数で座標指定矩形指定トリミング指定の3つに動作変化します.

引数imageにはHTMLImageElement(Imageオブジェクト), HTMLCanvasElement, HTMLVideoElement, ImageBitmapの何れかを指定します.

下はimg要素で読み込んだ画像ファイルです. これをカンバス要素に描画してみましょう.

座標指定

位置パラメータが2つの場合の動作で, 指定した座標(dx, dy)を起点に画像が描画されます. canvasサイズからはみ出た部分は無視されます.

矩形指定

位置パラメータが4つの場合の動作で, 指定した矩形(dx, dy, dw, dh)に従って画像が伸縮されます.

またdw, dh値に負の値を指定することで, 矩形の基準(通常は左上)を右下等に変更することが出来ます.

トリミング指定

位置パラメータが8つの場合の動作で, 2つの矩形パラメータに従い画像を指定した矩形範囲(sx, sy, sw, sh)で切り取り, その内容をカンバスの(dx, dy, dw, dh)領域に描画します. 画像の切り出し, 画像の分離(スプライト画像の切り出し)等に用います.

画像描画時のマッピング

画像挿入に関わるテクニック

画像の挿入に際して覚えておくと便利なテクニックについて示します.

座標軸変換による画像の変形

drawImageメソッドでは予め座標軸を変形しておくことで画像を変形することができます. 下の例は画像を左右反転させた例です.

canvas要素の参照

drawImageメソッドでは画像の参照先としてcanvas要素が使えます. 例えばアニメーション処理を行う際に前景と背景とを別々に描画し, 最後に一つにまとめるといった構成を採ることも可能です.

また自分自身を書き込むことも出来ます.

画像サイズを取得する

img要素における画像サイズにおいてもcanvas要素と同じく画像そのものサイズと画像の描画サイズの2つが定義されています.

HTMLImageElement.width/height
画像の見た目のサイズを取得する. CSSによるサイズが指定されていない場合, スクリーンに描画されていない場合は画像そのものサイズを返す.
HTMLImageElement.naturalWidth/naturalHeight
img要素そのもののサイズを取得する. 通常画像のサイズに合わせられるが, srcset属性が有効な場合は選択された画素密度の分除算される.

この値を用いるとアスペクト比(横縦比)を維持しながら画像を描画できます.

また, 画像の加工を目的としているのであればnaturalWidth/naturalHeight値をcanvas要素に転写します.

画像ロードとdrawImageメソッドの実行順

外部画像を読み込む場合, HTML文書に定義されているimg要素を参照する動的にHTMLImageElement(Image)オブジェクトを生成して参照するImageBitmapを生成して参照する(詳しくは後述します)の3つの方法が存在します. この時, srcプロパティを変更した際に行われる画像ファイルの読込・分析メイン処理とは別のスレッドで行われるため, drawImageメソッドを実行したタイミングによってはまだ画像のロードが終わっていないことがあります.

画像の読み込みと画像の取得

従ってこの状態のままdrawImageメソッドを実行してしまうと画像の書き込みに失敗します. 下の例ではimg要素に画像の参照先を設定した直後にdrawImageメソッドを実行しているため, 画像の描画成否が不定となります.

再現性に乏しいため一見厄介ですが, この問題を解決するにはimg要素の持つonloadイベントでカンバスの描画処理を行うようにします.

しかしこの方法をもってしても, srcプロパティとリスナ関数の登録処理の順番を変えると稀に描画処理に失敗します.

画像ロード待ちのバリエーション

img要素のロード待ちを実現するコードには幾つかバリエーションがあります.

onload属性の利用

img要素のonload属性にスクリプトを記述する方法があります.

completeプロパティの利用

img要素のcompleteプロパティを使って画像読み込みの完了を確認する方法です.

画像読み込み失敗への対処

ネットワークを介した画像読み込みは常に成功するとは限りません. そのため正常系の処理をloadイベントに記述すると共に, 異常系の処理をerrorイベントに記述することでより堅牢なスクリプトになります.

複数の画像ソースを利用する場合

canvas要素に読み込む画像が複数にわたる場合はもう少し細工を施す必要があります. 読み込みが完了した画像を数え, 全ての画像が読み込まれたことを確認してから描画処理を開始するようにします. HTMLImageElement.completeプロパティは画像を読み込んでいない際にもtrueを返すためこの用途においては適切ではありません.

decodeメソッドを用いた画像の読み込み待ち

Imageオブジェクトに新たに追加されたdecodeメソッドを用いると, これまでloadイベントに記述していた処理を, Promiseで書き換えることが可能です. つまり, 複数画像の読み込みもよりスマートに記述することが可能となります.

Image.decode()
画像の読み込み完了を待つPromiseを返します.

画像表示が制限された環境での動作

プライバシー設定等により画像の表示が制限されている環境ではimg要素(及びImage / HTMLImageElementオブジェクト)が適切なイベント(load / error)を発生しません. この場合, 下記に示す同期的な画像読み込みもしくはImageBitmapオブジェクトを使った方法を用います.

補足)同期的な画像読み込み

画像データの読み込みにHTMLImageElement/ImageBitmapではなく独自のデコーダを利用する場合は, 次のようにすると画像をcanvas要素に同期的に書き込むことが可能です.

  1. XMLHttpRequestを用いて画像データ(バイナリ)を同期的に取得する(openメソッドの第3引数にtrueを指定する).
  2. 上で得たバイナリからビットマップデータを復元する.
  3. ImageDataオブジェクトを介してビットマップデータをcanvas要素に転写する(画像ソースの生成).
  4. 必要に応じてdrawImageメソッドを使って上記canvas要素を別のcanvas要素に描画する.

この方法を用いるとコードが単純になる一方でスクリプトの応答性が損なわれるため, コードの実行状況がブラウザの動作に影響しないworker環境での利用が推奨されます.

decoding属性による読み込みの制御

img要素に新たに追加されたdecoding属性を用いると, 画像の読み込みを同期的に行う(sync)か非同期的に行う(async)かを指定できます.

ソース画像の自動選択

img要素に表示される画像の候補は単独とは限りません. picture要素やsrcset属性と組み合わされているimg要素をcanvas要素に転写するには工夫が必要です.

picture要素とcanvas要素

picture要素はimg要素と組み合わせて, 環境ごとに最適な画像ファイルだけを読み込み表示します. 例えばWEBP形式をサポートする環境ではWEBP形式の画像を, そうでない環境ではPNG形式の画像を表示すると言ったことが可能です.

picture要素をcanvas要素に描画するには, picture要素配下のimg要素をcanvas要素に描画するようにします.

なお, どの画像が実際に使われたのかを判定するには下記currentSrcプロパティを用います.

srcset属性とcanvas要素

img要素のsrcset属性を用いると, スクリーンのピクセル密度(及びズーム倍率)に応じた画像の選択が可能となります.

このようなimg要素を直接canvas要素に描画するのは得策ではありません. なぜなら画像本来のサイズを表すnaturalWidth/naturalHeight値がsrcset属性に記述された密度値(2x等)の影響を受けるからです. そこで, HTMLImageElementオブジェクトのcurrentSrcプロパティから現在選択されている画像URLを別のImageオブジェクトに読み込み, その上でcanvas要素に書き戻します.

HTMLImageElement.currentSrc
img要素に現在表示している画像ソースの実際のURLを取得する

画像の描画品質

drawImageメソッドによるラスタ画像の描画には注意すべき点があります.

アンチエイリアスの設定

imageSmoothingEnabledプロパティを用いるとラスタ画像をカンバスに描画する際の品質(アンチエイリアスの有無・品質)を設定できます. ドット絵等の低解像度の画像データを拡大する場合に利用します.

CanvasRenderingContext2D.imageSmoothingEnabled
画像を描画する際の品質を指定する. falseでアンチエイリアス(平滑化)を無効とする. (図形描画等には影響しない)
CanvasRenderingContext2D.imageSmoothingQuality
アンチエイリアスの品質を指定する. (low/medium/high)

画像縮小時の品質改善

imageSmoothingEnabledプロパティをtrueに設定していてもdrawImageメソッドで大きな画像を極端に縮小すると, CSSで縮小した場合と比べジャギー(色境界のギザギザ)が目立つことがあります.

CSSによる画像の縮小例

この問題は画像の縮小アルゴリズム(単純なバイリニア補間)によるもので, サイズ1/2未満の縮小を行った際に細かい部分のピクセルデータが欠落することで発生しています. 縮小前に広範囲のピクセルデータがブレンドされるようにすると改善します.

逐次縮小によるもの

出典:Html5 canvas drawImage: how to apply antialiasing

一度に目的のサイズに縮小するのではなく, 作業を何回かに分割して実行します.

フィルタによるもの

元画像を一旦canvas要素でぼかしてから縮小します. ぼかし半径は控えめに設定すると上手く行くようです.

画像のモザイク化

ソース画像をモザイク化するには様々な方法がありますが, imageSmoothingEnabledが使えるなら一旦小さなcanvas要素に描画した内容を引き伸ばすだけで実現できます.

CSSを用いた画像のモザイク拡大

出典:Showing how to zoom up a bitmap with crisp edges using HTML Canvas or CSS.

CSSのimage-renderingプロパティを指定することで, canvas画像をモザイク拡大する方法もあります. canvas要素側の処理が単純になる反面, 歴史的経緯から指定するスタイル値がブラウザ毎にバラバラという難点があります. またInternet Explorer9以降では-ms-interpolation-modeプロパティが効きません.

image-renderingプロパティにおけるの動作の違い(イメージと実動作)
元画像pixelatedcrisp-edges

画像読み込みの応用

ここまでは単純なURLを用いた画像読み込みを行いましたが, これを応用すると次のような場面においても画像を取得可能です.

Ajax画像の描画

XMLHttpRequestオブジェクトを使い, 画像データをバイナリデータ(Blobオブジェクト)として取得する方法です. 得られたBlobオブジェクトはURL.createObjectURLメソッドでBlobデータスキームに変換することで, img要素に渡すことが可能です.

一旦画像ファイルをBlob(ArrayBuffer)として扱う方法は, (ライブラリを使って)GIFアニメーションをフレーム画像に分割すると言った場合に用いられます.

ローカル画像の描画

input[type=file]要素で画像ファイルを取得する方法です. 得られたFileオブジェクトはBlobオブジェクトでもあるため, 画像ファイルが得られた後は先ほどと同じです.

ドロップ画像の描画

dropイベントから画像ファイルオブジェクトを取得する方法です. 画像ファイルが得られた後は先ほどと同じです.

いずれも何らかの手段で画像データに相当するBlob(File)を得るURL.createObjectURLメソッドを用いてBlobをimg要素に表示するimg要素には予めonloadイベントにcanvas要素への描画処理を記述しておく描画処理が完了したらURL.revokeObjectURLメソッドでBlobオブジェクトを開放すると言った手順をとっている点に着目しましょう.

補足)誤ドロップへの対処

ドロップ操作を採用する場合, ユーザビリティの観点から誤ドロップによる意図しないページ遷移への対処が必要です.

クリップボード画像の描画

出典:How can I let user paste image data from the clipboard into a canvas element in Firefox in pure Javascript?

WEBページ上の画像を右クリックした際に表示される「画像のコピー」を使ってコピーした画像を, ペースト操作(キーボード操作を含む)でcanvas要素に画像を描画する事が出来ます. 実現方法には概ねpasteイベントを用いるcontenteditable属性を用いるの2つがあります.

コピー用の画像(右クリックでコピーして以下のコードでペーストしてみましょう)

pasteイベントを用いるもの

pasteイベントを使って, クリップボードの中身を参照する方法です. canvas要素そのものはpasteイベントを発生させないので, canvas要素を囲む何らかのHTML要素を用意する必要があります. また, そのままではペースト可能な範囲が不明瞭なので何らかの誘導が必要となるでしょう. 動作する環境はChromeに限られます.

MutationObserverによるもの

contenteditable属性をtrueとした要素の特性をcanvas要素に応用したものです. FireFox/Chrome/InternetExplorerといった広範な環境で動作するので, 通常はこちらを選択すると良いでしょう. 以下に動作原理を示します.

例を示します. canvas要素の上に画像のペーストを検知するためのdiv要素を被せ, MutationObserverでDOMの変更を監視しています.

canvas要素にcontenteditable属性を設定する方法もありますが, 動作する環境が限られます.

テキストデータの貼り付け

先ほどの例はテキストの貼り付けに応用することができます. この場合, pasteListener内部のテキストを描画するようにします.

クリップボードへの画像転送

逆にcanvas要素の内容をクリップボードに転送する事も出来ます. ctrl+cキーによるコピー操作(コンテキストメニューによるものではない)をcopyイベントで取得し, canvas画像をHTMLコードとしてセットします. コンテキストメニューによる「画像のコピー」がcanvas画像全体をクリップボードに送るのに対して, この方法を用いると画像の一部分を転送可能です. またクリップボードに転送した画像は(HTMLの貼り付けをサポートする)他のアプリケーション(例えばLibreOffice)に直接貼り付けることが出来ます.

下の例では, canvas要素をクリックした後にctrl+cキーを入力することで破線の範囲がクリップボードに転送されます.

video要素の描画

canvas要素のソース画像としてビデオ映像を利用可能です. この場合, drawImageメソッドの引数にはHTMLVideoElementオブジェクトを渡します.

描画対象のvideo要素

video要素と描画タイミング

video要素の内容を正しくcanvas要素に転写するにはcanplayイベントの発生を待ちます. video要素による動作再生にはデータの読み込み(load)とは別に, ビデオデータのデコード処理を要するからです.

ビデオフレームの指定

ビデオ映像をcanvas要素に転写すると, その時点でのビデオフレーム映像が出力されます. そのため, HTMLVideoElementのcurrentTimeプロパティと組み合わせて, 任意のビデオフレームを取り出すことが可能です. この場合もcanplayイベントの発生を待ちます.

WEBカメラの映像の描画

WebRTCをサポートする環境では, WEBカメラの映像をvideo要素を介してcanvas要素に取り込むことが出来ます. navigator.mediaDevices.getUserMediaメソッドを実行するとWEBカメラ映像がMediaStreamオブジェクトとして得られるため, これをvideo要素で再生しcanvas要素に転写するのです. この機能は例えばWEBカメラによるバーコード/QRコードリーダの実現に応用されます.

WEBカメラ映像を表示するvideo要素

なおgetUserMediaメソッドの所在はかつてnavigator.getUserMediaとされており, 機能的には同等であるものの使用法が異なります.

出典:HTML5 での映像と音声の取得

getUserMediaメソッド実行の制限

getUserMediaメソッドによるカメラ映像の抽出は使い方によっては重大なセキュリティリスクを伴います. その為, ブラウザではhttps環境でのみ動作可能としているもの(Chrome)があります.

ストリーム画像キャプチャAPIの利用

現在策定が進められているストリーム画像キャプチャ(MediaStream Image Capture)APIを利用すると, メディアストリームから直接スナップショットを撮ることが可能になります. 出力結果はファイル(blob)のほか下記に示すImageBitmapオブジェクトとして取得出来るため, これまでどおりcanvas要素を使って内容を加工できます.

ImageCapture(track)
キャプチャ対象の映像トラックを指定するコンストラクタ関数.
Promise<Blob> imageCapture.takePhoto(photoSettings)
キャプチャ画像をファイルとして取得するプロミスを返す.
Promise<ImageBitmap> imageCapture.grabFrame()
キャプチャ画像をImageBitmapオブジェクトとして取得するプロミスを返す.

ImageBitmapによる画像読み込み

drawImageメソッドに指定する画像としては通常HTMLImageElement/HTMLCanvasElementを用いますが, ImageBitmapオブジェクトをサポートする環境では画像に類するデータ(例えばImageDataやBlob/File等)をImageBitmapオブジェクトに変換することで統一的にdrawImageメソッドに渡すことが可能です. また, DOMから切り離されたオブジェクトであるため, Worker内部でも画像データを扱えるというメリットもあります. なおImageBitmapオブジェクトは種類の異なるコンテキストオブジェクト間での画像データの授受を目的としていて, 今後バックグラウンドでの画像描画やWebGLにおけるテクスチャ画像の取り扱いと言った場面での活用が期待されています.

ImageBitmapオブジェクトの概観
ImageBitmapFactory(window/worker).createImageBitmap(source, sx, sy, sw, sh, option)
ImageBitmapオブジェクトを生成し, それを利用するためのプロミスオブジェクトを返す. sourceには画像オブジェクトを, sx,sy,sw,shにはその切り出したい範囲を指定する.
sourceとしてはHTMLImageElement, HTMLCanvasElement, HTMLVideoElement, Blob, ImageData, CanvasRenderingContext2D, ImageBitmap, SVGImageElementと言った広範な画像インターフェースを指定可能.
※Window/WorkerオブジェクトはImageBitmapFactoryインターフェースを実装しています.
optionには現状下記値の連想配列(ImageBitmapOptions)を指定可能.
imageOrientation
画像の方向(none/flipY…上下反転)
premultiplyAlpha
プレマルチプライ(アルファチャンネルの事前適用の有無)設定の指定(default/premultiply/none)
colorSpaceConversion
色空間の変更(none/default)
resizeWidth
リサイズ幅
resizeHeight
リサイズ高
resizeQuality
リサイズ品質(pixelated/low/medium/high)
colorSpace
色空間
※但し全てが動作するわけではありません.
ImageBitmap
WEB環境における汎用ビットマップ(ラスタ)画像を表すオブジェクト. CanvasRenderingContxt2D, WebGLRenderingContext, ImageBitmapRenderingContext共通で利用可能. transferableインターフェースを実装しているため, window-worker/worker-worker間でのデータ授受が可能です. なお, データ転送後は画像データの取り出しが出来なくなります.

createImageBitmapメソッドは直接ImageBitmapオブジェクトを返すわけではなく, それを利用するためのPromiseオブジェクトを返します. つまり, 得られたPromiseオブジェクトのthenメソッドの引数にimageBitmapが得られた際の処理を記述していきます.

ImageBitmap.close()
現在保持している画像データを開放する.
ImageBitmap.width
現在保持している画像の幅
ImageBitmap.height
現在保持している画像の高さ

なおcloseメソッドを指定することで画像データを明示的に破棄することが可能です.

ImageBitmapRenderingContextによる画像データの表示

ImageBitmapが利用可能な環境ではImageBitmapRenderingContextというAPIが追加され, canvas要素に直接ImageBitmapが内包するグラフィックデータを参照させることが可能です.

ImageBitmapRenderingContext.transferFromImageBitmap(bmp)
ImageBitmapの内容をcanvas要素に転送する. nullを渡すと画像がクリアされる. また, 渡したImageBitmapの中身は空になります.
※現在transferImageBitmapとの表記揺れがあります
ImageBitmapRenderingContext.canvas
このコンテキストオブジェクトを生成したHTMLCanvasElement

なお, transferFromImageBitmapメソッドはビットマップデータの所有権をcanvas要素に移譲するため, 引数として渡したImageBitmapの中身は空になります.

Fetch APIと組み合わせる

Promiseという仕組みを利用するのは一見面倒ですが, 現在検討中のFetch APIと組み合わせると次のように画像データの取得処理が順序良く簡潔に記述できます.

CSS画像のcanvas要素への描画

CSS Typed OM Level 1をサポートする環境では, 背景等のスタイル設定から読み込んだ画像を直接canvas要素に描くことが可能です.

Element.computedStyleMap()
ノードの現在のスタイルをStylePropertyMapReadOnlyオブジェクトとして取得する
StylePropertyMapReadOnly.get(propertyName)
プロパティ名に対応するプロパティ値をCSSStyleValueオブジェクトとして取得する. プロパティ値が画像の場合は, CSSURLImageValueオブジェクトが得られる.
CSSURLImageValue.intrinsicWidth
画像の幅
CSSURLImageValue.intrinsicHeight
画像の高さ
CSSURLImageValue.intrinsicRatio
画像のアスペクト比
CSSURLImageValue.url
画像のURL
CSSURLImageValue.state
画像の読込状況(unloaded/loading/loaded/error)

なお, CSSURLImageValueオブジェクトにはloadイベントに相当する仕組みが備わっていないため, 読み込みステータスを元にdrawImageメソッドを実行します.

画像フォーマット別検討事項

以下画像形式特有の検討事項についてまとめます.

JPEG形式…EXIF方向値を加味した画像の描画

JPEG画像にEXIFによる「画像の方向」が設定されている場合, (WebKit以外の)WEBブラウザではこの内容を無視します. 従ってこの方向を加味した画像の描画を行う場合, 一旦JPEG画像をAjaxによりバイナリデータとして取得し画像の方向データを抽出する必要があります. が, 比較的難度の高い処理となるため, Exif.jsと言ったサポートライブラリを用いると良いでしょう.

EXIF値と画像データの状態
EXIF値見た目の回転反転画像イメージ EXIF値見た目の回転反転画像イメージ
1なし実像
x,yを交換
(鏡像)
5左回転鏡像
2鏡像
x,yを交換
(鏡像)
6実像
(鏡像の鏡像)
3180度実像
x,yを交換
(鏡像)
7右回転鏡像
4鏡像
x,yを交換
(鏡像)
8実像
(鏡像の鏡像)

EXIF方向値が得られたら, その内容を元にcanvasのサイズを決定し座標軸を変換します. 下記にサンプルコード(実際の動作例はこちら)を示します.

要するにx,y軸の交換左右反転180度回転の有無から定まる\(2\times2\times2=8\)パターンというわけです. この後描画処理を続けるのであれば, 座標軸変換を元に戻すとよいでしょう.

GIF…アニメーションの制御

GIF(APNG)アニメーションファイルをcanvas要素に描画すると, 原則先頭の1フレームが出力されることになっています. が, 一部例外もあります. Safari等のWebKit系ブラウザではdrawImageメソッドの実行を繰り返すことでcanvas要素のグラフィックがアニメーションしてしまいます.

他のブラウザと読み込むフレームを一致させる場合は, loadイベントの直後にGIFアニメーションをcanvas要素に描画するようにします.

アニメーションの擬似制御

canvas要素で生成した静止画像を用いるとimg要素で表示しているGIF(APNG)アニメーションの停止・開始を表現することが可能です. 操作する毎にsrc属性の参照先をGIFファイルとcanvas画像ファイルとに切り替えるのです.

補足)GIF(APNG)アニメーションの挙動

(同一URLの)画像はメモリ内部で共有されています. 従って, 単一ページに同じアニメーションGIF画像を複数箇所に配置した場合, それらは全て同じタイミングで動作します. そのため, スクリプト操作により特定のアニメーションが再読込された場合, アニメーション全体が巻き戻されます. この問題を回避するにはURL文字列の末尾にランダム(シーケンシャル)なハッシュ値(例:a.gif#001)を追加したものを用います.

GIFアニメーションのフレーム分解

GIFアニメーションをフレーム画像に分解するにはjsgifを用います. 下記にを示します.

jsgifによるフレーム画像の分割

BMP形式…アルファ値の有無

BMP形式の画像はアルファ値を持つ場合があります. Edgeを除く大抵のWEBブラウザではこのアルファ値を正しく解釈します.

その他の画像形式

JPEG,GIF,PNG,BMP,SVG形式以外の画像形式については, 一旦画像データをImageDataオブジェクトに展開してからcanvas要素に描画します.

(ラスタ形式の)画像データのデコード作業そのものにはcanvas要素は必要ありません. また, デコード結果から直接透過BMP画像ファイルを生成することでcanvas要素を使わずにスクリーンに表示させることも可能です.

ベクタ画像の描画

PDFやWMF等のベクタ形式の画像は専用のライブラリを用いるか, (可能であれば)一旦SVGに変換することでcanvas要素に描画出来ます.

Service Workerを用いた画像形式変換

Service WorkerはWorkerプロセス(バックグラウンド処理)の一つで, WEBページが要求する様々なリクエストを代行します. この仕組みを用いると, 本来ブラウザがサポートしていない(WEBP, 暗号化した画像データ, SVGやJavaScript等の描画手続き)形式の画像が要求された際に, Service Worker内部で(BMP等の)処理可能な形式に変換して返すことができます. こうすることで, どのような画像リクエスト(img要素, CSS背景, スクリプトからの画像取得等)であっても, 元のHTMLコードに手を入れることなくデコード済みの画像が得られます. また, デコード処理はブラウザ側で実行されるため, ネットワークトラフィックは変化しません.

Service Workerによる画像形式変換

以下にWEBP形式を透過BMP形式に変換するサービススクリプトの例を作りました. Service Workerの動作特性上, WEBサイトをHTTPS環境下で運用する必要がある点と, リクエストを発生しないデータURIスキーム形式の画像については別途対処しなければならないことに注意して下さい.

同様にAPNG(WEBP)アニメーションをXNG(SVG)アニメーションに変換する用途が考えられます.

画像の出力

コンテキストメニューによるcanvas画像の保存

canvas要素に対して右クリック(ctrl+クリック)等を使ってコンテキストメニューを表示すると, 大抵のブラウザでは「画像の保存」等のコマンドが含まれていす. この操作を防ぐにはcontextmenuイベントをキャンセルします. 詳しくはイベントの項で解説します.

データURIスキーム形式での画像出力

canvas要素に描画した内容はtoDataURLメソッドを用いることでデータURIスキーム形式の文字列で抽出することができます. これはcanvas要素で描いたグラフィックを再利用する上で重要な役割を果たします.

HTMLCanvasElement.toDataURL(type[, param])
データスURIキーム形式で画像を取得する. ブラウザがサポートしない形式が渡された場合, PNG形式が選択される. 第2引数には画像の品質(圧縮率)を設定する.
image/png
PNG形式で取得する.
image/jpeg
JPEG形式で取得する. ※第2引数にJPEG品質値(0〜1)を設定可能.
image/webp
WEBP形式で取得する. Chrome等で有効. ※第2引数にWEBP品質値(0〜1)を設定可能.

データURIスキーム形式の画像データ

toDataURLメソッドを実行すると「data:image/xxx;base64,」から始まる文字列が得られます. これがデータURIスキーム形式で表現された画像ファイルです. image/xxxの部分をMIME typeと呼ばれる画像形式を表し, 後続の文字の羅列はbase64形式に変換された画像のバイナリデータです.

この形式はHTMLやCSS等のテキストベースのファイルの中にファイルデータを埋め込む用途で用いられ, img要素のsrc属性やCSSのbackground-imageプロパティにそのまま記述することが出来ます. またa要素のhref属性に設定してユーザーに画像ファイルを保存させることも可能です.

リンクのサンプル
画像埋め込みのサンプル

HTMLコードに画像データを埋め込んだ例

画像フォーマットの指定

以下の例ではカンバスに描いた内容をtoDataURLで文字列に変換しています. toDataURLメソッドの引数には, 画像の形式をMIME-typeとして指定します. 通常は「image/png」もしくは「image/jpeg」の2種類が利用可能ですが, 環境によってはこれ以外の形式を指定することも可能です.

image/png…PNG形式

PNG32形式で画像を出力します. PNG形式は可逆圧縮により画像品質が維持されるため, 単純なグラフや図等の出力, 及びグラフィックを後々に再加工する際の一時保管に向きます. その一方で複雑なグラフィックに対してデータサイズが大きくなりがちです.

image/jpeg…JPEG形式

JPEG形式で画像を出力します. JPEG形式は非可逆圧縮を施すことで大幅に画像データを縮小します. パラメータとして圧縮度(0:高圧縮/低品質〜1:低圧縮/高品質の範囲)を指定できます. 圧縮に伴う品質劣化が目立ちにくい自然画や最終的な画像出力に向きます. また, 不透明度を表すアルファチャンネルに対応していないため, 背景色が黒色として画像が生成されます.

image/webp…WEBP形式

Chrome等のBlink系のブラウザでは, WEBP形式での出力をサポートします. WEBP形式をサポートしない環境ではPNG形式として出力されます.

image/bmp…BMP形式

HTML仕様では明示されていないものの, BMP形式での出力が可能なものもあります. JPEG形式と同様にアルファチャンネルは除去されます. 画像データが圧縮されないため高速に動作する一方, 極めてサイズが大きくなるデメリットがあります.

その他の形式

この他, 環境によっては別の画像形式による出力が可能です. 以下にこのブラウザがサポートしている出力形式について示します.

本ブラウザがサポートしている画像出力形式
image/png image/jpeg image/webp image/gif image/bmp
image/tiff image/jp2 image/vnd.microsoft.icon image/heif

非サポート形式指定時のフォールバック

Webブラウザがサポートしない画像形式を指定した場合は, 自動的にPNG形式にフォールバックされます. そのため, 特定の画像形式が必要な場合はその形式で出力可能かどうかを確認しておく必要があります.

toDataURLメソッド利用上の留意点

toDataURLメソッドは同期的に動作するため簡単に扱えるのが魅力ですが, デメリットもあります. canvas要素のサイズが大きくなるにつれて, toDataURLが生成するデータURIスキーム形式の文字列もそれに応じて長くなります. このような余りに長いURL文字列はブラウザの動作に問題を引き起こします.

このような場合はtoDataURLメソッドの代わりにtoBlobメソッドを使いましょう.

toBlobメソッドによる画像ファイルの取得

Blob(Binary Large OBject)はHTML5から導入されたオブジェクトで, バイナリデータとコンテンツタイプとをひとまとめにしたものです. Fileオブジェクトの基底オブジェクトでもあり, メモリ内部に展開されたファイルのようなものです. HTMLCanvasElementではtoBlobメソッドを用いることでcanvas要素に描画した内容を直接画像ファイル化することが可能です.

HTMLCanvasElement.toBlob(callback, type, encoderOptions)
画像データに対応するBlobオブジェクトを生成し, 処理を行う. callbackにはBlob生成後の処理を, type,encoderOptionsにはtoDataURLメソッドと同じく画像形式とそのパラメータを指定する.
HTMLCanvasElement.msToBlob()
Internet Explorer専用. 画像データに対応するBlobオブジェクトを同期的に直接生成する. 得られる画像データはimage/png形式固定. 上記toBlobとは使い方が異なるため注意が必要.

BlobをURL文字列に変換する

Blobオブジェクトそのものを編集することはできませんが, URL.createObjectURLメソッドを用いることでリンクやimg要素のsrc属性に指定可能なURL文字列に変換可能できます.

URL.createObjectURL(blob)
Blobオブジェクトを直接参照するURL(「blob:」から始まるBlobURIスキーム)を生成する.
URL.revokeObjectURL(bloburl)
URL.createObjectURLメソッドで生成したBlobURLを廃棄し, Blobオブジェクトへの参照を開放する.

なお, 得られたURL文字列はBlobオブジェクトへのポインタのようなものであり, 原則使い捨てにします. 従ってURLをサーバーに送信して再利用すると言ったことは出来ません.

URLの破棄

URL.createObjectURLで得たURL文字列はWEBページの生存期間中は有効であるため, たとえa要素のリンク先から削除されたとしてもメモリ内部でBlobへの参照を維持しています. そのため, (リンク先の変更などによって)不必要となったタイミングでURL.revokeObjectURLメソッドを使って明示的に破棄してください. さもないとBlobオブジェクトがいつまで経ってもメモリから開放されずメモリリークを引き起こします. UIイベントを元に繰り返しBlobを生成している場合に注意してください.

URLの破棄とBlobオブジェクトの開放

Blobを用いた画像データの送信

toBlobメソッドで得られたBlobをそのままFormDataに設定してサーバーに送信することが出来ます. 例を示します.

Blobの内容を取得する

toBlobメソッドで取得したBlob(画像ファイル)はFileReaderオブジェクトを用いるとArrayBufferを介したバイナリデータとして中身を参照出来ます. この仕組みを用いると得られたPNG画像にメタデータを付加したり, 内容を暗号化すると言った処理を行えます.

Blobの直接生成

toBlobメソッドをサポートしていない環境(SafariなどのWebKit環境)では次のようにしてBlobを生成することが可能です.

  1. canvasのtoDataURLメソッドを実行し, データURIスキーム形式のPNGデータを取得する.
  2. 上で得られた文字列からヘッダーとなる「data:image/png;base64,」を除去する.
  3. 上で得られたデータ部文字列をatobメソッドによりバイトデータ文字列に変換する.
  4. Uint8Arrayオブジェクトを生成する. 長さは上出えたバイトデータ文字列長とする.
  5. Blobコンストラクタを呼び出し, 「image/png」形式のBlobオブジェクトを生成する.
Blob(array, options)
Blobオブジェクトを生成するコンストラクタ. arrayにBlob化したいデータを配列として指定する. optionにはBlob化する際のパラメータを指定する. ここではtype値で画像形式を指定する.

以下は上記をまとめて独自にtoBlobメソッドを実装したものです. (msToBlobメソッドによる出力はPNG形式に固定されてしまうため, 無視してよいでしょう.)

ピクセルデータを用いた画像ファイル生成

canvasグラフィックを構成するピクセルデータから画像データを構成し, 直接ファイル(Blob)化する方法もあります. なお, ピクセル操作の詳細については次の項で解説します.

透過BMP画像の直接生成

WEBブラウザが表示可能な画像形式のうち, 非圧縮BMP形式はcanvas要素から容易に画像ファイルを生成可能です. canvas要素標準のメソッドで得られるBMP画像と異なり(適切にフォーマットを選択すれば)アルファ値を保持することもできます. またデータのの圧縮・伸長と言った処理が存在しないため, 画像ファイルを(CSS背景に設定するなどの)メモリ内で使い回すケースに於いては有効なテクニックです.

下記はHTMLCanvasElementを拡張した例です. Blobオブジェクトが同期的に生成されている点に着目して下さい. また, こうすることでWebGL環境下でも透過BMP画像の出力が可能となります.

PNG形式の最適化

canvas要素標準のPNG画像出力は必ずしも画像に対して最適化されたものとはなりません. よりコンパクト化する必要がある場合は次のJavaScriptライブラリを用いるとよいでしょう.

なお開発者のブロク解説による通りそのまま利用するには非常に重いライブラリであるため, Web Workersと組み合わせて利用すると良いでしょう. なおPngEncoderコンストラクタに渡すパラメータについてはソースコード等を参照してください.

ピクセルの操作

ピクセル操作によるグラフィック

canvas要素のグラフィックは内部でビットマップデータ(ピクセル情報の配列)として管理されており, ImageDataオブジェクトを用いてこの内容を外部に取り出せます. また, 得られたピクセルデータは内容を編集した後に再度canvas要素に書き戻すことが可能で, これらを用いると様々な画像処理アルゴリズムをブラウザ上で再現することが出来ます.

ピクセル操作に関わるAPI

ImageDataオブジェクトに関わるメソッドとしてはcreateImageData, getImageData, putImageDataの3つが提供されています. これらの関係を図で表すと次のようになります.

ピクセル情報の構成

ImageDataオブジェクトの入手

ImageDataオブジェクトはcanvas要素におけるピクセルの配列に相当するオブジェクトであり, ctx.createImageData(width, height)(中身は黒の透明/ゼロクリアで充填されている)ctx.createImageData(imageData)(サイズのみ引き継ぐ)ctx.getImageData(x, y, width, height)(canvas要素の描画内容のスナップショットを格納する)の何れかを実行することで取得することが出来ます.

CanvasRenderingContext2D.createImageData(width, height)
CanvasRenderingContext2D.createImageData(imageData)
指定したサイズのImageDataオブジェクトを生成する. もしくは元のImageDataと同じサイズのImageDataオブジェクトを生成する.
CanvasRenderingContext2D.getImageData(x, y, width, height)
canvas要素の指定した矩形領域に相当するImageDataを取得する.

ImageDataオブジェクトはcanvas要素やコンテキストオブジェクトとは独立した存在で, ImageDataの内容にリアルタイム性はありません.

ピクセル情報の構成

ImageDataオブジェクトのdataプロパティにはピクセル情報を格納するデータ配列(Uint8ClampedArray)が設定されています.

ImageData.width
ImageDataの幅.
ImageData.height
ImageDataの高さ.
ImageData.data
ImageDataが内部に保持しているピクセルデータで, 実体はUint8ClampedArrayオブジェクトである. ImageDataの左上ピクセルから右上→一段下の左上ピクセル→右上ピクセル…右下ピクセルの順に並んでいる. 各ピクセルデータはrgbaの4つの値から構成されており, 全体としての長さはwidth×height×4となる. 配列の中身は0〜255の値で構成されている.

dataプロパティのデータの並びは左上から右下までを1列に並べたものとなっており, 1ピクセルはR(赤)G(緑)B(青)A(不透明度)から構成されています. 例えば座標\((2,3)\)(左上ピクセルを\((0,0)\)とする場合)の緑の値を取得する場合はimageData.data[(3 * width + 2) + 1]とします.

ImageDataのdataプロパティを編集する場合は0〜255の間で値を編集します. これはcanvas要素における画素データのrgba要素が256階調(8bit)で表現されていることによります. なお0〜255の範囲外の値を設定した場合はUint8ClampedArrayオブジェクトの仕様により強制的に値0もしくは255に切り詰められます.

ImageDataオブジェクトの書き戻し

ImageDataの編集が完了したら, putImageDataメソッドでその内容をcanvas要素に書き戻します.

CanvasRenderingContext2D.putImageData(imagedata, x, y[, dx, dy, dw, dh])
ImageDataの内容をcanvas要素に書き戻す. 左上座標(x, y)を基準に[dx, dy, dw, dh]の範囲のピクセルを書き換える.

下記はfillRectメソッドを使用せず矩形範囲を自力で塗りつぶした例です.

なおputImageDataメソッドはピクセルの情報を直接入れ替えるため, クリップ, 座標軸変換, 色の合成等の画像合成に関わる全ての処理の対象外です.

putImageDataに矩形範囲を指定した場合, ImageDataのその矩形範囲外のピクセルデータはcanvas要素に書き戻されません.

透明ピクセルの扱い

putImageDataメソッドにより書き戻したピクセルデータに透明(アルファ値が0)なものが含まれていた場合, canvas要素内部では当該ピクセルに付随するRGBの値は0(つまり黒色)に強制されます. これによりバイナリデータの透明ピクセルへの隠蔽を防ぎます.

ピクセル操作とパフォーマンス

ピクセル操作を行う際はそのパフォーマンス傾向について十分に理解しておく必要が有ります.

ピクセルデータの入出力とパフォーマンス

getImageData/putImageDataメソッドの処理内容は, 余計な画像処理を伴わないことから本質的に配列操作です. 従ってそれに掛かるコストは単純に操作対象となるピクセルの数に比例するように思えますが, 話はそう簡単ではありません. なぜならcanvas要素内部のグラフィックデータをJavaScriptからアクセス可能とするためにメモリの転写を伴うからです.

その結果, 環境によっては処理範囲が限定的(極論1ピクセルの入出力)であっても, このグラフィックデータの取り出しに手間取ることで多大なパフォーマンスの劣化を伴うことが有ります. つまりピクセル操作は本質的にコストの高い操作と言え, アニメーションを始めとした頻繁にグラフィックを書き換える用途には不向きです.

またImageDataの生成するとその都度メモリが消費されます. そのため必要以上にgetImageDataメソッドを実行すると, オブジェクトの開放に伴うガベージコレクションが頻発することでブラウザの動作が不安定になります. 従ってgetImageDataメソッドの実行はできる限り少ないほうが良いのです. 特に矩形範囲の切り出しが何度も必要な場合は, 安易にgetImageDataメソッドを繰り返すのではなく, グラフィック全体を単一のImageDataに書き出してから自力で配列操作を行うようにして下さい. また後述するImageDataコンストラクタを用いたメモリの再利用も視野に入れましょう.

ピクセルデータの加工とパフォーマンス

ピクセルデータの加工処理は通常巨大な配列演算となるため, 処理の対象となるデータのサイズによってはブラウザの動作に影響を及ぼします. そのため後述する型付き配列の特性やasm.js/WebAssemblyを使った高速化, Web Workerを使った処理のバックグラウンド化と組み合わせることを検討しましょう. 但しその際にはパフォーマンスの計測は必ず行って下さい. これらの操作はコード記述の内容によって最適化の効き具合が変化し, 動作速度に大きな影響を与えるからです.

型付き配列の活用

dataプロパティの実体Uint8ClampedArrayは型付き配列(ArrayBufferView/TypedArray)であり, その特性を活かすことで処理を簡潔に記述できます.

TypedArray.length
(ピクセルデータ)配列のサイズを取得する.
TypedArray.slice(start, end)
配列のサブ配列を取得する. 配列の中身は元の配列と共有されない.
TypedArray.subarray(start, end)
配列のサブ配列を取得する. 配列の中身は元の配列と共有される.
TypedArray.filter(callback, thisobj)
条件に合致したものから新たな配列を生成する. ※配列走査が発生するのでなるべく利用は控えたい
TypedArray.reverse()
元の配列の並びを逆にした配列を生成する.
TypedArray.set(array, offset)
配列の内容をarrayで上書きする.offsetは上書きを開始する位置を指定する.
TypedArray.buffer
配列の内部構成(メモリ)を取得するプロパティ.
Uint8Array(buffer)/Uint16Array(buffer)…
指定したバッファに対するインターフェース配列を生成するコンストラクタ.

参考)型付き配列のこれまで

型付き配列の概念はcanvas要素の成熟と共に生まれました. とりわけ多くのメモリ操作を要するWebGL技術では, 当初から既存のJavaScript配列では動作速度等の要件を満たせないことが判っており, より高速に動作するデータコンテナの設計が必須でした.

そこで既に提案されていたCanvasPixelArray(現Uint8ClampedArray)にヒントを得て, 新たにCanvasFloatArray(旧WebGLFloat32Array, 現Float32Array)が考えだされました. これらは何れもシステムメモリを直接参照するように工夫されており, 一般の配列よりも高速に動作します. この特徴からcanvas要素以外の用途からも注目を集め, 最終的にcanvasの名を排してより汎用的な型付き配列仕様として整理されました. Uint8ClampedArrayはこのCanvasPixelArrayの名残です.

出典:Typed Arrays: ブラウザでバイナリデータを扱う

ImageDataオブジェクトの複製

ImageDataオブジェクトを複製する場合はsetメソッドを用います.

切り詰め処理を省く

data.bufferプロパティの内容は別の型付き配列に渡すことが出来ます. 得られた型付き配列は元のdataプロパティと中身を共有しているため, 型付き配列に対する操作は直接dataプロパティに反映されます. この仕組みを利用すると様々な処理が単純化されます.

例えば色成分に対する演算結果が0〜255の範囲に収まることが判っている場合, Uint8ClampedArrayによる切り詰め処理は無駄と言えます. この場合, ピクセルデータをUint8Array形式に変換することで僅かながら処理の高速化が狙えます.

ピクセルデータをまとめて取り扱う

同様にピクセル毎の色を成分毎に分解する必要のない場合や配列操作の回数を減らしたい場合, ピクセルデータを格納しているUint8ClampedArrayを一旦Uint32Arrayに変換すると操作が単純化され, 処理速度が向上します.

Uint32Arrayを使ったピクセルデータの集約

RGBA値の復元

この方法で得られたピクセルデータからRGBA値を復元するにはちょっとした細工が必要です. Uint32Arrayに変換した過程でWEBブラウザが動作する環境(CPU)のエンディアン(複数バイト値を整数に換算する際の並び順)に依存した値となるからです. 下記にその相関を示します.

エンディアン毎の整数解釈
4バイト並び
(RGBA)
32bitIntとしての値
big endianlittle endian
1a2b3c4d
0x1a2b3c4d(RGBA) 0x4d3c2b1a(ABGR)

この関係を念頭に置きつつ, ビット演算を施すことでRGBA値を抽出・上書きします.

とは言え, 昨今はlittle endian環境が大多数を占めているため, canvas要素の利用においてはさほど問題とはならないとの意見もあります.

ピクセルのグループ化

編集対象のピクセルデータをまとめて扱うことで, スクリプトの記述が簡略化されます.

行データの抽出

slice/subarrayメソッド用いるとグラフィックの行データが得られます. 得られた行データはsetメソッドで一括で書き込むことが出来ます. また, 行データはreverseメソッドを使って左右反転することが可能です.

列データの抽出

列データを抽出するにはforEachメソッドを利用します. 下記ではピクセルの転置を行っています.

これらのピクセル抽出処理はfilterメソッドを使っても記述できますが, 配列に対する全走査が発生するため避けたほうが無難です.

ピクセル操作の応用

ピクセル操作はグラフィック情報の抽出だけでなく, 視点を変えることで様々な用途に応用できます.

色のRGBA値を得る

canvas要素でグラフィックを描く場合, 色情報の形式を統一したい場合があります. 例えばHTML色「navy」に塗りつぶされているピクセルを抽出したい場合, まずnavyに対応するRGB値を求めねばなりません. このような場合は実際にその色を描画してみて, ピクセルの値を抽出すると良いでしょう. fillStyleプロパティを参照しても良いのですが, 文字列として返されるため使い勝手が良くありません.

rgba:(hex:)

図形の描画範囲の取得

ピクセル値の有無から塗りつぶしが存在する範囲(バウンディングボックス)が得られます.

図形の面積を算出する

図形を塗りつぶした後, ピクセル値の有無を数え上げることで図形の面積(単位:\(px^2\))が得られます.

塗りつぶしの実装

ここで言う塗りつぶしは特定の連結領域を指定した色で置き換えるもので, いわゆるバケツ機能を指します. 基準となるピクセルの隣接部が塗りつぶし対象かどうかを判定し, 順次その範囲を広げていきます(シード・フィルアルゴリズム).

フィルタの実装

filterプロパティによるフィルタ(SVGフィルタを含む)だけでは表現できないものでも, ピクセル操作で実現可能なものであれば自作できます.

RGB交換

ピクセル毎のRGB(A)値を交換し, canvasに書き戻します.

グレイスケール

ピクセル毎にR,G,Bの重み付き平均を取ることでグレイスケール化することが出来ます.

減色

ここでは例として単純なものを示します.

この他にもぼかしや, 輪郭抽出などのコンボリューション行列フィルタの実装も可能です. 詳しくはこちらを参照して下さい.

ピクセル操作の高度な最適化

大抵のピクセル操作はその過程で多量の計算リソースを消費するため, それに適した環境やAPIセットを選択することで処理速度の向上が狙えます.

asm.jsの利用

一般にピクセル操作は型付き配列に対する数値演算の塊であることから, asm.jsを適用することで余計な型判定処理が省かれ, 処理速度が向上します.

SIMD.jsの利用

SIMD(シームディ)は一つの命令(single instruction)を複数のデータ(multiple data)に並行適用する手法を指します. 今日の標準的なCPUではSIMDに相当する命令セットを備えていて, 多量の演算を要する場合に利用することで処理速度が向上します. SIMD.jsはこのSIMDをJavaScript環境から呼び出すAPI群であり, 主に型付き配列と組み合わせて利用します. 次の例では(レジスタ構造に対応する)SIMD.Uint32x4オブジェクトを用いて4ピクセルを一度に処理しています.

とは言えcanvas要素におけるピクセル並び(R,G,B,A)を鑑みると, そのままSIMDを適用可能なケースは限られます.

ImageDataの直接生成とメモリの再利用

ImageDataオブジェクトはcreateImageDataメソッドを用いずとも直接生成可能です. ImageDataオブジェクトを何度も使い捨てにする用途であれば, 予め多めのメモリを確保しておくことで単一のメモリ領域を使いまわすことが出来ます.

ImageDataが利用するメモリの再利用
  1. 十分なサイズのメモリ領域(ArrayBuffer)を確保する.
  2. Uint8ClampedArray(ArrayBufferView)を生成する.
  3. ImageDataを生成する.
  4. ImageData(もしくはUint8ClampldArray)の内容を編集しcanvasに書き戻す.

この方法を使えばAjax機構で得た画像の生データ(ArrayBuffer)を直接canvasに読み込ませることも可能です.

オフセット値についての注意点

ArrayBufferオブジェクトから型付き配列Uint8ClampedArrayを作る際のオフセット値はできれば4の倍数としましょう. さもないとUint32Arrayを使ったピクセル結合処理に失敗します.

オフスクリーンレンダリング

オフスクリーンレンダリングとは

通常スクリーンに表示されているcanvas要素に直接描画することはコストの高い操作です. なぜなら, apiを呼び出す毎にスクリーンの内容(つまりグラフィックメモリ)が書き換わるからです. この特性は単一のグラフィックのサイズが小さいうち, 書き換え頻度が少ないうちは問題となりませんが, アニメーションや複雑な描画処理を要する場合に深刻なパフォーマンス劣化をもたらします. そこで一旦グラフィックをスクリーンとは別の場所で完成させた上で, スクリーンに書き戻す方法が考え出されました. これをオフスクリーンレンダリングと呼びます.

canvas要素を用いる上でオフスクリーンレンダリングを実現する場合, 別途HTMLCanvasElementを直接用いる方法と, 専用のOffscreenCanvasオブジェクトを用いる方法の2つが挙げられます. なお, 後者は現在APIが策定中であり一部の環境でのみ動作します.

基本のオフスクリーンレンダリング

canvas要素におけるオフスクリーンレンダリングを行うには次のようにします.

  1. スクリーン表示用のcanvas要素(A)を取得する
  2. グラフィック描画用のcanvas要素(B)をメモリ内に生成する
    これはスクリプト変数に格納するだけでdomに追加する必要はない.
  3. (B)にグラフィックを描画していく
  4. (B)が描きあがったらその内容を(A)に転写する
  5. ※繰り返す場合は3〜4を繰り返す.

OffscreenCanvasの利用

このオフスクリーンレンダリングに特化したAPIとしてOffscreenCanvasオブジェクトが提案されています.

OffscreenCanvasへの描画権移譲

HTMLCanvasElementオブジェクトのtransferControlToOffscreenメソッドを実行するとcanvas要素の描画モードがplaceholderとなり, グラフィックの描画をOffscreenCanvasオブジェクトに移譲します.

canvas要素とOffscreenCanvasの関係
HTMLCanvasElement.transferControlToOffscreen()
OffscreenCanvasオブジェクトを生成し, グラフィックの描画を任せる. OffscreenCanvasオブジェクトはtransfarableなのでWorker環境に転送して描画処理を別スレッドで行うことも可能.

コンテキストの取得とグラフィックの書き戻し

OffscreenCanvasオブジェクトにはHTMLCanvasElement相当のAPIが定義されています. 例えばgetContextメソッドを実行すると専用のOffscreenCanvasRenderingContext2Dオブジェクトが得られます.

OffscreenCanvas.getContext(type [, arguments])
グラフィック描画のためのコンテキストオブジェクトを取得する. (2d/webgl)
なおパラメータ2dを渡すとOffscreenCanvasRenderingContext2Dオブジェクトが返されます.
OffscreenCanvas.width
カンバス画像の幅
OffscreenCanvas.height
カンバス画像の高さ

得られたコンテキストオブジェクトには大本のcanvas要素にグラフィック書き戻すためのcommitメソッドが追加されています.

OffscreenCanvasRenderingContext2D.commit()/WebGLRenderingContext.commit()
オフスクリーンカンバスでの描画結果を元のカンバスに書き戻す.
OffscreenCanvasRenderingContext2D.canvas
コンテキストオブジェクトを生成したOffscreenCanvasオブジェクト.

例を示します.

描画結果のImageBitmap化

OffscreenCanvasオブジェクトは直接生成することも可能で, transferToImageBitmapメソッドを使うと描画内容をImageBitmapオブジェクトとして取得できます.

OffscreenCanvas(width, height)
直接OffscreenCanvasオブジェクトを生成するコンストラクタ.
OffscreenCanvas.transferToImageBitmap()
画像データをImageBitmapオブジェクトとして取得する.

描画結果のBlob化

OffscreenCanvasオブジェクトで描いたグラフィックはconvertToBlobメソッドでBlobオブジェクトとして取り出せます.

OffscreenCanvasRenderingContext2D.convertToBlob({type: "[type]", quality:"[quality]"})
オフスクリーンカンバスでの描画結果をBlobとして取得するためのPromiseを返します.

イベントの利用

OffscreenCanvasオブジェクトはEventTargetインターフェースを実装しているため, カスタムイベントによる機能分割が可能です.

OffscreenCanvasRenderingContext2D使用上の制限

OffscreenCanvasオブジェクトが生成するOffscreenCanvasRenderingContext2Dオブジェクトは, CanvasRenderingContext2Dオブジェクトとほぼ同じAPIを提供しますが, 一部利用できないものがあります.

後者は機能的に厳しい制限に思えますが, 別途通常のcanvas要素にテキストを描画したものをImageData/ImageBitmapオブジェクト等を用いてOffscreenCanvas側に渡すことで解決します. 例を示します.

ここではグリフ形状をcanvasグラフィックに落としたものをOffscreenCanvas側で分解・再構成しています. この他, SVGやcufon形式のフォントを用いる方法が考えられますが, いずれもテキストの描画処理を自作せねばなりません.

Web Workersを用いたバックグラウンド処理化

以上はブラウザのメインスレッドを用いたグラフィックの描画でしたが, これをより効果的に行うためにはWorkerオブジェクトを使ってグラフィック描画処理を別スレッドで行わせます.

Workerオブジェクトを生成するとメインとなるブラウザ処理とは別のプロセスが起動し, 処理を非同期に実行させることが可能となります. なおWorker内部では利用可能なオブジェクトに制限があるため, 現状データ分析や他言語からの画像処理アルゴリズムの移植と言ったケースに利用されます.

ブラウザ(Worker呼び出し)側

一般的なオブジェクトと同様にWorkerインスタンスを生成し, postMessageメソッドで処理を引き渡します.

Worker(url)
Workerを生成する. 引数としてバックグラウンド処理化したいスクリプトのURLを指定する.
Worker.onmessage = function(e){}
バックグラウンド処理からのメッセージを受信した際のコールバック関数を指定する. addEventListenerメソッドを使っても良い.
引数eはMessageEvent. dataプロパティにWorker内部で呼び出されたpostMessageの引数が設定されている.
Worker.onerror = function(e){}
バックグラウンド処理においてエラーが発生した際のコールバック関数を指定する. addEventListenerメソッドを使っても良い.
引数eはErrorEvent(message/filename/lineno)
Worker.postMessage(message[,[transfer]])
バックグラウンド処理にメッセージを送信する. 引数には処理に渡したいデータをオブジェクト(連想配列)にまとめて指定することが可能. これはつまり, どのようなメッセージをWorkerに送るべきかについての設計が必要ということでもある.
ポストしたメッセージデータは通常バックグラウンド処理に渡す際にコピーされるが, ArrayBufferオブジェクト(Transferableなオブジェクト)であれば第2引数にリストとして記述しておくとバックグラウンド処理に転送(アクセス権が移譲)されデータコピーに伴う負荷を回避できる. なお, 転送されたオブジェクトは元のスレッドからはアクセスできなくなる.
Worker.terminate()
バックグラウンド処理を強制終了する.

Worker内部

変数selfはグローバルスコープを参照するので一般にwindowと同じものを指しますが, Worker内部ではWorkerGlobalScopeを指します. そのため, 特定のオブジェクト(基本データ型, 配列, Uint8Array等のTypedArray, setTimeout/setInterval, XMLHttpRequest等)以外は利用できません.

WorkerGlobalScope.postMessage(data[,[transfer]])
ブラウザ処理側にメッセージを送信する. 引数には処理結果として返したいデータをオブジェクトにまとめて指定する. なお第2引数に指定したArrayBufferオブジェクト(Transferableなオブジェクト)はメイン処理に転送される.
WorkerGlobalScope.onmessage = function(e){}
ブラウザ処理側からのメッセージを受信した際のコールバック関数を指定する. addEventListenerメソッドを使っても良い.
引数eはMessageEvent. dataプロパティにブラウザ側で呼び出されたpostMessageの引数が設定されている.
WorkerGlobalScope.close()
バックグラウンド処理を終了する. これ以上Workerによる処理が必要なければ, 明示的に呼び出してプロセスを開放する必要がある.
WorkerGlobalScope.importScripts(url[, …])
バックグラウンド処理内で使いたいJavaScriptライブラリを読み込む.

図にすると次のようになります. スクリーン描画を行うメインスレッドとWorkerスレッドとはメッセージ通信(messageイベント)を介して非同期に通信を行うため, 一般的なメソッド呼び出しのように同期的に処理結果が得られるわけではありません.

Workerを使ったオフスクリーンレンダリング

Web Workersを利用した例を示します. データの授受にgetImageDataメソッドで取得したUint8ClampedArrayを利用しますが, 内部のArrayBufferがTransferableなのでこれをpostMessageメソッドの第2引数に指定しています.

補足)MessageChannelの活用

workerでの処理の成否を通知する場合, 通知の内容にフラグ値を持たせる方法が考えられます.

これとは別にメッセージチャンネルを作る方法もあります. MessageChannelオブジェクトはメッセージの通信経路を表し, プロパティport1/port2(MessagePortオブジェクト)との間でメッセージ通信が可能です.

MessageChannel()
2つの異なるコンテキスト間の(双方向の)通信経路を生成する.
MessagePort
メッセージ通信を行う際のインターフェース. transferableであるため, messageイベントを介して別のコンテキストにオブジェクトを移譲できる.
MessageChannel.port1/MessageChannel.port2
MessagePortオブジェクト. 片方のポートに対してpostMessageメソッドを実行するともう片方のmessageイベントとして通知される.

ここでは処理の成功と失敗の2つのメッセージチャンネルを用いています.

このようにMessagePortオブジェクトはメッセージ通信を介してworkerやiframe内等の別のwindow環境に受け渡すことができ, 柔軟な処理経路の実現が可能となります.

SharedArrayBufferによるピクセルデータの共有

Workerとの間で画像データを処理の都度転送し合うのは無駄な処理と言えます. そこで次期ECMA ScriptではSharedArrayBufferオブジェクトが提案されており, この仕組みを用いるとメインスレッドやWorkerスレッド間でメモリを共有することが出来ます.

OffscreenCanvasを使って描画処理をWorkerに移譲する

OffscreenCanvasオブジェクトはTransferableインターフェースを実装しています. そのため, transferControlToOffscreenで生成したOffscreenCanvasをWorkerコンテキスト側に転送してcanvas要素の描画を行わせることが可能です.

Worker環境でのグラフィック操作

OffscreenCanvasの登場により, Worker環境単独での画像処理の実現が現実味を帯びてきました. そこで, canvasを利用する際のwindow環境とWorker環境での違いについてまとめてみましょう.

機能(利用できるAPI)の比較
機能window環境Worker環境
DOMへのアクセス可能不可
canvasの取得HTMLCanvasElement, OffscreenCanvasOffscreenCanvas
画像ファイルの取得input type="file", FileAPI, Fetch APIFetch API
画像の読み込みHTMLImageElement, ImageBitmapImageBitmap
画像データの取り出しtoDataURL, toBlob(callback)convertToBlob(promise)
ファイルの読み込みFileReaderFileReaderSync
画像データの受送信XMLHttpRequest, Form(送信)XMLHttpRequest
テキストの描画可能不可
†SVGの読み込みは不可
‡XML/HTMLのドキュメントツリーの取得は不可

以下に実際の動作例を示します.

Worker環境下でのSVGの描画

Worker環境ではSVGの内容を解析するためのDOM操作が出来ないことから, 一般的なImageBitmapを使ったOffscreenCanvasへのSVG画像の転写が行えません. そのためSVGコードの内容を元にcanvasを操作し自分の手でグラフィックを構築するようにします. この場合コードの分析を簡単にするためSVGの構造をできるだけシンプルにしたり, 予めSVGをJSON化しておくと良いでしょう.

ループ処理の非ブロック化

負荷の掛かる処理をWorker環境で行えない場合(例えばフォントを用いるなど), 処理を複数の関数呼び出しに分離しそれらを非同期に呼び出すことで(ある程度のパフォーマンス劣化と引き換えに)WEBブラウザの応答性が改善します.

このような処理を実現するには様々な方法が考えられますが, ここではgenerator関数とPromiseを使った方法を紹介します.

このコードの実際の動作はこちらで確認できます. 後はこのcanvas要素を適宜オフスクリーンカンバス化すれば良いのです.

canvas要素におけるセキュリティ

同一生成元ポリシーによるセキュリティ管理

canvas要素はグラフィックを描画する上で様々な機能を提供しており非常に高機能である反面, 様々な危険性を孕んでいます. そのため, 下記に示すメソッドについては特定の条件下においては外部に画像データが取り出せないようになっています.

なぜなら, 無条件にデータが取り出せてしまうと次のようなケースにおいて情報漏えいの原因となってしまうからです.

そのため, canvas要素から画像データを抽出するには, 少なくともcanvas要素に描かれた画像と, スクリプトが動作しているWEBページが保管されている場所が一致していなければならない(つまり, 管理者が同一である)という制限が加えられました. これを同一生成元ポリシーと呼びます. たとえ問題が発生したとしても、そのリスクを最小限に留めるようにしているのです.

同一生成元の定義

ここで「同一生成元(same-origin)」であるとは参照しているURLのプロトコルが同一ポート番号が同一ホストが同一の全てを充たすことを指します. 例えばWEBページhttp://www.sample.com/main.htmlと生成元が同一と判定されるものは, 次の5つのうち上の2つのみです.

origin-cleanフラグとセキュリティエラー

先ほどのメソッド群の利用可否はcanvas要素の内部で真偽値(origin-cleanフラグ)として管理されています. フラグの初期値はtrueですが下記のような同一生成元ポリシーを充たさない操作が為されると値がfalseに変更され, この状態で当該メソッドの何れかを実行するとcanvas要素はセキュリティエラー(SecurityError)を発生します.

例を示します.

canvas要素が発したセキュリティエラーは通常のエラーと同様にtry-catch構文で制御できます. また一旦falseとなったorigin-cleanフラグを外部から操作するすべはないため, 継続してcanvas要素のフル機能を使いたいのであれば新たなcanvas要素を生成し直します.

特定環境下におけるorigin-cleanフラグの強制

特定の環境下では上位ディレクトリを参照する「../」を利用した画像の参照を行った場合, origin-cleanフラグがfalseと判定されることがあります. 例を示します.

通常の利用では問題は発生しませんが, FireFox環境かつfileデータスキームで(ローカルの)HTMLファイルを直接開いた場合, 上位ディレクトリの参照はorigin-cleanフラグがfalseと判定されます.

ユーザー操作による例外

origin-cleanフラグがfalseとなったcanvas画像であってもコンテキストメニュー(右クリックメニュー)から外部画像ファイルとして保存することは可能です. なぜならこの操作はスクリプトによる自動実行ではなく, ユーザーの手によって行われたことが明らかだからです. 従って権利付きの画像のカジュアルコピーを回避する目的でorigin-cleanフラグを用いることは出来ません.

フラグの連鎖

origin-cleanフラグがfalseのcanvas要素の内容を別のcanvas要素に描き込んだ場合, 描き込み先のcanvas要素のフラグもfalseとなります.

origin-cleanフラグの連鎖

例を示します.

データURIスキームとBlob URIスキームの取り扱い

data:で始まるデータURIスキームとblob:で始まるBlob URIスキーム形式で表された画像データは, 通常の利用の範囲では同一生成元ポリシーを侵すことはありません. そのため, canvas要素に転写したとしてもorigin-cleanフラグに影響しないはずです. が, 条件によってはポリシー違反として判定されることがあります.

レンダリングエンジン別同一生成元ポリシー判定
GeckoBlinkWebKitEdgeHTMLこのブラウザ
data:ラスタ画像
SVG
SVG+HTML ××
blob:ラスタ画像
SVG
SVG+HTML ×××
親となるページがHTML URIの場合
○はポリシー違反なし, ×は違反(toDataURLメソッドの実行でエラーが発生する)
†SVGにforeignObject要素を使ってHTMLを挿入した場合

同一生成元ポリシーによる制限の回避

同一生成元ポリシーによるリソース参照の制限は次のいずれかの手法を用いることで回避できます. これは複数のドメインでリソースを共有する用途において有効です.

Access-Control-Allow-Originヘッダーの付与による方法

canvas要素の動作はクロスドメインでのリソース参照についての仕様を規定するHTTPアクセス制御(CORS:Cross-Origin Resource Sharing)に準拠しています. WEBサーバーがレスポンスヘッダーにAccess-Control-Allow-Origin値を適切に付加することにより, origin-cleanフラグを汚染すること無くクロスドメインでの画像参照が可能となります. ブラウザ, WEBサーバー共にCORSに対応している必要はありますが, 既存のシステムへの影響を最小限に留めることが可能です.

参考:enable cross-origin resource sharing
HTMLImageElement.crossOrigin
corsによるリクエストの方法を指定する. レスポンスヘッダーにAccess-Control-Allow-Origin値が設定されていた場合, 元の文書と生成元が同一であるものとして扱われる.
anonymous
認証無しのリクエストを行う.
use-credentials
認証ありのリクエストを行う.
[未指定]
これまでどおりのリクエストを行う.

window-iframe間のメッセージ通信を用いる方法

画像提供元のドメインにリソースを外部公開するためのWEBページを用意し, ここからwindow間通信を使ってクロスドメインでの画像データを入手する方法です. 例を示します.

JSONPを用いる方法

JSONP(JSON with padding)はscript要素におけるクロスドメインでのスクリプト読み込みをデータ授受に応用したものです. ここでは画像データを文字列リテラルとして読み込みます.

但し, 画像データを文字列化する過程でネットワーク通信量が増大する点と, JSONP固有のセキュリティリスクの存在に注意して下さい.

WEBプロキシを経由する方法

同一ドメインに画像取得のためのWEBプロキシサーバーを構築し, 画像の生成元を偽装する方法です. なおこのようなプロキシサーバーはローカル環境や保護された範囲での稼働が望ましく一般公開すべきではありません.

補足)それ以外のセキュリティ対策

CORS以外にもセキュリティを強化する方策はあります. 何れもスクリプトやコンテンツの取り扱いに関わるもので, canvas専用というわけではありませんが憶えておくと役に立つでしょう.

サンドボックス化したiframe内でのスクリプトの実行

sandbox属性をもつiframe要素では, 読み込んだサブ文書からiframe要素を所有するメイン文書・ウインドウへのアクセスが厳しく制限されます. その結果スクリプトの影響範囲が単一WEBページに限られるため安全性が向上します.

とは言え本機構をサポートしない環境や, WEBページの直接表示, 想定外のバグの混入のリスクを鑑みると, 信頼のおけないスクリプトコードの実行環境としては機能が不十分です.

CSPによるコンテンツ読み込み制限

コンテンツセキュリティポリシー(CSP:Content Security Policy)とは, WEBページが利用可能なコンテンツの範囲に制限を設ける仕組みです. WEBサーバーが発行するCSPヘッダ(もしくはmeta要素)を元にWEBブラウザは読み込むコンテンツや実行するスクリプトを選別します. こうすることでXSSを誘発するscript要素が混入させられたとしても, スクリプトそのものの実行が阻害されるためリスクが軽減されます.

Canvas fingerprinting

Canvas fingerprinting(canvas指紋)とはcanavs要素に対する描画結果がシステム毎に微妙に異なることに着目し, getImageDataメソッドから抽出したピクセルデータをWEBページ来訪者(閲覧しているコンピュータ)の識別に利用するものです. cookieによるトラッキング処理と同様, 専用のライブラリと共に今日幅広いサイトに於いて採用されています.

Canvas fingerprinting対策

参考)Firefox 58 warns you if sites use Canvas image data

Canvas fingerprintingはその仕組み上(cookieと異なり)クロスサイトでの訪問者識別が可能であるにも関わらず, canvas要素が動作するだけで利用でき, かつCORS等の影響も受けません. そのため, ユーザー側での対処が著しく困難なことからプライバシー保護の観点で問題視されることがあります.

この事態を受け, FireFoxではバージョン58からCanvas fingerprintingの利用に制限を設ける(WEBサイトごとの許可制とする)仕組みを標準装備しています. 本機構を有効化するとgetImageDataメソッド呼び出し時に確認ダイアログが表示されるようになり, Canvas fingerprintingによるトラッキングの実行可否をユーザー側で選択可能となります.

privacy.resistFingerprintingキー有効化時に表示されるメッセージ(FireFox)

一方この仕組みは(画像加工の範疇での)通常のピクセルデータの抽出にも作用するため, そうではないにも関わらずあたかもユーザートラッキングを行っている(プライバシーを侵害している)ような印象をWEBページ来訪者側に与えることがあります.

このことから今後ピクセル操作を行う場合は, 来訪者に対しWEBページ上でどのような処理に用いているかを明示し, 操作の許可を促す必要が出てくるでしょう.

resistFingerprinting機構の発動条件

現在FireFox(privacy.resistFingerprintingの値がtrueである環境)においてはgetImageDataメソッドを実行することで無条件に発動します.

メッセージ表示時・拒否時の挙動とその判定

メッセージが表示される, もしくはピクセルデータの抽出が拒否された場合, getImageDataメソッドは常に0クリアされたImageDataオブジェクトを返します. そのため, 特に問題が無い限りスクリプトは正常終了し, 無色透明のPNG画像を読み込んだ場合と同等の処理結果が得られます.

従って, 事前にgetImageDataメソッドの利用可否を判定するには次のようにします.

  1. window.onload等, WEBページが表示された後に一度「本ページはピクセル操作を用いています」旨のメッセージ(alert等)を表示します. その後, 1px×1pxの小さなcanvas要素を用意し, fillRectメソッドで色を塗った後にgetImageDataメソッドを実行します. (メッセージの表示を促す)
  2. 実際の処理を行う前にもう一度小さなcanvas要素に対してgetImageDataメソッドを実行し, ImageDataが空ではないことを確認します.

canvas要素における画像リソースの隠蔽

WEBを介したデータのやりとりは必然的にデータの複製を伴います. 従って, 価値のある画像をユーザー側のブラウザに表示しつつ画像の複製を禁止することは事実上不可能です. しかし要件によってはこの矛盾した内容を強引に解決すべき事もあり得ます. ここではcanvas要素を用いてユーザーによる画像コピーを防ぐ(困難にする)方法(つまり, アプリケーションベンダーサイドのセキュリティ)について考察します.

アニメーションの実現と利用

基本的なアニメーションのパターン

canvas要素そのものにはアニメーションを行う機構は存在しません. 従ってJavaScriptの機能を用いて, 徐々にグラフィックを書き換えることでアニメーションを表現します.

基本的な処理の流れは次の通りです.

  1. 事前に描画したい画像等をロードしておく.
  2. canvas要素の内容の一部/全体をクリアする. (全体上書きが発生するなら必要なし)
  3. 図形の位置を算出する.
  4. 図形を描画する.
  5. この2〜4の処理を繰り返す.
  6. (必要に応じて)規定回数繰り返した, もしくは規定時間が過ぎたら処理を終了する.

この繰り返し処理を実現する方法には無限ループを用いる(非推奨)setIntervalメソッドを用いる(ゲーム等)setTimeoutを用いる(古典的)requestAnimationFrameを用いる(汎用)等様々なものがありますが, 最も素朴な方法はsetTimeoutによる処理でしょう.

アニメーションタイミング制御APIの利用

アニメーションを実現するのであれば, 古典的なsetTimeout関数によるループ処理よりも専用のアニメーションタイミング制御APIを用いましょう. 本仕様が定義するrequestAnimationFrameメソッドは引数に渡したコールバック関数の実行を次のスクリーン書き換えのタイミングまで遅延させます. そのため, setTimeout関数と同様の手法でアニメーションループを記述できますが, 決定的に異なる点として特定の条件下においてコールバック関数の実行を間引くことでアニメーションに関わるコストを最適化する点が挙げられます. 例えば背後に回ったタブにおいては関数呼び出しの頻度が下げられます.

このことから, コールバック関数呼び出しのタイミングを明示できず単位時間あたりの描画回数が不定となります. 従ってアニメーションの進捗を描画回数でなく, 現在時刻から開始時刻を引いた値で考えます. これをタイムラインアニメーションと呼びます.

window.requestAnimationFrame(callback)
アニメーションフレームを要求する. handle値が返される. callback関数の引数にページ表示時からの経過時間(高精度)が渡される. コールバック関数はスクリーンのリフレッシュのタイミングに呼びだされるが, 条件(高負荷な場合やタブやウインドウがフォーカスを失っている場合)により呼び出し頻度が調整される.
window.cancelAnimationFrame(handle)
アニメーションの実行をキャンセルする.

なお, パラパラマンガのように予め描く内容が固定されているケースに於いてはsetTimeout関数を使ったほうが良いこともあります.

高精度タイムスタンプの利用

Date.nowメソッドが現在時刻の整数ミリ秒表現(DOMTimeStamp)を返すのに対し, (window.)performance.nowメソッドは当該ドキュメントの表示開始時からの経過時間を浮動小数点数ミリ秒形式(DOMHighResTimeStamp)で返します. そのためこの値を用いるとアニメーションの精度が高まります. なお, Date.nowメソッドとは時刻値の基準がそれぞれ異なるため, 混在させることは出来ません.

Web Animations APIのcanvas要素への応用

WEB環境におけるアニメーションの実現にはCSSアニメーションによるもの(主にSVGにおける)SMILアニメーションによるものスクリプトによるものの3つがあり, これらは並行して発展してきました. しかし本質的に同じものであることから, アニメーション機構をWeb Animationsという単一の仕様に統合する動きがあります. この仕様の元, スクリプト向けに新たにWeb Animations APIが定義され, アニメーションを“作る”のではなく“制御”する統一的な手段が提供されます.

Web Animations APIでは特定のノードに対するスタイルを徐々に変化させることでアニメーションを表現しますが, その過程でアニメーションの進捗値が得られます. この値を元にcanvasグラフィックを描くことで, 他のアニメーションとcanvasアニメーションとを同期することが可能です. 例を示します.

  1. KeyframeEffectオブジェクトを生成する.
    Animationオブジェクトを生成するために, まずどのようなアニメーションとするか(アニメーションの長さ, 繰り返し回数など)をKeyframeEffectオブジェクトで定義する.
  2. Animationオブジェクトを生成する.
    コンストラクタには上記KeyframeEffectオブジェクトのほか, document.timelineを渡す. これはアニメーションをこのdocumentの管理下に置くことを示す.
  3. Animationのステータス値に応じたcanvas描画処理を制御する
    アニメーションには"idol"(停止中), "pending"(実行待ち), "running"(実行中), "paused"(一時停止中), "finished"(完了済み)の状態が定義されており, これらの内容を元にcanvasグラフィックの描画を継続する(つまりrequestAnimationFrameメソッドを実行する)かどうかを判断する.
  4. Animationの進捗値を元にcanvas描画処理を記述する
    アニメーションからはアニメーション進捗値(progress:0〜1)が得られる. この値は(1)で記述したアニメーション設定を反映しており, この内容を元にグラフィックを描くことでAnimationオブジェクトを使ったアニメーションの制御が可能となる.
  5. playメソッドを実行し, アニメーションを開始する.
    一時停止, キャンセルと言った処理をAnimationオブジェクトに任せる.

描画結果を活かしたアニメーション

通常canvas要素を用いたアニメーションでは一旦グラフィック全体を削除して一から描画し直しますが, 描画済みの内容を次のフレーム画像に活かす方法もあります.

アニメーション処理の実際

canvas要素によるアニメーションはその実装の仕方でパフォーマンスに雲泥の差が生まれます. 以下に注意すべき点について考察します.

オフスクリーンレンダリングの活用

画面に表示されているcanvas要素への操作はスクリーンの書き換えを伴います. 一般にこの書き換えは処理コストが高く, できるだけその頻度を減らすべきです. そこで一旦メモリ内の(スクリプト変数に格納された)HTMLCanvasElementインスタンスに対して1フレーム分描画した後, DOM内のcanvas要素に複写するようにします. 画面には常に出来上がったフレームのみが描画されるため, 必要最低限のスクリーン書き換えで済みます. また内部的に1秒間に60フレーム分の描画を行い, 実際の表示にrequestAnimationFrameメソッドを用いることも可能です. この場合, スクリーン描画の頻度変更が容易となります.

オフスクリーンレンダリングを用いたアニメーションの構造

一方canvasサイズが大きくなるにつれて画像の転写に伴うコストが無視できなくなることから, 画像サイズが抑えめでcanvas全面を高頻度に書き換える際に有効なテクニックです.

グラフィックの再描画範囲を限定する

canvas要素に対する描画処理の大部分はピクセルデータに関わるメモリ操作であり, 全体に亘って値を書き換えるより必要な部分のみを編集したほうが効率的です. 従って予め書き換えた領域を記憶しておき次フレームの描画時に限定された範囲のみ復元するようにします. この手法は特にcanvas要素のサイズが大きく(例えばcanvas画像をスクリーン背景とするケース), グラフィックの描画が局所的な場合に効果的です.

グラフィックを構造化(レイヤ化)する

頻繁に書き換える必要のない背景画像については別のcanvas要素に描画するようにし, 複数のcanvas要素を重ねることで目的のグラフィックを得るようにします. つまり画像合成をWEBブラウザ側に任せるのです. 更に前面に配置するcanvas要素は必要最低限のサイズとしておき, CSSで描画位置を指定すると効果的です.

canvasグラフィックの階層化

なおスナップショット画像を撮れないように思えますが, 保存アクションの際にcanvasを合成すれば良いだけの話です. また, canvas要素が重なることでイベントの取得が面倒な場合は, pointer-eventsプロパティをnoneとすることで上位レイヤを無視することが可能です.

stroke/fillメソッドの実行回数を減らす

canvasAPIの呼び出しにはコストが掛かります. 従って, グラフィックの描画をできるだけ少ない手数で行うように工夫します. 例えば同じ色の図形は一回のメソッド呼び出しで描画できないか検討します. Path2Dオブジェクトを用いて図形を再利用するのも効果的です.

アンチエイリアスの発生を抑える

stroke図形が複数の画素にまたがる場合, コストの高いアンチエイリアス処理が働きます. そのため事前にパラメータの小数値を調整するなどして描画対象のグラフィックがピクセル境界に収まるようにし, 暗黙的なアンチエイリアスが発生しないようにします. 同様にimageSmoosingEnabledプロパティをfalseとして画像スケール時の処理を省くのも効果的です.

複雑なグラフィックは出力結果をキャッシュする

複雑なパス図形や影などのフィルタが掛かっている画像はフレーム描画処理の都度生成するのではなく, 一旦別のcanvas等に処理結果をとっておき使い回すようにします.

stateの変更回数を減らす

fillStyleやstrokeStyle等のプロパティ変更は意外にもコストが高く付きます. 従って描画スタイルの変更がループ処理の内部に存在する場合はその処理をループの外に移動できないか検討します. 同様にsave/restoreメソッドの実行頻度にも注意します.

アルファチャンネルを無効化する

描画対象のcanvasグラフィックに透過処理が必要ない場合は, 明示的にアルファチャンネルを無効化することで若干の処理速度の向上が見込めます.

will-changeプロパティを設定する

CSSのwill-changeプロパティにcontentsを設定することで, 当該要素が今後アニメーションすることをブラウザに通知し, スクリーン描画の最適化にヒントを与えられます. しかしどのような最適化が為されるかは環境毎に異なるため, 必ずしも効果があるとは限りません.

過剰なcanvas要素の生成を抑える

オフスクリーンのcanvas要素上にスプライト画像を構築する場合は, 小さなcanvas要素を多数用意するよりも大きなcanvas要素に複数のスプライト画像を描いておくほうが画像選択時のオーバーヘッドが抑えられます.

出典:maachangの日記:[HTML5]Canvasの作りすぎに注意

隠れたアニメーションを停止する

複数のcanvasアニメーションを利用する場合, スクリーンから見えないものについてはIntersection Observer APIやPage Visibility APIを用いて開始を遅延したり一時停止しておくことでWEBブラウザに対する負荷を軽減できます.

この他, 一般的に言われているパフォーマンスチューニングテクニックはcanvasアニメーションにおいても有効です. 一方, canvasアニメーションのパフォーマンスを改善する場合, 後からスクリプトの構造に手を入れるのはなかなか難しい作業です. そのため, 面倒であっても最初にどのようにアニメーションを実現するかについて設計しましょう. その上で達成すべき目標(フレームレート等)を立て, 少しずつ実装していくことをおすすめします.

canvasアニメーションを動画として扱う

canvas要素で生成したアニメーションは一般にそのcanvas要素でのみ確認可能です. が, 現在検討されているWebRTC及びMedia Capture from DOM Elementsを用いると, canvasアニメーションを動画(ストリーミング)として扱うことができます. 環境さえ許せばPC間でのアニメーション共有ですら可能ですが, ここではブラウザ内で完結するvideo要素への転送とアニメーションの録画機能についてのみ述べます.

canvas要素の内容をvideo要素に転送する

canvas要素のcaptureStreamメソッドを実行するとCanvasCaptureMediaStreamオブジェクトが得られます. このオブジェクトをvideo要素のsrcObjectプロパティに渡すことでcanvas要素の描画状態がvideo要素に転送されます.

HTMLCanvasElement.captureStream(rate)
ストリームオブジェクト(CanvasCaptureMediaStream)を生成する. 引数にはフレームレートを指定する.
HTMLMediaElement.srcObject
再生対象のストリームオブジェクトを取得・設定する.
CanvasCaptureMediaStream
canvas要素をキャプチャするストリームオブジェクト. MediaStreamオブジェクトを継承している.
CanvasCaptureMediaStream.canvas
キャプチャ中のcanvas要素を取得する.
CanvasCaptureMediaStream.requestFrame()
実行時のcanvas要素内の描画内容を強制的にストリームに出力する. グラフィック書き換えのタイミングで実行すると効率が良いかもしれない.

このように得られたストリーム映像は一般のビデオ映像と異なりアルファ値が含まれていることがわかります. また, canvasアニメーションをストリーミング映像化する過程でグラフィック品質は劣化します.

以前見たとおりcanvas要素はvideo要素の映像を取り込むことができます. 従ってCanvasCaptureMediaStreamと組み合わせると, video要素の映像をcanvas要素で加工し, その結果を別のvideo要素に出力することも可能です. 但し本格的なオーサリングツールとしての活用は相応のマシンパワーが要求されるなど実用には厳しいかもしれません.

ストリーミングの記録

CanvasCaptureMediaStreamオブジェクトはMeidaRecorderオブジェクトでBlob(video/webm)形式に変換可能です. 得られたBlobオブジェクトはURLオブジェクトでurl文字列に変換することで, videoオブジェクトで録画結果を再生したり, a要素を使ってダウンロードできます.

MediaRecorder(stream, options)
ストリームオブジェクトを録画するためのレコーダを生成する. optionsには映像を記録する際のパラメータを連想配列形式で指定する.
mimeType
ビデオ保存時の動画形式を指定する video/webm, video/mp4等
audioBitsPerSecond
音声1秒あたりに割り当てるbit数を指定する
videoBitsPerSecond
映像1秒あたりに割り当てるbit数を指定する
bitsPerSecond
ビデオ(音声・映像)1秒あたりに割り当てるbit数を指定する
MediaRecorder.start()
録画を開始する.
MediaRecorder.stop()
録画を終了する.
MediaRecorder.ondataavailable
録画結果のデータ出力が整った際の処理を設定する. 引数として与えられるBlobEventのdataプロパティが録画結果のBlobオブジェクトにあたる.
MediaRecorder.isTypeSupported(mimeType)
指定した形式をサポートしているかを判定する.
[保存]

アニメーション出力方法・まとめ

以下にアニメーション出力方法についてまとめましょう. 新たに追加された仕組みにより, 今後下に掲げる3つの方法でcanvasアニメーションを出力可能です.

  1. canvas/canvas
    通常のcanvas要素でアニメーションを作成し, スクリーン上のcanvas要素に表示する方法. これまで広く使われてきた方法で, 広範な環境で動作します.
  2. video/canvas
    通常のcanvas要素でアニメーションを作成し, スクリーン上のvideo要素に表示する方法. streamを介するため動作環境が限られます.
  3. canvas/offscreenCanvas
    offscreenCanvasでアニメーションを生成し, スクリーン上のcanvas要素に表示する方法です.

実際にフレームレート等を計測したわけではありませんが, 2のvideo/canvasの方法は1のcanvas/canvasよりも若干CPUの負荷が軽減されるようです. また仕様が未確定なものの, 3のOffscreenCanvasを用いた方法は画像転送時に余計な処理が入らないことから, パフォーマンス面に優れることが想定されます.

画像ファイルへのアニメーションの記録

アニメーションをアニメーション画像として記録することも出来ます. ビデオと異なり, 得られた画像はimg要素の他にcss背景として利用することも可能です.

XNG画像の生成

XNG形式は, SVGを使ったパラパラアニメーションの俗称で, アニメーションを構成するフレーム画像をSVGに埋め込み, CSSやSMILを使って動きを表現するものです. オリジナルの仕様ではSMILを用いていますが, これをCSS Animationsに置き換えることでほとんどのブラウザで動作するようになります.

なお, 生成したXNGアニメーションはWEBブラウザ専用になるため, システム間での動画の受け渡しには向いていません.

アニメーションGIF画像の生成

汎用的なアニメーション画像とするにはgif.jsを使ってアニメーションGIFとすると良いでしょう.

SVG(scalable vector graphics)との連携

SVG画像はJPEG形式やPNG形式のラスタ画像と同様にcanvas要素に描き込むことができますが, その特性を活かすことでcanvasグラフィックの表現力がより高まります.

動的に生成されたSVGの描画

SVGの内容はテキストで表されるため, スクリプトを使って内容を自由に編集できます. そのため, 下記手順に則ると動的に生成したSVG画像をcanvas要素に描画することが可能です.

  1. SVGのソースコードを生成する
    SVGのソースコードの入手方法としては概ね次の2つがあります.
    • SVGSVGElementオブジェクトをルートとするツリー構造を入手する.
      この方法は必要となるSVG構造を予めHTMLページにインラインSVGとして挿入しておいた, もしくはAjax機構でSVGDocumentを入手した場合に利用します. 得られたツリー構造はXMLSerializerオブジェクト/outerHTMLプロパティでSVGのソースコードに変換します.
    • SVGソースコードをスクリプトで生成する.
      この方法はSVGのソースコードを直接生成するもので, ES6で導入されたテンプレート文字列の利用が効果的です.
    なおimg要素でSVGを読み込む都合上, 生成するSVGは単体で動作するものとしてください. 必要となるリソース(画像, スタイル, フォント等)はstyle要素として埋め込んだり, データURIスキーム形式として全て埋め込むようにします. また, スクリプトを含むSVGは正しく動作しません.
  2. SVGのソースコードをURL文字列に変換する
    上で得られたSVGのソースコードをimg要素が読み込める形式に変換します. この方法としては概ね次の2つがあります.
    • encodeURIComponentメソッドを使ってデータURIスキーム形式に変換する
    • Blobオブジェクトを生成し, URL.createObjectURLメソッドを使ってblobURIスキーム形式に変換する
  3. 得られたURL文字列をImageオブジェクトに読み込ませてcanvas要素にその内容を転写する
    通常の画像転写と同様にloadイベント中に処理を記述します.

コードのサンプルを示します.

このテクニックを用いると, SVGが提供している豊富な機能をそのままcanvas要素に流用することが出来ます.

SVGを介したHTMLの描画

SVGにはforeignObject要素というXHTMLやMathMLで定義された要素を埋め込むための仕組みが存在します. この仕組みを利用するとHTMLの内容をcanvas要素に描画することができます. 例として下のテーブルを描画してみましょう.

table in canvas
ヘッダヘッダヘッダ
111213
212223
313233
414243
HTMLをPNG形式に出力した結果

なお, HTML要素の表示に要するスタイル値はSVGファイル内部のstyle要素として補う必要があります.

WEBフォントの適用

WEBフォントを描画する場合は, SVGにWEBフォントを埋め込むようにします. その際非同期処理の連続となる点に注意して下さい.

  1. (もしあれば)スタイルシートから@fontface規則内に記述されているWEBフォントへのURLを抽出する.
  2. XMLHttpRequestを用いてWEBフォントファイルをBlobオブジェクトとして入手する.
  3. FileReaderを用いて上で得たフォントファイルBlobをデータURIスキーム形式に変換する.
  4. フォントURL文字列をSVGのstyle要素に埋め込む.
  5. 得られたSVGソースをimg要素に設定し, loadイベントをトリガとしてSVG画像をcanvas要素に転写する.

以下にGoogle Fontsを利用した場合の例を示します.

filterプロパティを用いずにSVGフィルタを利用する

canvas要素で描いた内容をtoDataURLメソッドを用いて一旦SVGファイルに埋め込み, それを再度canvas要素に書き戻すことでSVGフィルタをcanvasグラフィックに適用可能です. この方法はorigin-cleanフラグを汚染しないため, toDataURLメソッドやtoBlobメソッドを実行してもセキュリティエラーとなりません.

transform3dを使った画像の変形

現在CanvasRenderingContext2Dオブジェクトに座標の3D変換に関わるAPIは備わっていません. 従って画像に遠近法による変形を施すにはCSS transformによる変形を施した画像をSVG経由でcanvasに書き込みます.

SVGDOMの利用

上記はSVGグラフィックそのものをcanvasに描画する例でしたが, SVGDOMやSVGの機能そのものを直接canvasでの描画処理に応用することもできます.

SVGパスデータ文字列を使ったパス図形の定義

Path2Dオブジェクトを使うことでSVGパスデータ文字列を使ったグラフィックの描画が可能です.

SVGPathElementを用いたパス計算

SVGのSVGPathElementにはHTMLCanvasElementには存在しない様々なベジェ曲線やパス図形に関わるユーティリティーメソッドが提供されています. 従ってこれらをcanvas要素でのグラフィック描画に活かすことで表現の幅を広げることが可能です. 下の例ではgetPointAtLengthメソッドを使ってベジェ曲線上の特定の長さの位置に印をつけています.

パスに沿ったテキストの描画

先ほどの応用で, 1文字ずつ描画位置を決定することでパスに沿ったテキストの描画が可能です.

レスポンシブなSVGの描画

SVG画像の中には敢えて画像サイズを指定せず常に描画範囲いっぱいにグラフィックを描くものがあります.

レスポンシブなSVG画像

これをレスポンシブなSVGと呼び, このような画像を直接canvas要素に描画すると一部の環境で正しく動作しません.

この場合, 描画対象のSVGをAjaxで取得したものに幅と高さを追加し, その後でcanvas要素に描画します.

SVGにおけるcanvas要素

先ほど見たとおり, SVGではforeignObject要素を介してHTMLの要素を挿入可能で, これはcanvas要素においても有効です. 従ってJavaScriptの動作が可能な環境であれば, SVGグラフィックの一部をcanvas要素に任せることが可能です. 以下に例を示します.

但しその動作は限定的で, SVGの全ての機能(例えばuse要素による参照)がcanvas要素に作用するとは限りません.

canvas要素の直接配置

SVG2ではSVG環境でのcanvas要素の振る舞いがグラフィックを表すものとして明確化されました. 従って次のSVGコードはSVG2仕様においては意味のあるものとなります.

HTML環境における制限

HTML環境では名前空間を指定する手段が存在しないため, HTML文書にsvg要素を記述し, その子孫にcanvas要素を配置することは出来ません. HTMLのパーシングルールにより, canvas要素はsvg要素の外に追い出されるからです. なおスクリプトを使ってsvg要素を頂点とするサブツリーに動的にcanvas要素を配置することは可能です.

補足)XML文書中のcanvas要素

canvas要素はSVGのみならず任意のXML文書に挿入することができます. このような文書は(XHTMLとの)ミックス文書と呼ばれ, WEBブラウザでは自動的にスタイルが適用されグラフィカルに表示されます. なおcanvas要素(およびscript要素)には, その所属を明らかとするためxmlns属性による名前空間(http://www.w3.org/1999/xhtml)の指定が必要です.

SVGファビコンのラスタライズ

ファビコンとはWEBページに設定されたアイコン画像であり, タブやブックマーク(お気に入り)の識別に利用されます. 例えばこのページのファビコンはです. ファビコンとして要求される画像のサイズが環境ごとに異なるため, いつでも最適な出力結果が得られるSVGはファビコン画像に最適です. しかし現在SVGファビコンをサポートする環境は限られています. そのためSVGをファビコン画像として利用するには, 一旦その内容をcanvas要素に転写しラスタ画像化します.

なお複数のlink要素を用意している点が冗長に感じますが, システムが要求するファビコンサイズを判定する手段が無いためこうせざるを得ません.

canvgを使ったSVGの描画

canvgはcanvas要素にSVGのレンダリング機能を追加するライブラリです. 導入するとコンテキストオブジェクトにdrawSvgメソッドが追加され, SVGファイルへのURLSVGのソースコード(SVG)DocumentSVGSVGElementと言ったSVG構造をcanvas要素に直接描くことが可能となります.

SVG仕様の全てを実現するものではありませんが, 凝った構成でなければ良好な出力結果が得られます. そのため, SVGを頻繁にラスタライズする用途に於いてはimg要素を用いるよりも記述がシンプルになります.

canvas要素にSVG構造を導入する

canvas要素はグラフィックに構造を持ちませんが, この役割をSVGに任せることが出来ます. canvas要素配下のSVGに対する操作を監視し, 変更が有った際にSVGの内容をcanvas要素にレンダリングします.

この方法は実験的なもので, 実際の役に立つテクニックとは言えませんが, (SVG)DOMに対する操作を直接canvas要素に反映できる点で興味深いところがあります. また, 独自のグラフィック構造をcanvas要素に定義出来ることを示しています.

canvas要素とイベント

canvas要素が発生するイベント

canvas要素も一般のHTMLノードと同様に各種イベントを発生しますが, focus/blur等のフォーカスに関わるものや, keydown/keyup/keypress等のキー入力に関わるイベントの発生についてはtabindex属性を要します.

なお, canvas要素の子孫にinput要素や(有効な)a要素が含まれていた場合はイベントバブリングによりcanvas要素にもイベントの発生が通知されます.

この動作は後述するcanvas要素におけるアクセシビリティに関わるものです.

カーソル座標とcanvas座標

canvas要素が発するポインターイベントにはcanvas要素における座標値offsetX / offsetYが格納されています. 従ってこの値に沿ってグラフィックを書き換えると, ユーザー操作に応じたグラフィックが得られます.

イベント属性を用いたケース

イベント属性にスクリプトを記述した場合は, カーソル位置を変数arguments[0]に格納されたイベントオブジェクトから取得します.

オフセット値が使えない場合

カーソル座標の取り扱いはCSSOM Viewによって標準化されています. が, 長らくベンダ依存の状況が続いていたため現状でも一部の環境でoffsetX / offsetYプロパティをサポートしないものがあります. またtouchEventから得られるタッチ位置(touch)には特定のノードから算出されるオフセット座標値を取得するAPIが存在しません.

getBoundingClientRectメソッドの利用

以下は同等の値をgetBoundingClientRectメソッドとclientX / clientYプロパティから算出するものです.

iframe要素でスクリーン座標を書き換える

ポインターイベントから取得出来る座標はイベントを発生したHTML文書の表示領域から算出されます. そこでcanvas要素に関わる部分をiframe/object要素化し, カーソル座標の基準を書き換えてしまう方法もあります.

動的にiframe要素を生成することも可能です.

座標変換が必要な場合

グラフィック描画の都合上座標変換が必要な場合は, 現在の変換行列の逆行列を求めて, canvas要素上の座標をcanvasグラフィック内部の座標に変換します. なお, 現状のcanvasAPIではtransform行列や逆行列が得られないので, SVGDOMの仕組みを利用する必要があります.

アニメーション機構の応用

イベントドリブンなスクリーン書き換えはその発生頻度を考慮しないと著しいパフォーマンス劣化を招きます. そのため, canvasアニメーションでののテクニックを応用することで, 必要最低限度のスクリーン書き換えで済ますことを考えます. 以下に手順を示します.

  1. 処理の開始を宣言する.
  2. requestAnimationFrameメソッドを使って定期的にパラメータの変更有無を確認し, 変更されていた時に限りグラフィックを描画する.
  3. イベント発生時にはグラフィック描画のためのパラメータのみを書き換える.
  4. 処理を終了する.

先程の例のようにマウス位置にグラフィックを描く場合, 素朴にはmousemoveイベントでcanvas要素を書き換えますが, それでは書き換え頻度が過剰です. そこでmouseenterとmouseoutイベントをグラフィック描画処理の開始/終了指示に, mousemoveイベントをグラフィック描画に必要となる座標パラメータの書き換えに割り当てて, 実際のグラフィック書き換えはrequestAnimationFrameメソッドに任せています.

スクリプトによるイベントのトリガ

WEBページ上のイベントは通常ユーザーの操作によって作られますが, これをスクリプトから着火(トリガ)する方法があります. これはユーザー補助や操作(やテスト)の自動化と言った際に便利な機能です.

Element.dispatchEvent(event)
指定したイベントを着火する

canvas要素に対してloadイベントを着火することも出来ます.

カスタムイベントの利用

CustomEventオブジェクトを使えば, 任意名称のイベント(カスタムイベント)を作ることが出来ます.

CustomEvent(typeArg, {detail: [values]}
カスタムイベントを生成する. dispatchEventメソッドでカスタムイベントを着火すると, addEventLisntenerメソッド側ではtypeArgに指定した名称でイベントを捕捉する.

ペイント状況に応じたイベント伝播

canvas要素はペイント状況にかかわらずカンバス領域全体がカーソルイベントを発生させます. その為, グラフィックが塗られている部分でのみcanvas要素特有のイベント処理を行わせたい場合は, カーソル座標からピクセルの色(アルファ値)を取得し, その内容を元にcanvas要素直下のノードにイベントを伝播するか判定します.

  1. canvas要素直下のノードにイベントを伝播するには, まず自身が発生させたイベントを無効化します. e.stopPropagationメソッドを実行することでDOMツリー上の親要素へのイベント伝播を阻止できます.
  2. その後(レイアウト上の)canvas要素直下のノードを取得します. このノードはcanvas要素にスタイル値pointer-events:noneを設定し, その後でdocument.elementFromPointメソッドを実行することで得られます.
  3. 得られたノードに対してElement.dispatchEventメソッドを実行しイベントを発行します.
  4. 最後にpointer-eventプロパティを元の内容に戻します.

複数のcanvas要素が重なっていた場合

複数のcanvas要素が重なっていた場合も同様の仕組みで対処できます.

処理シーケンスを示します. dispatchEventが次のcanvas要素のclickイベントを発生するため, イベントがまるでcanvas要素の重なりを掘り進むように伝播していくことが判ります.

重なったcanvas要素間のイベントの伝播

ペイント機能の実装

canvas要素をペイントツールに利用する場合, マウス等のポインターの動きをパス図形の定義に割り当てます. 以下に最も単純なサンプルを示します.

この他にもアイディア次第で様々な描画処理を実現できるでしょう.

マルチタッチを利用したペイント

タブレット等のマルチタッチをサポートする環境ではtouchEventを元にcanvas要素を描くことが出来ます. その際, 複数存在しうるタッチ毎にパス図形(Path2Dオブジェクト)を割り当てることで, 一つのcanvasを複数のユーザーが同時に操作することが可能となります.

筆圧を加味した“stroke”

ペンによるポインターイベントではカーソル座標の他に筆圧に相当するpressure値が得られます. この値を線の太さに割り当てる(可変幅ストローク)ことでより自然な使い勝手となります. しかしstrokeメソッドによる描画では線の太さが均一となるため, パス図形を使ってストローク図形を定義します.

まず筆圧値が変化する前後での座標を中心とした2つの円を考えます. これらの円を連結するように図形を定義することでストローク幅が変化する様子を表現します.

基本的な可変幅ストロークの考え方

パス図形と座標の包含関係の判定

現在カンバスが保持しているパス図形やスタイルと座標(≒canvas要素の画素位置)の包含関係を確認できます. 例えばある領域にマウスカーソルが当たった際にカンバスの内容を書き換えると言った用途に使えます.

CanvasRenderingContext2D.isPointInPath(x, y[, fillRule])
CanvasRenderingContext2D.isPointInPath(path, x, y[, fillRule])
指定した点が現在のパス図形の領域内か判定する.
CanvasRenderingContext2D.isPointInStroke(x, y)
CanvasRenderingContext2D.isPointInStroke(path, x, y)
指定した点が現在のパス図形のストローク領域(線のスタイルを含む)内か判定する.

例を示します. この例では内部の矩形にマウスカーソルを当てることで, 塗り潰しの色・線の色を変化させています.

Path2Dオブジェクトと組み合わせる

Path2Dオブジェクトをサポートする環境であれば, パス図形毎に領域判定が可能となります.

線のスタイルとisPointInStroke

isPointInStrokeメソッドは座標の包含関係の判定にlineWidth, lineCap, lineJoinプロパティやsetLineDashメソッドによって設定された線のスタイルを使います.

canvas要素とコンテキストメニュー

canvas要素を右クリック(ないしはctrl+クリック, メニューキーの押下)するとcontextmenuイベントが発生し, canvas要素に対するコンテキストメニューが表示されます. このメニューには画像の保存と言った機能が含まれています.

コンテキストメニューの無効化

このコンテキストメニューの表示を無効とするには, contextmenuイベントをキャンセルします.

補足)コンテキストメニューの追加

一部のブラウザではコンテキストメニューに独自のコマンドを挿入することが可能です.

アクセシビリティ

アクセシビリティとは

コンテンツを配信する際にその内容が相手に正しく伝わること, 及びその尺度をアクセシビリティ(accessiblity)と呼びます. WEB環境において, HTMLで記述したコンテンツはそのままでは伝えたい内容を機械的にマークアップしたものでしかなく, 受け手側の条件(視聴覚条件, 年齢条件)・環境(スクリーン, OS, ブラウザ)によっては正しく解釈されるとは限りません. 従ってより広範な環境へ内容が正しく伝わる・動作するように心がけましょう.

アクセシビリティとフォールバック

外部リソースを参照したりマルチメディアに関わる要素は, 特定の条件下においてその役割を失います. 例えばスクリーンリーダー環境下では視覚的なコンテンツは意味を持ちません. そのためこのような要素については, そこにどのようなデータが存在するのかを記述しておくことが推奨されています. この記述をフォールバック(fallback・代替)コンテンツと呼び, 元となるコンテンツを表示・出力しない・できない場合にその代わりとして表示されます. こうすることで(完全ではないにせよ)アクセシビリティが改善, つまり制作者の意図が相手に伝わりやすくなります.

canvas要素におけるフォールバックコンテンツの設定

フォールバックコンテンツは原則当該要素の子として記述します. フォールバックコンテンツの指定が可能なものとしては, object要素, iframe要素, canvas要素等があります. img要素ではalt属性に代替文字列を設定します.

その際, フォールバックコンテンツには元となる要素が有効な場合と同等の情報を有するもの, つまり静的な代替画像, 描画した内容についての説明, 描画した文字列などを配置します. またcanvas要素だけの特徴としてフォーカスを受け取ることが可能なノード(a要素によるリンク, input/select要素, tabindexを指定した要素)を配置可能です.

canvas要素におけるフォールバックコンテンツ表示の条件

canvas要素におけるフォールバックコンテンツが表示される条件は次の何れかを充たす場合です.

その際, canvas要素が単なるdiv要素と同じくフローコンテントとして扱われます. つまり, canvas要素に対しても:before/:after擬似要素を設定することが可能です. もちろんcanvas要素が有効な環境では内容は無視されます.

インタラクティブなフォールバック

逆にフォールバックコンテンツからcanvas要素を見た場合を考えましょう. すると先ほどの動作は「canvas要素が動作する環境ではコンテンツがよりリッチに表示される」と言い換えられます. そこでcanvas要素におけるフォールバックコンテンツは, スクリーン上非表示であるにも関わらずフォーカスの移動やキーボードによる値の変更が可能となっています. この動作をインタラクティブなフォールバックと呼び, このおかげでコンテンツの機能(例えばイベント機構)を維持しつつ, 表示内容のみを再定義(override)することが可能となります.

これはつまりカスタムコントロールを作ることが出来るということであり, フォールバックコンテンツにリンクやinput要素等が存在する場合は, あなたはcanvas要素上に同等の機能・見た目を実装しなければなりません. canvas要素の有効無効は, 同じ内容を(より)グラフィカルに表現するか否かの差に過ぎず, 機能的には全く同等であるべきだからです.

この観点からcanvas要素のAPIにはこのUI機構の実装をサポートするための仕組みが提供されています. 以下はcanvas要素を用いてフォーカス移動処理を実装したものです.

このカンバス要素には 四角三角 の3つの図形が描かれています.

カスタムコントロールの構築

先ほど見たとおりフォーム部品をcanvas要素で囲んでカスタムコントロールとすることが出来ます. canvas要素が動作しない環境でも元のフォーム部品が表示されることでWEBページの機能が維持されることに着目しましょう. 下の例ではcanvasをクリックし, 矢印キーを使って値を変更すると, その内容でグラフィックが書き換わります.

テキスト入力機構について

フォーム部品の中でもテキスト入力系のものをcanvas要素上で再現するのは実装コスト的におすすめしません. 検討すべき事項が広範に亘る上, 実装内容に不備があった際にユーザビリティを損ねるからです. そのため, canvas要素の上にinput要素を重ね, 必要に応じてその内容をcanvas要素に書き戻すようにします.

フォーカスリングの描画

コントロールにフォーカスが存在していることを表すにはグラフィックの内容を書き換えただけでは不十分です. drawFocusIfNeededメソッドを用いてフォーカスがどこにあるのかをブラウザ側に伝える必要があるからです. こうすることでフォーカスがあたった際の自動スクロール等が動作するようになります.

出典:canvas要素のフォーカス領域を指定する
動作サンプル:Accessible canvas clock※ですが, 現在はAPIが古くなっており動作しません.
CanvasRenderingContext2D.drawFocusIfNeeded()
フォーカスリングを描画する. 引数にElementを指定すると, その要素がフォーカスを持っている場合に, 現在のパス図形に沿ってフォーカスリングを描画する. かつてdrawSystemFocusLingメソッドという名称でした.

WEBページの自動スクロール

scrollPathIntoViewメソッドを用いるとWEBページを自動的にスクロールさせ, 当該パス図形範囲をスクリーン上に表示させます. これはフォーカスが当たった際の動作をcanvas内の図形において再現します.

CanvasRenderingContext2D.scrollPathIntoView()
CanvasRenderingContext2D.scrollPathIntoView(path)
現在のパス図形を表示できる位置までWEBページをスクロールする.

ヒット領域の設定

ヒット領域はcanvasグラフィックにおける構造を表す概念で, canvas要素上で発生したイベントをその発生位置からヒット領域ごとに振り分けるために用います. 丁度img要素におけるクリッカブルマップ(usemap属性, map/area要素を用いる)のような仕組みを付与するものですが, ヒット領域はフォールバックコンテンツと結びつけることでイベントを直接内部に伝達することも出来ます. 下記に概念図を示します.

ヒット領域の概念

このようにもともとフラットなcanvasグラフィックにDOMに似たツリー構造を持たせることが可能となります.

CanvasRenderingContext2D.addHitRegion(params)
ヒット領域を追加する. カンバスに擬似的に子要素として振る舞う領域を追加する. 引数にはパラメータセットを指定する.
path
ヒット領域とするパス図形. 省略するとコンテキスト中のパス図形を用いる.
fillRule
ヒット領域の定義ルール. nonzero/evenoddの何れかを指定する.
id
ヒット領域に対する識別子. canvas要素の当該ヒット領域上でMouseEvent/TouchEventが発生した際, イベントオブジェクトのregionプロパティにこの値が設定される. 未指定の場合, 下記のcontrolを指定する必要がある.
label
ヒット領域に対応するcontrolが存在しないケースに対するヒット領域に対するラベル(ARIA role).
role
ヒット領域に対応するcontrolが存在しないケースに対する視聴覚ロール(ARIA role)
parentID
ヒット領域に対する親ヒット領域のIDを指定する. (ヒット領域を階層化できる)
control
ヒット領域に対応する要素. canvas要素配下のフォームオブジェクトに限られる. ヒット領域上で発生したイベントはcontrol要素上で着火したものとして扱われる.
cursor
ヒット領域上でのマウスカーソルの種類.
CanvasRenderingContext2D.removeHitRegion(id)
指定したidの要素に対するヒット領域を削除する.
CanvasRenderingContext2D.clearHitRegions()
ヒット領域を全て削除する.
MouseEvent.region/TouchEvent.region
イベントを発した領域が設定されている.

ヒット領域が定義されたcanvas要素では, その上で発生したMouseEventのregionプロパティにヒット領域のIDが設定されます.

IDの代わりにcontrolを指定しておくと, イベント発生時にcontrolでイベントが発生したものとして扱われます.

範囲選択操作とcanvas要素

通常canvas要素はWEBページ上でのドラッグ操作などによる選択の対象外です. そのため, フォールバック用途でcanvas要素下に配置したテキストデータは残念ながら次の用途には使えません.

何れも使い勝手に直結する制限ですが, これらはブラウザ毎の特性差があまりに大きく, 統一的な回避策が見つかりません. 一部ブラウザではCSSのuser-selectプロパティの値をtextとすることでcanvas要素の内容が選択可能となります. 下の例は, 文字の描画をcanvas要素で行ったもので, 使い勝手を通常のテキストと比較するためのものです.

あいえお

このような画像にグリフの役割を持たせる場合はcanvas要素よりもSVGを用いたほうがよいでしょう.

WAI-ARIAとcanvas要素

WAI-ARIAとはHTML等の文書構造に意味的な役割(role属性)を付加することで, ブラウザやスクリーンリーダが文書の内容をより適切に解釈できるようにする仕組みです. また, HTMLの各要素が本来携えている役割を無視して別の目的に流用した際, role属性を明示することで文書作成者の意図をシステムに伝えることが可能となります.

この仕組みは視覚に障害を持つユーザーへのアクセシビリティを確保する上で必須となるもので, canvas要素等のグラフィック要素に対しては専用の属性値(WAI-ARIA Graphics Module)が用意されています.

role属性
ノードが文書内で果たしている意味的な役割を指定する. 例えば次のような値を設定する.
graphics-document
レイアウトや内容に意味があるノードであることを示す.
graphics-object
graphic-documentを構成している部品であることを示す.
graphics-symbol
グラフィックを構成する上で意味を持つ最小構成部品であることを示す.
img
ノードが文書内で画像としての役割を持つことを示す. アスキーアートはこの属性値を指定することになる.
presentation
ノードが装飾の目的だけに利用されていることを示す.

例を示します.

薄暗い林の中にこもんちゃんが立っている.

描画処理のコンポーネント化

グラフィック描画処理をまとめる

グラフの出力やブログパーツと言ったWEBページ横断的に利用可能なグラフィック処理は, 予めコンポーネント化しておくことで後々の管理がやりやすくなります. 以下に代表的なコンポーネント化の手法を示します.

カスタム属性を用いたパラメータ定義

描画処理をコンポーネント化するにあたり, 必要となる各種パラメータを「data-」から始まるカスタム属性を使ってHTMLに埋め込んでしまう方法があります. 例を示します.

このようにカスタム属性の値の変更をMutationObserverオブジェクトを使って監視すると, 属性値の変更処理とグラフィックの再描画処理とが分離されてコンポーネントを再利用しやすくなります.

外部ページによるグラフィック描画

グラフの出力やブログパーツなど汎用的に利用可能なグラフィック描画機能は, 独立したWEBページ(コンポーネント)にまとめることで複数のページから呼び出し可能となります. その際, 処理を行うために必要となるパラメータはpostMessageメソッドを使って当該WEBページに引き渡すようにします. コンポーネント側ではmessageイベントを使って処理の呼び出しを監視します.

postMessageの引数としてメッセージ送信可能なオブジェクトには条件があり, 容易に複製が得られるものに限られます. 例えば, 文字列・数値等のネイティブ型, ArrayBuffer/ArrayBufferViewと言ったデータコンテナ, 及びそれらをまとめたObject型が, またcanvas要素に関わるものであればImageDataオブジェクトが送信可能です. 逆に内部状態を持っていて容易に複製出来ないもの(HTMLElementやCanvasRenderingContext2D等)をpostMessageに渡すとエラーとなります. 例を示します.

この方法の優れている点はクロスドメインでのコンポーネント呼び出しが可能な点です. つまり, コンポーネントをWEBサーバーに配置してしまえばどこからでもその機能を呼び出せるのです.

補足)window間でのcanvas要素の授受について

同一オリジンのWEBページを表示しているiframe要素についてはcontentWindowプロパティを介し内部のDOMツリーを自由に操作できます. そのため, iframe内部で生成したcanvas要素をそのままメインwindowのDOMツリーに配置することも理屈上は可能です. しかし, WEBブラウザによってはこの操作に伴いグラフィックの内容が削除されることがあります. そのため, window間の画像データの授受にはImageDataオブジェクトを用いるようにします.

Web Components仕様の活用

Web ComponentsはWEB部品を定義するための仕様群を指し, 大きく分けてtemplate要素HTML ImportsShadow DOMCustom Elementsから構成されています.

canvas要素のテンプレート化

canvas要素直下にscript要素を配置することで, カンバスと描画処理とをセットで扱うことが可能でした. この構造をtemplate要素として管理しておくと, canvas構造を複写するだけでグラフィックを描けます.

カスタム要素による継承

Custom Elements仕様を用いると, 既存のHTML要素に独自の機能をもたせたカスタム要素を構成できます. ここではcanvas要素を拡張したmy-img要素を作ってみましょう.

最適なcanvasサイズの算出

高解像度スクリーンとcanvas要素

出典:High DPI Canvas

Retinaディスプレイのようにスクリーン環境の機能が向上するにつれ, WEBでのピクセル値の扱いも変化しています. つまり(CSSにおける)1px平方を複数のスクリーン画素によって描くことで, これまでよりも精細なグラフィックが描けるようになりました. その反面, canvas要素はサイズをピクセル値で指定するために非常に厄介な問題が発生しています. つまり, 同じ処理を行っているにも関わらず, 環境によっては得られたグラフィックの精細度が足らず, canvas要素の部分のみがボケたような印象となります. この問題はcanvas要素が動作している環境ごとに最適な出力サイズを算出することで解決します.

デバイスピクセル比とcanvas内部ピクセル比

高解像度環境でcanvas要素を扱う場合, デバイスピクセル比とcanvas内部ピクセル比の2つを考える必要があります. 前者はCSSにおける1ピクセルあたりのスクリーンデバイス上での画素数を表し, 後者はcanvasグラフィックの内部で1ピクセルあたりに確保される画素(グラフィックの最小分解単位)数です. これらは次のAPIで取得できます.

window.devicePixelRatio
デバイスピクセル比. undefinedの場合は1(devpx/px) ※なお, WEBブラウザにおけるズーム倍率も含まれる.
CanvasRenderingContext2D.webkitBackingStorePixelRatio
canvas内部ピクセル比. Safari専用(bspx/px)

この内容は環境(OS,ブラウザ)で大きく異なるので, 同じスクリプトを発行したとしても, 必ずしも環境毎に最適化された出力が得られません.

ピクセル比を加味したグラフィックの描画

canvas要素で描いたグラフィックが最も美しくスクリーン上に表示されるには, canvas内部の1画素がスクリーン上の1画素に対応する場合です. そのため事前に絶対ピクセル比(canvas最小分解単位あたりのデバイスピクセル数)を求めておき, その結果に応じてカンバスサイズを広げ, scaleメソッドを使って座標スケールを調整しておきましょう. こうすることでメインとなる描画処理に一切手を入れること無く, 動作環境毎に最適なグラフィックを描くことが可能です.

高解像度ピクセルデータの取得

Safari環境ではwebkitBackingStorePixelRatioに応じて1ピクセルが複数の画素データをもつため, 通常のgetImageDataではなく高解像度環境専用のピクセルデータ取得APIが定義されています.

CanvasRenderingContext2D.webkitGetImageDataHD()
高解像度ピクセルデータをImageDataオブジェクトとして取得する
CanvasRenderingContext2D.webkitPutImageDataHD()
高解像度ピクセルデータを挿入する

ズーム操作とcanvas要素

上記と同様の問題はWEBブラウザのズーム機能を有効化した際にも発生します. CSSにおける1px平方がズーム倍率によって複数のスクリーン画素に割り当てられるからです. そのため予めズーム倍率を加味するようにcanvas要素の大きさを設定しておくことで, 任意のズーム倍率においても最適なグラフィックを描くことが可能です. ズーム倍率は先ほどのwindow.devicePixelRatioプロパティで取得可能です.

resizeイベントを用いた再描画

ユーザによるズーム操作はwindowオブジェクトの発するresizeイベントを使って検知出来ます. つまりこのイベントをトリガとしてグラフィックを描くことで, 動的なズーム倍率変更に対しても最適なグラフィックの描画が可能となります.

印刷操作とcanvas要素

canvas要素はもともと印刷用途には不適ですが, スクリーンに表示しているものとは別の印刷専用の高精細なcanvas要素を用意しておいてグラフィックを切り替える方法もあります. 以下はその実装例です. メディアクエリ機構を用いて印刷処理開始のタイミングで専用のグラフィックを描画しています. 実際の動作例はこちらです.

なお, 解像度の高いcanvas要素はシステムへの負担となるため, 印刷内容が判読出来る程度の解像度に抑えるべきです. 一般的なスクリーン解像度(画素密度)を96dpiとするなら, 印刷用canvas要素のサイズは元画像と比べて1.5〜3倍(プリンタ解像度換算で150〜300dpi)程度とするのが妥当でしょう.

CSSとcanvas

canvas要素と基底線

canvas要素やsvg要素等のコンテンツを埋め込む役割を担う要素は, 特に明示しない限り要素の下端がテキストの基底線(baseline)に揃えられます(display:inline-block;における標準動作). そのため, グラフィックの整列を行う際に追加のスタイル設定が必要となることがあります.

インラインcanvas要素内外の文字列を揃える

canvas要素をインラインで利用する場合, canvas内外のテキストを揃えることで文字列全体の印象がよくなります. その際は基底線の位置を意識し, 文字列が見きれないようにします.

canvas要素内外の文字列の整列

canvasグラフィック直下の空白

div要素等のブロック要素でcanvas要素を囲んだ際に生まれる隙間は基底線揃えによって生じています. この隙間を除去するにはcanvas要素にvertical-align:bottom;(下端揃え)を指定します.

未指定
bottom
vertical-alignプロパティの作用(左:未指定, 右:bottom)

CSSプロパティとの連携

描画スタイルの中にはCSSの設定内容を流用可能なものがあります. 例えばフォントスタイルをCSSから取得すると, HTML文書内のテキストとcanvasグラフィック内部のテキストの見た目を統一することができます. canvas要素に現在適用されているスタイルを取得するにはwindow.getComputedStyleメソッドを用います.

window.getComputedStyle(element)
指定した要素の現在のスタイル設定情報を取得する.

流用可能なプロパティ

以下にcanvasへ流用可能なCSSプロパティのうち, 比較的単純に利用できるものを示します.

canvasへ流用可能なCSSプロパティ
CSSプロパティstyleプロパティ対応するctxプロパティ内容・備考
widthwidth(width)canvasのサイズとする.
正確にはbox-sizingの設定を参照する必要がある.
heightheight(height)
fillfillfillStyle塗りの色
fill-rulefillRule(fillメソッド等)塗りつぶし領域の算出
strokestrokestrokeStyleストロークの色
stroke-widthstrokeWidthlineWidthストロークの幅
「px」を取り除く.
stroke-linecapstrokeLinecaplineCapストローク端点の形状
stroke-linejoinstrokeLinejoinlineJoinストローク頂点の形状
stroke-miterlimitstrokeMiterlimitmiterLimitストローク頂点の尖りの限界
stroke-dasharraystrokeDasharraysetLineDash破線のスタイル
「px」を取り除き, 「,」で分割して利用する.
font-stylefontStylefontフォント設定
内容をスペースで連結する.
font-variantfontVariant
font-weightfontWeight
font-sizefontSize
font-familyfontFamily

フォールバックコンテンツのスタイルの利用

グラフィックを描く際にフォールバックコンテンツのスタイルを参照することも可能です.

div in canvas

擬似クラスによるスタイル変更値の適用

:hover:active等の擬似クラスによるスタイルの変更はcanvas要素から離れた箇所で引き起こされることもあるため, requestAnimationFrameメソッドを使って定期的にスタイル値を監視し, 変化が有った際にグラフィックを書き換えます.

color変更
div in canvas

CSS変数によるグラフィックのパラメータ化

参考:CSS Variables を JavaScript から操作する

CSS記述に於いてCSS変数(「--」から始まるスタイルプロパティ)を用いると, その内容をvar関数を介して他のプロパティから参照出来ます.

この仕組みを用いると, canvasグラフィック専用のスタイルを定義できます. スクリプトからCSS変数の内容を参照するにはgetPropertyValueメソッドを用います.

この手法は複数のcanvasグラフィックの見た目を統合する場合に有効です. なおCSS変数の仕組み上任意のパラメータを定義することが可能ですが, アプリケーションとしてのパラメータやコンテンツは意味論的にカスタム属性(data-*属性)やcanvas要素配下に記述すべきです.

canvas形状の変形

canvas要素は通常矩形(四角形)ですが, CSSやSVGと組み合わせることでその見た目を変化させられます.

CSSによる変形

CSSスタイル・border-radiusを設定することで見た目が角丸四角形・円・楕円に変化します.

SVGによるクリップ

CSSのclip-pathプロパティとSVGのclipPath要素とを組み合わせてcanvas要素を任意形状に切り出す(クリップする)ことが可能です. clipPath要素配下に切り出したい図形の形状を指定し, それをclip-pathプロパティから参照します.

またこの方法ではイベントの発生もその図形範囲に制限されます. 下の例ではcanvas要素に描画した内容からクリップ範囲を決定しています.

canvas要素とグラフィックの配置

canvasグラフィックのサイズとスクリーン上の描画範囲との間に違いがあると, 通常グラフィックが描画範囲のサイズに引き伸ばされますが, この挙動はプロパティobject-fitobject-positionで変更することが可能です.

object-fit
(canvas要素における)描画範囲に対するグラフィック配置の仕方を指定します.
cover
アスペクト比を維持しつつ, 描画範囲全体が隠れるように拡大・縮小配置する
contain
アスペクト比を維持しつつ, グラフィック全体が表示されるように拡大・縮小配置する
fit
描画範囲全体にグラフィックを引き伸ばす(初期値)
none
グラフィックのサイズ変更を行わない
scale-down
グラフィックが描画範囲からはみ出る場合は, グラフィック全体が表示されるように拡大・縮小する
object-position
グラフィックを配置する際の基準座標. 初期値はcenter
object-fitプロパティの効果
covercontainfitnonescale-down
横方向 cover contain fit none scale-down
縦方向 cover contain fit none scale-down

object-fitプロパティではグラフィックの配置に関わる設定を行います. しかしnone以外の設定値ではcanvasグラフィックの最終的な出力結果を再度拡大縮小するため品質が劣化します. 従って同等の処理はscale等の座標軸変換メソッドで行うべきです.

一方none値はobject-positionプロパティと組み合わせることで, グラフィックを書き換えることなくその描画位置のみを変化させることが可能です. 従って小さなグラフィックを広範囲に動かす用途, もしくは大きな画像を描画した後その描画範囲を変更する用途において有効です.

canvas要素によるグラフィック装飾

CSSスタイル値のpointer-events:noneはカーソルに関わる一切の処理(カーソルイベントの発生, :hover擬似クラスの有効化等)を無効化します. このスタイルを用いると, canvas要素を他の要素に被せその見た目を装飾することができます. 例を示します.

canvas要素によるグラフィックはCSS等の制約を受けないため, アイディア次第で様々な用途に応用できます. 例えばappearanceプロパティでフォーム部品のスタイルを無効化し, canvas要素で見た目を再定義する方法が挙げられます.

補足)appearanceプロパティによる振る舞いの制御

先程とは逆にcanvas要素にappearanceプロパティを設定し, 見た目や振る舞いを変更することも可能です. 例えばappearance: buttonとすることで, canvas要素の挙動をボタン化することが出来ます.

appearanceプロパティとbutton要素によるcanvas要素のボタン化

とは言え, 実用上ボタン化以外に有効な使い途はありません.

canvas要素と背景

canvas要素にも背景画像を定義することが出来ます. つまり, canvas要素は実質静的なスクリーンと動的なスクリーンの2層構造をとります. この特徴を活かすことで例えば次の場合に於いてグラフィック描画部の構造がシンプルに保たれます.

canvasグラフィックを用いた背景

先の構造を逆転させ, canvas要素で描いた内容を何らかの要素の背景とすることを考えます. この場合, グラフィックの内容を一旦データURIスキーム形式もしくはBlobURIスキーム形式としてCSSプロパティに設定します. しかし, ブラウザによっては独自機構を用いる, もしくは次期CSS仕様を先取りすることで背景画像に直接canvasの内容を設定することができます.

CSS背景によるcanvasグラフィック表示のメリット

CSSとcanvas要素とを組み合わせると次のようなメリットが得られます.

element関数+CSS.elementSources(w3c標準)

background-image:element(#id)
CSS4のelement関数を用いて指定したIDの要素のグラフィックを背景画像とする.
CSS.elementSources.set(id, element)
DOMオブジェクトをグラフィック参照先として登録する. CSS4で検討されている.

Gekko系ブラウザ(FireFox等)

background-image:-moz-element(#id)
指定したIDの要素のグラフィックを背景画像とする(CSS4のelement関数に相当する). 従って, canvas要素を参照することで動的な背景とすることが可能となる.
document.mozSetImageElement(id, canvas);
documentにCSSの-moz-element関数中から参照可能なHTMLCanvasElementオブジェクトを追加する. FireFoxの独自機能.

WebKit系ブラウザ(Safari,Epiphany等)

background-image:-webkit-canvas(id)
背景画像をcanvasによるグラフィックとする. idは下記メソッドにおける識別子を指定する.
document.getCSSCanvasContext(type, id, width, height)
内蔵canvasに対応するコンテキストオブジェクトを取得する.

Blink系ブラウザ(Chrome,Opera等)

Blink系ブラウザでは残念ながら背景画像にcanvas要素を設定することは出来ません. その代わりにShadow DOM仕様を用いて擬似的に背景画像を挿入する方法を紹介します. Shadow DOMはWeb Components仕様の一角を為す技術で, 既存のDOM構造に手を入れずに追加のツリー構造を挿入する仕組みです. そのため, 使い方によってはCSSに似た効果が得られます. 以下にサンプルコードを示します.

  1. canvas背景を挿入したい要素(div等の単純なものに限る)に対してattachShadowメソッドを実行する.
  2. 得られたShadowRootオブジェクトにcanvas要素とslot要素(div要素配下の挿入先)を挿入する. canvas要素はslot要素の背後にまわるようにスタイルを設定しておく.
  3. canvas要素にグラフィックを描画する.

複数の要素に同じ背景を設定する場合は, ここから更にcanvasグラフィックの複製を行います. なお, 背景画像の敷き詰めと言った作業を自力で実現する必要があるため, 親要素のサイズが頻繁に変化するような用途では記述が煩雑化します.

CSS Painting APIによる画像生成

現在CSSでの画像生成にJavaScriptを用いる方法が検討されており, そのためのCSS Painting APIを一部のWEBブラウザ(Chrome65で先行実装)で確認できます. なお利用可能なAPIに様々な制限があり(動作にはHTTPS環境を要する, 描画処理を専用のPaintWorkletコンテキストで行うため, DOMなどへのアクセスが出来ない等), 通常のcanvas要素と同じ処理を行えるわけではありません.

EdgeHTML系ブラウザ(Edge等)

現状正攻法, 代替策ともに見つかっていません. 従って背景画像に相当するcanvas要素を直接DOMに挿入するしかなく, CSSほどの手軽さはありません.

カーソルをcanvasグラフィック化する

ユーザの操作補助の目的でカーソル画像をインタラクティブにアニメーション化する場合, 次の4つの方法が考えられます.

  1. toDataURL/toBlobメソッドで取得した画像でカーソルを書き換える
    最も素朴な方法で, 画像の更新頻度が極端に低い(アニメーションしない)場合に有効です. 一方, 高頻度なカーソル画像の書き換えのためだけにtoDataURLメソッドを利用することはシステム負荷を鑑みると避けたほうが無難です.
  2. CSS element関数を用いる
    背景画像と同様にelement関数を使ってカーソル画像にcanvas要素の内容を割り当てるものです. ですが, 現状本機能を使える環境はありません.
  3. カーソル専用のcanvas要素を用意し, ポインター座標を追跡させる
    もともとのカーソルを隠したうえでcanvas要素をによる擬似的なカーソルを作る方法で, この方法ならほぼどのような要件にも対処可能です. 但しその代償として文書の簡潔性(アクセシビリティ)が侵されます.
  4. 全画面化および位置を固定化したcanvas要素にカーソル画像を配置する
    アイディア的には上と同じですが, canvas要素の位置がスクリーン全体に固定化されています. この場合はobject-fit:noneによるグラフィックのリサイズ無効化とobject-positionによるグラフィックの位置指定を組み合わせると良いでしょう.

ここでは3番めの方法に対するサンプルコードを示します.

自作カーソルを設定した要素

shape-outsideプロパティとcanvas要素

CSS ShapesはCSSで扱う図形の概念で, テキストの回り込みと言った用途に用いられます. しかし標準的な図形が矩形, 楕円, 多角形に限られており表現力に欠けます. 一方, 図形の指定に画像データを渡すことが出来るため, canvas要素で描いた図形を元に自由なテキストレイアウトが可能です.

shape-outside
要素の外形を指定する. floatプロパティと組み合わせることで, テキストの回り込みを自由に設定できる. url関数を使って画像データを渡すことが出来るのでtoBlobメソッドを使ったcanvasグラフィックを渡すことで任意のテキスト回り込みを定義できる.
このdiv要素にはcanvas要素で描いたグラフィックによるテキストの回り込み設定が施されています. shapeOutsideプロパティにtoBlobで得たグラフィックを設定することで基本図形だけでは表現出来ない自由なテキストレイアウトを実現しています. (Chromeのみで動作を確認)
canvas要素を使ったテキスト回り込み

サイズ変更とcanvas要素

大抵のCSSスタイルはcanvas要素においても有効ですが, 次のようなグラフィックサイズに関わるものはcanvas要素を囲む要素に指定したほうが良いことがあります.

リサイズ可能なcanvas要素

CSSプロパティresizeを設定すると, 当該要素の右下にサイズ変更用のつまみが追加されます. (フォールバックされていない)canvas要素はresizeプロパティの適用対象外なため, この機能を使ってグラフィックの大きさを変更可能としたい場合は, canvas要素を囲むdiv要素にresize:bothを指定します.

なお, 現在のHTMLDOMのAPIでは要素のサイズ変更に対するresizeイベントは定義されていません. そのため, 変更されたサイズに応じたグラフィックを描くには何らかの代替手段を講じる必要があります. ここではmousemoveイベントを使ってサイズ変更を監視していますが, ResizeObserverオブジェクト/requestAnimationFrameメソッドによる監視も考えられます.

なお, サイズに応じたグラフィックの再描画が必要ないのであれば, canvas要素にobject-fit: noneobject-positionを指定する方法もあります.

ResizeObserverの利用

ResizeObserverオブジェクトを使うことで, canvas要素の(スクリーン上の)サイズを監視し, 変更が有った際に描画処理を呼び出すことが可能です.

この方法は, canvas要素のサイズ変更処理とサイズ変更時に行われる描画処理とを分離する効果があるため, 様々な場面に応用が可能です.

canvas要素とWEB環境

canvas要素によるカーソルの占有

マウスカーソルが発するポインターイベントは通常スクリーン上のカーソル位置を規準に生成されます. そのため, canvas要素が受け取れるイベントはcanvas要素の描画範囲に限られます. 一方でcanvas要素は逐次内容を書き換えることで内部で無限の広がりを表現することが出来るため, 単一方向への継続的な移動と言った操作が発生します. 例えば地図の表示範囲を変更する操作がこれにあたります. 従って先の範囲限定的なイベント取得ではcanvas要素からカーソルが外れることで操作が途切れてしまうし, フルスクリーン環境ではスクリーン端にカーソルが到達したらそれ以上移動できないということになります.

この問題を解決するには幾つか方法がありますが, ここではWEBブラウザが提供しているPointer Lock APIを用いてcanvas要素がマウスカーソルを占有する方法を示します.

Pointer Lock APIとは

Pointer Lock APIは特定のHTML要素に対してポインターイベントを占有させる仕組みで, カーソル位置の代わりに新たにカーソルの移動量を取得可能とします. そのため, ノードの描画位置やスクリーン範囲等の境界値に依存しない継続的なイベント検知が可能です. なおカーソルの位置の管理をポインター占有側に移譲するため, 副次的にブラウザによるカーソルは表示されなくなります. これらの特性から事実上canvas要素のための仕組みと言って良いと思われます.

element.requestPointerLock()
ポインターの占有(ポインターイベントの発生箇所の固定)を開始する. 占有を解除するにはESCキーを押下するもしくはexitPointerLockメソッドを実行する. ポインター占有中はclientX/Y等のカーソル座標の内容が固定される(意味がなくなる).
document.pointerLockElement
ポインターを占有している要素を取得する.
document.exitPointerLock()
ポインターの占有を終了する.
document pointerlockchangeイベント
ポインターの占有状況が変化した際に発生する.
document pointerlockerrorイベント
ポインターの占有操作に失敗した際に発生する.
mouseevent.movementX/movementY
ポインターの移動量(前回のmousemoveイベント発生時からの差分値)を取得する.

例を示します.

canvas要素のフルスクリーン化

ポインターの占有と同様にFullscreen APIを用いると特定の要素をスクリーン全体に広げて描画することが可能です.

element.requestFullscreen()
当該要素をフルスクリーン化する.
document.fullscreenElement
フルスクリーン化している要素を取得する.
document.fullscreenEnabled
フルスクリーン化可能かどうかを判定する.
document.exitFullscreen()
フルスクリーン動作を終了する
document fullscreenchangeイベント
フルスクリーンの状況が変化した際に発生する.
document fullscreenerrorイベント
フルスクリーン操作に失敗した際に発生する.

一般にスクリーンのサイズは環境によって異なるため, canvasサイズの調整が必要となります. 従ってフルスクリーン化前後でグラフィック内容を維持する場合はどのように実現するかが問題となります.

例を示します.

なお, canvas要素そのものではなく, フルスクリーンを解除するコントロールとcanvas要素とをひとまとめにしたものをフルスクリーン化すべきです. なぜなら, キーボードによるフルスクリーンの解除はタブレット等のキーボードレスの環境で問題となりうるからです(ソフトウェアキーボードを表示する手間をユーザーに強いるのは不適切です). もちろんアプリケーション側でフルスクリーン解除の仕組みを提供する場合はこの限りではありません.

canvas要素とポップアップ

window.openメソッドによるポップアップの内容を書き換えることで, canvasグラフィックのプレビュー・印刷用のウィンドウとして利用できます.

canvasアニメーションをポップアップに表示する場合は, video要素を用います.

ポップアップの内容はウィンドウがいつ閉じられても良いように基本的に使い捨てにします.

canvas要素と色空間

WEB環境と色空間

色空間(カラースペース)は現実世界においてヒトが知覚可能とされている色(とその周辺)の一部を切り取り, 立体的に定量化した概念です. 代表的なものとしては, 絶対的な色の基準(光の強さ)として規格化されたLaB色空間を筆頭にRGB, CMYK, DCI-P3等があります.

canvas要素を含むWEB仕様では標準規格として色をsRGB色空間, もしくはそれと相互変換可能なlinearRGB, HSL色空間で扱います.

RGB色空間

RGB色空間では色を光の三原色(赤色・緑色・青色)に分解し, それぞれの光(信号)の強さを数値化したもので表します. WEB環境ではそれぞれの色成分を256階調(8-8-8・24bit)で扱います.

linearRGB色空間とsRGB色空間

参考:Color Interpolation in SVG

RGB色空間には光(電気信号)の強さを直接数値化したlinearRGB色空間と, ヒトの目に映る色の濃さを数値化したsRGB色空間の2つがあり, 一般のWEB仕様ではsRGB色空間を標準的に利用しlinearRGB色空間はSVGフィルタ等の極限られた用途にしか使われません.

ここで, 人の目に映る色の強さ(sRGB色空間での値\(c\))と実際の機械的な光の強度(linearRGB色空間での値\(C\))(\(c,C\in[0,1]\))との間には概ね次の関係式が成り立つことが知られています.

\begin{equation} c = C^{2.2}\\ C = c^{1/2.2} ≒ c^{0.4545} \end{equation}
sRGB色空間での値cとlinearRGB色空間での値Cとの関係
linearRGB色空間でのグラデーション(上)とsRGB色空間でのグラデーション(下)

この指数のことをガンマ値と呼び, ガンマ値を用いて人間の目に自然なグラデーションに映るように信号の強度を調整することをガンマ補正と呼びます.

HSL色空間

参考: wikipedia:HLS色空間

HSL色空間ではsRGB色空間で扱う色を色相(色の構成・赤色を基準とした角度で表す)・彩度(鮮やかさ)・輝度(≒色の明るさ)の3つの値で表します. なお, 色の3次元モデルの構成から様々なパターンが考えられ, WEB仕様では概ね, HSL円柱モデルHSL双円錐モデルの2つが用いられています.

HSL円柱モデル

CSS Color Module仕様で採用している色空間モデルで, hsl関数を用いて色を指定するとこの色空間が用いられます.

HSL色空間(円柱モデル)による色の表現

下記にそのサンプルを示します.

HSL色の出力サンプル

HSL双円錐モデル

Compositing and Blending仕様のmix-blend-modeが採用している色空間モデルで, hue, color, saturation, luminosity合成を行うと, このモデル下でのHSL値を元に画像が合成されます.

HSL色空間(双円錐モデル)による色の表現

円柱モデルと双円錐モデルにおけるRGB値対応

色相・彩度・輝度と同じ概念を用いつつも, モデルが異なるとsRGB値との数値対応が異なるため, 混在させると色化けを招きます. 以下その違いについてまとめます.

HSL色空間のモデルによる色対応の違い
円柱モデル(CSS Color)双円錐モデル(mix-blend-mode)
色相(h)
0〜360
\begin{cases} 0 & (M = m) \\ (g - r) / {\it diff} \times 60 + 60 & (m = b) \\ (b - g) / {\it diff} \times 60 + 180 & (m = r) \\ (r - b) / {\it diff} \times 60 + 300 & (m = g) \end{cases}
彩度(s)
0〜1
\begin{cases} 0 & (l = 1) \\ \cfrac{{\it diff}}{1 - |{\it sum} - 1|} & (l ≠ 1) \end{cases} \begin{equation} M - m \end{equation}
輝度(l)
0〜1
\begin{equation} {\it sum}/2 \end{equation} \begin{equation} r \times 0.3 + g \times 0.59 + b \times 0.11 \end{equation}
備考sRGB色空間での\(r,g,b\in[0,1]\)値が与えられたものとし,
\(m={\it min}(r, g, b), M={\it max}(r, g, b), {\it diff}=M-m, {\it sum}=M+m\)とします.
なお, 円柱モデル下でのHSL値からRGB値を求めるにはピクセル操作を用いるのが最も簡単です.

以下は上記関係式をもとにhsl関数で用いるHSL値を求めるコードです.

hsla:(hex:)

SVGのfeColorMatrixにおける輝度値の扱い

SVGのfeColorMatrix要素では輝度値を\(r \times 0.2125 + g \times 0.7154 + b \times 0.0721\)として求めるため, HSL関数, mix-blend-modeとはまた別の結果となります.

色空間とカラープロファイル

一般に画像データを扱う場合, 色を表す値と共に色空間を考える必要があります. そのため, 画像データを正確に保存するにはどの色空間の下で色を数値化したかを表すカラープロファイル情報を添付するようにします. このカラープロファイル情報を元に, 画像データはスクリーンやプリンタ等のデバイス毎に最適な色に変換されるからです. この仕組みをカラーマネジメントシステム(CMS)と呼びます.

色空間とカラープロファイルによる色の管理

色化けとその原因

表示対象の色を出力デバイス上で完璧に再現することは本質的に出来ないため, CMSを介して2つの色が出来る限り近づくように調整します. が, この過程に於いて何らかの齟齬が起こると色味が大きく変化します. これを色化けと呼びます.

色化けが発生する原因としては主に次の2つが考えられます.

WEBでの色の取扱いに関わる課題

WEB仕様ではCMSのサポートについては明言していません. そのため次のような課題を抱えており, 色の再現度に重きを置く用途には不向きとされています.

画像表示時の問題

通常WEBブラウザはJPEGやPNG形式の画像を標準的にsRGB色空間で記録されたものとして扱います. しかし一般にはsRGB以外の色空間で保存された画像データも存在するため, このような画像をWEBブラウザから読み込むと色化けが発生します. 下記はいずれもJPEG形式の画像ですが, 画像を保存した際に利用したカラープロファイルが異なります. 明らかに色味が異なる(右のほうが鮮やかに見える)場合, その環境ではCMYK色空間を正しく解釈できていません.

sRGB色空間CMYK色空間
RGB色空間でのJPEG画像 CMYK色空間でのJPEG画像
CMYK色空間で保存されたJPEG画像の表示

なおPNG形式に於いてもDCI-P3色空間で作成したものが存在します.

ページ表示時の色化け

スクリーン表示時の(出力デバイスの)カラープロファイル解釈もブラウザによって異なります. そのため, 同じWEBページを表示した際にもブラウザによって見た目が(微細に)変化すると言った現象が起こります.

色の詳細度の問題

RGBAの各色成分を8bit(256階調)で表す現行のCSS/canvas仕様では画像を加工する上で色の詳細度が足りません(フォトレタッチ処理時に色ずれ・色飛びが発生しやすい).

色に関わるパラメータ

将来的な広色域(True Tone)ディスプレイの普及を見据え, 現在CSSにおける色の取り扱いの拡張が検討されています. それに伴いcanvas要素においてもgetContextメソッドに色空間情報を付加するパラメータ拡張が提案され, それに付随した仕様の検討が始まりました.

コンテキスト設定に対する色パラメータの指定

canvas要素に対してはgetContextメソッドにcolorSpace, pixelFormat, linearPixelMathの3つのオプションパラメータが追加されます.

colorSpace:色空間の指定

パラメータcolorSpaceにはcanvas要素が用いる色空間を指定します. 指定可能な値は次の通りです.

pixelFormat:ピクセル値の構成

パラメータpixelFormatにはピクセルにおける色成分毎に割り当てるbit数を指定します. 指定可能な値は次の通りです.

linearPixelMath:ガンマ補正の有無

パラメータlinearPixelMathには色の値にガンマ補正を施すかを指定します. trueを指定すると色の値を直接信号強度(光の強さ)に割り当てます(linearRGB). 規定はfalseでガンマ補正ありとなります.

パラメータの内容はgetContextAttributesメソッドで取得します.

色空間指定による影響を受けるAPI

色パラメータ追加の影響を受けるAPIは以下のとおりです.

ブラウザ環境の保護

グラフィック描画タイミングの管理

単一のページに(例えばこのページのように)大量のcanvas要素によるグラフィックを描く場合, 一度に扱うのではなくスクリーンに表示されうるものから順に描画することでWEBページの応答性が改善します. 下記はこの動作をIntersection Observerを使って実現したものです.

シングルWEBページアプリケーションとメモリリーク

ブラウザ環境の変化はWEBページの利用にも変化をもたらしました. これまで使い捨てだったページ読み込み処理は, JavaScriptやCSS等のロードすべきモジュールの増大に伴い, もはやコストの高い操作となりました. そのためデータの読み込みはなるべくAjaxで行い, 単一のWEBページの生存期間をできるだけ長くする手法(シングルWEBページアプリケーション)が広まりつつあります.

それに伴いスクリプト処理においても注意すべき点が出てきました. その最たるものがJavaScriptにおけるメモリリークの問題です. 致命的な問題であるものの, これまではWEBページの廃棄と共にメモリが開放されるためさほど重要視されませんでした. しかしWEBページの寿命が長くなると, 些細なリークであっても積み重ることでパフォーマンスの劣化や突然のクラッシュなど深刻な問題を引き起こします.

canvas要素においてメモリリークを引き起こす可能性がある部分は次のとおりです. ページロード時から徐々にメモリが消費されていく様子が確認できた場合, このような不具合が隠れていないか確認して下さい.

メモリの再利用によるコスト削減

メモリリークに注意すると共に, そもそものメモリ消費量を抑えることも重要な課題です. canvas要素で描いた内容を複数箇所に表示する場合, ほんの少し工夫することで消費するメモリの量を抑えることができます.

Blobオブジェクトによるグラフィックの共有

複数箇所に同一のグラフィックを描く場合, 単一canvas要素で描いた内容をBlobデータとしてimg要素に設定します.

canvasアニメーションをvideo要素に出力する

canvasアニメーションを複写する場合はaptureStreamメソッドを用いてvideo要素に表示するとグラフィック操作に伴うコストが抑えられます.

リソースの強制開放

出典:Canvas Context Loss and Restoration
参考:Canvas が専有するリソースをパージ可能になる Canvas Context Loss and Restoration について

上記のような対策を行ったとしても, 不測の事態は発生しえます. 例えばWEB機能の向上に伴いコンテンツの内容も年々高機能化しています. が, 一方で全ての環境がその動作要件を満たしているとは限りません. そのため, WEBページによる過剰なマシン負荷を起因とするブラウザもしくはOSのクラッシュは十分にあり得るシナリオです. また, 最小化やバックグラウンド動作していることから利用頻度が明らかに低いことが判っているリソースは適宜メモリから開放することでシステム全体のパフォーマンスが改善します.

このような前提のもと, 現在canvas要素に対してコンテキストオブジェクトの強制開放機構が提案されています. ともすれば描画処理パワーの大半を占有しうるコンテキストオブジェクトをブラウザ側から強制的に破棄可能とすることでシステムを保護する目論見です. これはそもそもWebGL環境における要望事項でしたが, Blink(Chrome)はこのアイディアをCanvasRenderingContext2Dにおいても提供(先行実装)しています.

ctx.getContext("2d", {storage: "discardable"})
コンテキストを破棄可能とする. システムに負荷がかかった際, 必要とされていないことが明らかな場合, 強制的にコンテキストを破棄しcontextlostイベントを発生する.
ctx.getContext("2d", {storage: "persistent"})
逆にリソースの開放を一切しない(つまりこれまでどおりの用途とする)
ctx.isContextLost()
現在のコンテキストオブジェクトが破棄させられているかの判定値. trueの場合, コンテキストオブジェクトに対する一切の操作が不可能となる. canvasサイズが許容範囲外の場合にもtrueが返される.
HTMLCanvasElement.contextlostイベント
コンテキストオブジェクトが破棄された際に実行する処理を設定する. canvasサイズが許容範囲外の場合にも発生する.
HTMLCanvasElement.contextrestoredイベント
コンテキストオブジェクトが復帰した際に実行する処理を設定する.

WebGLとの連携

CanvasRenderingContext2DとWebGLの比較

CanvasRenderingContext2D(ctx2D)とWebGLRenderingContext(WebGL)とは同じcanvas要素にグラフィックを描くとは言え, その得意としている分野は異なっており自ずと使い分けが必要になります.

WebGLの特徴

なおcanvas要素に描かれた内容はctx2D, WebGL相互に再利用することが可能です.

WebGLによる遠近法変換

ここではWebGLとの連携の例として遠近法変換によるグラフィック変形のサンプルを挙げます. わかりやすさを優先しているため, 余計な処理は省いています. なお詳細な解説は割愛します.

Tips

canvas要素と動作検証

グラフィック描画過程の確認

canvas要素は手続き的にグラフィックを描くため, その描画過程を追うことでデバッグします. その際, alert関数やdebuggerステートメント等のスレッドを一時停止するコードを挿入する方法では, タイミングによってはスクリーンの更新処理を阻害することでcanvas要素の実際の内容を確認出来ません. この場合, toDataURLメソッドを用いると確実にその時点でのグラフィックが得られます. なお, デバッグコードの消し忘れリスクを鑑みると, WEBブラウザ標準の開発ツールから当該コマンド部にブレイクポイントを設定するのが無難です.

ブレイクポイントの設定(Chrome)

デバッグツールの利用

ブラウザによってはcanvas要素専用のデバッグツールを提供するものがあります.

コンソールへのcanvasグラフィックの出力

参考:Canvasの画像をコンソールに描画する

Chromeではconsole.logメソッドによって開発者ツールに出力されたログにスタイルを付けることが出来ます. この仕組みを利用することでログにcanvas要素のグラフィックを出力することが可能です.

コンソールログに出力されたcanvasグラフィック

canvas操作の一括トラップ

Proxyオブジェクトを用いるとコンテキストオブジェクトに対する操作をトラップすることが可能です.

この仕組みを用いると, 操作ログを採取したりcanvasの複製を行うといったことを実現できます.

テストの自動化

ある程度規模の大きくなったスクリプトに対し, その正しさを担保するにはjasmine(JsUnit)やSelenium等のテスト自動化ツールの導入が効果的です. しかしcanvas要素においてはグラフィックの出力結果そのものが検証対象となるため, どのようなテストを行うべきかが問題となります. テスト戦略毎の特徴について考えてみましょう.

何れにせよテストの自動化だけでは十分な内容とするのは難しいため, 肉眼による動作検証と合わせてどこまで自動化すべきかを検討して下さい.

テスト用アクセッサの定義

CanvasGradient及びCanvasPatternオブジェクトは一度生成したらその内容を判断する術がありません. これらをテスト対象とするならば独自のAPIを定義して内部情報を取得可能とします.

その他の検証箇所

canvas要素によるグラフィック処理に問題がある場合, スクリプト記述の他に次のような箇所に原因が隠れているかもしれません.

汎用グラフィック処理への応用

Canvas2Svg.jsによるSVG生成

SVGを生成する場合, SVGDOMを利用するのが一般的です. が, ライブラリ「Canvas2Svg.js」を用いると, canvas API互換のAPIを用いてSVGを生成することが可能です.

なお, 本ライブラリは図形定義APIの呼び出しをパス文字列に変換することに主眼を置いており, canvas固有の機能(ピクセル操作, グラデーション, パターン)や複雑な操作(例えば座標軸変換を跨いだパス図形の定義等)をさせることは出来ないようです.

node-canvasによるグラフィック操作

node-canvasはNode.js環境で動作するcanvas要素互換のベクタグラフィック描画ライブラリです. Node.jsはJavaScriptコードの実行環境の一つで, WEBブラウザから独立して動作する特徴を持ちます. そのため, JavaScriptコードをShellスクリプトやコマンドスクリプトと同等のアプリケーション処理に利用できる他, WEBサーバー上でサーバーサイドスクリプトとして実行することも可能です.

下記はnode-canvasを使った画像ファイルの生成例です. WEBブラウザと動作環境が異なるためファイル操作APIに違いがあるもののcanvas操作においてはさほど違いはありません. このようにnode-canvasを利用するとcanvasスクリプトをより汎用の画像処理に応用することが可能です.

node-canvasの導入

以下はUbuntuでのnode-canvasの導入手順です.

canvas要素との互換性

node-canvasは基本的にcanvas要素の仕様に則した実装を心がけています. が, 動作環境の違いや実装時期の絡みから細かい部分で実際のcanvas要素とは異なっていたり, 独自の機能を備えています.

WebDriver APIを用いたcanvas操作

上記はcanvas互換の環境によるグラフィック生成ですが, WebDriver APIを用いる方法もあります. WebDriver APIはW3Cで標準化が進められているWEBブラウザを外部プログラム・スクリプトから制御するための仕組み(乱暴に言えばExcelに対するCOMコンポーネント環境に相当するもの)で, 現在主要なWEBブラウザのほとんどで本機能をサポートするドライバソフトウェアが提供されています.

WebDriver APIを用いたブラウザの外部操作

多くはWEBアプリケーションのテスト自動化の目的で導入しますが, 処理の自動化に着目してcanvasグラフィックを介した画像データの生成に応用することができます. いちいちWEBブラウザが起動する煩わしさがありますが, node-canvasでの互換動作とは異なり最新のAPIが利用できる点で有利です. なおWEBブラウザを起動する都合上, デスクトップツールとしての活用が主な用途となるでしょう.

下記はFirefoxをNode.js環境からWebDriver APIで操作した例です.

WebDriver環境はWebブラウザ内部のコンテキスト(window環境)とは異なるため, 利用できるAPIセットが異なります. そのためcanvas要素を直接操作するのではなく, 処理を記述したHTML文書をWEBブラウザに実行させています. ここでHTML文書は必要に応じて自動生成するようにしても良いでしょう.

自動化環境を整える

WEBブラウザを自動化するには通常次の環境を整えます.

  1. 自動化対象のWEBブラウザ
    主要なWEBブラウザであれば大抵自動化可能ですが, 下記ドライバソフトウェアが提供されているかを確認します.
  2. WEBブラウザに対応するWebDriverソフトウェア
    ドライバソフトウェアはWEBブラウザベンダが製品毎に提供しています. この中からブラウザが動作する環境(Windows/OSX/Linux, 32bit/64bit)別の正しいものを用意します.
  3. 自動化プログラムの実行環境
    自動化プログラムの動作環境としてはNode.js, Java, .NET, Ruby, Perl, Python等が挙げられます. なお, WebDriver APIを操作することができれば言語に制限はありません.
  4. WebDriver APIをバインドするライブラリ
    上記動作環境毎に用意されたSelenium WebDriverバインドライブラリを導入します.
  5. 自作の自動化プログラム
    導入したライブラリを元にWEBブラウザを操作するプログラムを記述します.

ヘッドレスChromeの利用

Chromeブラウザを--headlessスイッチと共に起動するとブラウザがヘッドレスモード(オフスクリーン)で実行されます. この仕組みは主にデバッグ目的で利用されますが, スクリーンショットを撮ることが可能なことからcanvasグラフィックの自動出力に応用できます.

なお, ヘッドレスChrome単体では外部リソース読み込みなどを非同期処理で行うとwindow.loadイベント後に行われるスクリーンの撮影処理が先行することがあります.

Internet Explorerの利用

Windows環境ではInternet ExplorerをWSH等のスクリプト環境から直接操作することができます. そのため, 上記WebDriverを導入することなくcanvasグラフィックの自動出力を実現可能です. 次の例では処理をWSHジョブスクリプト内部で全て完結しています. (Windows10環境で動作を確認しています)

Internet Explorerにおけるcanvas要素の機能は一昔前のものですが, 実用上問題は無いでしょう.

補足)ExplorerCanvasを利用する

ExplorerCanvasの動作原理

レガシーIE上でHTML5のcanvas要素をエミュレートさせるExplorerCanvasは, 各種メソッドに対する操作をVML(Vector Markup Language)に置き換えています. 従って生成されるグラフィックは厳密にはベクタ形式の画像です. また, ライブラリが作られてから時間が経過しているため対応していないAPIも多々あります.

これらは何れも魅力的な機能ですが, 残念ながらExplorerCanvasでは利用することができません. また, メソッドを実行する毎にDOM上にVML要素に相当するオブジェクトが蓄積していくため, 余り凝ったことをした場合, ブラウザへの負荷が懸念されます. 従ってExplorerCanvasを介したcanvas要素の利用はグラフの描画など, あくまでサポート的な利用に留めておくべきです. この点でどうしてもグラフィカルな処理が必要というのであれば, 無理にcanvasを利用するのではなく, 素直にFlashなど既存技術をベースに検討したほうが良いでしょう.

ExplorerCanvasの導入

ExplorerCanvasを利用するには他のJavaScriptライブラリと同様, HTMLのhead要素の中で次の宣言をします.

これだけでcanvas要素にアクセスする準備は整いました. 他の環境と同じくgetElementByIdメソッド等でcanvas要素に相当するオブジェクトを取得し, 各種描画メソッドを実行してグラフィックを描くことができます. が, 細かい部分での注意点が存在します. いずれもcanvas要素そのものの操作に関わるものなので, 動的に要素を生成するケースを想定している場合は注意して下さい.

実際の動作についてはこちらを参照して下さい.

補足)Promiseの利用

Promiseとは

PromiseはJavaScriptにおける非同期処理を取り扱う仕組みの一つで, 従来のイベントモデルやコールバックモデルを用いたものよりも処理を簡潔に記述できることから, ECMAScript2016で標準化されて以降WEB APIにおいて積極的に取り入れられています. Promiseを利用するAPIのうち, canvasグラフィックに関わるものとしては次のものがあります.

代表的なAPIとしては次のものがあります.

new Promise(function)
Promiseを生成する. Promiseで遅延したい(Promise内部の処理が完了してから実行したい)処理を関数として渡す. 関数の引数には処理成功時/失敗時にコールバックする関数が渡される.
promise.then(onfulfilled, onrejected)
処理成功時の処理, 失敗時の処理を指定する.
promise.catch(onrejected)
処理失敗時の処理を指定する.
Promise.all([promise, …])
PromiseオブジェクトをひとくくりとしたPromiseオブジェクトを返す. (Promiseオブジェクトの配列を, 配列値を取得するPromiseに変換する)

Promiseの特徴としていつ完了するかわからない非同期処理を流れに沿って記述できる点が挙げられ, 従来のイベントモデルにおける関数呼び出しよりもコードの可読性が高まります.

Promiseの利用

Promiseによる処理は自分で定義することが可能です. 例を示します.

ここではスクリーンリフレッシュを妨害しうるconfirm関数をsetTimeout関数を使って非同期化し, Promiseで後続の処理を遅延実行しています.

イベントモデルのPromise化

非同期処理が既にイベントモデルとして記述されている場合, 処理をPromiseオブジェクトでラップすることが出来ます. 例えばImageオブジェクトを用いた画像読み込み処理をPromiseでラップすると次のようになります.

このテクニックはWorkerによるバックグラウンド処理をPromise化する際にも利用できます.

async関数とawaitステートメントの利用

Promiseは従来のイベントモデルにおける問題をある程度解決したものの, 複雑な例外処理を扱うには機能が不足していることが判ってきました. そこでECMAScript2017ではスクリプト構文そのものにPromise機構を組み込む試みが為されており, 新たに定義されたasync関数内であれば, awaitステートメントを用いて非同期処理特有の記述を見た目上同期的なコードとして簡潔に記述できます.

複数のPromiseを同時並行に扱う場合はPromise.allメソッドをawaitステートメント付きで呼び出します.

参考文献