マウスで選択した画像範囲を移動して貼り付ける[Canvasの矩形選択3]
HTML5のCanvasとJavaScriptを使用して、マウスで選択した画像範囲を移動して貼り付ける画像関連のサンプルです。「Canvasの矩形選択」シリーズの最終話となります。
ブラウザによってはローカル環境ではセキュリティエラーでサンプルを実行できませんので、サーバーにアップしてから動作確認をして下さい。
実際に試してみる
マウスで範囲を指定すると、その矩形をドロップで移動できます。
次に矩形以外のキャンバスをクリックすると、矩形をキャンバスへ貼り付けする事が可能です。
選択範囲の矩形はダブルクリックで解除できます。また、範囲を指定中にキャンバスの範囲外でマウスアップをした場合は、範囲選択が無効となります。
動作環境
・ Internet Explorer 11(32bit/64bit)
・ Microsoft Edge(32bit)
・ Chrome / FireFoxの最新版
※タブレット、スマートフォンやその他のブラウザは未確認です。
サンプルコード
座標系の操作なので、ちょっとややこしいです。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script> // キャンバス var src_canvas,src_ctx; // ソースキャンバス var dst_canvas,dst_ctx; // ダストキャンバス(メイン) var rec_canvas,rec_ctx; // 矩形キャンバス // イメージ var image; // 矩形用 var RECT_BORDER_WIDTH = 2; // 線の太さ var RECT_MIN_WIDTH = RECT_MIN_HEIGHT = 3; // 最小サイズ var rect_down_flg = false; var rect_sx = rect_sy = 0; var rect_ex = rect_ey = 0; var rect_move_flag = false; var rect_move_x = rect_move_y = 0; // 色の反転 function getTurningAround(color) { // 灰色は白にする if(color >= 88 && color <= 168){ return 255; // 色を反転する }else{ return 255 - color; } } // キャンバスの初期化 function initCanvas() { dst_ctx.drawImage(src_canvas,0,0); rect_down_flg = false; rect_move_flag = false; rect_sx = rect_ex = 0; rect_sy = rect_ey = 0; rect_move_x = rect_move_y = 0; rec_canvas.style.display ='none'; rec_canvas.width = rec_canvas.height = 1; } window.onload = function () { // ---------------------- // 変数 // ---------------------- src_canvas = document.getElementById("SrcCanvas"); src_ctx = src_canvas.getContext("2d"); dst_canvas = document.getElementById("DstCanvas"); dst_ctx = dst_canvas.getContext("2d"); rec_canvas = document.getElementById("RecCanvas"); rec_ctx = rec_canvas.getContext("2d"); rec_canvas.width = rec_canvas.height = 1; image = document.getElementById("img_source"); // ---------------------- // キャンバス用(メイン) // ---------------------- dst_canvas.onmousedown = function(event){ // 矩形キャンバスをソースキャンバスへ描画する if(rec_canvas.style.display == 'block'){ // 出力先キャンバスの位置情報 var rect = dst_canvas.getBoundingClientRect() ; // 矩形キャンバスのXY座標 var left = parseInt(rec_canvas.style.left.replace("px","")); var top = parseInt(rec_canvas.style.top.replace("px","")); // 矩形キャンバスが移動している場合のみ描画する if (!( ((left + RECT_BORDER_WIDTH) == (rect.left + Math.min(rect_sx,rect_ex))) && ((top + RECT_BORDER_WIDTH) == (rect.top + Math.min(rect_sy,rect_ey))) ) ){ // IE/Edge if (window.navigator.msSaveBlob) { src_ctx.drawImage(rec_canvas, Math.round(left - rect.left + RECT_BORDER_WIDTH - window.pageXOffset), Math.round(top - rect.top + RECT_BORDER_WIDTH - window.pageYOffset)); // Chrome/FireFoxなど }else{ src_ctx.drawImage(rec_canvas, Math.floor(left - rect.left + RECT_BORDER_WIDTH - window.pageXOffset), Math.floor(top - rect.top + RECT_BORDER_WIDTH - window.pageYOffset)); } } } rect_down_flg = true; // 座標を求める var rect = event.target.getBoundingClientRect(); rect_sx = rect_ex = event.clientX - rect.left ; rect_sy = rect_ey = event.clientY - rect.top ; // 矩形の枠色を反転させる var imagedata = dst_ctx.getImageData(rect_sx, rect_sy, 1, 1); dst_ctx.strokeStyle = 'rgb(' + getTurningAround(imagedata.data[0]) + ',' + getTurningAround(imagedata.data[1]) + ',' + getTurningAround(imagedata.data[2]) + ')'; // 線の太さ dst_ctx.lineWidth = RECT_BORDER_WIDTH; // 矩形の枠線を点線にする dst_ctx.setLineDash([2, 3]); // 矩形キャンバスの非表示 rec_canvas.style.display ='none'; // canvasのドロップを禁止する(IE/Edge用) return false; }; dst_canvas.onmousemove = function(event){ if(rect_down_flg){ // 座標を求める var rect = event.target.getBoundingClientRect(); rect_ex = event.clientX - rect.left ; rect_ey = event.clientY - rect.top ; // 元画像の再描画 dst_ctx.drawImage(src_canvas,0,0); // 矩形の描画 dst_ctx.beginPath(); // 上 dst_ctx.moveTo(rect_sx,rect_sy); dst_ctx.lineTo(rect_ex,rect_sy); // 下 dst_ctx.moveTo(rect_sx,rect_ey); dst_ctx.lineTo(rect_ex,rect_ey); // 右 dst_ctx.moveTo(rect_ex,rect_sy); dst_ctx.lineTo(rect_ex,rect_ey); // 左 dst_ctx.moveTo(rect_sx,rect_sy); dst_ctx.lineTo(rect_sx,rect_ey); dst_ctx.stroke(); } }; dst_canvas.onmouseup = function(event){ // キャンバスの範囲外は無効にする if(rect_sx === rect_ex && rect_sy === rect_ey){ // キャンバスの初期化 initCanvas(); } // 矩形の画像を取得する if(rect_down_flg){ // 矩形のサイズ rec_canvas.width = Math.abs(rect_sx - rect_ex); rec_canvas.height = Math.abs(rect_sy - rect_ey); // 指定のサイズ以下は無効にする[3x3] if(!(rec_canvas.width >= RECT_MIN_WIDTH && rec_canvas.height >= RECT_MIN_HEIGHT)){ // キャンバスの初期化 initCanvas(); }else{ // 矩形キャンバスへ画像の転送 // IE/Edge if (window.navigator.msSaveBlob) { rec_ctx.drawImage(src_canvas, Math.round(Math.min(rect_sx,rect_ex)),Math.round(Math.min(rect_sy,rect_ey)), Math.max(rect_sx - rect_ex,rect_ex - rect_sx),Math.max(rect_sy - rect_ey ,rect_ey - rect_sy), 0,0,rec_canvas.width,rec_canvas.height); // Chrome/FireFoxなど }else{ rec_ctx.drawImage(src_canvas, Math.floor(Math.min(rect_sx,rect_ex)),Math.floor(Math.min(rect_sy,rect_ey)), Math.max(rect_sx - rect_ex,rect_ex - rect_sx),Math.max(rect_sy - rect_ey ,rect_ey - rect_sy), 0,0,rec_canvas.width,rec_canvas.height); } // 矩形キャンバスを選択範囲へ移動する if(rect_sx >= rect_ex){ rec_canvas.style.left = (event.clientX - RECT_BORDER_WIDTH + window.pageXOffset) + 'px'; }else{ rec_canvas.style.left = (event.clientX - rec_canvas.width - RECT_BORDER_WIDTH + window.pageXOffset) + 'px'; } if(rect_sy >= rect_ey){ rec_canvas.style.top = (event.clientY - RECT_BORDER_WIDTH + window.pageYOffset) + 'px'; }else{ rec_canvas.style.top = (event.clientY - rec_canvas.height - RECT_BORDER_WIDTH + window.pageYOffset) + 'px'; } // 矩形キャンバスの表示 rec_canvas.style.display ='block'; // 矩形選択のクリア dst_ctx.drawImage(src_canvas,0,0); } } rect_down_flg = false; }; // ---------------------- // 矩形キャンバス(移動用) // ---------------------- rec_canvas.onmousedown = function(event){ rect_move_flag = true; var rect = event.target.getBoundingClientRect(); rect_move_x = event.clientX - rect.left; rect_move_y = event.clientY - rect.top; // Edgeの処理(動作はFireFoxと同じ) // ※この処理がないと移動した時のスクロールがおかしくなる if (window.navigator.msSaveBlob) { if (navigator.userAgent.toLowerCase().indexOf('edge') > -1){ return false; } } }; document.onmouseup = function(){ rect_move_flag = false; }; document.onmousemove = function(event){ if(rect_move_flag){ rec_canvas.style.left = (event.clientX - rect_move_x + window.pageXOffset) + 'px'; rec_canvas.style.top = (event.clientY - rect_move_y + window.pageYOffset) + 'px'; } }; // 矩形キャンバスの解除 rec_canvas.ondblclick = function(event){ rec_canvas.width = rec_canvas.height = 1; rec_canvas.style.display ='none'; // Chromeでダブルクリックした際に、他の部品にフォーカスが移るのを解除 if(!document.all){ window.getSelection().removeAllRanges(); } }; } function onDragOver(event){ event.preventDefault(); } function onDrop(event){ onAddFile(event); event.preventDefault(); } // ユーザーによりファイルが追加された function onAddFile(event) { var files; var reader = new FileReader(); if(event.target.files){ files = event.target.files; }else{ files = event.dataTransfer.files; } // ファイルが読み込まれた reader.onload = function (event) { // イメージが読み込まれた image.onload = function (){ src_canvas.width = dst_canvas.width = image.width; src_canvas.height = dst_canvas.height = image.height; // キャンバスに画像を描画 src_ctx.drawImage(image,0,0); //初期化 initCanvas(); }; // イメージが読み込めない image.onerror = function (){ alert('このファイルは読み込めません。'); }; image.src = reader.result; }; if (files[0]){ reader.readAsDataURL(files[0]); document.getElementById("inputfile").value = ''; } } </script> </head> <body ondrop="onDrop(event);" ondragover="onDragOver(event);" style="background:#ddd;"> <input type="file" id="inputfile" accept="image/jpeg,image/png,image/gif,image/bmp,image/x-icon" onchange="onAddFile(event);"> <img id="img_source" style="display:none;"> <p></p> <canvas id="SrcCanvas" style="display:none;"></canvas> <canvas id="DstCanvas"></canvas> <canvas id="RecCanvas" style="position:absolute;cursor:move;border:2px dashed #ccc;display:none;"></canvas> </body> </html>
大きな画像でスクロールバーが表示される時にも対応しています。
詳細解説
[イメージ]
img_source | 読み込んだ画像(非表示) |
---|
[キャンバス]
SrcCanvas | 出力元のキャンバス(非表示) ※最初は読み込んだ画像で、貼り付けされると更新されていく。 |
---|---|
DstCanvas | 出力先(編集用)のキャンバス |
RecCanvas | 矩形用のキャンバス(表示 or 非表示) |
[流れ]
画像ファイルを読む込むと、img_sourceに画像を設定します。次にSrcCanvas、DstCanvasに画像をコピー転送します。
そして、DstCanvasのキャンバスの上で範囲を選択すると、範囲の点線がDstCanvasに描画されます。範囲選択が確定すると、その範囲の画像をRecCanvasにコピー転送して、RecCanvasを範囲選択の部分に重なるように移動させます。
RecCanvasはマウスで移動する事が可能です。
RecCanvasが表示されている時に、DstCanvasのキャンバスの上をマウスダウンすると、RecCanvasをSrcCanvasへ描画します。その後にSrcCanvasの内容をDstCanvasへコピー転送します。
[座標]
座標系で注意する点は浮動小数点を整数に変換する時はIE/Edgeは四捨五入、それ以外のブラウザは切り捨てとなります。その他に絶対位置などの計算処理がありますが、解説が大変なので割愛します。
IE11、Edge、Chrome、FireFoxで動作確認をしましたので、林檎さんのサファリパークでも動くと思います。
以上となります。不明点は各自でコメントを確認して下さいね。