テーマの基本 その17
(グラデーション編)

テーマの基本その7でやった「単色ベタ塗り編」ですが、今回のグラデーションを紹介するつもりで単色を用意したはずなのに、なかなかグラデーションを紹介することができませんでした。
忘れていたわけではないのですが、紹介するタイミングがなかったのです。

テーマの基本その15でやった「総括編」で、オリジナルバッテリーを作りましたので、「確認編」を挟んだタイミングでグラデーションを紹介します。
というのも、「総括編」「確認編」で、グラデーションを紹介するためのネタに辿り着くことができるからです。
そのネタというのが、IOS5のオリジナルバッテリーです。

1つ1つ、部品から作っていきましょう。

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

iOS5は、まだジョブズが発表していた頃ですので、フラットデザインでもなければ、@3x画像なんて存在していません。
iOS5のバッテリーを1つ1つおさらいしましょう。

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

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

@2x画像ですので、これまでの@3x画像と比べると、やはり小さく感じてしまいます。

iOS5のバッテリーを構成する画像群

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



今回の目的としているグラデーションは、一番上の画像群にあるグラデーションです。
2段目の画像群は暗色背景用、3段目の画像群は通知背景用となっておりますが、iOS9では通知画面でもホーム画面でも変わりはないので、3段目は考えないことにします。
さらに、1段目と2段目は、メインカラーとベースカラーの切り替えと連動させれば良いと思っていたのですが、色の切り替わりや背景の明暗などを知ることができないため、1段目と2段目も切り替えるタイミングが分からないのです。
したがって、今回はグラデーションメインということで、1段目のみとします。

オリジナルのままで良いのか!?

1段目の画像群は明色背景用であり、目的とする画像でもあるわけなので、オリジナルのまま作ろうと思いますが、充電に関する画像が、残量に関係なく緑色で満たされています。
ここで考えさせられたのは、充電中の残量です。
オリジナルのままで良いなら、がんばって作るだけなのですが、オリジナルを踏襲しながらも残量を反映させるべきかと思うのです。
2段目も同様に、

オリジナルの寸法を確認する

では、iOS5のオリジナルバッテリーを採寸していきましょう。
@2x画像であることを忘れずに・・・

バッテリー外枠(背景)

バッテリーの外枠サイズから確認しましょう。


@2x画像ですので、偶数の寸法はありがたいですね。
白地だと分かりにくいのですが、2ピクセルの白影があります。
iOS5の棚を参考にしていただければ分かると思いますが、全体の影ですので、電極の下にもあります。

丸みの半径

iOS5でのバッテリー外枠も角が丸くなっております。
とりあえず、確認しましょう。


線幅2ピクセルの四角形ですが、角が欠けてる程度の丸みですので、この半径は2ピクセルとするべきでしょう。
採寸するまでもなく完了とします。

残量表示部分

iOS5でのバッテリー残量表示は、完全なる四角形です。


グラデーションの参考になります。

充電中を示すアレ

忘れちゃいけない充電中を示すアレです。
座標を拾う目的があるため、細かく確認しないといけませんね。


かなり幅を取っているんですね。

充電完了を示すアレ

今では忘れ去られていた、充電完了を示すアレです。
座標を拾う目的があるため、こちらも細かく確認しないといけませんね。


色が薄くなっている部分を円弧扱いすべきかどうか、悩ましいところですね。

寸法を@3x用にする

採寸した寸法は@2x用ですので、@3x用に変更しましょう。
@2x用寸法のまま作り始めても良いのですが、作って確認し、1.5倍してまた確認するのは二度手間かと思いますので、作る前に@3xにしておきます。
(なお、この行程は、テスト機がiPhone6Plusであるために存在します)

バッテリー外枠(背景)

バッテリーの外枠サイズを@3x化します。


@2x寸法は偶数ばかりでしたので、かなり助かりました。

丸みの半径

線幅は2のまま進めようか迷っていますが、丸みの半径はどうしましょうね。


やはり、寸法に追従した方が良さそうですので、丸みの半径は3としましょう。
そうなると、線幅も3にしないと不具合が生じそうなので、線幅は3で進めようかと思います。

残量表示部分

これは、寸法よりもグラデーションですね。


サイズはどうあれ、色の変化を追いかけなければなりませんね。

充電中を示すアレ

充電中の画像も@3x化します。
7と9では、どうしようか決まらなかったので、全体の16を@3x化し、そこから導き出すことにします。


他の寸法も、全体の寸法から算出しました。
1の差が、どのように影響するか、作ってみないと分かりません・・・

充電完了を示すアレ

充電完了の画像も@3x化します。


@2x寸法は偶数ばかりでしたので、特に考えることなく@3x化できました。

座標を拾う

寸法を確認しましたので、プログラミングのために座標を拾っていきましょう。

バッテリー外枠(背景)

では、バッテリー外枠の座標を拾いましょう。


何だかゴチャゴチャしてしまいました。
円弧にしなくても良さそうだったのですが、とりあえず円弧で作るために、座標を拾いました。
もともと偶数だらけの@2Xを@3X化したので、すべての座標が3の倍数となっております。

残量表示部分

これが今回の肝なのですが、座標を拾うと言うより、グラデーションの変化割合を解析することが目的になりますね。


iOS5では、画像の中心部分を引き伸ばして表示しますので、上の画像を切り取ることは筋違いです。
ここで解析しようと思ったのですが、緑色の上に白色によるグラデーションを重ねたらどうか?という発想が生まれましたので、作りながら調整しようと思います。

充電中を示すアレ

目的の稲妻マークは直線で構成されているため、思ったより簡単ですね。


3の倍数にしてみました。
オリジナルに対し、若干縦長になると思いますが、作ってみないと分かりません・・・

色情報を拾う

今回は、グラデーションがメインですので、グラデーションの解析もしておきましょう。

バッテリー背景(内部)

では、バッテリー背景(内部)の色情報から拾っていきましょう。


もうここまで来ると、私のメモとしての意味合いが強くなってますね。
変化に気付きやすくするために、RGBのカラーコードを16進数と10進数の2種類で表示しています。

バッテリー背景(外側)

次は、バッテリー背景の外側です。
上の画像を見てお分かり頂けると思いますが、2ピクセルの線幅の中で、2本のグラデーションが存在しています。

実は、3日ほど眺めていたのですが、どうしても難しく考えてしまいます。
そこで、ここは妥協しようと思います。
2ピクセルの一本線という形にしようと思います。

さて、一本線とはいえ、グラデーションを忘れてはいけません。
そこで、2本のグラデーションをどのようにまとめるか考えたのですが、内側のグラデーションを採用したいと思います。
上部を#2F3648、下部を#7B828Fとし、上から下へと変化するグラデーションを施そうと思います。
電極の部分は、上部#434956、下部#41495Cのグラデーションとします。


文字を少し大きくしてみました。

残量表示部分

残量表示部分に表示されるグラデーションも、色情報を拾います。


何か発見できるかと思ったのですが、何も見つかりません。
こういう場合は可視化することが定番なので、RGBそれぞれで変化をグラフ化しても、あまり意味がなさそうですね。
この分だと、近似値でやることになりそうです。

グラデーションの方法を知る

ここで、どのようにグラデーションを掛け方を紹介します。
グラデーションには、線形と円形がありますが、ここでは線形グラデーションを紹介します。

向きを指定する

実は、グラデーションの向きを指定する方法はありません。
「グラデーションを掛ける領域を指定する時に、向きが確定してしまう」というのが実情です。
単純に、四角形を領域に指定すると、四角形を確定する開始点から終了点へ向かいます。

高さのない横長の領域を指定すると、グラデーションは水平方向へ変化しますので、縞は縦になります。
逆に、幅のない縦長の領域を指定すると、グラデーションは垂直方向へ変化しますので、縞は横になります。

具体的には、コンテキストのcreateLinearGradientメソッドで以下のように指定します。
ctx.createLinearGradient(x0, y0, x1, y1)
( x0, y0 )と( x1, y1 )を対角線の頂点とする四角形が、グラデーションの領域となり、この頂点によってグラデーションの向きが決まります。

色を指定する

グラデーションの色を指定するのは簡単です。
領域に対する変化割合と、その時点での色を指定します。
変化が0%の色を指定し、変化が100%の色も指定すると、2色間を変化するグラデーションになります。
変化の割合毎に色を指定すれば、複数の色を変化していくグラデーションを掛けることができます。

具体的には、先ほどのコンテキストのcreateLinearGradientメソッドでCanvasGradientオブジェクトが得られますので、このオブジェクトにあるaddColorStopメソッドを使い、以下のように指定します。
grad.addColorStop( 割合, 色を示す文字列 );
CanvasGradientオブジェクトを grad としています。
割合は0〜1で指定し、色は文字列で指定しますので、#のついた16進数で指定してもいいですし、rgb() でも rgba() でも構いません。
認識されるなら、色の名前を記述しても問題ありません。
とにかく、文字列で指定することに注意しましょう。

やってみる

では、四角形をグラデーションで塗りつぶしてみましょう。
大きさは、バッテリーを意識した大きさにします。
グラデーションのテストなので、色をいろいろ指定することにします。

四角形をグラデーションで塗りつぶすプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {

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

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#FF0000" );
 grad.addColorStop( 0.2, 'rgb(0, 255, 0)' );
 grad.addColorStop( 0.4, "rgb(0, 0, 255)" );
 grad.addColorStop( 0.6, "rgba(255, 255, 0, 0.3)" );
 grad.addColorStop( 0.8, "orange" );

 ctx.fillStyle = grad;
 ctx.fillRect(0, 0, canvas.width, canvas.height);

 return canvas.toDataURL("image/png");
}
キャンバスは、1:2の横長にしました。
CanvasGradientオブジェクトを grad に格納し、この grad に対してグラデーションの色を追加していく形です。
addColorStop で grad に色を追加しますが、色がいろいろ詰まった grad を塗りつぶしのスタイルに丸投げします。
これで、塗りつぶしがグラデーションで指定されましたので、あとは塗りつぶすだけとなります。

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

いろいろな文字列で指定しても、きちんと反映されていることが分かります。
60%のところで透過色を指定しています。
うっすら背景が透けていることがお分かりいただけると思います。
色指定は80%で終わっていますが、その先は、100%までその指定色が続きます。


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

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

では、グラデーションを使って1つずつやっつけていきましょう。

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

手始めに、電極を省いたバッテリー背景から描画していきましょう。
内側のグラデーションは、後から上書きすれば良いので、線を描くのではなく、塗りつぶしで描画することにします。

電極無しの塗りつぶし背景を描画するプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {

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

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

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#2F3648" );
 grad.addColorStop( 1, "#7B828F" );
 ctx.fillStyle = grad;

 ctx.beginPath();
 ctx.moveTo( p_Left+r, p_Top );
 ctx.lineTo( p_Right-r, p_Top );
 ctx.arc( p_Right-r, p_Top+r, r, -pi/2, 0, false);
 ctx.lineTo( p_Right, p_Bottom-r );
 ctx.arc( p_Right-r, p_Bottom-r, r, 0, pi/2, false);
 ctx.lineTo( p_Left+r, p_Bottom);
 ctx.arc( p_Left+r, p_Bottom-r, r, pi/2, pi, false);
 ctx.lineTo( p_Left, p_Top+r);
 ctx.arc( p_Left+r, p_Top+r, r, pi, pi*3/2, false);
 ctx.closePath();
 ctx.fill();

 return canvas.toDataURL("image/png");
}
キャンバスは、寸法を合わせるために、縦は高さの半分、横は縦の1.9倍にしました。
完全塗りつぶしなので、線幅の存在がありません。
角丸の半径を3とするために、高さを20で割っています。

グラデーションですが、開始色と終了色による2色のグラデーションです。
このグラデーションを適用する図形ですが、総括編で角丸四角形を描画していますので、そのまま流用しました。


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

色が色なので分かりにくいのですが、2色間の変化が分かりますでしょうか?


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

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

先ほどのバッテリー背景に電極を追加していきましょう。
もちろん、キャンバスも広げます。

バッテリーの背景全体を描画するプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {

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

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

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#434956" );
 grad.addColorStop( 1, "#41495C" );
 ctx.fillStyle = grad;

 ctx.beginPath();
 ctx.moveTo( p_Right, p_Top + m*3 );
 ctx.lineTo( p_Right + m, p_Top + m*3 );
 ctx.arc( p_Right + m, p_Top + m*3 + r, r, -pi/2, 0, false);
 ctx.lineTo( p_Right + m*2, p_Bottom - m*3 - r );
 ctx.arc( p_Right + m*2 -r, p_Bottom - m*3 - r, r, 0, pi/2, false);
 ctx.lineTo( p_Right, p_Bottom - m*3);
 // ctx.arc( p_Right, p_Bottom - m*3 - r, r, pi/2, pi, false);
 // ctx.lineTo( p_Right - r, p_Top + m*3 + r);
 // ctx.arc( p_Right, p_Top + m*3 + r, r, pi, pi*3/2, false);
 ctx.closePath();
 ctx.fill();

 grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#2F3648" );
 grad.addColorStop( 1, "#7B828F" );
 ctx.fillStyle = grad;

 ctx.beginPath();
 ctx.moveTo( p_Left+r, p_Top );
 ctx.lineTo( p_Right-r, p_Top );
 ctx.arc( p_Right-r, p_Top+r, r, -pi/2, 0, false);
 ctx.lineTo( p_Right, p_Bottom-r );
 ctx.arc( p_Right-r, p_Bottom-r, r, 0, pi/2, false);
 ctx.lineTo( p_Left+r, p_Bottom);
 ctx.arc( p_Left+r, p_Bottom-r, r, pi/2, pi, false);
 ctx.lineTo( p_Left, p_Top+r);
 ctx.arc( p_Left+r, p_Top+r, r, pi, pi*3/2, false);
 ctx.closePath();
 ctx.fill();

 return canvas.toDataURL("image/png");
}
電極の分だけキャンバスの幅を増やすために、幅を縦の2.1倍にしました。
電極を描画するために、最小単位の変数 m を導入しました。
ここで言う最小単位とは、@3xであれば3、@2xであれば2のことを言います。
これは、あくまでも私が勝手に作ったもので、一般的なものではありません。
寸法から座標を拾った時に、最小単位の倍数にしたので、このタイミングで導入しました。
p_Rightを基準に電極の座標を指定しているので、結構ゴチャゴチャしてしまいました。

電極は右側だけ角丸なので、左上から描き始めて左下でclosePathすればできるので、左側の円弧の部分を // でコメントアウトし、描画しないようにしています
コメントアウトしなければ角丸四角形となりますが、敢えてコメントアウトしたのは、後々関数化するためです。

コメントアウトして右側だけ角丸の四角形を描画しても、コメントアウトを外して完全角丸四角形を描画しても、電極の左側の角丸の分だけバッテリー背景が重なるように描画しますので、どちらでも問題ありません。


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

順調ですね。


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

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

バッテリーらしい姿ができましたが、本来これは外側の線です。
線幅だけ小さい角丸四角形を上書きすることで、これまで描画した塗りつぶしの図形を、グラデーションの掛かった線にしましょう。

バッテリー背景の中身を上書きするプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {

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

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

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#434956" );
 grad.addColorStop( 1, "#41495C" );
 ctx.fillStyle = grad;

 ctx.beginPath();
 ctx.moveTo( p_Right, p_Top + m*3 );
 ctx.lineTo( p_Right + m, p_Top + m*3 );
 ctx.arc( p_Right + m, p_Top + m*3 + r, r, -pi/2, 0, false);
 ctx.lineTo( p_Right + m*2, p_Bottom - m*3 - r );
 ctx.arc( p_Right + m*2 -r, p_Bottom - m*3 - r, r, 0, pi/2, false);
 ctx.lineTo( p_Right, p_Bottom - m*3);
 // ctx.arc( p_Right, p_Bottom - m*3 - r, r, pi/2, pi, false);
 // ctx.lineTo( p_Right - r, p_Top + m*3 + r);
 // ctx.arc( p_Right, p_Top + m*3 + r, r, pi, pi*3/2, false);
 ctx.closePath();
 ctx.fill();

 grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#2F3648" );
 grad.addColorStop( 1, "#7B828F" );
 ctx.fillStyle = grad;

 ctx.beginPath();
 ctx.moveTo( p_Left+r, p_Top );
 ctx.lineTo( p_Right-r, p_Top );
 ctx.arc( p_Right-r, p_Top+r, r, -pi/2, 0, false);
 ctx.lineTo( p_Right, p_Bottom-r );
 ctx.arc( p_Right-r, p_Bottom-r, r, 0, pi/2, false);
 ctx.lineTo( p_Left+r, p_Bottom);
 ctx.arc( p_Left+r, p_Bottom-r, r, pi/2, pi, false);
 ctx.lineTo( p_Left, p_Top+r);
 ctx.arc( p_Left+r, p_Top+r, r, pi, pi*3/2, false);
 ctx.closePath();
 ctx.fill();

 p_Left += r;
 p_Top += r;
 p_Right -= r*2;
 p_Bottom -= r*2;
 grad = ctx.createLinearGradient( 0, p_Top, 0, p_Bottom+r );
 grad.addColorStop( 0, "#4D4D4D" );
 grad.addColorStop( 0.1875, "#4D4D4D" );
 grad.addColorStop( 0.875, "#C5C5C5" );
 grad.addColorStop( 1, "#C5C5C5" );
 ctx.fillStyle = grad;

 ctx.fillRect( p_Left, p_Top, p_Right, p_Bottom );

 return canvas.toDataURL("image/png");
}
塗りつぶしの四角形を一回り小さくするために、左上の座標を半径rだけ加え、内側になるようにしました。
右下の座標も、半径rだけ内側にすれば良いかと思いましたが、fillRectを使おうとすると、塗りつぶす幅と高さを指定しなければならないため、半径rの2倍を引きました。
しかしながら、グラデーション領域の指定は座標ですので、p_Bottomに関して引き過ぎた r だけ戻しています。

グラデーションですが、色情報を拾った時に、16分割されたうちの上3つとした2つがそれぞれ同じ色であることが分かっているので、その分の色を2つ追加してあります。


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

悪くないですね。
再現できてる気がします。


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

電極の中身まで描画する

引き続き、電極もやっつけましょう。
やり方は、先ほどのやり方と同じです。

電極の中身まで上書きするプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {

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

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

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#434956" );
 grad.addColorStop( 1, "#41495C" );
 ctx.fillStyle = grad;

 ctx.beginPath();
 ctx.moveTo( p_Right, p_Top + m*3 );
 ctx.lineTo( p_Right + m, p_Top + m*3 );
 ctx.arc( p_Right + m, p_Top + m*3 + r, r, -pi/2, 0, false);
 ctx.lineTo( p_Right + m*2, p_Bottom - m*3 - r );
 ctx.arc( p_Right + m*2 -r, p_Bottom - m*3 - r, r, 0, pi/2, false);
 ctx.lineTo( p_Right, p_Bottom - m*3);
 // ctx.arc( p_Right, p_Bottom - m*3 - r, r, pi/2, pi, false);
 // ctx.lineTo( p_Right - r, p_Top + m*3 + r);
 // ctx.arc( p_Right, p_Top + m*3 + r, r, pi, pi*3/2, false);
 ctx.closePath();
 ctx.fill();

 grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#2F3648" );
 grad.addColorStop( 1, "#7B828F" );
 ctx.fillStyle = grad;

 ctx.beginPath();
 ctx.moveTo( p_Left+r, p_Top );
 ctx.lineTo( p_Right-r, p_Top );
 ctx.arc( p_Right-r, p_Top+r, r, -pi/2, 0, false);
 ctx.lineTo( p_Right, p_Bottom-r );
 ctx.arc( p_Right-r, p_Bottom-r, r, 0, pi/2, false);
 ctx.lineTo( p_Left+r, p_Bottom);
 ctx.arc( p_Left+r, p_Bottom-r, r, pi/2, pi, false);
 ctx.lineTo( p_Left, p_Top+r);
 ctx.arc( p_Left+r, p_Top+r, r, pi, pi*3/2, false);
 ctx.closePath();
 ctx.fill();

 grad = ctx.createLinearGradient( 0, p_Top + m*3 + r, 0, p_Bottom - m*3 - r );
 grad.addColorStop( 0, "#FDFDFD" );
 grad.addColorStop( 1, "#898989" );
 ctx.fillStyle = grad;

 ctx.fillRect( p_Right, p_Top + m*3 + r, m, m*2 );

 p_Left += r;
 p_Top += r;
 p_Right -= r*2;
 p_Bottom -= r*2;
 grad = ctx.createLinearGradient( 0, p_Top, 0, p_Bottom+r );
 grad.addColorStop( 0, "#4D4D4D" );
 grad.addColorStop( 0.1875, "#4D4D4D" );
 grad.addColorStop( 0.875, "#C5C5C5" );
 grad.addColorStop( 1, "#C5C5C5" );
 ctx.fillStyle = grad;

 ctx.fillRect( p_Left, p_Top, p_Right, p_Bottom );

 return canvas.toDataURL("image/png");
}
電極は、変更を加えた p_Right では基準にできませんので、変更を加える前の p_Right を基準にします。
そのため、先ほどの上書きよりも先に記述します。

電極ですので、m を使って算出していますが、線幅に関する算出には r を使っています。


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

オリジナルと比べたくなりますね。

そんな訳で、オリジナルと並べてみました。

左側がオリジナルで、右側が描画した図形です。
拡大することで、やっと「ちょっと変??」と思える程度かと思います。


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

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

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

残量表示部分をグラデーションで作る

とりあえず、残量表示部分のグラデーションを実現しないといけません。
これができて、やっと残量に反映させることができるため、避けては通れません。
ただ、もう一度表示しておきますが、グラデーションが単純ではないのです。


ここまでくるまでにも、いろいろ考えたのですが、やはり縦方向と横方向のグラデーションを組み合わせるしかないという結論に達しました。

そこで、グラデーションを縦方向と横方向に分けてみることにしました。
考えたのは、縦方向が緑のグラデーションで、横方向が白のグラデーションです。
縦横の緑と白を上手く組み合わせれば、良い感じになるのではないかと考えたのです。

とりあえず、一番白の影響の無い中央の色を拾ってグラデーションを作ってみました。
縦方向の緑のグラデーションを描画するプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 var canvas = document.createElement("canvas");
 canvas.height = height/2;
 canvas.width = canvas.height * 2;
 var ctx = canvas.getContext("2d");

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 1/16, "#70D83A" );
 grad.addColorStop( 2/16, "#93E75D" );
 grad.addColorStop( 3/16, "#BDF886" );
 grad.addColorStop( 4/16, "#C5FF8D" );
 grad.addColorStop( 5/16, "#C5FF86" );
 grad.addColorStop( 6/16, "#B6FA76" );
 grad.addColorStop( 7/16, "#A3F163" );
 grad.addColorStop( 8/16, "#86E249" );
 grad.addColorStop( 9/16, "#63CC2B" );
 grad.addColorStop( 10/16, "#52BC1E" );
 grad.addColorStop( 11/16, "#43AA15" );
 grad.addColorStop( 12/16, "#35990E" );
 grad.addColorStop( 13/16, "#2B8C0A" );
 grad.addColorStop( 14/16, "#278608" );
 grad.addColorStop( 15/16, "#349312" );
 grad.addColorStop( 16/16, "#44A31D" );
 ctx.fillStyle = grad;

 ctx.fillRect( 0, 0, canvas.width, canvas.height );

 return canvas.toDataURL("image/png");
}
グラデーションだけですので、全体的には簡素な造りになっていますが、グラデーションの指定部分は、1つ1つ色を指定しています。


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

このグラデーションの両端に白のグラデーションを重ねることになるのですが、それが問題なのです。
とりあえず、白の透過色を上から重ねれば良さそうだということはわかっています。
ここまでくると、やってから考えることも必要なのでしょうね。

では、とにかくやってみましょう。
横方向のグラデーションは、基本色が白で、透過率を変化させます。
その透過率は、中央が0%で、両端を50%とします。
とにかくやってみたプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {
 var canvas = document.createElement("canvas");
 canvas.height = height/2;
 canvas.width = canvas.height * 2;
 var ctx = canvas.getContext("2d");

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 1/16, "#70D83A" );
 grad.addColorStop( 2/16, "#93E75D" );
 grad.addColorStop( 3/16, "#BDF886" );
 grad.addColorStop( 4/16, "#C5FF8D" );
 grad.addColorStop( 5/16, "#C5FF86" );
 grad.addColorStop( 6/16, "#B6FA76" );
 grad.addColorStop( 7/16, "#A3F163" );
 grad.addColorStop( 8/16, "#86E249" );
 grad.addColorStop( 9/16, "#63CC2B" );
 grad.addColorStop( 10/16, "#52BC1E" );
 grad.addColorStop( 11/16, "#43AA15" );
 grad.addColorStop( 12/16, "#35990E" );
 grad.addColorStop( 13/16, "#2B8C0A" );
 grad.addColorStop( 14/16, "#278608" );
 grad.addColorStop( 15/16, "#349312" );
 grad.addColorStop( 16/16, "#44A31D" );
 ctx.fillStyle = grad;

 ctx.fillRect( 0, 0, canvas.width, canvas.height );

 grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad = ctx.createLinearGradient( 0, 0, canvas.width, 0 );
 grad.addColorStop( 0/25, "rgba( 255, 255, 255, 0.5 )" );
 grad.addColorStop( 1/25, "rgba( 255, 255, 255, 0.25 )" );
 grad.addColorStop( 2/25, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 23/25, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 24/25, "rgba( 255, 255, 255, 0.25 )" );
 grad.addColorStop( 25/25, "rgba( 255, 255, 255, 0.5 )" );
 ctx.fillStyle = grad;

 ctx.fillRect( 0, 0, canvas.width, canvas.height );

 return canvas.toDataURL("image/png");
}
とりあえず、25分割して両端2つ分に透過色を指定しました。


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

何だか出来ているように見えますが、端っこの色を比べてみましょう。

左がオリジナルで、右が描画したモノです。
白が強すぎますね。
透過率50%では強すぎるので、半分の25%にしてみましょう。

白グラデーションの変更した部分だけのプログラムは、以下の通りです。
 grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad = ctx.createLinearGradient( 0, 0, canvas.width, 0 );
 grad.addColorStop( 0/25, "rgba( 255, 255, 255, 0.25 )" );
 grad.addColorStop( 2/25, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 23/25, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 25/25, "rgba( 255, 255, 255, 0.25 )" );
 ctx.fillStyle = grad;
白が変化する中間を指定していたのですが、線形変化しますので省きました。

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

左がオリジナルで、右が描画したモノです。
良くなりましたね。
若干、緑が濃く見えますので、端っこの透過率0.25を0.3にしましょう。

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

左がオリジナルで、右が描画したモノです。
まだまだ違うように見えますが、もともとの緑色の違いもありますので、これで良しとしましょう。


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

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

では、描画できたグラデーションとバッテリー残量を反映させることを考えていきましょう。

なぜ考えなければならないかと言うと、バッテリー残量は「割合」であるのに対し、グラデーションは絶対的な値だからです。
残量に応じてグラデーション領域を変化させなければならないのですが、グラデーションの両端2ピクセルに対し、グラデーションの割合も変化させなければなりません。
割合とピクセルの整合性が問われる訳です。

ここで、簡単に考えてみました。
全体を見て、如何に分かりやすくしようと考えるから分からなくなると思ったので、1つ1つ変数を使って確実に進めてみようと思います。
バッテリー残量は割合ですので、描画するためには、割合を実ピクセルに変換しないといけませんが、これはすでにやっていることですので、これまで同様 rectWidth を使って進めます。
rectWidth によって、描画幅をピクセルで得ることができますので、白グラデーションの両端となる2ピクセルが rectWidth の何パーセントになるのかを求めることができます。
そのために wp という変数を導入します。

バッテリー残量にグラデーションを反映させたプログラムは、以下の通りです。
(height, percent, charge, low, lpm, m_color, b_color) {

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

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

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#434956" );
 grad.addColorStop( 1, "#41495C" );
 ctx.fillStyle = grad;

 ctx.beginPath();
 ctx.moveTo( p_Right, p_Top + m*3 );
 ctx.lineTo( p_Right + m, p_Top + m*3 );
 ctx.arc( p_Right + m, p_Top + m*3 + r, r, -pi/2, 0, false);
 ctx.lineTo( p_Right + m*2, p_Bottom - m*3 - r );
 ctx.arc( p_Right + m*2 -r, p_Bottom - m*3 - r, r, 0, pi/2, false);
 ctx.lineTo( p_Right, p_Bottom - m*3);
 // ctx.arc( p_Right, p_Bottom - m*3 - r, r, pi/2, pi, false);
 // ctx.lineTo( p_Right - r, p_Top + m*3 + r);
 // ctx.arc( p_Right, p_Top + m*3 + r, r, pi, pi*3/2, false);
 ctx.closePath();
 ctx.fill();

 grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#2F3648" );
 grad.addColorStop( 1, "#7B828F" );
 ctx.fillStyle = grad;

 ctx.beginPath();
 ctx.moveTo( p_Left+r, p_Top );
 ctx.lineTo( p_Right-r, p_Top );
 ctx.arc( p_Right-r, p_Top+r, r, -pi/2, 0, false);
 ctx.lineTo( p_Right, p_Bottom-r );
 ctx.arc( p_Right-r, p_Bottom-r, r, 0, pi/2, false);
 ctx.lineTo( p_Left+r, p_Bottom);
 ctx.arc( p_Left+r, p_Bottom-r, r, pi/2, pi, false);
 ctx.lineTo( p_Left, p_Top+r);
 ctx.arc( p_Left+r, p_Top+r, r, pi, pi*3/2, false);
 ctx.closePath();
 ctx.fill();

 grad = ctx.createLinearGradient( 0, p_Top + m*3 + r, 0, p_Bottom - m*3 - r );
 grad.addColorStop( 0, "#FDFDFD" );
 grad.addColorStop( 1, "#898989" );
 ctx.fillStyle = grad;

 ctx.fillRect( p_Right, p_Top + m*3 + r, m, m*2 );

 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 0, "#4D4D4D" );
 grad.addColorStop( 3/16, "#4D4D4D" );
 grad.addColorStop( 14/16, "#C5C5C5" );
 grad.addColorStop( 1, "#C5C5C5" );
 ctx.fillStyle = grad;

 ctx.fillRect( p_Left+r, p_Top+r, p_Right-r*2, p_Bottom-r*2 );

 var rectWidth = (p_Right-p_Left-r*2) * percent / 100;
 grad = ctx.createLinearGradient( 0, 0, 0, p_Bottom-r*2 );
 grad.addColorStop( 1/16, "#70D83A" );
 grad.addColorStop( 2/16, "#93E75D" );
 grad.addColorStop( 3/16, "#BDF886" );
 grad.addColorStop( 4/16, "#C5FF8D" );
 grad.addColorStop( 5/16, "#C5FF86" );
 grad.addColorStop( 6/16, "#B6FA76" );
 grad.addColorStop( 7/16, "#A3F163" );
 grad.addColorStop( 8/16, "#86E249" );
 grad.addColorStop( 9/16, "#63CC2B" );
 grad.addColorStop( 10/16, "#52BC1E" );
 grad.addColorStop( 11/16, "#43AA15" );
 grad.addColorStop( 12/16, "#35990E" );
 grad.addColorStop( 13/16, "#2B8C0A" );
 grad.addColorStop( 14/16, "#278608" );
 grad.addColorStop( 15/16, "#349312" );
 grad.addColorStop( 16/16, "#44A31D" );
 ctx.fillStyle = grad;

 ctx.fillRect( p_Left+r, p_Top+r, rectWidth, p_Bottom-r*2 );

 grad = ctx.createLinearGradient( p_Left+r, 0, p_Left+r+rectWidth, 0 );
 var wp = r/rectWidth;
 grad.addColorStop( 0, "rgba( 255, 255, 255, 0.3 )" );
 grad.addColorStop( wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1-wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1, "rgba( 255, 255, 255, 0.3 )" );
 ctx.fillStyle = grad;
 ctx.fillRect( p_Left+r, p_Top+r, rectWidth, p_Bottom-r*2 );

 return canvas.toDataURL("image/png");
}
バッテリーの背景を完成させた時に、p_Left など基準となる座標を弄ったのですが、後々面倒なことが分かったので、やめました。
その影響で帳尻を合わせなければならなかった部分を、赤くしておきました。

rectWidth 以降が今回の追加分ですが、その全てを赤くしていません。
それは、basic17_5 と basic17_6 を合わせたからですので、その中で注目すべき部分のみ赤くしています。
wp の算出が、今回の大きな解決策だったのですが、やってみたら単なる割り算でした。
話の流れでは 2/rectWidth とすべきなのですが、@2xにおいての2ピクセルなので、@3xでは3ピクセルにするべく半径r を使いました。


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

良いんじゃないですか!?
残量の両端も白っぽくなっています。

我ながら感動デス!!


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

関数化する

次から次へと追加したので、とても長くなってしまいました。
バッテリー残量を反映させるところまでできましたので、ここで関数化しておきましょう。

これまで描画した図形は、角丸四角形と四角形です。
角丸の半径を0と考えると四角形になりますので、1つにまとめることができます。
基準となる4つの頂点と角丸の半径を引数にすれば、これまでの図形全てが関数で置き換えることができそうですね。

関数化して、できるだけ短くしたプログラムは、以下の通りです。
(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;

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#434956" );
 grad.addColorStop( 1, "#41495C" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );

 grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#2F3648" );
 grad.addColorStop( 1, "#7B828F" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left, p_Top, p_Right, p_Bottom, r );

 grad = ctx.createLinearGradient( 0, p_Top + m*3 + r, 0, p_Bottom - m*3 - r );
 grad.addColorStop( 0, "#FDFDFD" );
 grad.addColorStop( 1, "#898989" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right, p_Top + m*3 + r, p_Right + m, p_Bottom - m*3 - r , 0 );

 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 0, "#4D4D4D" );
 grad.addColorStop( 3/16, "#4D4D4D" );
 grad.addColorStop( 14/16, "#C5C5C5" );
 grad.addColorStop( 1, "#C5C5C5" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );

 var rectWidth = (p_Right-p_Left-r*2) * percent / 100;
 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 1/16, "#70D83A" );
 grad.addColorStop( 2/16, "#93E75D" );
 grad.addColorStop( 3/16, "#BDF886" );
 grad.addColorStop( 4/16, "#C5FF8D" );
 grad.addColorStop( 5/16, "#C5FF86" );
 grad.addColorStop( 6/16, "#B6FA76" );
 grad.addColorStop( 7/16, "#A3F163" );
 grad.addColorStop( 8/16, "#86E249" );
 grad.addColorStop( 9/16, "#63CC2B" );
 grad.addColorStop( 10/16, "#52BC1E" );
 grad.addColorStop( 11/16, "#43AA15" );
 grad.addColorStop( 12/16, "#35990E" );
 grad.addColorStop( 13/16, "#2B8C0A" );
 grad.addColorStop( 14/16, "#278608" );
 grad.addColorStop( 15/16, "#349312" );
 grad.addColorStop( 16/16, "#44A31D" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 grad = ctx.createLinearGradient( p_Left+r, 0, p_Left+r+rectWidth, 0 );
 var wp = r/rectWidth;
 grad.addColorStop( 0, "rgba( 255, 255, 255, 0.3 )" );
 grad.addColorStop( wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1-wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1, "rgba( 255, 255, 255, 0.3 )" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 return canvas.toDataURL("image/png");
}
関数化してみましたが、グラデーションの色指定を何とかすれば、さらに短くできますね。


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

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

無理やり3の倍数にしたことが気になっていた稲妻マークです。
早く完成させて、表示を見比べたいですね。

寸法も座標も拾ってあるので、とにかく作ってみましょう。
稲妻マークを描画するプログラムは、以下の通りです。
(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;

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#434956" );
 grad.addColorStop( 1, "#41495C" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );

 grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#2F3648" );
 grad.addColorStop( 1, "#7B828F" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left, p_Top, p_Right, p_Bottom, r );

 grad = ctx.createLinearGradient( 0, p_Top + m*3 + r, 0, p_Bottom - m*3 - r );
 grad.addColorStop( 0, "#FDFDFD" );
 grad.addColorStop( 1, "#898989" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right, p_Top + m*3 + r, p_Right + m, p_Bottom - m*3 - r , 0 );

 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 0, "#4D4D4D" );
 grad.addColorStop( 3/16, "#4D4D4D" );
 grad.addColorStop( 14/16, "#C5C5C5" );
 grad.addColorStop( 1, "#C5C5C5" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );

 var rectWidth = (p_Right-p_Left-r*2) * percent / 100;
 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 1/16, "#70D83A" );
 grad.addColorStop( 2/16, "#93E75D" );
 grad.addColorStop( 3/16, "#BDF886" );
 grad.addColorStop( 4/16, "#C5FF8D" );
 grad.addColorStop( 5/16, "#C5FF86" );
 grad.addColorStop( 6/16, "#B6FA76" );
 grad.addColorStop( 7/16, "#A3F163" );
 grad.addColorStop( 8/16, "#86E249" );
 grad.addColorStop( 9/16, "#63CC2B" );
 grad.addColorStop( 10/16, "#52BC1E" );
 grad.addColorStop( 11/16, "#43AA15" );
 grad.addColorStop( 12/16, "#35990E" );
 grad.addColorStop( 13/16, "#2B8C0A" );
 grad.addColorStop( 14/16, "#278608" );
 grad.addColorStop( 15/16, "#349312" );
 grad.addColorStop( 16/16, "#44A31D" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 grad = ctx.createLinearGradient( p_Left+r, 0, p_Left+r+rectWidth, 0 );
 var wp = r/rectWidth;
 grad.addColorStop( 0, "rgba( 255, 255, 255, 0.3 )" );
 grad.addColorStop( wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1-wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1, "rgba( 255, 255, 255, 0.3 )" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 ctx.fillStyle="black";
 ctx.beginPath();
 ctx.moveTo( r+m*11, r );
 ctx.lineTo( r+m*9, r+m*3 );
 ctx.lineTo( r+m*13, r+m*3 );
 ctx.lineTo( r+m*6, r+m*8 );
 ctx.lineTo( r+m*8, r+m*5 );
 ctx.lineTo( r+m*4, r+m*5 );
 ctx.closePath();
 ctx.fill();

 return canvas.toDataURL("image/png");
}
3の倍数で寸法取りできているので、最小単位 m と線幅代わりの r を使って描画しました。


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

何となく、違和感を感じます。
やはり、無理があったのでしょうか。

確認編で、座標は少数でも構わないことが分かっているので、妥協した寸法を見直してみましょう。


問題とするのは、奇数の寸法です。
@3x化した時に、うまく3の整数倍になるようにしてしまったのですが、少数でも良いので、きちんと@3x化してみようと思います。

@3x化バージョン2の寸法は、以下の通りです。


この寸法を基に、最小単位 m を使って、以下のようにプログラムを書き換えてみました。
 ctx.fillStyle="black";
 ctx.beginPath();
 ctx.moveTo( r+m*11.5, r );
 ctx.lineTo( r+m*9, r+m*3.5 );
 ctx.lineTo( r+m*13, r+m*3.5 );
 ctx.lineTo( r+m*5.5, r+m*8 );
 ctx.lineTo( r+m*8, r+m*4.5 );
 ctx.lineTo( r+m*4, r+m*4.5 );
 ctx.closePath();
 ctx.fill();
これで、オリジナルに忠実な比率になるはずです。


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

これぞオリジナルって感じですね!

しかし、よく見ると・・・

オリジナルには影があるんですね。
今回は、充電中でもバッテリー残量を反映させるので、バッテリー残量が少ない場合、バッテリー背景の上に稲妻マークが描画されるので、なおさら白い影が必要になります。


影も必要ですが、稲妻マークは充電中のみ表示させるモノなので、充電中かどうかの判断も必要です。
これら2つを解決して、今回の仕上げとしましょう。

仕上げのために書き換えた プログラムは、以下の通りです。
 if( charge == true ) {
  ctx.fillStyle="black";
  ctx.shadowColor="white";
  ctx.shadowBlur = r;
  ctx.beginPath();
  ctx.moveTo( r+m*11.5, r );
  ctx.lineTo( r+m*9, r+m*3.5 );
  ctx.lineTo( r+m*13, r+m*3.5 );
  ctx.lineTo( r+m*5.5, r+m*8 );
  ctx.lineTo( r+m*8, r+m*4.5 );
  ctx.lineTo( r+m*4, r+m*4.5 );
  ctx.closePath();
  ctx.fill();
 }
充電中かどうかの if 文と影の指定を追加しました。
ボカシの強さは、半径r にしました。


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

薄っすらと、白い影がついています。
ここで、オリジナルと比べてみましょう。

左がオリジナルで、右が描画したモノです。
オリジナルは@2xなので、描画したものに比べると少し荒いのですが、決して悪くはなさそうです。
ただ、バッテリー本体にも影が付いているのをすっかり忘れていたのですが、影は全体ではなく下側に付いています。
ですので、下方向に1ピクセルだけズラしましょう。

影をズラしたプログラムは、以下の通りです。
 if( charge == true ) {
  ctx.fillStyle="black";
  ctx.shadowColor="white";
  ctx.shadowBlur = r;
  ctx.shadowOffsetY = 1;
  ctx.beginPath();
  ctx.moveTo( r+m*11.5, r );
  ctx.lineTo( r+m*9, r+m*3.5 );
  ctx.lineTo( r+m*13, r+m*3.5 );
  ctx.lineTo( r+m*5.5, r+m*8 );
  ctx.lineTo( r+m*8, r+m*4.5 );
  ctx.lineTo( r+m*4, r+m*4.5 );
  ctx.closePath();
  ctx.fill();
 }
できれば、半径r などのようにheightから算出されたモノを使いたいところですが、多少外枠に掛かっている程度なので、デバイスに関係なく1としました。

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

こんな感じですね。
先ほどのように、オリジナルと並べてみます。

緑色に掛かっている稲妻マークの影を見ていただくと、良い感じになっていることがお分かりいただけるかと思います。
我ながら、上出来ですね!

ちなみに、今回の描画で思い出したバッテリー外枠の影ですが、仕上げでやろうと思います。


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

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

画像には、もう1つ凝ったモノがあります。
あまり見る機会がないのですが、充電完了の画像です。

寸法も座標も拾ってあるので、とにかく作ってみようと思ったのですが、参考にしようと見直してみたら、座標を拾っていませんでした・・・
ただ、このプラグマークの寸法は、完全に3の倍数ですので、このままやってみます。

プラグマークを描画するプログラムは、以下の通りです。
(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;

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#434956" );
 grad.addColorStop( 1, "#41495C" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );

 grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#2F3648" );
 grad.addColorStop( 1, "#7B828F" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left, p_Top, p_Right, p_Bottom, r );

 grad = ctx.createLinearGradient( 0, p_Top + m*3 + r, 0, p_Bottom - m*3 - r );
 grad.addColorStop( 0, "#FDFDFD" );
 grad.addColorStop( 1, "#898989" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right, p_Top + m*3 + r, p_Right + m, p_Bottom - m*3 - r , 0 );

 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 0, "#4D4D4D" );
 grad.addColorStop( 3/16, "#4D4D4D" );
 grad.addColorStop( 14/16, "#C5C5C5" );
 grad.addColorStop( 1, "#C5C5C5" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );

 var rectWidth = (p_Right-p_Left-r*2) * percent / 100;
 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 1/16, "#70D83A" );
 grad.addColorStop( 2/16, "#93E75D" );
 grad.addColorStop( 3/16, "#BDF886" );
 grad.addColorStop( 4/16, "#C5FF8D" );
 grad.addColorStop( 5/16, "#C5FF86" );
 grad.addColorStop( 6/16, "#B6FA76" );
 grad.addColorStop( 7/16, "#A3F163" );
 grad.addColorStop( 8/16, "#86E249" );
 grad.addColorStop( 9/16, "#63CC2B" );
 grad.addColorStop( 10/16, "#52BC1E" );
 grad.addColorStop( 11/16, "#43AA15" );
 grad.addColorStop( 12/16, "#35990E" );
 grad.addColorStop( 13/16, "#2B8C0A" );
 grad.addColorStop( 14/16, "#278608" );
 grad.addColorStop( 15/16, "#349312" );
 grad.addColorStop( 16/16, "#44A31D" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 grad = ctx.createLinearGradient( p_Left+r, 0, p_Left+r+rectWidth, 0 );
 var wp = r/rectWidth;
 grad.addColorStop( 0, "rgba( 255, 255, 255, 0.3 )" );
 grad.addColorStop( wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1-wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1, "rgba( 255, 255, 255, 0.3 )" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 if( charge == true && percent < 100 ) {
  ctx.fillStyle="black";
  ctx.shadowColor="white";
  ctx.shadowBlur = r;
  ctx.shadowOffsetY = 1;
  ctx.beginPath();
  ctx.moveTo( r+m*11.5, r );
  ctx.lineTo( r+m*9, r+m*3.5 );
  ctx.lineTo( r+m*13, r+m*3.5 );
  ctx.lineTo( r+m*5.5, r+m*8 );
  ctx.lineTo( r+m*8, r+m*4.5 );
  ctx.lineTo( r+m*4, r+m*4.5 );
  ctx.closePath();
  ctx.fill();
 }

 if( charge == true && percent == 100 ) {
  ctx.fillStyle="black";
  ctx.shadowColor="white";
  ctx.shadowBlur = r;
  ctx.shadowOffsetY = 1;
  ctx.beginPath();
  ctx.moveTo( r+m*2, r+m*3 );
  ctx.lineTo( r+m*6, r+m*3 );
  ctx.lineTo( r+m*8, r+m*1 );
  ctx.lineTo( r+m*12, r+m*1 );
  ctx.lineTo( r+m*12, r+m*2 );
  ctx.lineTo( r+m*14, r+m*2 );
  ctx.lineTo( r+m*14, r+m*3 );
  ctx.lineTo( r+m*12, r+m*3 );
  ctx.lineTo( r+m*12, r+m*5 );
  ctx.lineTo( r+m*14, r+m*5 );
  ctx.lineTo( r+m*14, r+m*6 );
  ctx.lineTo( r+m*12, r+m*6 );
  ctx.lineTo( r+m*12, r+m*7 );
  ctx.lineTo( r+m*8, r+m*7 );
  ctx.lineTo( r+m*6, r+m*5 );
  ctx.lineTo( r+m*2, r+m*5 );
  ctx.closePath();
  ctx.fill();
 }

 return canvas.toDataURL("image/png");
}
充電中は稲妻マークですが、充電完了時にはプラグマークとなるため、稲妻マークの条件に「100%未満」を追加しました。


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

影も稲妻マークと同様に付けたので、良い感じかと思います。
これも、オリジナルと比べてみましょう。

左がオリジナルで、右が描画したモノです。
かなり慣れたのか、一発OKではないでしょうか。


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

赤色も作る

描画することばかり考えていたので忘れていたのですが、低電力時の赤色を忘れていました。
バッテリー残量20%で切り替わるアレです。

基本的には緑色と同じですので、やり方も同じですね。

近似的に作る

緑と同様に作り始めたのですが、16色全てを調べて指定するのは、とても面倒ですし、プログラムも長くなってしまいます。
せっかくなので、緑色の時に考えた「近似的」なモノを作ってみましょう。
色情報を拾ってあるので、それを元にグラフを描き、近似的なモノを作ろうと思ったのですが、単純に「山」と「谷」で何とかならないかと思い、やってみました。

部分的ですが、「山」と「谷」を残したグラデーションのプログラムは、以下の通りです。
 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 1/16, "#70D83A" );
 grad.addColorStop( 4/16, "#C5FF8D" );
 grad.addColorStop( 5/16, "#C5FF86" );
 grad.addColorStop( 13/16, "#2B8C0A" );
 grad.addColorStop( 14/16, "#278608" );
 grad.addColorStop( 16/16, "#44A31D" );
 ctx.fillStyle = grad;
色的に「山」と「谷」になりそうな2点ずつを残し、消去しました。
色と色の間は、勝手に補完してくれるので、何とかなると思うのです。
もちろん、開始と終了は残してあります。


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

うぉ〜!!
16色全てを指定するのが馬鹿らしく思えるほどの仕上がりではないですか!

念のため、拡大してみます。
感動が冷めないので、いつもより大きくしてみます。

左が16色指定で、右が近似的にしたモノです。
ここまで大きくして分かったのですが、明るい緑の部分を広げれば何とかなりそうです。

では、やってみましょう。
以下のように、変更しました。
 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 1/16, "#70D83A" );
 grad.addColorStop( 3/16, "#C5FF8D" );
 grad.addColorStop( 6/16, "#C5FF86" );
 grad.addColorStop( 13/16, "#2B8C0A" );
 grad.addColorStop( 14/16, "#278608" );
 grad.addColorStop( 16/16, "#44A31D" );
 ctx.fillStyle = grad;
単に、明るい部分の幅を広げただけです。

拡大して比べます。

十分妥協できるレベルかと思います。

近似的な赤を作る

近似的な緑を作りましたので、赤も近似的に作ります。
緑に合わせて、赤も色を拾いましょう。


この情報を元に、赤で作ってみます。
部分的ですが、赤いグラデーションのプログラムは、以下の通りです。
 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 1/16, "#D83A3B" );
 grad.addColorStop( 3/16, "#FFA3B6" );
 grad.addColorStop( 6/16, "#FFA9B8" );
 grad.addColorStop( 13/16, "#8D0809" );
 grad.addColorStop( 14/16, "#860808" );
 grad.addColorStop( 16/16, "#9E2117" );
 ctx.fillStyle = grad;
緑に対応した部分を赤の情報で置き換えました。


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

ほぉ、良い感じに速そうですね。
赤くなっただけで反応してしまうお年頃なもので・・・

ただ、実際にお目にかかれるのは低電力時のみですので、あまり拘るほどではないかもしれません。
しかし、個人的には良い出来だと思っているので、分岐編でやったように、フルゲージ表示にしてしまいましょう。

部分的ですが、低電力時フルゲージにするプログラムは以下の通りです。
 var rectWidth = (p_Right-p_Left-r*2) * percent / 100;
 rectWidth *= percent<=20 ? 5 : 1;
残量が20%以下になったら5倍するという命令を、分岐編の仕上げパート3で紹介した条件演算子を使って、一行で済ませています。
色分けのために if 文を使っているので、赤色指定のところで5倍しても構いません。

全体のプログラムは、以下の通りです。
(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;

 var grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#434956" );
 grad.addColorStop( 1, "#41495C" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );

 grad = ctx.createLinearGradient( 0, 0, 0, canvas.height );
 grad.addColorStop( 0, "#2F3648" );
 grad.addColorStop( 1, "#7B828F" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left, p_Top, p_Right, p_Bottom, r );

 grad = ctx.createLinearGradient( 0, p_Top + m*3 + r, 0, p_Bottom - m*3 - r );
 grad.addColorStop( 0, "#FDFDFD" );
 grad.addColorStop( 1, "#898989" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right, p_Top + m*3 + r, p_Right + m, p_Bottom - m*3 - r , 0 );

 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 0, "#4D4D4D" );
 grad.addColorStop( 3/16, "#4D4D4D" );
 grad.addColorStop( 14/16, "#C5C5C5" );
 grad.addColorStop( 1, "#C5C5C5" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );

 var rectWidth = (p_Right-p_Left-r*2) * percent / 100;
 rectWidth *= percent<=20 ? 5 : 1;
 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
  if ( percent <= 20 ) {
  grad.addColorStop( 1/16, "#D83A3B" );
  grad.addColorStop( 3/16, "#FFA3B6" );
  grad.addColorStop( 6/16, "#FFA9B8" );
  grad.addColorStop( 13/16, "#8D0809" );
  grad.addColorStop( 14/16, "#860808" );
  grad.addColorStop( 16/16, "#9E2117" );
// rectWidth *= 5;
 }
 else {
  grad.addColorStop( 1/16, "#70D83A" );
  grad.addColorStop( 3/16, "#C5FF8D" );
  grad.addColorStop( 6/16, "#C5FF86" );
  grad.addColorStop( 13/16, "#2B8C0A" );
  grad.addColorStop( 14/16, "#278608" );
  grad.addColorStop( 16/16, "#44A31D" );
 }
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 grad = ctx.createLinearGradient( p_Left+r, 0, p_Left+r+rectWidth, 0 );
 var wp = r/rectWidth;
 grad.addColorStop( 0, "rgba( 255, 255, 255, 0.3 )" );
 grad.addColorStop( wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1-wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1, "rgba( 255, 255, 255, 0.3 )" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 if( charge == true && percent < 100 ) {
  ctx.fillStyle="black";
  ctx.shadowColor="white";
  ctx.shadowBlur = r;
  ctx.shadowOffsetY = 1;
  ctx.beginPath();
  ctx.moveTo( r+m*11.5, r );
  ctx.lineTo( r+m*9, r+m*3.5 );
  ctx.lineTo( r+m*13, r+m*3.5 );
  ctx.lineTo( r+m*5.5, r+m*8 );
  ctx.lineTo( r+m*8, r+m*4.5 );
  ctx.lineTo( r+m*4, r+m*4.5 );
  ctx.closePath();
  ctx.fill();
 }

 if( charge == true && percent == 100 ) {
  ctx.fillStyle="black";
  ctx.shadowColor="white";
  ctx.shadowBlur = r;
  ctx.shadowOffsetY = 1;
  ctx.beginPath();
  ctx.moveTo( r+m*2, r+m*3 );
  ctx.lineTo( r+m*6, r+m*3 );
  ctx.lineTo( r+m*8, r+m*1 );
  ctx.lineTo( r+m*12, r+m*1 );
  ctx.lineTo( r+m*12, r+m*2 );
  ctx.lineTo( r+m*14, r+m*2 );
  ctx.lineTo( r+m*14, r+m*3 );
  ctx.lineTo( r+m*12, r+m*3 );
  ctx.lineTo( r+m*12, r+m*5 );
  ctx.lineTo( r+m*14, r+m*5 );
  ctx.lineTo( r+m*14, r+m*6 );
  ctx.lineTo( r+m*12, r+m*6 );
  ctx.lineTo( r+m*12, r+m*7 );
  ctx.lineTo( r+m*8, r+m*7 );
  ctx.lineTo( r+m*6, r+m*5 );
  ctx.lineTo( r+m*2, r+m*5 );
  ctx.closePath();
  ctx.fill();
 }

 return canvas.toDataURL("image/png");
}
赤色指定する部分で、5倍するという一行をコメントアウトしてあります。

バッテリー残量20%の結果は、こんな感じです。

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

もう、完成レベルで良いのではないでしょうか。


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

仕上げる

ほぼ完成しましたので、仕上げていきましょう。

と言っても、影を付けるだけ!?

バッテリー背景に影を付ける

気になっていた影を付けましょう。
コンテキストには、影を指定するプロパティがあるので、影をつけること自体はとても簡単です。
しかし、施したい影はした方向のみなのですが、コンテキストのプロパティで影を指定すると、四方八方に影が広がります。
オフセットもあるのですが、影の方向を指定するのではなく、影を移動させるので、横方向に広がった影はどうしようもないのです。
試してみると、こんな感じです。

悪くはないのですが、実は、キャンバスも広げなければなりませんし、広げた分だけ座標も見直さなければならないし、影は簡単でも、後が大変だったのです。

そこで考えたのは、影の指定でボカすのではなく、ボカシの無い影を下にズラしながら重ねるという方法です。
この方法は、サイト作成でよく使う方法なのですが、スタイルシートのように考えていました。
しかし、いざ始めてみると・・・
どうやってやるんだ???と、進むことすらできませんでした。
というのも、canvasでの影とその対象は同一レイヤーなので、影を重ねるなら、そのためにまた描画しなければならないのです。
そもそも、影を重ねるということができないのです。

そういうことなら、レイヤー的に考えるだけですね。
影のために、また描画しなければならないのであれば、影の色で描画してしまえば良いと考えたのです。
レイヤー的な最下層に、ズラして配置すれば、影として振る舞えますよね。
もちろん、影っぽく見えなければならないので、透過率を指定し、2〜3枚のレイヤーを重ねる必要があります。

関数化もしてあるし、塗りの色を透過率込みで指定すれば良いので、難しくは無いと思います。
では、やってみましょう。

影を付けたプログラムは、以下の通りです。
(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;

 ctx.fillStyle = "rgba( 255, 255, 255, 0.3 )";
 draw_rRect( p_Right-r, p_Top + m*3+2, p_Right + m*2, p_Bottom - m*3+2, r );
 draw_rRect( p_Right-r, p_Top + m*3+1, p_Right + m*2, p_Bottom - m*3+1, r );
 var grad = ctx.createLinearGradient( 0, p_Top + m*3, 0, p_Bottom - m*3 );
 grad.addColorStop( 0, "#434956" );
 grad.addColorStop( 1, "#41495C" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );

 ctx.fillStyle = "rgba( 255, 255, 255, 0.3 )";
 draw_rRect( p_Left, p_Top+2, p_Right, p_Bottom+2, r );
 draw_rRect( p_Left, p_Top+1, p_Right, p_Bottom+1, r );
 grad = ctx.createLinearGradient( 0, p_Top, 0, p_Bottom );
 grad.addColorStop( 0, "#2F3648" );
 grad.addColorStop( 1, "#7B828F" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left, p_Top, p_Right, p_Bottom, r );

 grad = ctx.createLinearGradient( 0, p_Top + m*3 + r, 0, p_Bottom - m*3 - r );
 grad.addColorStop( 0, "#FDFDFD" );
 grad.addColorStop( 1, "#898989" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right, p_Top + m*3 + r, p_Right + m, p_Bottom - m*3 - r , 0 );

 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 0, "#4D4D4D" );
 grad.addColorStop( 3/16, "#4D4D4D" );
 grad.addColorStop( 14/16, "#C5C5C5" );
 grad.addColorStop( 1, "#C5C5C5" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );

 var rectWidth = (p_Right-p_Left-r*2) * percent / 100;
 rectWidth *= percent<=20 ? 5 : 1;
 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
  if ( percent <= 20 ) {
  grad.addColorStop( 1/16, "#D83A3B" );
  grad.addColorStop( 3/16, "#FFA3B6" );
  grad.addColorStop( 6/16, "#FFA9B8" );
  grad.addColorStop( 13/16, "#8D0809" );
  grad.addColorStop( 14/16, "#860808" );
  grad.addColorStop( 16/16, "#9E2117" );
// rectWidth *= 5;
 }
 else {
  grad.addColorStop( 1/16, "#70D83A" );
  grad.addColorStop( 3/16, "#C5FF8D" );
  grad.addColorStop( 6/16, "#C5FF86" );
  grad.addColorStop( 13/16, "#2B8C0A" );
  grad.addColorStop( 14/16, "#278608" );
  grad.addColorStop( 16/16, "#44A31D" );
 }
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 grad = ctx.createLinearGradient( p_Left+r, 0, p_Left+r+rectWidth, 0 );
 var wp = r/rectWidth;
 grad.addColorStop( 0, "rgba( 255, 255, 255, 0.3 )" );
 grad.addColorStop( wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1-wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1, "rgba( 255, 255, 255, 0.3 )" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 if( charge == true && percent < 100 ) {
  ctx.fillStyle="black";
  ctx.shadowColor="white";
  ctx.shadowBlur = r;
  ctx.shadowOffsetY = 1;
  ctx.beginPath();
  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 );
  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.closePath();
  ctx.fill();
 }

 if( charge == true && percent == 100 ) {
  ctx.fillStyle="black";
  ctx.shadowColor="white";
  ctx.shadowBlur = r;
  ctx.shadowOffsetY = 1;
  ctx.beginPath();
  ctx.moveTo( r+m*2, p_Top+r+m*3 );
  ctx.lineTo( r+m*6, p_Top+r+m*3 );
  ctx.lineTo( r+m*8, p_Top+r+m*1 );
  ctx.lineTo( r+m*12, p_Top+r+m*1 );
  ctx.lineTo( r+m*12, p_Top+r+m*2 );
  ctx.lineTo( r+m*14, p_Top+r+m*2 );
  ctx.lineTo( r+m*14, p_Top+r+m*3 );
  ctx.lineTo( r+m*12, p_Top+r+m*3 );
  ctx.lineTo( r+m*12, p_Top+r+m*5 );
  ctx.lineTo( r+m*14, p_Top+r+m*5 );
  ctx.lineTo( r+m*14, p_Top+r+m*6 );
  ctx.lineTo( r+m*12, p_Top+r+m*6 );
  ctx.lineTo( r+m*12, p_Top+r+m*7 );
  ctx.lineTo( r+m*8, p_Top+r+m*7 );
  ctx.lineTo( r+m*6, p_Top+r+m*5 );
  ctx.lineTo( r+m*2, p_Top+r+m*5 );
  ctx.closePath();
  ctx.fill();
 }

 return canvas.toDataURL("image/png");
}
影の分 (r) だけキャンバスの高さが変わります。
下にだけ高さを増すと、全体的に上に表示されるので、上下に r を加えるため、r×2を加算しています。
r の算出場所も変わりますが、canvas.height の値が変わるため、その後の canvas.height の使っている場所は height/2 に置き換えました。
高さが変わったので、基準点(p_Top, p_Bottom)も変更しています。

バッテリー本体と電極の背景それぞれに影を付けています。
その影は、透過率30%の白を指定しています。
1ピクセルずつズラして描画しているので、重なった部分の影は透過率60%となる計算です。

稲妻マークとプラグマークですが、0を基準としていたため、p_Top を加えました。


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

ちょっと濃い感じもしますが、実は、0.2でやったら薄かったので、これで良しとします。

今回の完成度に、我ながらニヤニヤしていたのですが、ニヤニヤしている場合ではありませんでした。
稲妻マークとプラグマークの影が、「影」なのです。
ボカしたものをズラした「影」なのです。
これらの下にも、影レイヤーを入れ、コンテキストのプロパティによる影指定をやめた方が良いのかもしれないのです。
これも試しておいた方が良さそうですね。


このサンプルのダウンロード ー> basic17_12.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");
 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;

 ctx.fillStyle = "rgba( 255, 255, 255, 0.3 )";
 draw_rRect( p_Right-r, p_Top + m*3+2, p_Right + m*2, p_Bottom - m*3+2, r );
 draw_rRect( p_Right-r, p_Top + m*3+1, p_Right + m*2, p_Bottom - m*3+1, r );
 var grad = ctx.createLinearGradient( 0, p_Top + m*3, 0, p_Bottom - m*3 );
 grad.addColorStop( 0, "#434956" );
 grad.addColorStop( 1, "#41495C" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right-r, p_Top + m*3, p_Right + m*2, p_Bottom - m*3, r );

 ctx.fillStyle = "rgba( 255, 255, 255, 0.3 )";
 draw_rRect( p_Left, p_Top+2, p_Right, p_Bottom+2, r );
 draw_rRect( p_Left, p_Top+1, p_Right, p_Bottom+1, r );
 grad = ctx.createLinearGradient( 0, p_Top, 0, p_Bottom );
 grad.addColorStop( 0, "#2F3648" );
 grad.addColorStop( 1, "#7B828F" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left, p_Top, p_Right, p_Bottom, r );

 grad = ctx.createLinearGradient( 0, p_Top + m*3 + r, 0, p_Bottom - m*3 - r );
 grad.addColorStop( 0, "#FDFDFD" );
 grad.addColorStop( 1, "#898989" );
 ctx.fillStyle = grad;
 draw_rRect( p_Right, p_Top + m*3 + r, p_Right + m, p_Bottom - m*3 - r , 0 );

 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
 grad.addColorStop( 0, "#4D4D4D" );
 grad.addColorStop( 3/16, "#4D4D4D" );
 grad.addColorStop( 14/16, "#C5C5C5" );
 grad.addColorStop( 1, "#C5C5C5" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Right-r, p_Bottom-r, 0 );

 var rectWidth = (p_Right-p_Left-r*2) * percent / 100;
 rectWidth *= percent<=20 ? 5 : 1;
 grad = ctx.createLinearGradient( 0, p_Top+r, 0, p_Bottom-r );
  if ( percent <= 20 ) {
  grad.addColorStop( 1/16, "#D83A3B" );
  grad.addColorStop( 3/16, "#FFA3B6" );
  grad.addColorStop( 6/16, "#FFA9B8" );
  grad.addColorStop( 13/16, "#8D0809" );
  grad.addColorStop( 14/16, "#860808" );
  grad.addColorStop( 16/16, "#9E2117" );
// rectWidth *= 5;
 }
 else {
  grad.addColorStop( 1/16, "#70D83A" );
  grad.addColorStop( 3/16, "#C5FF8D" );
  grad.addColorStop( 6/16, "#C5FF86" );
  grad.addColorStop( 13/16, "#2B8C0A" );
  grad.addColorStop( 14/16, "#278608" );
  grad.addColorStop( 16/16, "#44A31D" );
 }
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 grad = ctx.createLinearGradient( p_Left+r, 0, p_Left+r+rectWidth, 0 );
 var wp = r/rectWidth;
 grad.addColorStop( 0, "rgba( 255, 255, 255, 0.3 )" );
 grad.addColorStop( wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1-wp, "rgba( 255, 255, 255, 0.0 )" );
 grad.addColorStop( 1, "rgba( 255, 255, 255, 0.3 )" );
 ctx.fillStyle = grad;
 draw_rRect( p_Left+r, p_Top+r, p_Left+r+rectWidth, p_Bottom-r, 0 );

 function draw_charging( offset ) {
  ctx.beginPath();
  ctx.moveTo( r+m*11.5, p_Top+r +offset );
  ctx.lineTo( r+m*9, p_Top+r+m*3.5 +offset );
  ctx.lineTo( r+m*13, p_Top+r+m*3.5 +offset );
  ctx.lineTo( r+m*5.5, p_Top+r+m*8 +offset );
  ctx.lineTo( r+m*8, p_Top+r+m*4.5 +offset );
  ctx.lineTo( r+m*4, p_Top+r+m*4.5 +offset );
  ctx.closePath();
  ctx.fill();
 }
 if( charge == true && percent < 100 ) {
  ctx.fillStyle = "rgba( 255, 255, 255, 0.3 )";
  draw_charging( 2 );
  draw_charging( 1 );
  ctx.fillStyle="black";
  draw_charging( 0 );
 }

 function draw_charged( offset ) {
  ctx.beginPath();
  ctx.moveTo( r+m*2, p_Top+r+m*3 +offset );
  ctx.lineTo( r+m*6, p_Top+r+m*3 +offset );
  ctx.lineTo( r+m*8, p_Top+r+m*1 +offset );
  ctx.lineTo( r+m*12, p_Top+r+m*1 +offset );
  ctx.lineTo( r+m*12, p_Top+r+m*2 +offset );
  ctx.lineTo( r+m*14, p_Top+r+m*2 +offset );
  ctx.lineTo( r+m*14, p_Top+r+m*3 +offset );
  ctx.lineTo( r+m*12, p_Top+r+m*3 +offset );
  ctx.lineTo( r+m*12, p_Top+r+m*5 +offset );
  ctx.lineTo( r+m*14, p_Top+r+m*5 +offset );
  ctx.lineTo( r+m*14, p_Top+r+m*6 +offset );
  ctx.lineTo( r+m*12, p_Top+r+m*6 +offset );
  ctx.lineTo( r+m*12, p_Top+r+m*7 +offset );
  ctx.lineTo( r+m*8, p_Top+r+m*7 +offset );
  ctx.lineTo( r+m*6, p_Top+r+m*5 +offset );
  ctx.lineTo( r+m*2, p_Top+r+m*5 +offset );
  ctx.closePath();
  ctx.fill();
 }
 if( charge == true && percent == 100 ) {
  ctx.fillStyle = "rgba( 255, 255, 255, 0.3 )";
  draw_charged( 2 );
  draw_charged( 1 );
  ctx.fillStyle="black";
  draw_charged( 0 );
 }

 return canvas.toDataURL("image/png");
}
バッテリー背景の影と同様にするには、関数化が必要でしたので、サクッと関数化しました。
ほぼコピペで済み、ズラす量を offset とし、これを引数にしました。



細くて分かり辛いと思いますが、全体に広がる影よりも、下方向のみの影の方がしっくりきます。
やはり、オリジナルの完成度は高いですね。


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



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


保存する

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

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

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