2010年04月17日

lislis Paintの技術的説明まとめ

前記事のlislis Painterについての技術的説明のまとめ。
同種のプロダクトを作成する方々の参考になれば幸いだ。


Fill Paint (Flood Paint)機能について

現在のHTML5標準では同色塗りつぶしの機能がない。
一部ブラウザの独自実装にはあるようなので、将来的には標準化される可能性が高い。
lislis Painterではどうしているかというと、ImageDataを取得して、シードフィルアルゴリズムで塗りつぶしを行っている。
これで、Chrome、FireFox、Opera、SafariのいずれでもPaint Fillが使えるようになっている。
初めは画面全体をgetImageDataして、ペイントした後にputImageDataで描き戻せばいいと思っていたけれど、これだと問題があった。
ChromeではgetImageDataして、そのままputImageDataで描き戻すだけで、色が微妙に変わってしまうのだ。
1回では気づかないくらいの微妙な変化だけど、ペイントを繰り返すと誤差が蓄積して全く違う色になっていってしまう。
これを回避するためには、新しく透明なCanvasを作り、そこに描画して、描画レイヤにdrawImageしてやる必要がある。
将来的には不要になりそうなバッドノウハウの類だけれど、ご参考までに。


レイヤについて

単純に同じサイズのCanvasエレメントを重ねて表示しているだけ。
レイヤを統合するときには、まず統合用のCanvasを生成して、下のレイヤから順番にdrawImageして合成する。
これは非常に簡単なやりかたで、動作も軽い。
レイヤの可視/不可視、レイヤごとの透明度指定などは簡単にできる。(未実装だけど)
反面、乗算/スクリーン/オーバーレイなどの合成モードの設定はできない。


Undoについて

描画した領域をgetImageDataしてImageDataをUndoバッファとして保存。
Undoするときは、新しいImageDataから順番にputImageDataでレイヤに描き戻す。
getImageData/putImageDataにはブラウザによって微妙な誤差があり、少しだけ色が変わったりぼやけたりする問題が発生する。
Undoではあまり目立たないので、この誤差には目をつぶっている。


今回のまとめ

以上、lislis Painter 1.02までの開発で、注意が要りそうだった部分を簡単にまとめた。
全般的に、Canvasの描画精度の低さには気をつけないと、どんどん誤差が蓄積して絵が荒れていくので要注意。


追記:
putImageData、drawImageの画質劣化について、Serendip様各環境での詳細な検証をされています。
Mac版/Windows版の各ブラウザでの動作結果が掲載されており、非常に参考になります。

posted by lislis at 14:35| Comment(0) | TrackBack(1) | HTML5

2010年04月13日

HTML5のCanvasでペイントツールを作る

今回はCanvasを利用したペイントツールを作った。
Wacomペンタブレットの筆圧感知にも対応した。
筆圧感知のためにはここから使用環境にあわせたプラグインをインストールする必要がある。

lislis Painter 1.04
lislis Paint Image
Show me
/ Download zip


機能について

Tools (ツール)

Brush (ブラシ)

ペン先が平坦なブラシツール
太さを1くらいにしてペンタブレットで描くと、鉛筆っぽいタッチになる。

Air Brush (エアブラシ)

輪郭がぼやけたブラシツール
※現状では色を塗り重ねると、グラデーションに段々が出たり、ブラシの縁が微妙に黒ずんで、まるでクリームを盛りつけたようになることがある。

Syringe (スポイト)

クリックすると表示されている画像から色を取得する。
alt+clickでも同様の動作。

Eraser (消しゴム)

透明色で塗るブラシ。
Brushと同じくペン先は平坦

Fill Rect (矩形塗りつぶし)

矩形領域を選択色で塗りつぶす。

Clear Rect (矩形消去)

矩形領域を透明色で塗りつぶす。

Paint Fill (領域塗りつぶし:バケツツール)

クリックした場所と同じ色の閉領域を塗りつぶす。
Paint Fillダイアログでいくつかの設定が可能。

  • Paint By:
    塗りつぶし時の参照レイヤを設定する。
    • Integrated Layer: (デフォルト)
      全てのレイヤを重ねた状態の(見たままの)画像を参照してペイントする。
    • Current Layer:
      現在の描画先レイヤを参照してペイントする。
    • Current Layer 1 or 2...:
      指定した番号のレイヤを参照してペイントする。
  • Antialias:
    アンチエイリアスをかける。デフォルトでONになっている。
    Brushツールで描いた線はアンチエイリアスがかかっているので、これをONにしないと塗りに隙間があいてしまう。
    細い線は塗りに削られて消えてしまうので、細い線で描いた領域を塗る場合は、レイヤ1に線を描いてレイヤ2でPaint Fillすると良好な結果を得られる。
  • Threshold:
    同色範囲のしきい値。
    Brushで塗りつぶした領域は色が均等でないため、全体を塗りつぶすにはこの値を10程度まで上げる必要がある。

Layers (レイヤ)

描画先のレイヤを選択する。
眼のアイコンでレイヤーの可視/不可視切り替え。

Edit (編集)

Undo (元に戻す)

直前の描画をキャンセルして元に戻す。
ctrl+zでも同様の動作。
※Operaではデフォルトで、ctrl+zを押すと直前に閉じたタブを開き直す機能が同時に働いてしまう。

Clear All (全消去)

全てのレイヤを消去する。
Undoで元に戻すことはできないので注意。

Post Image (画像投稿)

描いた絵をギャラリーページに投稿する。
投稿後は強制的にギャラリーページに移動する。

Color (色選択)

RGBもしくはHSLで描画色を選択する。
Aは描画色の透明度を指定する。

Brush (ブラシサイズ)

ブラシサイズの指定
四角い領域をクリックするとブラシサイズを変更できる。
1,2,4,8...と書かれたボタンをクリックすることでもサイズを選択できる。
このブラシサイズはBrush, Air Brush, Eraserに適応される。


互換性

Chrome 4.1: ○
FireFox 3.6.3: ○
Opera 10.51: ○
IE 8.0: ×

IEでは全く動作しない。ExplorerCanvasがサポートしていないImageDataやtoDataURLを使うことを最初から予定していたので、現行のIEへの対応はあきらめている。
Chrome、FireFox、Operaではいずれも正常に動作する。
ただ、ブラウザによって動作の重さや画質に違いがある。Operaでは特に消しゴムツールが重い。


今回のまとめ

欲しい機能はまだまだ一杯あるが、まずは自分が絵を描くのに最低限必要な機能だけ実装した。
追加したい機能としては、、レイヤー関係の統合・追加/削除、範囲選択(マスク)、直線描画など。
色選択はカラーホイールでも選択できるようにしたい。

エアブラシの画質が汚いのはCanvasのビット深度が8ビットなので致し方ない。
RGBAをそれぞれ実数で保持する仮想キャンバスに描画して、それをCanvasのImageDataに描画する形にすれば画質は改善されるだろう。
でも今のところそこまでやるつもりはなく、うまく誤魔化す方法を思案中。

あとはサーバ側のプログラム。
描いた絵をサーバにPOSTして保存するのは簡単なので、掲示板やSNSと組み合わせれば、ちょっとしたWebサービスが出来る。


本ソフトウェアはjQueryを使用しています。


追記:
Syringeはレイヤー上の色を取る仕様だったが、非常にわかりにくいので、実際に目に見えている色を取得するように変更した。


追記:
ツールにPaint Fill(Flood Fill)を追加


追記:
UIの一部をテキストからアイコンに変更。
色選択でHSLとRGBをタブで切り替えられるようにした。
レイヤの可視/不可視アイコン追加。


追記:
投稿機能と投稿画像ギャラリーを試験的設置。
クライアント側ではCanvas.toDataURL()でPNG形式のDataURLを取得し、jQuery.post()でサーバーに送信している。
サーバ側では、POSTされたデータがPNGのDataURLであることを確認して、DBに記録している。

posted by lislis at 19:07| Comment(7) | TrackBack(0) | HTML5

2010年04月07日

次回予告

ちょっと私用で立て込んでおりますが、次回はこんな感じのことをやります。

ペンタブで筆圧感知

ペンタブレットの筆圧検知にはこれを使います。


2010/04/13追記:だいぶ形になってきました。互換性検証と細部の調整中。

ペンタブで筆圧感知2
posted by lislis at 23:13| Comment(0) | TrackBack(0) | 日記

2010年04月05日

jQuery ArrowMark: HTML5のCanvasでjQueryプラグインを作る

今回はCanvasを使ったjQueryプラグインを作成してみた。
任意のHTMLエレメントを矢印でつなぐという、単純なプラグインだ。
個人的に前からこういうのが欲しかったのだけれど、探しても見あたらないので自分で作ってみた。
サンプルデモでは複数のdivを矢印でつないでいる。下のほうのスクロールエリアをスクロールさせてみると、矢印がdivの位置に追従するようになっている。
また、divの部分をクリックすると、そこから飛び出す矢印がトグル動作で消えたり出たりする。

jQuery ArrowMark
jQuery ArrowMark Image
Show me
/ Download

使い方

これを使うにはjQuery 1.4.2以降の"jquery.js"、ArrowMarkプラグインの本体"arrowmark.js"の順番に読み込む必要がある。
具体的には、以下のように記述すると、"from"から"to"に矢印が描かれる。

<script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="arrowmark.js"></script> <script type="text/javascript"> $(function(){ $("div#from").arrowMark($("div#to")); }); </script> <div id="from" style="position:absolute; top:0px; left:0px">From</div> <div id="to" style="position:absolute; top:50px; left:100px">To</div>

上記の方法では、矢印で結ばれたエレメントのどちらかの位置が変わると、矢印とずれてしまう。
その場合は{monitor:true}というオプションを追加すると、矢印がエレメントの動きに追従するようになる。
これは負荷のかかる処理なので、位置が動かない場合は指定しない方が軽くなる。

$("div#from").arrowMark($("div#to"), {monitor: true});

オプションは他にも矢印のデザインやエレメントとの間隔を設定するものなど幾つかある。 以下にオプションとデフォルト値を列挙する。

strokeColor : "#ffffff" // Border color fillColor : "#000000" // Fill color lineWidth : 0 // Border width barWidth : 3 // Width of the bar of the ArrowMark arrowWidth : 12 // Width of the head of the arrow arrowLength : 16 // Length of the head of the arrow clipMargin : 4 // Margin from the elements zIndex : 10 // z-index monitor : false // If true, the Arrowmark chaces after connected elements

arrowMarkのかわりにarrowMarkByLinkを使うと、ページ内リンクしているaエレメントの親同士を矢印で結ぶ。
これもarrowMarkと同じオプションが使える。
サンプルではこちらを使用している。

<script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="arrowmark.js"></script> <script type="text/javascript"> $(function(){ $("a.from").arrowMarkByLink(); }); </script> <div id="from" style="position:absolute; top:0px; left:0px"><a class="from" href="#to"></a>From</div> <div id="to" style="position:absolute; top:50px; left:100px"><a name="to"></a>To</div>

その他に、矢印のCanvasエレメントを取得するgetArrowMarkメソッドがある。
下記のようにすると、クリックするたびに矢印が出たり消えたりする。

$("div#from").toggle( function(){ $(this).getArrowMark().hide(); }, function(){ $(this).getArrowMark().show(); } );

矢印を削除する場合はdeleteArrowMarkメソッドを使う。
下記の例ではクリックすると矢印が消える。

$("div#from").click( function(){ $(this).deleteArrowMark(); } );

最後に、このプラグインはExplorerCanvasでも動作する。
ExplorerCanvasを同じディレクトリに入れて、下記の1行をheadの上のほうに入れてやれば、IEにも対応する。

<!--[if IE]><script type="text/javascript" src="excanvas.js"></script><![endif]-->

Chrome 4.1: ○

FireFox 3.6.2: ○

Opera 10.51: ○

Safari 4.0.5: ○

IE 8.0: ○ (ExplorerCanvas使用時)

jQueryのプラグインなので、実用性重視でIEにも対応できるようにした。
ExplorerCanvasでの既知の問題点として、矢印にfadeIn/fadeOutなどアルファ値を変更するjQueryメソッドを使用すると、矢印の縁が黒くなる問題点が確認されている。
どうやらアンチエイリアスで半透明になっている部分が不透明になってしまうようだ。


今回のまとめ

これ、わざわざCanvasを使わなくてもCSS3でやれたかも。

posted by lislis at 18:44| Comment(1) | TrackBack(0) | jQuery

2010年04月04日

HTML5のCanvasでエフェクトアニメーションを作る

今回はHTML5のCanvasエレメントで波紋が動くエフェクトを作る実験だ。

Canvas Wave
Canvas Wave Image
Show me
/ Download

このデモでは、画像の適当な場所をクリックすると、そこを中心に波状のエフェクトアニメーションが動き出す。
CPUパワーを使いすぎないように描画間隔を調整しているので、処理が重すぎてハングアップすることはない。はず。

処理の流れ

まず、Canvasの画像をgetImageDataでImageDataオブジェクトに変換して、そのピクセルを直接いじって特殊効果をかけている。
効果をかけたImageDataをputImageDataでCanvasに書き戻す。
これをタイマーで適当に間隔をあけて繰り返すと、このサンプルのようなエフェクトができる。
フレームごとに400*267のピクセルを全部書き換えており、さらに1ピクセルごとに三角関数や平方根の計算をしたらどのくらいのスピードになるか。という、ベンチマークのために作ってみた。


Chrome 4.1: ○

FireFox 3.6.2: ○

Opera 10.51: ○

Safari 4.0.5: ○

上記の「モダンな」ブラウザでは問題なく動作する。
自前で直接ImageDataのピクセルを操作しているので、どのブラウザでも挙動に違いはない。
ただし、動作速度の違いがフレームレートに現れる。現状ではChromeが最も速く、滑らかなアニメーションになる。

IE 8.0: ×

IEは完全に非対応。現状のExplorerCanvasはgetImageData/putImageDataをサポートしていないので動かしようがない。ExplorerCanvasが使っているVMLには該当する機能がないようなので、今後もサポートされる見込みは薄い。


今回のまとめ

というわけで、CanvasのImageDataを使って直接ピクセルをいじって描画すると、互換性はあまり気にならない。
各ブラウザでJavaScriptの速度が向上しているので、アニメーションを行っても十分に実用的なフレームレートが出せる。
パフォーマンスでは専用ハードウェアを活用したバイナリコードに及ばないけれど、理屈の上では3Dだろうが動画のデコードだろうがなんでもできる可能性がある。
ただし、IEは完全に蚊帳の外だ。残念ながら、IE9でさえCanvasをサポートしないので動かせない。
現状でWebサービスでIEを除外するのは現実的ではないので、「あってもなくても困らないエフェクト」くらいしか使えない気はする。
しかし、ImageDataは表現の幅を飛躍的に高める可能性を秘めているので、捨てるにはもったいなすぎる。もっと面白い使い道を考えていきたい。

posted by lislis at 14:46| Comment(0) | TrackBack(0) | HTML5