- トップ
- iOS10
- Lithium Ion のテーマを作る
- テーマの基本 その14(キャンバス貼り付け編)
テーマの基本 その14
(キャンバス貼り付け編)
今回も前回のリベンジです。
前回の「コピペ編」では、こんなバッテリーを作りました。
しかし、もともと作ろうとしていたのは、枠線とのスキマがあるかないか程度のものです。
それをコピペで実現しようとすると、以下のようになってしまったのです。
キャンバスに描画した図形をビットマップデータとして抜き出しているので、外枠に重なって貼り付けてしまうと、外枠が消えてしまうのです。
ビットマップデータなので仕方ないですね。
これが透過情報を含んだデータであれば、重なっても枠線が消えることがないのでしょうね。
そんなことを考えながら、今回は、コイツをやっつけましょう!!
武器を探す
貼り付けを繰り返すという流れできたので、流れは止めたくありません。
コピペに変わる何かがあるのでしょうか。
透過情報を含むデータをコピーできるなら、それで解決なのですが、いろいろ武器を探さないと・・・
武器がないなら・・・
ざっと探しましたが、見当たりません。
もし、何もないとなると、繰り返しの中で描画することになります。
ここで考えたのですが、前回の最終形態において、「塗りつぶしを貼り付けた上に枠線を描画する」というやり方に変更すればできるのではないか?と思ったのです。
中途半端なコピペと中途半端な描画の組み合わせとなりますが、武器が無いなら無いなりにできることをやってみようと思います。
外枠を直接キャンバスに後から描画するサンプルは、以下の通りです。
(height, percent, charge, low, lbr, m_color, b_color) {
var line_Width = 1;
var r = height / 5;
var s = 0;
var m = 5;
var canvas_fill = document.createElement("canvas");
canvas_fill.height = r * 2;
canvas_fill.width = canvas_fill.height;
var ctx_fill = canvas_fill.getContext("2d");
ctx_fill.fillStyle="rgb(" + m_color.join()+ ")";
ctx_fill.arc( r, r, r-(line_Width+s), 0, 2*Math.PI );
ctx_fill.fill();
var img_fill = ctx_fill.getImageData(line_Width+s, line_Width+s, canvas_fill.width-2*(line_Width+s), canvas_fill.height-2*(line_Width+s));
var canvas = document.createElement("canvas");
canvas.height = height / 2;
canvas.width = 2*r * 5 + m*4;
var ctx = canvas.getContext("2d");
var p_Center = height / 4 ;
for ( i = 0 ; i < 5 ; i++ ){
if ( i+1 <= percent/20 ) {
ctx.putImageData(img_fill, i*(2*r+m)+line_Width+s, p_Center-r+line_Width+s);
}
else if ( i < percent/20 ) {
var rectWidth = 2*(r-(line_Width+s)) * (percent%20) / 20;
ctx.putImageData(img_fill, i*(2*r+m)+(line_Width+s), p_Center-(r-(line_Width+s)), 0, 0, rectWidth, 2*(r-(line_Width+s)));
}
ctx.lineWidth = line_Width;
ctx.strokeStyle="rgb(" + b_color.slice(0, 3).join()+ ")";
ctx.beginPath();
ctx.arc( i*(2*r+m)+r, p_Center, r-line_Width/2 , 0, 2*Math.PI );
ctx.stroke();
}
return canvas.toDataURL("image/png");
}
スキマを0にし、繰り返しの最後で外枠を描画しています。
こんな感じになりました。
あれれ・・・
できちゃってますね・・・
スキマを1にしたらどうなのでしょうか・・・
あれれ・・・
完璧じゃないですか!?
これで終わっても良さそうですね。
このサンプルのダウンロード ー> basic14_1.zip
コピペに代わるもの・・・
さらにもう少し探してみましたが、コピペに代わるものはありません。
コピペではないのですが、キャンバスに描画し、そのキャンバスを丸ごと貼り付ける方法ならありました。
別の貼り付け方で考える
キャンバスならば、図形がない部分は透明ですので、キャンバスを貼り付ける方法なら「願ってもない方法」かもしれません。
ここからは、キャンバスを貼り付ける方法で進めていこうと思います。
キャンバスを貼り付ける
キャンバスを貼り付けるという方向性が決まったので、掘り下げていきましょう。
キャンバスを貼り付ける方法を確認する
キャンバスを貼り付けるには、以下のように記述します。
ctx.drawImage( image, 配置するX座標, 配置するY座標 )
ここでの image は、HTMLかキャンバスかビデオを指定できますので、今回はキャンバスを指定します。
(画像を表示するとばかり思っていたので、気付くまでに時間がかかりました)
さらに、drawImage は、貼り付けるときに伸縮させることもできます。
伸縮させて貼り付けるには、以下のように記述します。
ctx.drawImage( image, 配置するX座標, 配置するY座標, 配置後の幅, 配置後の高さ )
切り抜きではなく伸縮ですので、今回のサンプルでは使いませんが、使える機能ではありますね。
さらにさらに、切り抜くこともできます。
切り抜いて貼り付けるには、以下のように記述します。
ctx.drawImage( image, 元イメージの指定X座標, 元イメージの指定Y座標, 元イメージの指定幅, 元イメージの指定高さ, 配置後のX座標, 配置後のY座標, 配置後の幅, 配置後の高さ )
パラメーターが多いのですが、元イメージのどの領域をどの大きさで配置するかということを一度に指定するので、長くなるのは仕方ないですね。
今回は切り抜きを行ないますので、この記述方法を使います。
貼り付ける
では、枠線を重ねて貼り付けてみましょう。
せっかくなので、アウディのエンブレムを表現してみます。
枠線を4つ重ねて貼り付けるプログラムは、以下の通りです。
(height, percent, charge, low, lbr, m_color, b_color) {
var r = height / 5;
var canvas_stroke = document.createElement("canvas");
canvas_stroke.height = r * 2;
canvas_stroke.width = canvas_stroke.height;
var ctx_stroke = canvas_stroke.getContext("2d");
ctx_stroke.strokeStyle="rgb(" + b_color.slice(0, 3).join()+ ")";
ctx_stroke.arc( r, r, r , 0, 2*Math.PI );
ctx_stroke.stroke();
var canvas = document.createElement("canvas");
canvas.height = height / 2;
canvas.width = canvas.height * 3;
var ctx = canvas.getContext("2d");
var p_Center = height / 4 ;
for ( i = 0 ; i < 4 ; i++ ){
ctx.drawImage( canvas_stroke, i*r*1.5, p_Center-r);
}
return canvas.toDataURL("image/png");
}
基本は前回までを引用しています。
drawImage で配置するのは、コンテキストではなくキャンバスになっていることに注意してください。
配置座標は、アウディのエンブレムを意識したため、横方向に工夫してあります。
結果は、こんな感じです。
線幅を考えていないので、多少欠けていますが、重なってもきちんと表示されることが確認できました。
このサンプルのダウンロード ー> basic14_2.zip
drawImageで書き換える
キャンバスを貼り付けることで、重なってもきちんと表示されることが確認できましたので、ごっそり書き換えていきましょう。
書き換えるのは、前回スキマを大きくすることで何とか完成させたbasic13_5です。
コピーする必要はありませんので、getImageDataの文はなくなります。
貼り付けはdrawImageで貼り付けますので、putImageDataの文を書き換えます。
drawImageで書き換えたプログラムは、以下の通りです。
(height, percent, charge, low, lbr, m_color, b_color) {
var line_Width = 1;
var r = height / 5;
var s = 0;
var m = 5;
var canvas_stroke = document.createElement("canvas");
canvas_stroke.height = r * 2;
canvas_stroke.width = canvas_stroke.height;
var ctx_stroke = canvas_stroke.getContext("2d");
ctx_stroke.lineWidth = line_Width;
ctx_stroke.strokeStyle="rgb(" + b_color.slice(0, 3).join()+ ")";
ctx_stroke.arc( r, r, r-line_Width/2 , 0, 2*Math.PI );
ctx_stroke.stroke();
var canvas_fill = document.createElement("canvas");
canvas_fill.height = r * 2;
canvas_fill.width = canvas_fill.height;
var ctx_fill = canvas_fill.getContext("2d");
ctx_fill.fillStyle="rgb(" + m_color.join()+ ")";
ctx_fill.arc( r, r, r-(line_Width+s), 0, 2*Math.PI );
ctx_fill.fill();
var canvas = document.createElement("canvas");
canvas.height = height / 2;
canvas.width = 2*r * 5 + m*4;
var ctx = canvas.getContext("2d");
var p_Center = height / 4 ;
for ( i = 0 ; i < 5 ; i++ ){
ctx.drawImage(img_stroke, i*(2*r+m), p_Center-r);
if ( i+1 <= percent/20 ) {
ctx.drawImage(img_fill, i*(2*r+m), p_Center-r);
}
else if ( i < percent/20 ) {
var rectWidth = 2*(r-(line_Width+s)) * (percent%20) / 20;
ctx.drawImage(canvas_fill, line_Width+s, line_Width+s, rectWidth, 2*(r-(line_Width+s)), i*(2*r+m)+line_Width+s, p_Center-r+line_Width+s, rectWidth, 2*(r-(line_Width+s)));
}
}
return canvas.toDataURL("image/png");
}
スキマは0にしました。
切り抜きの drawImage が長くなりすぎましたね。
結果は、こんな感じです。
できてますね。
では、スキマを変化させてみましょう。
スキマを1から3まで変化させてみました。
前回は、スキマを3にして、無理やり終わっていたのですが、やはりスキマは狭くて良いと思います。
では、次に線幅を変化させて確認してみましょう。
スキマは、2にしておきます。
線幅1は試してありますが、比較のために1から3まで変化させてみました。
デバイスにも依ると思いますが、線幅はハッキリ見える程度に細い方が良さそうですね。
このサンプルのダウンロード ー> basic14_3.zip
仕上げる
完成はしましたが、スマートではない部分があります。
ここからは、出来上がったものを仕上げていきます。
変数を導入して短くする
プログラムの中には長い部分があります。
特に、切り抜きのdrawImage は、とても長くなってしまっています。
これを短くしたいのですが、よく見ると、line_Width+s が多く使われています。
線幅とスキマの分だけズラすので、当然の結果なのですが、スキマの s が使われるのは、必ず line_Width と一緒に line_Width+s として使われています。
ですので、ここは思いっきりまとめてしまいましょう。
line_Width+s を、新たな s とするのです!!
変数でまとめることで短くするのですが、使う変数が1文字なので、とても短くなりますね。
全体を見渡している時にもう1つ気付いたのですが、高さ中央を表す変数 p_Center が単体で使われていることがなく、p_Center-r として使われています。
描画ではなく貼り付けなので、基準座標を指定するために、中心から半径の分だけズラさなければなりません。
ここも変数で置き換えて短くしましょう。
せっかくなので、これも1文字変数にしてしまいましょう。
どう考えても y ですね!!
変数を使って短くしたプログラムは、以下の通りです。
(height, percent, charge, low, lbr, m_color, b_color) {
var line_Width = 1;
var r = height / 5;
var s = 1;
s += line_Width ;
var m = height / 12;
var canvas_stroke = document.createElement("canvas");
canvas_stroke.height = r * 2;
canvas_stroke.width = canvas_stroke.height;
var ctx_stroke = canvas_stroke.getContext("2d");
ctx_stroke.lineWidth = line_Width;
ctx_stroke.strokeStyle="rgb(" + b_color.slice(0, 3).join()+ ")";
ctx_stroke.arc( r, r, r-line_Width/2 , 0, 2*Math.PI );
ctx_stroke.stroke();
var canvas_fill = document.createElement("canvas");
canvas_fill.height = r * 2;
canvas_fill.width = canvas_fill.height;
var ctx_fill = canvas_fill.getContext("2d");
ctx_fill.fillStyle="rgb(" + m_color.join()+ ")";
ctx_fill.arc( r, r, r-s, 0, 2*Math.PI );
ctx_fill.fill();
var canvas = document.createElement("canvas");
canvas.height = height / 2;
canvas.width = 2*r * 5 + m*4;
var ctx = canvas.getContext("2d");
var p_Center = height / 4 ;
var y = p_Center - r ;
for ( i = 0 ; i < 5 ; i++ ){
ctx.drawImage(img_stroke, i*(2*r+m), y);
if ( i+1 <= percent/20 ) {
ctx.drawImage(img_fill, i*(2*r+m), y);
}
else if ( i < percent/20 ) {
var rectWidth = 2*(r-s) * (percent%20) / 20;
ctx.drawImage(canvas_fill, s, s, rectWidth, 2*(r-s), i*(2*r+m)+s, y+s, rectWidth, 2*(r-s));
}
}
return canvas.toDataURL("image/png");
}
変数を使って、かなり短くできることが分かりました。
今回は、もう1つポイントがあります。
line_Width+s を新たな s とするのですが、s = ine_Width+s とするところが、s += line_Width となっております。
+= がポイントで、増減値を指定することで、変数の値を変化させることができます。
このように記述することで、s に line_Width の値が加算されます。
円と円の間隔 m も高さベースにし、デバイス対応しました。
このサンプルのダウンロード ー> basic14_4.zip
関数を導入して短くする
プログラムの中には似ている部分があります。
特に、円の描画は、キャンバスもコンテキストも似ているのに、わざわざ別にしています。
線を引くか塗りつぶすかの違いはありますが、描く円は半径が違うだけですので、関数を使ってまとめましょう。
キャンバスも分けることをせず、同一キャンバスに円を描き、関数を呼び出した後で、線を引くのか塗りつぶすのかを指定することにします。
円の半径は、関数を呼び出すときの引数とします。
drawImage で切り抜いていましたが、キャンバス上で切り抜けば、表示する場所が他の表示と同じになりますので、これも関数で置き換えましょう。
キャンバス上で切り抜くには、clip() を使います。
キャンバスへの円の描画も、キャンバスの貼り付けも、どちらも表示に関することなので、関数名をなかなか決めることができませんでした。
そこで、どちらも draw とし、引数の半径で処理を分けることにします。
関数を使ったプログラムは、以下の通りです。
(height, percent, charge, low, lbr, m_color, b_color) {
function draw( radius ) {
if ( radius == 0 ) {
ctx.drawImage(canvas_draw, i*(2*r+m), y);
}
else {
ctx_draw.clearRect( 0, 0, r*2, r*2);
ctx_draw.lineWidth = line_Width;
ctx_draw.strokeStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
ctx_draw.fillStyle = "rgb(" + m_color.join()+ ")";
ctx_draw.beginPath();
ctx_draw.arc( r, r, radius , 0, 2*Math.PI );
}
}
var line_Width = 1;
var r = height / 5;
var s = 1;
s += line_Width ;
var m = height / 12;
var canvas_draw = document.createElement("canvas");
canvas_draw.height = r * 2;
canvas_draw.width = canvas_draw.height;
var ctx_draw = canvas_draw.getContext("2d");
var canvas = document.createElement("canvas");
canvas.height = height / 2;
canvas.width = 2*r * 5 + m*4;
var ctx = canvas.getContext("2d");
var p_Center = height / 4 ;
var y = p_Center - r ;
for ( i = 0 ; i < 5 ; i++ ){
draw( r-line_Width/2 );
ctx_draw.stroke();
draw( 0 );
if ( i+1 <= percent/20 ) {
draw( r-s );
ctx_draw.fill();
draw( 0 );
}
else if ( i < percent/20 ) {
var rectWidth = 2*(r-s) * (percent%20) / 20;
draw( r-s );
ctx_draw.save();
ctx_draw.clip();
ctx_draw.fillRect( s, s, rectWidth, 2*(r-s) );
draw( 0 );
ctx_draw.restore();
}
}
return canvas.toDataURL("image/png");
}
関数を作り込むことで、表示する部分がシンプルになり、見やすくなりました。
関数において、円の半径を引数にしていますが、半径が0でない場合は円を描画し、半径が0の場合は、キャンバスを貼り付けます。
関数を呼び出し、円を描画した後は、輪郭を表示するのか塗りつぶすのかを指定します。
そのため、関数内でどちらの色指定もしています。
切り抜きは clip() を使うのですが、その後も同じキャンバスを使いますので、切り抜き前後で描画状態を保存・復元しています。
これがないと、切り抜きの次の円がある場合でもきちんと表示されません。
同じキャンバスで円を描画するので、描画する前に消さなければなりません。
そのために clearRect を使って消去してから描画しています。
このサンプルのダウンロード ー> basic14_5.zip
当サイトの更新状況を、アラートで表示するかどうかの設定をします。
保存する