2013/02/21

[JS]Canvasでよく使う描画テクまとめ

HTMLで画像をいじくりたい時は、canvasを利用して編集するのは一般的ですが、WindowsストアアプリではHTML+CSS+JSでのアプリ開発ができる事もあって、簡単な画像編集であれば、C#やVBを使うより分かりやすいし資料が多く、C++でDirectXをガリガリ書くよりお手軽。入出力もファイルピッカーを使えば簡単に実装できます。今回は、Windowsのコードではなく、Canvasを利用する時のJavaScriptを使いどきに合わせてまとめていきます。



少し長いので目的の内容に飛ぶアンカー用意しました。


画像の取り込み

JavaScriptで画像を編集するには、とにかくCanvasへ画像を取り込む必要があります。画像の読み込みは簡単ですね。
var img = new Image();
img.src = "http://www.w3.org/html/logo/downloads/HTML5_Logo_256.png";

画像のソース取得は、時間がかかるので、取得した画像をCanvasへ表示させる場合は、ソースを取得するのを待つ必要があります。なので、以下のようにします。
var c, ctx, img;
c = document.getElementById("canvas");
ctx = c.getContext("2d");
img = new Image();
img.src = "http://www.w3.org/html/logo/downloads/HTML5_Logo_256.png";
img.onload = function(){
 ctx.drawImage(img, 0,0,img.width, img.height, 0,0,img.width, img.height);
};

上記のソースを実行すると、以下のようになります。





HTML5のロゴが映っていれば成功です。onloadに画像取得後に実行したいソースを書くのがミソです。複数の画像を取りに行く場合は、srcの設定を列挙して同時に取りに行きます。
var c, ctx,img1, img2;
 
 c = document.getElementById("canvas");
 ctx = c.getContext("2d");
 img1 = new Image();
 img2 = new Image();
 img1.src = "http://www.w3.org/html/logo/downloads/HTML5_Logo_128.png";
 img2.src = "http://www.w3.org/html/logo/downloads/HTML5_Logo_64.png";
 img1.onload = function(){
  img2.onload = function(){
  ctx.drawImage(img1, 0,0,img1.width, img1.height, 0,0,img1.width, img1.height);
  ctx.drawImage(img2, 0,0,img2.width, img2.height, 0,128,img2.width, img2.height);
  ctx.drawImage(img2, 0,0,img2.width, img2.height, img2.width,128,img2.width, img2.height);
 }
 };

上記のソースを実行すると、以下のようになります。





最後にロードしたImageのonloadを経由してロードが完了している事を確認してから処理に入るイメージです。


Canvasの内容をコピーする

次に加工の部分です。
先ほどは画像をロードしてcanvasに書き込みました。その際に使うのはdrawImageメソッドで、このメソッドの引数は最大で9つありますが、引数の順番はこちらのサイトで確認してください。 → drawImage() メソッド(HTML5.jp)

canvasが扱える画像のリソースはImageオブジェクトだけではなく、描画済みのcanvasから直接画像を取ってくることもできます。
その時は、ImageDataをCanvasから作成して取り込みます。
var c, ctx,c2, ctx2, img1, img2, imageData;
 
 c = document.getElementById("canvas");
 ctx = c.getContext("2d");
 c2 = document.getElementById("canvas2");
 ctx2 = c2.getContext("2d");
 img1 = new Image();
 img1.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABOVJREFUeNrsWz1MFEEUfnu75w8ROcEY/IVGpOQKLTT+FTYmBGy09JTWwsqYWKiFjYnRwhajrRXE0iCHhYUWHJUcNoe/FCCnklNgd8eZvRF2l9m9ndnZn4u85GVzf7sz33zvve/N7gGI2R3siPod2/vI5u7XLD+Dfdz2ehF7jv62wPiu13WFLQPpMjL52/T4MI4LahLO0UVXR5Zdx95tY0KkJoMBBUrjccZnCvW7tveKtveLHuccjItyaQuB2E1GCFSog4RQGLGtfrHBOd2hV8VeSoIBz7CfpR7WprA/opO5whF646JJU0shK0m+mLCxKpUhULFRtOJKcI2+76ZpyfW9Kg0F92dV13nA5zybtmmbFsyUIF/63N9ToOqsmaxy4MXMU1lJ8LJkuRuHkWTZEICgOqDShOwONOagAMw2IQCBxpy4EGppl9OOGCsAy0tmZEIoshBQtyiSAECRhsB/nwNSbwihSPcDUq+zjVWxMQcCAAuKarMxIuiYpVSBTG8esjcec/8um83CzrY2oWvWHhRAL79dDwEj2hDwa3VTYaYhNlYpSRDVluKf8PwXKefhAcAzptDHD/EDsLAOgKmjwGMNA8BUakugKT5WaVIYzX8DZffejStVnvQuXaoGRvuOxOjPC4AvrdDCHBMAY3QYzGk2CKR0a52qlD5AVLnyhEAzbTpGAkDDEGDa9tbUymBeAPxRnZ9jX+DQ4SRksHwGYGnZNB0hz1ilVQETawFWOlOwTNZgiI2+moGtHdu5r7X6ZsShA0JEADcAJBH2MT/5/Ys9ySN5AOIevcBWgV7AmHnnFEKrSDhZ8ybBVHSFDXRANUoAvAc1PZmMDDbi6wWITaROBm9sgycSYUC9SV+KdfXjlsIN42vl/jVQWoJre13JQHbPFvbKHOiFbZduMiT3V2dC1MPlAJEqAH5tMW9F0hfVcAMzw0n21O4KKx372CXw0/v0hoA2MATqwFVGR/gEdNwVcq1Mx342y2pOvaEL3hARYgCWmKnvCHklu9QQ8OwIW/g3PdSDvZ4q0NkJxqsDfCmGFtgAKAIdodKyM1gOdMrgSqIAeCe0TlBPnGfuGLFiP3t80DMJotpPqWOTenvc9NgdJhPXhm6tlUqyT0ikszn5Gq90K6g9x0A7ctQ6elF/vQpMS6O/KAAkEZ4RVYIkHFTi5y6CpmnQmhN/KNxFf24NIBoCP6TVeoXv2YAAMvhHHAD4D7IcXVfolsFhO0HREChC/V8dbGk7fA8y+ZPWDdNM/lT4SWPhY8y8Bb38DvTSmKsTZI4tuST4TwsYL59bblGMAkF2hoKWQ5LorEmXXjnuAEdhkT8kZWV7ullCqoEFBAZFJexo27W2ymR1dWvSYxvkridQIe4JruUhkUl97u9BMsBRu3qgdU/WUdq4th++m46Ho7AMVlLHAN8VnJ0BY1lNcgjCABCq5RIBDa84uRe4uozcOqAaJwDeYkh2DjHqk9aXkXVEpthmTSpDgFlF0L9VxpP+g6TU+igAuEAZcBrq//LqDrXKOJvrmNZkwownPhs1P+TvNRMg+AyTlOdUcVXopkCcpsAEzg9KBvxozco9ZKKj5CjjfqUSBa0wIASEAQpGX8jTlWwTLsoeqxJ1TGMwcjZ2DAZgR4WuMqH1SNQPaUYOAAOQPsqMAVslsdM61n3HvwIMAEbY5Ec+KNRqAAAAAElFTkSuQmCC";
 img1.onload = function(){
  ctx.drawImage(img1, 0,0,img1.width, img1.height, 0,0,img1.width, img1.height);
  imageData = ctx.getImageData(0,0,img1.width, img1.height);
  ctx2.putImageData(imageData, 0,0);
 };

上記の例では上のCanvasに表示した画像をコピーして下のCanvasに表示しています。上記のソースを実行すると、以下のようになります。






今回は、画像データがbase64で指定されていますが、これはgetImageDataメソッドのセキュリティに関する仕様を回避するためで、元データが異なるドメインの場合は、getImageDataメソッドを実行したところでエラーが発生します。この場合は画像をソースコードと同一のドメイン内、もしくはbase64形式でソースコードに埋め込む事で回避できます。


Canvasの絵を動かす

次に、Canvasに表示した画像を動かしてみます。
var c, ctx,img;
 var x = 0;
 c = document.getElementById("canvas1");
 ctx = c.getContext("2d");
 img = new Image();
 img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABOVJREFUeNrsWz1MFEEUfnu75w8ROcEY/IVGpOQKLTT+FTYmBGy09JTWwsqYWKiFjYnRwhajrRXE0iCHhYUWHJUcNoe/FCCnklNgd8eZvRF2l9m9ndnZn4u85GVzf7sz33zvve/N7gGI2R3siPod2/vI5u7XLD+Dfdz2ehF7jv62wPiu13WFLQPpMjL52/T4MI4LahLO0UVXR5Zdx95tY0KkJoMBBUrjccZnCvW7tveKtveLHuccjItyaQuB2E1GCFSog4RQGLGtfrHBOd2hV8VeSoIBz7CfpR7WprA/opO5whF646JJU0shK0m+mLCxKpUhULFRtOJKcI2+76ZpyfW9Kg0F92dV13nA5zybtmmbFsyUIF/63N9ToOqsmaxy4MXMU1lJ8LJkuRuHkWTZEICgOqDShOwONOagAMw2IQCBxpy4EGppl9OOGCsAy0tmZEIoshBQtyiSAECRhsB/nwNSbwihSPcDUq+zjVWxMQcCAAuKarMxIuiYpVSBTG8esjcec/8um83CzrY2oWvWHhRAL79dDwEj2hDwa3VTYaYhNlYpSRDVluKf8PwXKefhAcAzptDHD/EDsLAOgKmjwGMNA8BUakugKT5WaVIYzX8DZffejStVnvQuXaoGRvuOxOjPC4AvrdDCHBMAY3QYzGk2CKR0a52qlD5AVLnyhEAzbTpGAkDDEGDa9tbUymBeAPxRnZ9jX+DQ4SRksHwGYGnZNB0hz1ilVQETawFWOlOwTNZgiI2+moGtHdu5r7X6ZsShA0JEADcAJBH2MT/5/Ys9ySN5AOIevcBWgV7AmHnnFEKrSDhZ8ybBVHSFDXRANUoAvAc1PZmMDDbi6wWITaROBm9sgycSYUC9SV+KdfXjlsIN42vl/jVQWoJre13JQHbPFvbKHOiFbZduMiT3V2dC1MPlAJEqAH5tMW9F0hfVcAMzw0n21O4KKx372CXw0/v0hoA2MATqwFVGR/gEdNwVcq1Mx342y2pOvaEL3hARYgCWmKnvCHklu9QQ8OwIW/g3PdSDvZ4q0NkJxqsDfCmGFtgAKAIdodKyM1gOdMrgSqIAeCe0TlBPnGfuGLFiP3t80DMJotpPqWOTenvc9NgdJhPXhm6tlUqyT0ikszn5Gq90K6g9x0A7ctQ6elF/vQpMS6O/KAAkEZ4RVYIkHFTi5y6CpmnQmhN/KNxFf24NIBoCP6TVeoXv2YAAMvhHHAD4D7IcXVfolsFhO0HREChC/V8dbGk7fA8y+ZPWDdNM/lT4SWPhY8y8Bb38DvTSmKsTZI4tuST4TwsYL59bblGMAkF2hoKWQ5LorEmXXjnuAEdhkT8kZWV7ullCqoEFBAZFJexo27W2ymR1dWvSYxvkridQIe4JruUhkUl97u9BMsBRu3qgdU/WUdq4th++m46Ho7AMVlLHAN8VnJ0BY1lNcgjCABCq5RIBDa84uRe4uozcOqAaJwDeYkh2DjHqk9aXkXVEpthmTSpDgFlF0L9VxpP+g6TU+igAuEAZcBrq//LqDrXKOJvrmNZkwownPhs1P+TvNRMg+AyTlOdUcVXopkCcpsAEzg9KBvxozco9ZKKj5CjjfqUSBa0wIASEAQpGX8jTlWwTLsoeqxJ1TGMwcjZ2DAZgR4WuMqH1SNQPaUYOAAOQPsqMAVslsdM61n3HvwIMAEbY5Ec+KNRqAAAAAElFTkSuQmCC";
 img.onload = function(){
  move();
 };
 
 function move(){
  setTimeout(function(){
   ctx.clearRect(0,0,c.width, c.height);
   ctx.drawImage(img, 0,0,img.width, img.height, x,0,img.width, img.height);
   x++;
   if (x < c.width){
    move();
   }
  }, 100);
 }

上記のソースでは、HTML5のロゴが100ミリ秒毎に1ピクセル毎に右へ移動します。
毎回、描画前にCanvasをクリアして再描画していますが、これは実用に耐えないやり方です。実際には、HTML5のロゴの後ろには背景があったりします。その場合は、表示しないCanvasに対してプリレンダリング(バッファリング)して、画面を一度作ってから全てをコピーする方法があります。
var c, ctx,c2, ctx2,img;
 var x = 0;
 c = document.getElementById("canvas1");  // 表示用
 ctx = c.getContext("2d");
 c2 = document.getElementById("canvas2"); // バッファ用
 ctx2 = c2.getContext("2d");
 img = new Image();
 img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABOVJREFUeNrsWz1MFEEUfnu75w8ROcEY/IVGpOQKLTT+FTYmBGy09JTWwsqYWKiFjYnRwhajrRXE0iCHhYUWHJUcNoe/FCCnklNgd8eZvRF2l9m9ndnZn4u85GVzf7sz33zvve/N7gGI2R3siPod2/vI5u7XLD+Dfdz2ehF7jv62wPiu13WFLQPpMjL52/T4MI4LahLO0UVXR5Zdx95tY0KkJoMBBUrjccZnCvW7tveKtveLHuccjItyaQuB2E1GCFSog4RQGLGtfrHBOd2hV8VeSoIBz7CfpR7WprA/opO5whF646JJU0shK0m+mLCxKpUhULFRtOJKcI2+76ZpyfW9Kg0F92dV13nA5zybtmmbFsyUIF/63N9ToOqsmaxy4MXMU1lJ8LJkuRuHkWTZEICgOqDShOwONOagAMw2IQCBxpy4EGppl9OOGCsAy0tmZEIoshBQtyiSAECRhsB/nwNSbwihSPcDUq+zjVWxMQcCAAuKarMxIuiYpVSBTG8esjcec/8um83CzrY2oWvWHhRAL79dDwEj2hDwa3VTYaYhNlYpSRDVluKf8PwXKefhAcAzptDHD/EDsLAOgKmjwGMNA8BUakugKT5WaVIYzX8DZffejStVnvQuXaoGRvuOxOjPC4AvrdDCHBMAY3QYzGk2CKR0a52qlD5AVLnyhEAzbTpGAkDDEGDa9tbUymBeAPxRnZ9jX+DQ4SRksHwGYGnZNB0hz1ilVQETawFWOlOwTNZgiI2+moGtHdu5r7X6ZsShA0JEADcAJBH2MT/5/Ys9ySN5AOIevcBWgV7AmHnnFEKrSDhZ8ybBVHSFDXRANUoAvAc1PZmMDDbi6wWITaROBm9sgycSYUC9SV+KdfXjlsIN42vl/jVQWoJre13JQHbPFvbKHOiFbZduMiT3V2dC1MPlAJEqAH5tMW9F0hfVcAMzw0n21O4KKx372CXw0/v0hoA2MATqwFVGR/gEdNwVcq1Mx342y2pOvaEL3hARYgCWmKnvCHklu9QQ8OwIW/g3PdSDvZ4q0NkJxqsDfCmGFtgAKAIdodKyM1gOdMrgSqIAeCe0TlBPnGfuGLFiP3t80DMJotpPqWOTenvc9NgdJhPXhm6tlUqyT0ikszn5Gq90K6g9x0A7ctQ6elF/vQpMS6O/KAAkEZ4RVYIkHFTi5y6CpmnQmhN/KNxFf24NIBoCP6TVeoXv2YAAMvhHHAD4D7IcXVfolsFhO0HREChC/V8dbGk7fA8y+ZPWDdNM/lT4SWPhY8y8Bb38DvTSmKsTZI4tuST4TwsYL59bblGMAkF2hoKWQ5LorEmXXjnuAEdhkT8kZWV7ullCqoEFBAZFJexo27W2ymR1dWvSYxvkridQIe4JruUhkUl97u9BMsBRu3qgdU/WUdq4th++m46Ho7AMVlLHAN8VnJ0BY1lNcgjCABCq5RIBDa84uRe4uozcOqAaJwDeYkh2DjHqk9aXkXVEpthmTSpDgFlF0L9VxpP+g6TU+igAuEAZcBrq//LqDrXKOJvrmNZkwownPhs1P+TvNRMg+AyTlOdUcVXopkCcpsAEzg9KBvxozco9ZKKj5CjjfqUSBa0wIASEAQpGX8jTlWwTLsoeqxJ1TGMwcjZ2DAZgR4WuMqH1SNQPaUYOAAOQPsqMAVslsdM61n3HvwIMAEbY5Ec+KNRqAAAAAElFTkSuQmCC";
 img.onload = function(){
  move();
 };
 
 function move(){
  setTimeout(function(){
   ctx2.clearRect(0,0,c.width, c.height);
   ctx2.drawImage(img, 0,0,img.width, img.height, x,0,img.width, img.height);
   imageData = ctx2.getImageData(0,0,c2.width, c2.height);
   ctx.putImageData(imageData,  0, 0);
   x++;
   if (x < c.width){
    move();
   }
  }, 100);
 }
今回の例では、描画量が少なすぎてバッファリングする恩恵はまったくないですが、負荷が上がってくると効果が現れます。
下にサンプルを置きます。

Canvasの描画の高速化は、なかなか奥が深く、色んな手法があるようです。そんな手法の一部がこちらにまとまっていますので、興味がある方はご一読ください。 → HTML5 canvas のパフォーマンスの改善

線太いんだけど

Canvasに1ピクセル幅の線を引くと、なんか太い事があります。場合によってはアンチエイリアスがかかったようにぼやける事も。 こんな感じ。



ブラウザによって挙動も若干違いますが、FireFoxだと、右から2本目の線が一番細くなります。細くなるってのもおかしな話ですが、裏側の計算方法とピクセル単位の描画の仕方が違うので致し方ないようです。詳しい話はこちらにまとまっているものがありますのでご一読ください。ただ、読み進めると深いところまで行きすぎて帰ってこれなくなるかもしれません:-) → HTML5 Canvasのブラウザによって異なる微妙な振る舞いについてまとめてみた。


ブラウザ上で動くようにサンプルを書きましたが、そのままWindowsのストアアプリ(メトロアプリ)にコピペしても文法上のエラーは出ないのでなかなかストアアプリは実装しやすいと思いました。
ただ、ストアに出す審査がちょっとややこしくて、せっかく作ったけどここで挫折もありえるなと思える内容です。今回のエントリとは何の関係もない愚痴でした。

この辺からブラウザでバリバリ動くアクションゲームなんかも遠い先には繋がっていると思いますので、ボチボチそういうのも手を出してみたいなと思う次第でした。

ちなみにこのサイトはHTML5に対応していません・・・:-P


JavaScriptでアートするプロジェクト、始めました → PixelArts
某女性芸術家の手法をパクっ模倣したり、あれやこれやとJavaScriptのコーディングスキルの限界と芸術センスの限界に挑戦!



0 件のコメント:

コメントを投稿