写真ズーム(拡大・縮小)処理②

概要

今回はマウスを使ってCANVAS上の写真の範囲を指定して、ズーム処理する方法について確認します。先回のスライダーを使って拡大する方法では、写真の中央を固定し拡大しましたが、今回の方法では、選択した部分を中心に拡大することが出来ます。

マウス操作による写真ズーム機能

(→マウス操作による写真ズームプログラムへのリンク)
下の図の通りマウスを使って拡大範囲を選択します。マウスダウンで始点、マウス移動後マウスアップで終点が確定し、2点を対角とする長方形の範囲を拡大表示します。基本的には長方形の中心が拡大表示時の画面中央になりますが、写真の端部領域選択時など、CANVAS全体に写真を表示する為に座標補正を行っています。次に拡大倍率は選択領域の水平・垂直方向それぞれについてCANVASサイズに収まる様に計算し、選択領域が全て表示される様に小さい倍率を採用します。また、今回最大倍率を10倍として制限しています。
尚、下の写真の最上段の表は、プログラム作成時の座標等の確認用で、いずれ除去する予定です。
下の写真は、上の写真の選択領域(点線の長方形部)を拡大したものです。拡大表示したCANVAS上の写真を更に領域選択し、倍率更新出来るようにしています。また、初期化ボタンを配置し、1クリックで初期状態に戻せる様にしました。

ズーム表示プログラミングメモ

プログラミングについてメモします。
ズーム表示を行う関数は行番114~160の関数“pic_zoom”です。
この関数を呼び出す場合は、①ズームスライダーを変更した時,②初期化ボタンをクリックした時,③マウスで写真上の領域を選択した時の3つです。
全てのケースに共通しているのは、ズーム後にCANVAS中心になる元画像座標と拡大倍率から拡大画像を表示するところです。行番134で拡大倍率からズーム後に表示する元画像の切り抜き範囲を求めます。次に行番135でズーム後中心となる元画像座標から表示始点を計算しています。行番137~140では、③領域選択時に画像端部を選択した時などの表示位置補正をしています。この様にして求めた元画像の始点と範囲をパラメータとして行番156でdrawImage()を実行することでズーム画像を描画します。

①②は、いずれも中心座標を変えず縮尺変更しますので、実際には拡大倍率さえ判れば良いということになります。一方、③の場合は選択領域から中心座標と倍率を求める必要があります。この処理を行番118~128で行います。
行番118では、マウスイベントを利用して選択した領域の始点と終点から中心座標を求めます。この座標はCANVAS座標ですので、倍率が1倍でCANVASと画像の解像度が同じ場合は問題ないのですが、倍率を変更した画像に対しては補正が必要です。
現在表示されている元画像の始点と範囲から、行番119~120では中心座標を補正します。同じ様に行番122~123では範囲を補正します。行番125~128では補正後の範囲から倍率を求めます。

(→マウス操作による写真ズームプログラムへのリンク)

<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>DRAWING TEST</title>

    <style>
	#cvs-layer { position:relative ; }
	#cvs-layer canvas { position:absolute ; top:0 ; left:0 ; }
	#draw_info th, #draw_info td { border: solid 1px #000000; border-collapse: collapse; }
	#draw_info { border: solid 1px #000000; border-collapse: collapse; }
	input { height:30px; }
    </style>

    <script>

	var cvs_pic ; var ctx_pic ;
	var cvs_edt ; var ctx_edt ;
	var cvs_wdt ; var cvs_hgt ; var img_h ; var img_w ;
	var zos_w ; var zom_w ; var zos_h ; var zom_h ; 
	var x_b=0; var y_b=0; var x_c=0; var y_c=0; var p_c_x=0; var p_c_y=0;
	var f_b ; var a_effect ; var a_efc_max=10;
	var sld_chk ; var tmp_pic ;
        var IE_Flag ;

	function draw_begin(){
	    var ua, isIE;
	    ua = window.navigator.userAgent.toLowerCase();
	    isIE = (ua.indexOf('msie') >= 0 || ua.indexOf('trident') >= 0);
 
	    cvs_wdt = 840 ; cvs_hgt = 600 ;
	    cvs_pic = document.getElementById('CANVAS_PIC') ; 
	    ctx_pic = cvs_pic.getContext("2d") ;
	    cvs_edt = document.getElementById('CANVAS_EDT') ; 
	    ctx_edt = cvs_edt.getContext("2d") ;
	    
	    sld_chk = document.getElementById("zoom-slider");
	    sld_chk.min = 1; sld_chk.max = a_efc_max; sld_chk.step = 'any';
	    sld_chk.value=1;

	    var image = new Image(); 
            image.src = "./sample.jpg" ;

	    image.onload = function () {
		img_h = image.height ; img_w = image.width ;

		cvs_hgt = Math.floor(img_h * cvs_wdt / img_w) ;
                cvs_pic.width = cvs_wdt ; cvs_pic.height = cvs_hgt ;
		cvs_edt.width = cvs_wdt ; cvs_edt.height = cvs_hgt ;

		document.getElementById('i101').innerHTML = img_w ;
		document.getElementById('i102').innerHTML = img_h ;
		document.getElementById('i103').innerHTML = cvs_wdt ;
		document.getElementById('i104').innerHTML = cvs_hgt ;

                ctx_pic.drawImage( image, 0 , 0 , cvs_wdt , cvs_hgt ) ;
		ctx_pic.strokeRect( 0 , 0 , cvs_wdt, cvs_hgt) ;

		//始点(X,Y),幅・高さ初期化
		zos_w = 0 ; zos_h = 0 ; zom_w = cvs_wdt ; zom_h = cvs_hgt ;

		tmp_pic = new Image ;
		tmp_pic.src = ctx_pic.canvas.toDataURL() ;

		cvs_edt.addEventListener( "mousedown" , mouse_dn , false ) ;
	    	cvs_edt.addEventListener( "mousemove" , mouse_mv , false ) ;
	    	cvs_edt.addEventListener( "mouseup", mouse_up , false ) ;

		if(isIE){
		    sld_chk.addEventListener( "change", pict_scale , false ) ;
		}else{
		    sld_chk.addEventListener( "input", pict_scale , false ) ;
		}
	    }
	}

	//▼▼▼▼▼▼ マウス検知 ▼▼▼▼▼▼

	function mouse_dn(event){
	    var rect = event.target.getBoundingClientRect() ;
	    x_b = event.clientX-rect.left ;
	    y_b = event.clientY-rect.top ;
	    f_b = true ;
	}

	function mouse_mv(event){
	    if(f_b){
		var rect = event.target.getBoundingClientRect() ;
		x_c = event.clientX-rect.left ;
		y_c = event.clientY-rect.top ;

		// 選択エリア確認用表示
		ctx_edt.lineWidth = 1 ; ctx_edt.setLineDash([2,2]);
		ctx_edt.clearRect( 0, 0, cvs_wdt , cvs_hgt ) ;
		ctx_edt.beginPath() ; ctx_edt.strokeRect( x_b , y_b, x_c-x_b , y_c-y_b );
		ctx_edt.arc( (x_b + x_c)/2 , (y_b + y_c)/2 , 10 , 0 , 2 * Math.PI , false) ; ctx_edt.stroke();
	    }
	}

	function mouse_up(event){ 
	    f_b=false;
	    pic_zoom("1") ;

	    ctx_edt.clearRect( 0, 0, cvs_wdt , cvs_hgt ) ;

	    //◆確認用◆
	    //ctx_edt.beginPath() ; ctx_edt.arc( cvs_wdt/2 , cvs_hgt/2 , 10 , 0 , 2 * Math.PI , false) ; ctx_edt.stroke();
	}

	//▲▲▲▲▲▲ マウス検知 ▲▲▲▲▲▲


	// ズーム表示メイン
	function pic_zoom(ptn){
	    if(ptn!="1"){ p_c_x = cvs_wdt/2 ; p_c_y = cvs_hgt/2 ; }			// 対象:範囲選択以外(初期化+スライダー)
	    else{
	    	var tmp_cnt_x = (x_b + x_c) / 2 ; var tmp_cnt_y = (y_b + y_c) / 2 ;	// 選択領域の中心座標
		p_c_x = zos_w + zom_w * tmp_cnt_x / cvs_wdt ;				// 拡大考慮 座標補正(X)
		p_c_y = zos_h + zom_h * tmp_cnt_y / cvs_hgt ;				// 拡大考慮 座標補正(Y)

		var tmp_wdt = zom_w * Math.abs(x_b - x_c) / cvs_wdt ;			// 拡大考慮 幅補正
		var tmp_hgt = zom_h * Math.abs(y_b - y_c) / cvs_hgt ;			// 拡大考慮 高さ補正

	    	var efc_x = Math.floor(100 * cvs_wdt / tmp_wdt ) / 100 ; 		// 幅方向倍率計算
	    	var efc_y = Math.floor(100 * cvs_hgt / tmp_hgt ) / 100 ; 		// 高さ方向倍率計算
	    	if(efc_x>efc_y){ a_effect=efc_y ; }else{ a_effect=efc_x ; }		// 倍率の小さい方を確定倍率とする
	    	if(a_effect > a_efc_max){ a_effect = a_efc_max ; }			// 倍率最大値補正
	    }

	    ctx_pic.clearRect(0, 0, cvs_pic.width, cvs_pic.height);
	    ctx_pic.globalAlpha = 1 ; ctx_pic.lineWidth = 1 ; ctx_pic.strokeStyle = "#000000" ;

	    zom_w = cvs_wdt/a_effect ; zom_h = cvs_hgt/a_effect ; 			// 拡大幅,拡大高さ
	    zos_w = p_c_x-zom_w/2 ; zos_h = p_c_y-zom_h/2;				// 始点X,始点Y

	    if(0>zos_w){zos_w=0;}							// 開始位置補正(X-)
	    if(0>zos_h){zos_h=0;}							// 開始位置補正(Y-)
	    if(zos_w+zom_w > cvs_wdt){zos_w=cvs_wdt-zom_w;}				// 開始位置補正(X+)
	    if(zos_h+zom_h > cvs_hgt){zos_h=cvs_hgt-zom_h;}				// 開始位置補正(Y+)

	    var pos_cnt = String(Math.floor(p_c_x))+","+String(Math.floor(p_c_y)) ;
	    var pos_stt = String(Math.floor(x_b))+","+String(Math.floor(y_b)) ;
	    var pos_end = String(Math.floor(x_c))+","+String(Math.floor(y_c)) ;

	    document.getElementById('i105').innerHTML = Math.floor(1000*a_effect)/1000 ;// 倍率
	    document.getElementById('i106').innerHTML = Math.floor(zos_w) ;		// 始点X
	    document.getElementById('i107').innerHTML = Math.floor(zos_h) ;		// 始点Y
	    document.getElementById('i108').innerHTML = Math.floor(zom_w) ;		// 拡大幅
	    document.getElementById('i109').innerHTML = Math.floor(zom_h) ;		// 拡大高

	    document.getElementById('i110').innerHTML = pos_stt ;			// 選択開始位置
	    document.getElementById('i111').innerHTML = pos_end ;			// 選択終了位置
	    document.getElementById('i112').innerHTML = pos_cnt ;			// 変換中心座標

	    ctx_pic.drawImage(tmp_pic , zos_w, zos_h , zom_w , zom_h , 0 , 0 , cvs_wdt , cvs_hgt ) ;
	    ctx_pic.strokeRect( 0 , 0 , cvs_pic.width , cvs_pic.height ) ;

	    if(ptn!="0"){document.getElementById("zoom-slider").value=a_effect ; }	// 対象:初期化ボタンと範囲選択
	}

	function pict_scale(event){ a_effect = event.target.value; pic_zoom("0") ; } 	// ズームスライダー

	function screen_init(){ a_effect = 1; pic_zoom("2") ; } 			// 初期化ボタン

    </script>
</head>

<body onLoad="draw_begin()">

    <TABLE id="draw_info">
	<TR>
	<TD id="i001">画像幅</TD>
	<TD id="i002">画像高</TD>
	<TD id="i003">CVS幅</TD>
	<TD id="i004">CVS高</TD>
	<TD id="i005">倍率</TD>
	<TD id="i006">始点X</TD>
	<TD id="i007">始点Y</TD>
	<TD id="i008">拡大幅</TD>
	<TD id="i009">拡大高</TD>
	<TD id="i010">x_b,y_b</TD>
	<TD id="i011">x_c,y_c</TD>
	<TD id="i012">拡大中心</TD>
	</TR>

	<TR>
	<TD id="i101"></TD>
	<TD id="i102"></TD>
	<TD id="i103"></TD>
	<TD id="i104"></TD>
	<TD id="i105"></TD>
	<TD id="i106"></TD>
	<TD id="i107"></TD>
	<TD id="i108"></TD>
	<TD id="i109"></TD>
	<TD id="i110"></TD>
	<TD id="i111"></TD>
	<TD id="i112"></TD>
	</TR>
    </TABLE>

    <TABLE><TR>
    <TD><input type="button" value="初期化" onclick="screen_init()"></TD><TD style="width:50px;"></TD>
    <TD>縮小</TD><TD><input id="zoom-slider" type="range"></TD><TD>拡大</TD>
    </TR></TABLE>

    <div id="cvs-layer">
    <canvas id="CANVAS_PIC"></canvas>
    <canvas id="CANVAS_EDT"></canvas>
    </div>

</body> 

</html>

◆ブラウザー判定◆
今回のスライダーによるズーム機能は、IEでは動かないことが判りました。今回は行番28~30でブラウザーが、IEか否か判定しています。更に行番70~74でブラウザーがIEの時には、”change”イベント、それ以外の時には、”input”を監視する様にしています。 この対策で、IE11とGoogleChromeで動作を確認しています。

まとめ

今回、拡大機能について確認しました。
次回は写真を拡大・縮小時にうまく描画出来るか確認してみようと思っています。異なるテーマを投稿した時には、うまく出来ていないものとご理解下さい。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です