テーマの基本 その18
(中抜き編)

前回の「グラデーション編」で、iOS5のオリジナルバッテリーを再現してみましたが、暗色背景用のバッテリーはグラデーションではないため、スルーしました。
実は、もう1つ理由があって、これまで紹介した方法では再現するのが難しいのです。
そこで、「中抜き編」と称しまして、今回再現してみたいと思います。

オリジナルの画像を確認する

では、スルーしたバッテリーを確認していきましょう。

動作中のオリジナルバッテリー

では、動作中のオリジナルバッテリーを確認していきましょう。
ズラッと並べてみます。

@2x画像ですので、拡大したPNG画像を並べました。
画像に穴が空いていて、背景が見えます。
今回は、これを再現しようとしています。

オリジナル画像群

次に、iOS5のユーザーインターフェイスとして用意された画像を確認しましょう。
こちらも、ズラッと並べてみます。

今この画像は、影が上にあるんですね。
よく見ると、内側にも薄っすら見えます。
これに関しては、プロパティで影を付けた方が無難ですね。

その他確認すべきこと

前回のグラデーション編でやったネタと被りますので、多少のズレはあるものの、寸法も座標も全く同じです。
ですので、おさらいすることもなく、進めていきます。

中抜きの方法を知る

ここで、どのように中抜きをするかを紹介します。
以前、切り抜き編を紹介しましたが、そんな感じのやり方です。

中抜きは、図形に穴を開けるイメージです。
四角で図形を抜くのであれば、clearRect を使えば簡単なのですが、四角しかできないため、キャンバス全体を消すという使い方がほとんどです。
もし、円で抜こうと思ったら、円の形になるように小さな円で何度も抜かなければなりません。

そこで、切り抜き編のように、2つの図形の重なりで処理しましょう。

イメージを固める

切り抜き編では、2つの図形が重なった部分が切り抜かれることを紹介しました。
中抜きも同様に、2つの図形を使います。

中抜きは、先に描画された図形に対し、後から描画した図形が重なった部分を透明にします。
そうすることで、図形の中身が抜けることになります。
先ほどの、円で抜くということも、円を描画すれば簡単にできるということです。

中抜きを指定する

切り抜きでも2つの図形を使いましたが、2つの図形の重なりをどのように処理するかに依ります。
切り抜き編では、clip() を使うことで、重なった部分が表示されるようにしました。
が、今回は clip() から離れます。

「先に描画した図形」と「後から描画した図形」との重なりの処理で、いろいろな結果を得られます。
中抜きもその中の1つで、「重なっていない部分が表示領域となり先に描画された図形が表示される」という処理を指定すると中抜きになります。

中抜きの処理は、以下のように指定します。
ctx.globalCompositeOperation = "destination-out";
2つの図形描画の間で指定すると、重なった部分がなくなります。

(注意)
後から描画する図形に透明度が指定されている場合は、重なった部分に影響が残ります。
完全に穴を開ける場合は、100%不透明の図形を描画しましょう。

やってみる

では、簡単な図形で試してみましょう。
図形は四角形とします。
四角を四角で抜こうと思ったのですが、円を描画すれば円でも抜けると紹介したので、円で抜こうと思います。

四角形を円で抜くプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {

 var canvas = document.createElement("canvas");
 canvas.height = height;
 canvas.width = height;
 ctx = canvas.getContext("2d");

 ctx.fillStyle = "rgba( 0, 0, 0, 0.5 )";
 ctx.fillRect( 0, 0, canvas.width, canvas.height );
 ctx.globalCompositeOperation = "destination-out";
 ctx.arc( height/4, height/3, height/8, 0, Math.PI*2 );
 ctx.arc( height - height/4, height/3, height/8, 0, Math.PI*2 );
 ctx.fill();
 ctx.fillRect( height*0.45, height*0.3, height*0.1, height*0.3 );
 ctx.fillStyle = "orange";
 ctx.fillRect( height*0.2, height*0.7, height*0.6, height*0.1 );

 return canvas.toDataURL("image/png");
}
高さフルサイズの正方形をキャンバスにしました。
透過率50%の黒で塗りつぶされた正方形を描画した直後に、コンテキストの globalCompositeOperation プロパティで中抜きの指定をしていますので、その後の描画の全てで抜きます。
円だけでなく、四角でも抜きました。


結果は、こんな感じです。

最後に口を描画するところで、塗りつぶしの指定を変更しました。
色がどうのこうのではなく、透過率が重要であることに注意して下さい。
口の四角だけが、完全に背景が見えていることが分かります。
どんな形であっても、描画さえできれば、その形で抜くことができることを知っていただければ十分です。

思ったよりイケメンに仕上がったので、これで終わります。


このサンプルのダウンロード ー> basic18_1.zip

バッテリーの背景を描画する

では、1つ1つやっていきましょう。
グラデーション編で、描画も関数化もすでにやっているので、座標を拾うようなことはせず、基本的にコピペで進めます。
中抜きで、バッテリー背景を作ってみましょう。

バッテリー背景の本体を描画する

では、電極を省いたバッテリー背景から描画していきましょう。

電極無しの塗りつぶし背景を描画するプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 function draw_rRect( left, top, right, bottom, r ) {
  ctx.beginPath();
  ctx.moveTo( left + r, top );
  ctx.lineTo( right - r, top );
  ctx.arc( right - r, top + r, r, -pi/2, 0, false);
  ctx.lineTo( right, bottom - r );
  ctx.arc( right - r, bottom - r, r, 0, pi/2, false);
  ctx.lineTo( left + r, bottom);
  ctx.arc( left + r, bottom - r, r, pi/2, pi, false);
  ctx.lineTo( left, top + r);
  ctx.arc( left + r, top + r, r, pi, pi*3/2, false);
  ctx.closePath();
  ctx.fill();
 }

 var canvas = document.createElement("canvas");
 canvas.height = height/2;
 canvas.width = canvas.height * 2.1;
 var ctx = canvas.getContext("2d");

 var r = height / 20;
 var p_Left = 0;
 var p_Right = canvas.height * 1.9;
 var p_Top = 0;
 var p_Bottom = canvas.height;
 var pi = Math.PI;

 ctx.fillStyle = "orange";
 draw_rRect( p_Left, p_Top, p_Right, p_Bottom, r );
 ctx.globalCompositeOperation = "destination-out";
 draw_rRect( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );


 return canvas.toDataURL("image/png");
}
basic17_11をベースにしています。
塗り指定は、オレンジにしただけです。


結果は、こんな感じです。

まるで、線を引いただけのような結果となりました。
念のため、1角を拡大してみましょう。

外側は丸くなっていますが、内側は完全に直角になっております。
角丸四角形を、四角形で中抜きしたことがお分かりいただけると思います。


このサンプルのダウンロード ー> basic18_2.zip

バッテリー背景の全体を描画する

先ほどのバッテリー背景に電極を追加していきましょう。

バッテリーの背景全体を描画するプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 function draw_rRect( left, top, right, bottom, r ) {
  ctx.beginPath();
  ctx.moveTo( left + r, top );
  ctx.lineTo( right - r, top );
  ctx.arc( right - r, top + r, r, -pi/2, 0, false);
  ctx.lineTo( right, bottom - r );
  ctx.arc( right - r, bottom - r, r, 0, pi/2, false);
  ctx.lineTo( left + r, bottom);
  ctx.arc( left + r, bottom - r, r, pi/2, pi, false);
  ctx.lineTo( left, top + r);
  ctx.arc( left + r, top + r, r, pi, pi*3/2, false);
  ctx.closePath();
  ctx.fill();
 }

 var canvas = document.createElement("canvas");
 canvas.height = height/2;
 canvas.width = canvas.height * 2.1;
 var ctx = canvas.getContext("2d");

 var r = height / 20;
 var m = height / 20;
 var p_Left = 0;
 var p_Right = canvas.height * 1.9;
 var p_Top = 0;
 var p_Bottom = canvas.height;
 var pi = Math.PI;

 ctx.fillStyle = "orange";
 draw_rRect( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );
 ctx.globalCompositeOperation = "destination-out";
 draw_rRect( p_Right, p_Top + m*3+r, p_Right + m*2-r, p_Bottom - m*3-r, 0 );

 draw_rRect( p_Left, p_Top, p_Right, p_Bottom, r );
 ctx.globalCompositeOperation = "destination-out";
 draw_rRect( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );

 return canvas.toDataURL("image/png");
}
前回と同様に、重ねて描画するために、電極から描画しました。


結果は、こんな感じです。

あらら・・・
最初に描画した電極しか見えません・・・

あっ・・・
中抜き指定のままでした・・・

中抜きの工程が終わり、普通に描画しようとするならば、初期状態に戻しておくべきでした。
初期状態にする指定は、"source-over" で、どちらも表示領域になり、後から描画した方が上になります。

"source-over" を追加指定すると、以下のようになります。

きちんと、表示されましたね。


このサンプルのダウンロード ー> basic18_3.zip

影を描画する

バッテリーらしい姿ができましたが、完成ではありません。
やはり、影が必要ですね。
とりあえずオレンジで進めてきましたが、ここで可変色を適用することにします。
そこで問題が浮かび上がってくるのが、影の色です。
可変色を適用するということは、影も可変させなければならなくなるということなのです。
これは、オリジナルを見れば一目瞭然なのですが、白線の影が黒で表現されているからです。
しかも、可変色は設定.appで指定するため、デフォルトの白か黒のままである保証もありません。

そこで、線の色と影の色が逆になればいいので、算出することを考えました。
可変色は、配列で取得することができ、その内容は0〜255の整数です。
ですので、線はそのままの色で描画し、影は255から引いた数字を使って描画したいと思います。
もちろん、影の色のために、変数を用意すべきでしょう。

バッテリーの影まで描画するプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 function draw_rRect( left, top, right, bottom, r ) {
  ctx.beginPath();
  ctx.moveTo( left + r, top );
  ctx.lineTo( right - r, top );
  ctx.arc( right - r, top + r, r, -pi/2, 0, false);
  ctx.lineTo( right, bottom - r );
  ctx.arc( right - r, bottom - r, r, 0, pi/2, false);
  ctx.lineTo( left + r, bottom);
  ctx.arc( left + r, bottom - r, r, pi/2, pi, false);
  ctx.lineTo( left, top + r);
  ctx.arc( left + r, top + r, r, pi, pi*3/2, false);
  ctx.closePath();
  ctx.fill();
 }

 var canvas = document.createElement("canvas");
 var r = height / 20;
 canvas.height = height/2 + r*2;
 canvas.width = height/2 * 2.1;
 var ctx = canvas.getContext("2d");

 var m = height / 20;
 var p_Left = 0;
 var p_Right = height/2 * 1.9;
 var p_Top = 0 + r;
 var p_Bottom = height/2 + r;
 var pi = Math.PI;
 var s_color = [ 255-b_color[0], 255-b_color[1], 255-b_color[2] ];

 ctx.fillStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
 ctx.shadowColor = "rgb(" + s_color.slice(0, 3).join()+ ")";
 ctx.shadowBlur = r/2;
 ctx.shadowOffsetY = -r/2;
 draw_rRect( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );
 ctx.globalCompositeOperation = "destination-out";
 draw_rRect( p_Right, p_Top + m*3+r, p_Right + m*2-r, p_Bottom - m*3-r, 0 );

 ctx.globalCompositeOperation = "source-over";

 draw_rRect( p_Left, p_Top, p_Right, p_Bottom, r );
 ctx.globalCompositeOperation = "destination-out";
 draw_rRect( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );

 return canvas.toDataURL("image/png");
}
影を導入することでキャンバスの高さが変わるため、基準点などにいろいろな変更があります。

影の色を、s_color としました。
ベースカラーの配列要素それぞれを、255 から引いて s_color に格納しています。
影のボカシ強さとズラす量を、同じにしました。


結果は、こんな感じです。

とりあえず、ベースカラーに対して、逆の色になっていますので、これはこれで大成功ですね。
完全白背景では、影が分からないくらいです。

影を上にズラしましたが、上部でしか影を確認できません。
しかも、その影がイタズラしているのか、ズラした分だけ線幅も減っています。
中抜きした電極と本体を重ねたと思っていたのですが、よくよく考えてみると、描画→中抜き→描画→中抜きという流れですので、思ったようにはならないのですね。

何がどんなイタズラをしているのか、調べてみましょう。
影が掛かる要素は4つありますので、4色で色分けしましょう。
本体の描画の影を赤、電極の描画の影を緑、本体の中抜きの影を青、電極の中抜きの影を黄色とします。
結果は、こんな感じです。

中抜きに関しては影がありませんが、影でも中抜きをしているため、線が細く見えてしまっています。
オレンジで描画したモノと、合成して比べてみると・・・

本来あるべき線がオレンジのラインです。
下は影の影響を受けていないため、同一レベルで合成されていますが、上部は影の影響で、ズレています。
赤や緑は、本来こうあるべき姿で描画されているので、問題ありません。
ということは・・・
中抜きでは、影を使わないようにすれば良いだけでは???ということで、やってみました。

できてます。
線が本来の幅を持っています。
しかし、やりたいこととの違いが目立ちます。
気になる程度ではなく、かなり大きな問題かと思いますので、ピックアップしましょう。
問題点1:電極の上に、本体の影が重なっている。
問題点2:線の内側に影がない。
これらを1つ1つ解決すべきか?
それとも、ある方法で一気に解決すべきか?

しばらく、考えます・・・


このサンプルのダウンロード ー> basic18_4.zip

中抜き方法を考え直す

しばらくと言うより、かなり考えたのですが、一気に解決させようと思います。
そのためには、これまで紹介した方法とは違う方法でないと実現できないと言う結論に達しました。
そこで、あるを紹介します。

これまでとは違う中抜き

これまでは、2つの図形の合成方法を指定することで、中抜きをしてきましたが、全く別のアプローチで中抜きを実現してみましょう。
次の画像を、ご覧ください。

この図形に色を塗ることを考えます。(すでに色が付いてますけど・・・)
閉じられた図形であり、工業系の方であれば、実際に見たことのある方もいるかと思います。

この図形の要素は、2つの円弧と2本の直線です。
この2本の直線をそれぞれ近付けます。
スキマがなくなるまで近付けたら、どうでしょう?

イメージは、以下の通りです。


ほらっ!!できた!!
中抜きされた円の出来上がり!!

これがイメージなのですが、イメージで終わらせませんよ。

描画方法

イメージができたところで、このまま実際の描画方法も理解しましょう。
理解を深めるために、全く同じ画像で進めます。

次の画像を、ご覧ください。

この図形を一筆書きで描画することを考えます。
外側の円弧を時計回りに描き始めると、図の矢印の向きで描画されるはずです。
ここで注目していただきたいのは、外側円弧の描画方向と内側円弧の描画方向です。
外側円弧は時計回りに描画されていますが、内側円弧は反時計回りに描画されています。

この一筆書きを保ったまま、先ほどのように、2本の直線を近付けましょう。

イメージは、以下の通りです。


完全な円が2つのみとなりましたが、描画方向が違うことで、中抜きが実現できていることがお分かり頂けると思います。

これが、「非ゼロ回転数ルール」というもので、聞いたことのある人はなかなかいないと思いますが、向きの違うパスは相殺されるという塗りつぶしのルールです。
フォントを作成したことのある方なら、向きに敏感かもしれませんね。

実際に描画してみる

外側と内側で描画方向が違うと、中抜きが実現できることが分かりましたので、実際にやってみましょう。
中抜きを確認できれば良いので、例の画像における最終形である2つの円でやってみます。

2つの円で中抜きを確認するプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {

 var canvas = document.createElement("canvas");
 canvas.height = height;
 canvas.width = height;
 var ctx = canvas.getContext("2d");

 var r = height / 2;
 ctx.fillStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
 ctx.arc( r, r, r*0.8, 0, 2*Math.PI, true );
 ctx.arc( r, r, r*0.4, 0, 2*Math.PI, false );
 ctx.fill();

 return canvas.toDataURL("image/png");
}
円の半径を変数にしました。
2つの円で、半径と回転方向が違うことを確認してください。


実際に描画してみると・・・

出た〜!!ドーナッッッツ!!!
きちんと中抜きされていますね。
不透明度100%のベースカラーで塗られていますので、背景によって変化しています。

影を付けて描画してみる

描画方法によって実現できた中抜きですが、そもそも、違う方法でやらなければならなくなった理由が影でした。
違う方法で中抜きが実現できましたので、影リベンジを果たしましょう。

影リベンジのプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {

 var canvas = document.createElement("canvas");
 canvas.height = height;
 canvas.width = height;
 var ctx = canvas.getContext("2d");

 var r = height / 2;
 ctx.fillStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
 ctx.shadowColor = "orange";
 ctx.shadowBlur = r/10;
 ctx.shadowOffsetY = -r/20;
 ctx.arc( r, r, r*0.8, 0, 2*Math.PI, true );
 ctx.arc( r, r, r*0.4, 0, 2*Math.PI, false );
 ctx.fill();

 return canvas.toDataURL("image/png");
}
影の色専用変数を使っています。

実際に描画してみると・・・

おおぉ〜
内側まで影がありますね〜
これをやりたかったんですぅ!!


このサンプルのダウンロード ー> basic18_5.zip

再びバッテリーの背景を描画する

違う方法で中抜きをし、その結果、内側まで影を付けることができましたので、もう一度バッテリーを描画してみましょう。
もちろん、違う方法でね!

回転方向を考慮した関数を作る

すでに関数化してありますので、回転を考慮した関数へと進化させましょう。
と言っても、すでにある関数と逆向きに描画する関数の2つにするだけなんですけどね・・・

回転方向を考慮した関数は、以下の通りです。
 function draw_rRect_cw( left, top, right, bottom, r ) {
  ctx.moveTo( left + r, top );
  ctx.lineTo( right - r, top );
  ctx.arc( right - r, top + r, r, -pi/2, 0, false);
  ctx.lineTo( right, bottom - r );
  ctx.arc( right - r, bottom - r, r, 0, pi/2, false);
  ctx.lineTo( left + r, bottom);
  ctx.arc( left + r, bottom - r, r, pi/2, pi, false);
  ctx.lineTo( left, top + r);
  ctx.arc( left + r, top + r, r, pi, pi*3/2, false);
 }

 function draw_rRect_ccw( left, top, right, bottom, r ) {
  ctx.moveTo( right - r, top );
  ctx.lineTo( left + r, top );
  ctx.arc( left + r, top + r, r, pi*3/2, pi, true);
  ctx.lineTo( left, bottom - r );
  ctx.arc( left + r, bottom - r, r, pi, pi/2, true);
  ctx.lineTo( right - r, bottom);
  ctx.arc( right - r, bottom - r, r, pi/2, 0, true);
  ctx.lineTo( right, top + r );
  ctx.arc( right - r, top + r, r, 0, -pi/2, true);
 }


ここでは、関数のみを紹介します。
回転方向を考慮して、関数名に「cw」や「ccw」を付加しました。
どちらも、beginPath() や、closePath() がありません。
描画に特化した関数ですので、必要であれば関数を呼び出す前や後に逐次追加します。
回転方向が逆になるサブパスで構成しますので、関数を呼び出すたびにbeginPath() で新たに描き始めてしまっては、「何がしたいの?」ってことになってしまいます。
輪郭を構成するパスと、中抜きのための逆向きのパスは、1つのパスとして取り扱われなければなりません。
そのため、関数内では beginPath() などを省きました。

関数を呼び出して描画する

では、basic18_4を基本にして、書き換えてみましょう。

回転方向を考慮した関数を使って書き換えたプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 function draw_rRect_cw( left, top, right, bottom, r ) {
  ctx.moveTo( left + r, top );
  ctx.lineTo( right - r, top );
  ctx.arc( right - r, top + r, r, -pi/2, 0, false);
  ctx.lineTo( right, bottom - r );
  ctx.arc( right - r, bottom - r, r, 0, pi/2, false);
  ctx.lineTo( left + r, bottom);
  ctx.arc( left + r, bottom - r, r, pi/2, pi, false);
  ctx.lineTo( left, top + r);
  ctx.arc( left + r, top + r, r, pi, pi*3/2, false);
 }
 function draw_rRect_ccw( left, top, right, bottom, r ) {
  ctx.moveTo( right - r, top );
  ctx.lineTo( left + r, top );
  ctx.arc( left + r, top + r, r, pi*3/2, pi, true);
  ctx.lineTo( left, bottom - r );
  ctx.arc( left + r, bottom - r, r, pi, pi/2, true);
  ctx.lineTo( right - r, bottom);
  ctx.arc( right - r, bottom - r, r, pi/2, 0, true);
  ctx.lineTo( right, top + r );
  ctx.arc( right - r, top + r, r, 0, -pi/2, true);
 }
 
 var canvas = document.createElement("canvas");
 var r = height / 20;
 canvas.height = height/2 + r*2;
 canvas.width = height/2 * 2.1;
 var ctx = canvas.getContext("2d");
 
 var m = height / 20;
 var p_Left = 0;
 var p_Right = height/2 * 1.9;
 var p_Top = 0 +r;
 var p_Bottom = height/2 +r;
 var pi = Math.PI;
 var s_color = [ 255-b_color[0], 255-b_color[1], 255-b_color[2] ];
 
 ctx.fillStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
 ctx.shadowColor = "rgb(" + s_color.join()+ ")";
 ctx.shadowBlur = r/2;
 ctx.shadowOffsetY = -r/2;

 ctx.beginPath();
 draw_rRect_cw( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );
 draw_rRect_cw( p_Left, p_Top, p_Right, p_Bottom, r );

 draw_rRect_ccw( p_Right, p_Top + m*3+r, p_Right + m*2-r, p_Bottom - m*3-r, 0 );
 draw_rRect_ccw( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );

 ctx.fill();
 
 
 return canvas.toDataURL("image/png");
}
回転方向が逆になれば良いので、どちら向きを先にしても構いません。
ここでは、輪郭を時計回りに描画し、中抜きを反時計回りにしました。
全てのパスを描画してから塗りつぶします。


結果は、こんな感じです。

良い感じに影が付いていますね。
対策前と比べてみましょう。

個人的に完成度は高いと思っているので、オリジナルと比べてみましょう。

もうお腹いっぱいって感じですね。
出木杉君です。


このサンプルのダウンロード ー> basic18_6.zip

バッテリー残量を反映させる

やっとバッテリー背景が描画できたので、バッテリー残量を反映させてみましょう。

基本的には、これまでやってきたことですぐできます。
ただ、今回は影が特徴です。
オリジナルの仕様もありますので、線幅分だけスキマを設けなければなりません。
逆に、これだけで済みます。

バッテリー残量を反映させたプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 function draw_rRect_cw( left, top, right, bottom, r ) {
  ctx.moveTo( left + r, top );
  ctx.lineTo( right - r, top );
  ctx.arc( right - r, top + r, r, -pi/2, 0, false);
  ctx.lineTo( right, bottom - r );
  ctx.arc( right - r, bottom - r, r, 0, pi/2, false);
  ctx.lineTo( left + r, bottom);
  ctx.arc( left + r, bottom - r, r, pi/2, pi, false);
  ctx.lineTo( left, top + r);
  ctx.arc( left + r, top + r, r, pi, pi*3/2, false);
 }
 function draw_rRect_ccw( left, top, right, bottom, r ) {
  ctx.moveTo( right - r, top );
  ctx.lineTo( left + r, top );
  ctx.arc( left + r, top + r, r, pi*3/2, pi, true);
  ctx.lineTo( left, bottom - r );
  ctx.arc( left + r, bottom - r, r, pi, pi/2, true);
  ctx.lineTo( right - r, bottom);
  ctx.arc( right - r, bottom - r, r, pi/2, 0, true);
  ctx.lineTo( right, top + r );
  ctx.arc( right - r, top + r, r, 0, -pi/2, true);
 }
 
 var canvas = document.createElement("canvas");
 var r = height / 20;
 canvas.height = height/2 + r*2;
 canvas.width = height/2 * 2.1;
 var ctx = canvas.getContext("2d");
 
 var m = height / 20;
 var p_Left = 0;
 var p_Right = height/2 * 1.9;
 var p_Top = 0 +r;
 var p_Bottom = height/2 +r;
 var pi = Math.PI;
 var s_color = [ 255-b_color[0], 255-b_color[1], 255-b_color[2] ];
 
 ctx.fillStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
 ctx.shadowColor = "rgb(" + s_color.join()+ ")";
 ctx.shadowBlur = r/2;
 ctx.shadowOffsetY = -r/2;
 ctx.beginPath();
 draw_rRect_cw( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );
 draw_rRect_cw( p_Left, p_Top, p_Right, p_Bottom, r );

 draw_rRect_ccw( p_Right, p_Top + m*3+r, p_Right + m*2-r, p_Bottom - m*3-r, 0 );
 draw_rRect_ccw( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );

 var rectWidth = ( p_Right - p_Left - r*4 ) * percent / 100;
 draw_rRect_cw( p_Left+r*2, p_Top+r*2, p_Left+r*2+rectWidth, p_Bottom-r*2, 0 );


 ctx.fill();
 
 
 return canvas.toDataURL("image/png");
}
例によって、rectWidth で描画するバッテリー残量の幅を算出しています。
rect の意味合いが変わってしまっていますが、このまま進めます。
幅の算出では、左右の線幅の分と、左右のスキマの分として、r×4を引いています。
描画は、輪郭と同様に時計回りの向きで描画しています。


結果は、こんな感じです。

OKですね。
ひと段落といった感じです。
あとは充電中とかやんないとね・・・


このサンプルのダウンロード ー> basic18_7.zip

充電中の稲妻マークを描画する

他と同様に、すでに寸法も座標も出してあるので、あとは描画するだけかと思ったのですが、オリジナルを確認すると・・・

気になるのは、稲妻マークの上下端です。
残量によっては、左右端も気になることでしょう。

回転方向による中抜きでは、輪郭が交差する場合、それぞれ色が交差するように塗りつぶされます。
そのため、単純な中抜きであれば良かったのですが、交差が増えると、目標とする塗りからどんどん遠ざかってしまいます。
試しに、やってみると・・・

ほら!やっぱり!!
上下端は細かいので、何とかごまかせそうな気もしますが、ここはきっちりやっつけましょう。

中抜きの方法を確認する

ここまでで、2つの中抜き方法を紹介しました。
2つの図形の重なりをどのように表示するかを指定する方法と、外側と内側で回転方向を変える方法です。
前者は比較的分かりやすかったかと思いますが、内側の影が苦手でした。
後者は回転方向を変えなければならないので、少し面倒ですが、内側の影も表示できました。
ただ、交差が増えると、面倒なことに・・・

かと言って、これまでの中抜き方法を否定するつもりもありませんので、適宜使い分けることを考えましょう。

バッテリー残量を反映させるところまでは、ほぼ完璧と言って良いと思います。
その図形を稲妻マークで中抜きすれば良いので、この場合は前者の中抜きが適していると思います。
あとは、影の付いた稲妻マークの輪郭だけを描画する方法なら実現できそうです。
しかし、ちょっと頭の中で描画してみても、稲妻マークの影が表に出てしまいます。

試しに、実際にやってみると・・・

対策としては、稲妻マークの影だけ最下層にすれば実現できそうです。
輪郭と影を分離し、中抜きしたバッテリーをサンドする感じですね。
図にすると、以下のようになります。


何とかできそうですね。
1つ1つやっつけていきましょう。

稲妻マークで中抜きする

では、残量を反映させたバッテリーを、稲妻マークで中抜きしましょう。
この中抜きは、2つの図形を合成する方法で中抜きします。

稲妻マークで中抜きするプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 function draw_rRect_cw( left, top, right, bottom, r ) {
  ctx.moveTo( left + r, top );
  ctx.lineTo( right - r, top );
  ctx.arc( right - r, top + r, r, -pi/2, 0, false);
  ctx.lineTo( right, bottom - r );
  ctx.arc( right - r, bottom - r, r, 0, pi/2, false);
  ctx.lineTo( left + r, bottom);
  ctx.arc( left + r, bottom - r, r, pi/2, pi, false);
  ctx.lineTo( left, top + r);
  ctx.arc( left + r, top + r, r, pi, pi*3/2, false);
 }
 function draw_rRect_ccw( left, top, right, bottom, r ) {
  ctx.moveTo( right - r, top );
  ctx.lineTo( left + r, top );
  ctx.arc( left + r, top + r, r, pi*3/2, pi, true);
  ctx.lineTo( left, bottom - r );
  ctx.arc( left + r, bottom - r, r, pi, pi/2, true);
  ctx.lineTo( right - r, bottom);
  ctx.arc( right - r, bottom - r, r, pi/2, 0, true);
  ctx.lineTo( right, top + r );
  ctx.arc( right - r, top + r, r, 0, -pi/2, true);
 }
 function draw_charging() {
  ctx.moveTo( r+m*11.5, p_Top+r );
  ctx.lineTo( r+m*9, p_Top+r+m*3.5 );
  ctx.lineTo( r+m*13, p_Top+r+m*3.5 );1
  ctx.lineTo( r+m*5.5, p_Top+r+m*8 );
  ctx.lineTo( r+m*8, p_Top+r+m*4.5 );
  ctx.lineTo( r+m*4, p_Top+r+m*4.5 );
  ctx.lineTo( r+m*11.5, p_Top+r );
 } 
 var canvas = document.createElement("canvas");
 var r = height / 20;
 canvas.height = height/2 + r*2;
 canvas.width = height/2 * 2.1;
 var ctx = canvas.getContext("2d");
 
 var m = height / 20;
 var p_Left = 0;
 var p_Right = height/2 * 1.9;
 var p_Top = 0 +r;
 var p_Bottom = height/2 +r;
 var pi = Math.PI;
 var s_color = [ 255-b_color[0], 255-b_color[1], 255-b_color[2] ];
 
 ctx.fillStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
 ctx.shadowColor = "rgb(" + s_color.join()+ ")";
 ctx.shadowBlur = r/2;
 ctx.shadowOffsetY = -r/2;
 ctx.beginPath();
 draw_rRect_cw( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );
 draw_rRect_cw( p_Left, p_Top, p_Right, p_Bottom, r );
 draw_rRect_ccw( p_Right, p_Top + m*3+r, p_Right + m*2-r, p_Bottom - m*3-r, 0 );
 draw_rRect_ccw( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );
 var rectWidth = ( p_Right - p_Left - r*4 ) * percent / 100;
 draw_rRect_cw( p_Left+r*2, p_Top+r*2, p_Left+r*2+rectWidth, p_Bottom-r*2, 0 );
 ctx.fill();
 
 ctx.globalCompositeOperation = "destination-out";

 ctx.shadowBlur = 0;
 ctx.shadowOffsetY = 0;
 ctx.beginPath();
 draw_charging();
 ctx.fill();
 
 return canvas.toDataURL("image/png");
}
稲妻マークの描画も関数にしてあります。
これは中抜き用でもありますし、描画用でもあります。

影の指定を解除しないといけないのですが、RGBAの透過率で色を見れなくしてもムダでしたので、ボカシとオフセットをリセットしました。
そして、中抜き用の図形を新たなパスとして描画し、塗りつぶします。


結果は、こんな感じです。

OKですね。
影の部分まで、きちんと抜けています。


このサンプルのダウンロード ー> basic18_8_3.zip

稲妻マークの影だけを最下層に配置する

次に、稲妻マークの影だけを描画します。
その性質上、影は最下層に配置しなければなりません。
これも、2つの図形をどのように合成するかで解決できます。

稲妻マークの影を最下層に配置するプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 function draw_rRect_cw( left, top, right, bottom, r ) {
  ctx.moveTo( left + r, top );
  ctx.lineTo( right - r, top );
  ctx.arc( right - r, top + r, r, -pi/2, 0, false);
  ctx.lineTo( right, bottom - r );
  ctx.arc( right - r, bottom - r, r, 0, pi/2, false);
  ctx.lineTo( left + r, bottom);
  ctx.arc( left + r, bottom - r, r, pi/2, pi, false);
  ctx.lineTo( left, top + r);
  ctx.arc( left + r, top + r, r, pi, pi*3/2, false);
 }
 function draw_rRect_ccw( left, top, right, bottom, r ) {
  ctx.moveTo( right - r, top );
  ctx.lineTo( left + r, top );
  ctx.arc( left + r, top + r, r, pi*3/2, pi, true);
  ctx.lineTo( left, bottom - r );
  ctx.arc( left + r, bottom - r, r, pi, pi/2, true);
  ctx.lineTo( right - r, bottom);
  ctx.arc( right - r, bottom - r, r, pi/2, 0, true);
  ctx.lineTo( right, top + r );
  ctx.arc( right - r, top + r, r, 0, -pi/2, true);
 }
 function draw_charging() {
  ctx.moveTo( r+m*11.5, p_Top+r );
  ctx.lineTo( r+m*9, p_Top+r+m*3.5 );
  ctx.lineTo( r+m*13, p_Top+r+m*3.5 );1
  ctx.lineTo( r+m*5.5, p_Top+r+m*8 );
  ctx.lineTo( r+m*8, p_Top+r+m*4.5 );
  ctx.lineTo( r+m*4, p_Top+r+m*4.5 );
  ctx.lineTo( r+m*11.5, p_Top+r );
 } 
 var canvas = document.createElement("canvas");
 var r = height / 20;
 canvas.height = height/2 + r*2;
 canvas.width = height/2 * 2.1;
 var ctx = canvas.getContext("2d");
 
 var m = height / 20;
 var p_Left = 0;
 var p_Right = height/2 * 1.9;
 var p_Top = 0 +r;
 var p_Bottom = height/2 +r;
 var pi = Math.PI;
 var s_color = [ 255-b_color[0], 255-b_color[1], 255-b_color[2] ];
 
 ctx.fillStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
 ctx.shadowColor = "rgb(" + s_color.join()+ ")";
 ctx.shadowBlur = r/2;
 ctx.shadowOffsetY = -r/2;
 ctx.beginPath();
 draw_rRect_cw( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );
 draw_rRect_cw( p_Left, p_Top, p_Right, p_Bottom, r );
 draw_rRect_ccw( p_Right, p_Top + m*3+r, p_Right + m*2-r, p_Bottom - m*3-r, 0 );
 draw_rRect_ccw( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );
 var rectWidth = ( p_Right - p_Left - r*4 ) * percent / 100;
 draw_rRect_cw( p_Left+r*2, p_Top+r*2, p_Left+r*2+rectWidth, p_Bottom-r*2, 0 );
 ctx.fill();
 
 ctx.globalCompositeOperation = "destination-out";
 ctx.shadowBlur = 0;
 ctx.shadowOffsetY = 0;
 ctx.beginPath();
 draw_charging();
 ctx.fill();

 ctx.globalCompositeOperation = "destination-over";
 ctx.shadowBlur = r/2;
 ctx.shadowOffsetY = -r/2;
 ctx.beginPath();
 draw_charging();
 ctx.stroke();
 
 return canvas.toDataURL("image/png");
}
"destination-over" を指定すると、そのあとの描画が下層へ潜り込みます。
描画したいのは影ですので、影の指定を復活させています。
稲妻マークは関数化してありますので、呼び出すだけで済むのですが、今回は塗りつぶしではなく、輪郭の表示ですので、stroke() で締めます。


結果は、こんな感じです。

内側の影で確認しましたが、良い感じかと思います。


このサンプルのダウンロード ー> basic18_8_4.zip

稲妻マークの輪郭を最上層に配置する

最後に、稲妻マークの輪郭を描画します。
今回は、これまで描画したものの一番上に描画します。
これも、2つの図形をどのように合成するかで解決できます。

稲妻マークの輪郭を最上層に配置するプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 function draw_rRect_cw( left, top, right, bottom, r ) {
  ctx.moveTo( left + r, top );
  ctx.lineTo( right - r, top );
  ctx.arc( right - r, top + r, r, -pi/2, 0, false);
  ctx.lineTo( right, bottom - r );
  ctx.arc( right - r, bottom - r, r, 0, pi/2, false);
  ctx.lineTo( left + r, bottom);
  ctx.arc( left + r, bottom - r, r, pi/2, pi, false);
  ctx.lineTo( left, top + r);
  ctx.arc( left + r, top + r, r, pi, pi*3/2, false);
 }
 function draw_rRect_ccw( left, top, right, bottom, r ) {
  ctx.moveTo( right - r, top );
  ctx.lineTo( left + r, top );
  ctx.arc( left + r, top + r, r, pi*3/2, pi, true);
  ctx.lineTo( left, bottom - r );
  ctx.arc( left + r, bottom - r, r, pi, pi/2, true);
  ctx.lineTo( right - r, bottom);
  ctx.arc( right - r, bottom - r, r, pi/2, 0, true);
  ctx.lineTo( right, top + r );
  ctx.arc( right - r, top + r, r, 0, -pi/2, true);
 }
 function draw_charging() {
  ctx.moveTo( r+m*11.5, p_Top+r );
  ctx.lineTo( r+m*9, p_Top+r+m*3.5 );
  ctx.lineTo( r+m*13, p_Top+r+m*3.5 );1
  ctx.lineTo( r+m*5.5, p_Top+r+m*8 );
  ctx.lineTo( r+m*8, p_Top+r+m*4.5 );
  ctx.lineTo( r+m*4, p_Top+r+m*4.5 );
  ctx.lineTo( r+m*11.5, p_Top+r );
 } 
 var canvas = document.createElement("canvas");
 var r = height / 20;
 canvas.height = height/2 + r*2;
 canvas.width = height/2 * 2.1;
 var ctx = canvas.getContext("2d");
 
 var m = height / 20;
 var p_Left = 0;
 var p_Right = height/2 * 1.9;
 var p_Top = 0 +r;
 var p_Bottom = height/2 +r;
 var pi = Math.PI;
 var s_color = [ 255-b_color[0], 255-b_color[1], 255-b_color[2] ];
 
 ctx.fillStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
 ctx.shadowColor = "rgb(" + s_color.join()+ ")";
 ctx.shadowBlur = r/2;
 ctx.shadowOffsetY = -r/2;
 ctx.beginPath();
 draw_rRect_cw( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );
 draw_rRect_cw( p_Left, p_Top, p_Right, p_Bottom, r );
 draw_rRect_ccw( p_Right, p_Top + m*3+r, p_Right + m*2-r, p_Bottom - m*3-r, 0 );
 draw_rRect_ccw( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );
 var rectWidth = ( p_Right - p_Left - r*4 ) * percent / 100;
 draw_rRect_cw( p_Left+r*2, p_Top+r*2, p_Left+r*2+rectWidth, p_Bottom-r*2, 0 );
 ctx.fill();
 
 ctx.globalCompositeOperation = "destination-out";
 ctx.shadowBlur = 0;
 ctx.shadowOffsetY = 0;
 ctx.beginPath();
 draw_charging();
 ctx.fill();

 ctx.globalCompositeOperation = "destination-over";
 ctx.shadowBlur = r/2;
 ctx.shadowOffsetY = -r/2;
 ctx.beginPath();
 draw_charging();
 ctx.stroke();

 ctx.globalCompositeOperation = "source-over";
 ctx.strokeStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
 ctx.shadowBlur = 0;
 ctx.shadowOffsetY = 0;
 ctx.beginPath();
 draw_charging();
 ctx.stroke();
 
 return canvas.toDataURL("image/png");
}
最上層に描画するために指定するのは "source-over" です。
輪郭表示ですので、輪郭の色も指定しています。
この輪郭に影は必要ありませんので、影の指定を0にしています。


結果は、こんな感じです。

良い感じではないでしょうか。


このサンプルのダウンロード ー> basic18_8_5.zip

仕上げる

とりあえず稲妻マークは描画しましたが、無条件で描画していました。
充電している時しか表示されないものなので、本来の動作にしましょう。
描画の順番によって、影をリセットしたり戻したりしたいるので、この辺も仕上げようと思います。
あと、忘れていたのですが、低電力になった時の赤色表示も、ここでやっつけようと思います。

いろいろ仕上げたプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 function draw_rRect_cw( left, top, right, bottom, r ) {
  ctx.moveTo( left + r, top );
  ctx.lineTo( right - r, top );
  ctx.arc( right - r, top + r, r, -pi/2, 0, false);
  ctx.lineTo( right, bottom - r );
  ctx.arc( right - r, bottom - r, r, 0, pi/2, false);
  ctx.lineTo( left + r, bottom);
  ctx.arc( left + r, bottom - r, r, pi/2, pi, false);
  ctx.lineTo( left, top + r);
  ctx.arc( left + r, top + r, r, pi, pi*3/2, false);
 }
 function draw_rRect_ccw( left, top, right, bottom, r ) {
  ctx.moveTo( right - r, top );
  ctx.lineTo( left + r, top );
  ctx.arc( left + r, top + r, r, pi*3/2, pi, true);
  ctx.lineTo( left, bottom - r );
  ctx.arc( left + r, bottom - r, r, pi, pi/2, true);
  ctx.lineTo( right - r, bottom);
  ctx.arc( right - r, bottom - r, r, pi/2, 0, true);
  ctx.lineTo( right, top + r );
  ctx.arc( right - r, top + r, r, 0, -pi/2, true);
 }
 function draw_charging() {
  ctx.moveTo( r+m*11.5, p_Top+r );
  ctx.lineTo( r+m*9, p_Top+r+m*3.5 );
  ctx.lineTo( r+m*13, p_Top+r+m*3.5 );1
  ctx.lineTo( r+m*5.5, p_Top+r+m*8 );
  ctx.lineTo( r+m*8, p_Top+r+m*4.5 );
  ctx.lineTo( r+m*4, p_Top+r+m*4.5 );
  ctx.lineTo( r+m*11.5, p_Top+r );
 } 
 var canvas = document.createElement("canvas");
 var r = height / 20;
 canvas.height = height/2 + r*2;
 canvas.width = height/2 * 2.1;
 var ctx = canvas.getContext("2d");
 
 var m = height / 20;
 var p_Left = 0;
 var p_Right = height/2 * 1.9;
 var p_Top = 0 +r;
 var p_Bottom = height/2 +r;
 var pi = Math.PI;
 var s_color = [ 255-b_color[0], 255-b_color[1], 255-b_color[2] ];
 
 ctx.fillStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
 ctx.shadowColor = "rgb(" + s_color.join()+ ")";
 ctx.shadowBlur = r/2;
 ctx.shadowOffsetY = -r/2;
 ctx.beginPath();
 draw_rRect_cw( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );
 draw_rRect_cw( p_Left, p_Top, p_Right, p_Bottom, r );
 draw_rRect_ccw( p_Right, p_Top + m*3+r, p_Right + m*2-r, p_Bottom - m*3-r, 0 );
 draw_rRect_ccw( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );
 ctx.fill();
 var rectWidth = ( p_Right - p_Left - r*4 ) * percent / 100;
 if ( percent <= 20 ) {
  rectWidth = rectWidth * 5;
  ctx.fillStyle = "red";
 }
 ctx.beginPath();
 draw_rRect_cw( p_Left+r*2, p_Top+r*2, p_Left+r*2+rectWidth, p_Bottom-r*2, 0 );
 ctx.fill();
 
 if ( charge == true ) {
  ctx.globalCompositeOperation = "destination-out";
  ctx.shadowBlur = 0;
  ctx.shadowOffsetY = 0;
  ctx.beginPath();
  draw_charging();
  ctx.fill();

  ctx.globalCompositeOperation = "source-over";
  ctx.strokeStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
  ctx.shadowBlur = 0;
  ctx.shadowOffsetY = 0;
  ctx.beginPath();
  draw_charging();
  ctx.stroke();

  ctx.globalCompositeOperation = "destination-over";
  ctx.shadowBlur = r/2;
  ctx.shadowOffsetY = -r/2;
  ctx.beginPath();
  draw_charging();
  ctx.stroke();
 }
 return canvas.toDataURL("image/png");
}
バッテリー残量を描画する前に、低電力の処理を追加しました。
色を赤くし、フルゲージで描画しようというものです。
そのため、残量表示の前に塗りつぶし、残量表示を新たなパスで描くようにしています。

さらに、中抜きの部分から、if 文を追加しました。
これは、充電中の処理ですね。

基本的に結果が変わるわけではありませんので、低電力時の表示を紹介します。

20%になったら、フルゲージになりました。
ここで充電を始めると・・・

イメージ通りです。
これで、稲妻マークまでは完成しました。


このサンプルのダウンロード ー> basic18_8_6.zip

充電完了のプラグマークを描画する

こちらも、すでに寸法も座標も出してあるので、あとは描画するだけかと思ったのですが、オリジナルを確認しておきます。

背景が完全に塗りつぶされているので、バッテリー残量関係ないですね。
単純に、描画処理を加えれば良さそうです。

充電完了処理を加えたプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 function draw_rRect_cw( left, top, right, bottom, r ) {
  ctx.moveTo( left + r, top );
  ctx.lineTo( right - r, top );
  ctx.arc( right - r, top + r, r, -pi/2, 0, false);
  ctx.lineTo( right, bottom - r );
  ctx.arc( right - r, bottom - r, r, 0, pi/2, false);
  ctx.lineTo( left + r, bottom);
  ctx.arc( left + r, bottom - r, r, pi/2, pi, false);
  ctx.lineTo( left, top + r);
  ctx.arc( left + r, top + r, r, pi, pi*3/2, false);
 }
 function draw_rRect_ccw( left, top, right, bottom, r ) {
  ctx.moveTo( right - r, top );
  ctx.lineTo( left + r, top );
  ctx.arc( left + r, top + r, r, pi*3/2, pi, true);
  ctx.lineTo( left, bottom - r );
  ctx.arc( left + r, bottom - r, r, pi, pi/2, true);
  ctx.lineTo( right - r, bottom);
  ctx.arc( right - r, bottom - r, r, pi/2, 0, true);
  ctx.lineTo( right, top + r );
  ctx.arc( right - r, top + r, r, 0, -pi/2, true);
 }
 function draw_charging() {
  ctx.moveTo( r+m*11.5, p_Top+r );
  ctx.lineTo( r+m*9, p_Top+r+m*3.5 );
  ctx.lineTo( r+m*13, p_Top+r+m*3.5 );1
  ctx.lineTo( r+m*5.5, p_Top+r+m*8 );
  ctx.lineTo( r+m*8, p_Top+r+m*4.5 );
  ctx.lineTo( r+m*4, p_Top+r+m*4.5 );
  ctx.lineTo( r+m*11.5, p_Top+r );
 } 
 function draw_charged() {
  ctx.moveTo( r+m*2, p_Top+r+m*3 );
  ctx.lineTo( r+m*2, p_Top+r+m*5 );
  ctx.lineTo( r+m*6, p_Top+r+m*5 );
  ctx.lineTo( r+m*8, p_Top+r+m*7 );
  ctx.lineTo( r+m*12, p_Top+r+m*7 );
  ctx.lineTo( r+m*12, p_Top+r+m*6 );
  ctx.lineTo( r+m*14, p_Top+r+m*6 );
  ctx.lineTo( r+m*14, p_Top+r+m*5 );
  ctx.lineTo( r+m*12, p_Top+r+m*5 );
  ctx.lineTo( r+m*12, p_Top+r+m*3 );
  ctx.lineTo( r+m*14, p_Top+r+m*3 );
  ctx.lineTo( r+m*14, p_Top+r+m*2 );
  ctx.lineTo( r+m*12, p_Top+r+m*2 );
  ctx.lineTo( r+m*12, p_Top+r+m*1 );
  ctx.lineTo( r+m*8, p_Top+r+m*1 );
  ctx.lineTo( r+m*6, p_Top+r+m*3 );
 } 
 var canvas = document.createElement("canvas");
 var r = height / 20;
 canvas.height = height/2 + r*2;
 canvas.width = height/2 * 2.1;
 var ctx = canvas.getContext("2d");
 
 var m = height / 20;
 var p_Left = 0;
 var p_Right = height/2 * 1.9;
 var p_Top = 0 +r;
 var p_Bottom = height/2 +r;
 var pi = Math.PI;
 var s_color = [ 255-b_color[0], 255-b_color[1], 255-b_color[2] ];
 
 ctx.fillStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
 ctx.shadowColor = "rgb(" + s_color.join()+ ")";
 ctx.shadowBlur = r/2;
 ctx.shadowOffsetY = -r/2;
 ctx.beginPath();
 draw_rRect_cw( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );
 draw_rRect_cw( p_Left, p_Top, p_Right, p_Bottom, r );
 draw_rRect_ccw( p_Right, p_Top + m*3+r, p_Right + m*2-r, p_Bottom - m*3-r, 0 );
 if ( charge == true && percent == 100 ) {
  draw_charged();
  ctx.fill();
  return canvas.toDataURL("image/png");
 }
 else {
  draw_rRect_ccw( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );
  ctx.fill();
 }
 var rectWidth = ( p_Right - p_Left - r*4 ) * percent / 100;
 if ( percent <= 20 ) {
  rectWidth = rectWidth * 5;
  ctx.fillStyle = "red";
 }
 ctx.beginPath();
 draw_rRect_cw( p_Left+r*2, p_Top+r*2, p_Left+r*2+rectWidth, p_Bottom-r*2, 0 );
 ctx.fill();
 
 if ( charge == true && percent < 100 ) {
  ctx.globalCompositeOperation = "destination-out";
  ctx.shadowBlur = 0;
  ctx.shadowOffsetY = 0;
  ctx.beginPath();
  draw_charging();
  ctx.fill();

  ctx.globalCompositeOperation = "source-over";
  ctx.strokeStyle = "rgb(" + b_color.slice(0, 3).join()+ ")";
  ctx.shadowBlur = 0;
  ctx.shadowOffsetY = 0;
  ctx.beginPath();
  draw_charging();
  ctx.stroke();

  ctx.globalCompositeOperation = "destination-over";
  ctx.shadowBlur = r/2;
  ctx.shadowOffsetY = -r/2;
  ctx.beginPath();
  draw_charging();
  ctx.stroke();
 }
 return canvas.toDataURL("image/png");
}
充電済みのプラグマークも関数化しましたが、中抜き用にするため逆向きにしました。

プラグマークを描画する場合、バッテリー背景の中抜きがありません。
したがって、バッテリー背景の中抜きをする前に、プラグマークを描画するかどうかの判断をしています。

プラグマークを描画したら、もうそれ以上何もしなくて良いので、必殺技で即時終了させています。
その必殺技とは・・・


結果は、こんな感じです。

感無量です。
しかしながら、作りながら気になったことがあるので、やっつけておきましょう。

関数化した時に気になったのですが、プラグマークは稲妻マークに比べ、描画ポイントが多いため、関数化によるシンプル感がないのです。
そこで、座標を配列に格納し、ループで回しながら描画することを考えました。
関数化するだけでなく、プログラミング全体としてもシンプル化は必要ですからね!

シンプル化した関数は、以下の通りです。
 function draw_charged() {
  var x = [ 2, 2, 6, 8, 12, 12, 14, 14, 12, 12, 14, 14, 12, 12, 8, 6 ];
  var y = [ 3, 5, 5, 7, 7, 6, 6, 5, 5, 3, 3, 2, 2, 1, 1, 3 ];
  ctx.moveTo( r+m*x[0], p_Top+r+m*y[0] );
  for( var i=1; i<x.length; i++ ){
   ctx.lineTo( r+m*x[i], p_Top+r+m*y[i] );
  }
 } 
稲妻マークのdraw_charging よりも短くできました。


このサンプルのダウンロード ー> basic18_9.zip



当サイトの更新状況を、アラートで表示するかどうかの設定をします。


保存する

その機能で月額1500円もするのですか??
その機能なら年額1500円で手に入りますよ!

当サイトもこちらのレンタルサーバーを利用しています。

Copyright (C) 2007 Bokechans.net All Rights Reserved.