ブラウザ上で動くお絵描きアプリについてプログラムをまとめ、公開しておきます。
以下の環境で動作確認をしています。
環境: Windows パソコン、Microsoft Edge ブラウザ
背景 ~ ブラウザ上でのお絵描き!
このサイトではこれまで、Windows や Linux のブラウザで動く Web アプリをまとめ、公開してきています。
描画については、画像の劣化が少ないベクター形式(svg 形式)を出力する Python のサンプルスクリプトなどをまとめ、公開してきています。
ベクター形式(svg 形式)のサンプルはいくつかまとめてきましたので、ラスター形式(png 形式、jpg 形式など)についても、何かブラウザで動くようなオーソドックスなアプリを作って、スクリプトや使い方をまとめておきたいところです。
ということで、ブラウザで動くお絵描きアプリについてプログラムをまとめ、公開しておくことにします。
ブラウザでの描画程度であれば、JavaScript を使うのが自然です。そこで今回は JavaScript を使うことにします。
HTML ファイルを作ってローカルに保存しておけば、新たなソフトウェアのインストールなどの手間もなく、カスタマイズして活用できることになります。
また、描画に関する機能をまとめておくことで、今後、サーバーと連携させるなど、アプリ作成の選択肢が広がることになります。
お絵描きアプリの動作サンプル
動作確認用のサンプルを以下に貼っておきます。以下のアプリ上で、マウスでお絵描きをしてみてください。
描いた画像は、png 形式でのダウンロードが可能です。
※ パソコンでの表示・使用を想定しています。スマートフォンについては画面レイアウトの確認をしておりません。ご理解いただきますようお願いいたします。
使い方
使い方は、上記のサンプルのとおりです。
アプリの見た目のまま、すぐに使えるのではないかと思います。
・ 白色の領域でマウスでドラッグすることで描画できます。
・ 描いた画像を消す場合は、[clear] ボタンをクリックします。
・ ラインを描きなおす場合は、[undo] ボタンをクリックします。2本分のラインまで、描画のやり直しが可能です。
・ 画像を保存する場合は、[save] ボタンをクリックしたのち、”download_link” となっている部分をクリックします。すると、画像をパソコンに png 形式でダウンロードできます。
・ ペンの色は、左側の色相環の部分をクリックすることで設定が可能です。
・ スライダーを使って、色相 hue、彩度 saturation、明度 brightness の値を設定することが可能です。スライダーの操作は、パソコンのキーボードの矢印キー(左、右、上、下)も使えます。
・ 操作画面左側に3つの矩形のパレット(デフォルトで、赤、緑、青の正方形となっているところ)があります。このパレットをクリックすることで、各色を設定できます。
描画用に設定されているパレットには、白色の枠が表示されます。パレットを選択した状態で、色相 hue、彩度 saturation、明度 brightness を微調整すると、微調整した色がパレットで保持されます。3つあるパレットをクリックして色を調整することで、微調整した色を3色まで保持でき、それぞれ1クリックで設定できます。
・ ペンの太さ(ラインの幅)は、画面左下に設定領域があります。画面の下側をスクロールすると出てきます。サイトのスペースの都合上、表示が隠れているだけです。
設定方法
① 以下を参考に、パソコン内に drawing_web_app1.html 等の名前でテキストファイルを作成し、以下のサンプルコードを貼りつけて保存してください。
例: c:\user\drawing_web_app1/drawing_web_app1.html
② ダブルクリックすると、ブラウザで起動できます。
サンプルコードの説明
・ サンプルコードで、document.addEventListener( … ) としたところで、マウスイベント、キーボードのイベントを監視しています。イベントを検出すると、mousemove1() 関数、mousedown1() 関数などを実行するよう設定しています。
・ let draw0 = … としたところで、描画のパラメータを定義しています。具体的には、描画のステータス、マウスドラッグ時の2点の座標、描画時の色やペンの太さなどに対応するパラメータを定義しています。
たとえば、描画を開始する際は、draw0.start1 が true となり、描画が終了すると、false になります。
・ 以後、色相環を RGB に変換するなどの関数を定義しています。
・ 色相 hue は、角度が 0 度、120度、240度となるとき、赤 R、緑 G、青 B に対応するよう定義されています。
画面で、右側が赤 R、左下側が緑 G、左上側が青 B に対応しています。
0~360度の角度を指定することで、これらの中間色を表現できます。モニター画面の座標は、X 軸方向は画面右方向、Y 軸方向は画面下方向となっていますので、X 軸方向(右方向)を 0 度として、時計回りに角度が定義されています。数学で出てくる角度 θ の取り方とは(見かけの)回転方向が逆になっています。
・ 描画領域(<canvas>…</canvas> で id=”canvas11″ とした領域)でマウスをクリックすると、mousedown1() が実行され、描画開始となります。
マウスをクリックした状態のまま、マウスを動かすと、mousemove1() 関数が繰り返し呼び出され、描画をし続ける動きとなります。
具体的には、mousemove1() 関数内で、draw1() 関数を呼び出しており、連続して取得した2点 (x1, y1)、(x2, y2) の間にラインを描いています。
マウスボタンを離すと、mouseup1() 関数が実行されて描画が終了します。また、マウスポインタが描画領域から外れても、描画を終了するようにしています。
・ <body> ~ </body> の部分で、表示画面を設定しています。
<canvas …></canvas> となっているところが画面上の各画像領域に対応しています。id=”canvas11″ としているところが、描画用の領域です。
少し考察 ~ 画像処理の機能はどのように扱うのが適切か
今回、ブラウザと JavaScript/HTML/CSS を使ってお絵かき用のアプリを作ってみました。
JavaScript 程度しか使っていませんが、描画に必要な基本機能は、ほぼ実現できることがわかります。
素の JavaScript だけで描画の機能を実現しているため、ローカルパソコンで/スタンドアロンで動きます。ブラウザやメモ帳は、パソコンに標準でついていますので、OS にかかわらず、何らのインストールをすることなく、描画や、座標・色相の計算、画像の生成が可能です。
移植性という観点で見ると、Linux やスマートフォンなど、異なる OS への移植性もよいといえます。サーバーもコンパイラも不要です。
ブラウザ + JavaScript の組み合わせで何らかの機能を実現したとき、不足してくる機能といえば、データの読み出しや保存となります。
データの保存・読み出しの機能を充実させるには、サーバーと連携して Web アプリ化すればよいですが、サーバーを動かす必要があります。
あるいは、Python + tkinter の組み合わせとするか、Windows などのコンパイラを使って、ローカルアプリ化すれば可能です。しかし、この場合、どのような OS でも動く、任意のブラウザ上で動くというメリットをあきらめる必要が出てくるため、悩ましいところです。
画像データについては、以前、SVG 形式についてサンプルスクリプトをまとめています。クライアントとサーバーという観点で見ると、画像データをネットワーク環境で扱いやすくするためには、ベクター形式(svg 形式)とするか、ラスター形式を扱うにしても、今回のように、クライアント側で描画するのがよさそうです。サーバーと連携させる場合は、マウスの軌跡やペンの設定値などのパラメータのみをサーバーと送受信させるようにすることで、描画機能を実現するのがよさそうです。
まとめ
ローカルのブラウザで動くお絵描きアプリについて JavaScript/HTML のスクリプトをまとめました。
これでブラウザ上での描画機能、入出力についても自由に扱えるようになりました。
他にも、ベクター形式で描画するスクリプトや、フラクタル画像を描画するスクリプトなどをまとめています。関心のある方は、下記の関連リンクなども参照してみてください。
関連リンク
・ ベクター形式でお絵描きをしてみる 【JavaScript】
・ ベクター画像で世界時計を作る 【JavaScript】
・ フラクタル図形を描画してみる 【Python & tkinter】
・ Python で自由曲線を描く 【HTML & SVG】
サンプルコード drawing_web_app1.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>drawing_web_app v0.1</title>
<style type="text/css">
.range_color1[ type="range" ] {
-webkit-appearance: none;
appearance: none;
background-color: #cccccc;
height: 4px;
width: 140px;
}
</style>
<script>
document.addEventListener('mousemove' , mousemove1 );
document.addEventListener('mousedown' , mousedown1 );
document.addEventListener('mouseup' , mouseup1 );
document.addEventListener('keyup' , mouseup1 );
let draw0 = {
start1: false,
x1: -1,
y1: -1,
x2: -1,
y2: -1,
w1: -1,
h1: -1,
pen1: 18,
pen2: 5,
pal1: 1,
hue0: 000,
sat0: 100,
bri0: 100,
rgb0: "#ff0000",
hue1: 000,
sat1: 100,
bri1: 100,
rgb1: "#ff0000",
hue2: 120,
sat2: 100,
bri2: 100,
rgb2: "#00ff00",
hue3: 240,
sat3: 100,
bri3: 100,
rgb3: "#0000ff",
}
function init1() {
draw0.start1 = false,
draw0.x1 = -1;
draw0.y1 = -1;
draw0.x2 = -1;
draw0.y2 = -1;
}
function init2() {
const object1 = document.getElementById( "canvas11" );
draw0.w1 = object1.width;
draw0.h1 = object1.height;
draw0.rgb0 = hsv2rgb1( draw0.hue0, draw0.sat0, draw0.bri0 );
draw0.rgb1 = hsv2rgb1( draw0.hue1, draw0.sat1, draw0.bri1 );
draw0.rgb2 = hsv2rgb1( draw0.hue2, draw0.sat2, draw0.bri2 );
draw0.rgb3 = hsv2rgb1( draw0.hue3, draw0.sat3, draw0.bri3 );
}
window.onload = function() {
init1();
init2();
set_def2range1();
draw_palette1();
draw_palette5();
draw_palette4();
set_size1();
}
function set_def2range1() {
draw0.rgb0 = hsv2rgb1( draw0.hue0, draw0.sat0, draw0.bri0 );
const object1 = document.getElementById( "range1" );
const object2 = document.getElementById( "range2" );
const object3 = document.getElementById( "range3" );
object1.value = draw0.hue0;
object2.value = draw0.sat0;
object3.value = draw0.bri0;
show_hsv1();
}
function set_range2def1( event1 ) {
const object1 = document.getElementById( "range1" );
const object2 = document.getElementById( "range2" );
const object3 = document.getElementById( "range3" );
draw0.hue0 = object1.value;
draw0.sat0 = object2.value;
draw0.bri0 = object3.value;
draw0.rgb0 = hsv2rgb1( draw0.hue0, draw0.sat0, draw0.bri0 );
show_hsv1();
set_def2pal1();
draw_palette4();
}
function set_def2pal1() {
if ( draw0.pal1 == 1 ) {
draw0.hue1 = draw0.hue0;
draw0.sat1 = draw0.sat0;
draw0.bri1 = draw0.bri0;
draw0.rgb1 = draw0.rgb0;
} else if ( draw0.pal1 == 2 ) {
draw0.hue2 = draw0.hue0;
draw0.sat2 = draw0.sat0;
draw0.bri2 = draw0.bri0;
draw0.rgb2 = draw0.rgb0;
} else if ( draw0.pal1 == 3 ) {
draw0.hue3 = draw0.hue0;
draw0.sat3 = draw0.sat0;
draw0.bri3 = draw0.bri0;
draw0.rgb3 = draw0.rgb0;
}
}
function set_pal2def1() {
if ( draw0.pal1 == 1 ) {
draw0.hue0 = draw0.hue1;
draw0.sat0 = draw0.sat1;
draw0.bri0 = draw0.bri1;
draw0.rgb0 = draw0.rgb1;
} else if ( draw0.pal1 == 2 ) {
draw0.hue0 = draw0.hue2;
draw0.sat0 = draw0.sat2;
draw0.bri0 = draw0.bri2;
draw0.rgb0 = draw0.rgb2;
} else if ( draw0.pal1 == 3 ) {
draw0.hue0 = draw0.hue3;
draw0.sat0 = draw0.sat3;
draw0.bri0 = draw0.bri3;
draw0.rgb0 = draw0.rgb3;
}
set_def2range1();
draw_border1();
}
function mouseup1( event1 ) {
init1();
id1 = event1.target.id;
if ( id1 == "canvas11" ) {
mousemove1( event1 );
} else if ( id1 == "canvas12" ) {
calc_hue1( event1 );
} else if ( id1 == "canvas21" ) {
draw0.pal1 = 1;
set_pal2def1();
} else if ( id1 == "canvas22" ) {
draw0.pal1 = 2;
set_pal2def1();
} else if ( id1 == "canvas23" ) {
draw0.pal1 = 3;
set_pal2def1();
} else if ( id1 == "range1" || id1 == "range2" || id1 == "range3" ) {
set_range2def1();
} else if ( id1 == "canvas31" ) {
draw0.pen1 = 1;
draw0.pen2 = 1;
draw_palette5();
} else if ( id1 == "canvas32" ) {
draw0.pen1 = 6;
draw0.pen2 = 2;
draw_palette5();
} else if ( id1 == "canvas33" ) {
draw0.pen1 = 10;
draw0.pen2 = 3;
draw_palette5();
} else if ( id1 == "canvas34" ) {
draw0.pen1 = 14;
draw0.pen2 = 4;
draw_palette5();
} else if ( id1 == "canvas35" ) {
draw0.pen1 = 18;
draw0.pen2 = 5;
draw_palette5();
} else if ( id1 == "canvas36" ) {
draw0.pen1 = 22;
draw0.pen2 = 6;
draw_palette5();
}
}
function calc_hue1( event1 ) {
let x1 = event1.offsetX - 70;
let y1 = event1.offsetY - 70;
let h1 = 360.0/( 2.0 * Math.PI ) * Math.atan2( y1, x1 );
if (h1 < 0.0 ) {
h1 = h1 + 360.0;
} else if (h1 > 360.0) {
h1 = h1 - 360.0;
}
h1 = parseInt( h1, 10 );
const object1 = document.getElementById( "range1" );
object1.value = h1;
set_range2def1();
}
function draw_border1() {
const object1 = document.getElementById( "canvas21" );
const object2 = document.getElementById( "canvas22" );
const object3 = document.getElementById( "canvas23" );
if ( draw0.pal1 == 1 ) {
object1.style.border = "solid 3px #dddddd";
object2.style.border = "solid 3px #000000";
object3.style.border = "solid 3px #000000";
} else if ( draw0.pal1 == 2 ) {
object1.style.border = "solid 3px #000000";
object2.style.border = "solid 3px #dddddd";
object3.style.border = "solid 3px #000000";
} else if ( draw0.pal1 == 3 ) {
object1.style.border = "solid 3px #000000";
object2.style.border = "solid 3px #000000";
object3.style.border = "solid 3px #dddddd";
}
}
function format1( val1 ) {
return ('000' + ( val1 ).toString(10).toUpperCase()).substr(-3);
}
function show_hsv1() {
const object3 = document.getElementById( "label1" );
const object4 = document.getElementById( "label2" );
const object5 = document.getElementById( "label3" );
object3.innerHTML = format1( draw0.hue0 ) + " : hue" ;
object4.innerHTML = format1( draw0.sat0 ) + " : saturation" ;
object5.innerHTML = format1( draw0.bri0 ) + " : brightness" ;
}
function draw_palette5() {
draw_palette3( "canvas31", 1, 1 );
draw_palette3( "canvas32", 2, 3 );
draw_palette3( "canvas33", 3, 5 );
draw_palette3( "canvas34", 4, 7 );
draw_palette3( "canvas35", 5, 9 );
draw_palette3( "canvas36", 6, 11 );
}
function draw_palette4() {
draw_border1();
draw_palette2( "canvas21", draw0.rgb1 );
draw_palette2( "canvas22", draw0.rgb2 );
draw_palette2( "canvas23", draw0.rgb3 );
}
function draw_palette3( canvas0, pen0, r1 ) {
let rgb1 = "#aaaaaa";
if ( draw0.pen2 == pen0 ) {
rgb1 = "#ffffff";
}
draw_palette2( canvas0, rgb1 );
context1.beginPath();
context1.arc( 15, 15, r1, 0 * Math.PI/180, 360 * Math.PI, false );
context1.fillStyle = "rgba(0,0,0,1.0)";
context1.fill();
context1.strokeStyle = "rgba(0,0,0,1.0)";
context1.lineWidth = 1;
context1.stroke();
}
function draw_palette2( canvas0, rgb1 ) {
const object1 = document.getElementById( canvas0 );
context1 = object1.getContext( "2d" );
context1.fillStyle = rgb1;
context1.fillRect( 0, 0, 30, 30 );
}
function int2hex1( n1 ) {
return ('00' + n1.toString(16).toUpperCase()).substr(-2);
}
function float2hex2( r1, g1, b1 ) {
return "#" + float2hex1( r1 ) + float2hex1( g1 ) + float2hex1( b1 );
}
function float2hex1( v0 ) {
n1 = parseInt( v0, 10 );
if ( n1 < 0 ) { n1 = 0; }
else if ( n1 > 255 ) { n1 = 255; }
str1 = int2hex1( n1 );
return str1;
}
function hsv2rgb1( h0, s0, v0 ) {
if (h0 < 0.0) { h0 = 0.0; }
else if ( h0 > 359.0 ) { h0 = 359.0; }
h1 = h0/60.0;
s1 = s0/100.0;
v0 = v0/100.0;
h2 = h1 - parseInt( h1 );
v1 = v0 * ( 1.0 - s1 );
v2 = v0 * ( 1.0 - s1 * h2 );
v3 = v0 * ( 1.0 - s1 * ( 1.0 - h2 ) );
if ( s1 == 0.0 ) {
r1 = v0;
g1 = v0;
b1 = v0;
} else if ( h1 < 0.0 ) {
r1 = v0;
g1 = v3;
b1 = v1;
} else if ( h1 < 1.0 ) {
r1 = v0;
g1 = v3;
b1 = v1;
} else if ( h1 < 2.0 ) {
r1 = v2;
g1 = v0;
b1 = v1;
} else if ( h1 < 3.0 ) {
r1 = v1;
g1 = v0;
b1 = v3;
} else if ( h1 < 4.0 ) {
r1 = v1;
g1 = v2;
b1 = v0;
} else if ( h1 < 5.0 ) {
r1 = v3;
g1 = v1;
b1 = v0;
} else {
r1 = v0;
g1 = v1;
b1 = v2;
}
r2 = 255.0 * r1;
g2 = 255.0 * g1;
b2 = 255.0 * b1;
return float2hex2( r2, g2, b2 );
}
function draw_palette1() {
const object1 = document.getElementById( "canvas12" );
context1 = object1.getContext( "2d" );
context1.lineWidth = 20;
let the1 = 0.0;
let r1 = 50.0;
let cx1 = 70.0;
let cy1 = 70.0;
let x1 = cx1 + r1;
let y1 = cy1;
let x2 = x1;
let y2 = y1;
for (let i1 = 1; i1 < 361; i1++) {
the1 = i1/360 * 2 * Math.PI;
x1 = cx1 + r1 * Math.cos( the1 );
y1 = cy1 + r1 * Math.sin( the1 );
context1.strokeStyle = hsv2rgb1( i1-1, 100, 100 );
context1.beginPath();
context1.moveTo( x2, y2 );
context1.lineTo( x1, y1 );
context1.stroke();
x2 = x1;
y2 = y1;
}
}
function mousedown1( event1 ) {
id1 = event1.target.id;
if ( id1 == "canvas11" ) {
backup_img1();
draw0.x1 = event1.offsetX;
draw0.y1 = event1.offsetY;
draw0.start1 = true;
}
}
function mousemove1( event1 ) {
if ( (event1.offsetX < 1) || (event1.offsetY < 1) || (event1.offsetX > draw0.w1) || (event1.offsetY > draw0.h1 ) ) {
init1( event1 );
}
if ( draw0.start1 ) {
draw1( event1 );
}
show_status1( event1 );
}
function draw1( event1 ) {
draw0.x2 = draw0.x1;
draw0.y2 = draw0.y1;
draw0.x1 = event1.offsetX;
draw0.y1 = event1.offsetY;
const object1 = document.getElementById( "canvas11" );
const context1 = object1.getContext( "2d" );
context1.lineCap = "round";
context1.lineJoin = "round";
context1.lineWidth = draw0.pen1;
context1.strokeStyle = draw0.rgb0;
context1.beginPath();
context1.moveTo( draw0.x1, draw0.y1 );
context1.lineTo( draw0.x2, draw0.y2 );
context1.stroke();
}
function clear1() {
clear0( "canvas11" );
clear0( "canvas13" );
clear0( "canvas14" );
}
function clear0( str1 ) {
const object1 = document.getElementById( str1 );
const context1 = object1.getContext( "2d" );
context1.clearRect(0, 0, draw0.w1, draw0.h1 );
}
function download1() {
const object1 = document.getElementById( "canvas11" );
const link1 = document.getElementById( "link1" );
link1.href = object1.toDataURL();
}
function set_size1() {
const object1 = document.getElementById( "canvas11" );
const object2 = document.getElementById( "canvas13" );
const object3 = document.getElementById( "canvas14" );
object1.width = document.getElementById( "text1" ).value;
object1.height = document.getElementById( "text2" ).value;
object2.width = object1.width;
object2.height = object1.height;
object3.width = object1.width;
object3.height = object1.height;
draw0.w1 = object1.width;
draw0.h1 = object1.height;
}
function backup_img1() {
copy_img0( "canvas13", "canvas14" );
copy_img0( "canvas11", "canvas13" );
}
function undo_img1() {
clear0( "canvas11" );
copy_img0( "canvas13", "canvas11" );
clear0( "canvas13" );
copy_img0( "canvas14", "canvas13" );
}
function copy_img0( id1, id2 ) {
const object1 = document.getElementById( id1 );
const object2 = document.getElementById( id2 );
const target2 = object2.getContext( "2d" );
const img1 = document.createElement( "img" );
img1.src = object1.toDataURL();
img1.onload = function() {
target2.drawImage( img1, 0, 0, object2.width, object2.height );
}
}
</script>
</head>
<body style="background-color:#111111; color:#bbbbbb; font-family:arial " >
<table>
<tr><td width="150">drawing web app</td><td>
<button onclick="clear1()" style="background-color:#cccccc; color:#000000 " >clear</button>
<button onclick="undo_img1()" style="background-color:#cccccc; color:#000000 " >undo</button>
<button onclick="download1()" style="background-color:#cccccc; color:#000000 ">save</button>
<a id="link1" download="image01.png">download_link</a><br>
</td></tr>
<tr><td valign="top">
<br>
<canvas id="canvas12" width="140" height="140" style="border:1px solid #000000; background-color:#222222 " ></canvas>
<br>
<canvas id="canvas21" width="030" height="030" ></canvas>
<canvas id="canvas22" width="030" height="030" ></canvas>
<canvas id="canvas23" width="030" height="030" ></canvas>
<br>
<label id="label1" for="range1" style="vertical-align:-10px" >000 : hue</label><br>
<input type="range" id="range1" class="range_color1" min="0" max="360" step="1" value="250"><br>
<label id="label2" for="range2" style="vertical-align:-10px" >000 : saturation</label><br>
<input type="range" id="range2" class="range_color1" min="0" max="100" step="1" value="100"><br>
<label id="label3" for="range3" style="vertical-align:-10px" >000: brightness</label><br>
<input type="range" id="range3" class="range_color1" min="0" max="100" step="1" value="100"><br>
<br>
<canvas id="canvas31" width="030" height="030" style="border:3px solid #000000; background-color:#ffffff " ></canvas>
<canvas id="canvas32" width="030" height="030" style="border:3px solid #000000; background-color:#ffffff " ></canvas>
<canvas id="canvas33" width="030" height="030" style="border:3px solid #000000; background-color:#ffffff " ></canvas><br>
<canvas id="canvas34" width="030" height="030" style="border:3px solid #000000; background-color:#ffffff " ></canvas>
<canvas id="canvas35" width="030" height="030" style="border:3px solid #000000; background-color:#ffffff " ></canvas>
<canvas id="canvas36" width="030" height="030" style="border:3px solid #000000; background-color:#ffffff " ></canvas><br>
<br><br>
<input type="text" id="text1" size="5" name="text1" value="800" style="background-color:#eeeeee " >
<label id="label4" for="text1">: width </label><br>
<input type="text" id="text2" size="5" name="text2" value="600" style="background-color:#eeeeee " >
<label id="label5" for="text2">: height </label><br>
<button onclick="set_size1()" style="background-color:#cccccc; color:#000000 " >clear & set size</button><br>
</td>
<td valign="top"><br>
<canvas id="canvas11" width="800" height="600" style="border:1px solid #000000; background-color:#f4f8ff; " ></canvas><br>
<canvas id="canvas13" width="800" height="600" style="border:1px solid #000000; background-color:#f4f8ff; display:none; " ></canvas>
<canvas id="canvas14" width="800" height="600" style="border:1px solid #000000; background-color:#f4f8ff; display:none; " ></canvas>
</td></tr>
</table>
</body>
</html>