サーチ…


多くの平行移動、拡大縮小、回転された画像をすばやく描画する

回転、拡大縮小、および平行移動されたイメージを描きたい状況がたくさんあります。回転はイメージの中心の周りに起こるはずです。これは、2Dキャンバスでこれを行う最も簡単な方法です。これらの機能は、2Dゲームでの使用に適しています.60秒ごとに1000以上の画像まで数百回レンダリングすることが期待されます。 (ハードウェアに依存します)

// assumes that the canvas context is in ctx and in scope
function drawImageRST(image, x, y, scale, rotation){
    ctx.setTransform(scale, 0, 0, scale, x, y); // set the scale and translation
    ctx.rotate(rotation);                       // add the rotation
    ctx.drawImage(image, -image.width / 2, -image.height / 2); // draw the image offset by half its width and height
}

変異体は、粒子系に有用なアルファ値も含むことができる。

function drawImageRST_Alpha(image, x, y, scale, rotation, alpha){
    ctx.setTransform(scale, 0, 0, scale, x, y); // set the scale and translation
    ctx.rotate(rotation);                       // add the rotation
    ctx.globalAlpha = alpha;
    ctx.drawImage(image, -image.width / 2, -image.height / 2); // draw the image offset by half its width and height
}

両方の関数がキャンバスコンテキストをランダムな状態にしておくことに注意することが重要です。機能は他のものに影響を与えることはありません。イメージのレンダリングが完了したら、デフォルトのトランスフォームを復元する必要があります

ctx.setTransform(1, 0, 0, 1, 0, 0); // set the context transform back to the default 

アルファバージョン(2番目の例)を使用してから標準バージョンを使用する場合は、グローバルアルファ状態が復元されていることを確認する必要があります

ctx.globalAlpha = 1;

上記の関数を使っていくつかのパーティクルといくつかの画像をレンダリングする例

// assume particles to contain an array of particles
for(var i = 0; i < particles.length; i++){
    var p = particles[i];
    drawImageRST_Alpha(p.image, p.x, p.y, p.scale, p.rot, p.alpha);
    // no need to rest the alpha in the loop
}
// you need to reset the alpha as it can be any value 
ctx.globalAlpha = 1;

drawImageRST(myImage, 100, 100, 1, 0.5);  // draw an image at 100,100
// no need to reset the transform 
drawImageRST(myImage, 200, 200, 1, -0.5); // draw an image at 200,200 
ctx.setTransform(1,0,0,1,0,0);            // reset the transform

画像の中心またはその周りのパスを回転する

ここに画像の説明を入力

以下のステップ#1-5は、画像/パス形状の元のポイント座標を変更することなく、任意の画像またはパス形状をキャンバス上の任意の場所に移動させ、任意の角度に回転させることができる。

  1. キャンバス[0,0]原点を図形の中心点に移動する

    context.translate( shapeCenterX, shapeCenterY );
    
  2. キャンバスを希望の角度(ラジアン単位)で回転させる

    context.rotate( radianAngle );
    
  3. キャンバスの原点を左上隅に戻します

     context.translate( -shapeCenterX, -shapeCenterY );
    
  4. 元の座標を使用して画像またはパス形状を描画します。

     context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
    
  5. いつもきれいに! #1の前にあった場所に変換状態を戻す

  • ステップ#5、オプション#1:すべての変換を逆の順序で元に戻す

       // undo #3
       context.translate( shapeCenterX, shapeCenterY );
       // undo #2
       context.rotate( -radianAngle );
       // undo #1
       context.translate( -shapeCenterX, shapeCenterY );
    
  • ステップ#5、オプション#2:ステップ#1を開始する前にキャンバスが変換されていない状態(デフォルト)になっている場合、すべての変換をデフォルト状態にリセットすることで、ステップ#1-3の効果を元に戻すことができます

       // set transformation to the default state (==no transformation applied)
       context.setTransform(1,0,0,1,0,0)
    

コードデモの例:

// canvas references & canvas styling
var canvas=document.createElement("canvas");
canvas.style.border='1px solid red';
document.body.appendChild(canvas);
canvas.width=378;
canvas.height=256;
var ctx=canvas.getContext("2d");
ctx.fillStyle='green';
ctx.globalAlpha=0.35;        

// define a rectangle to rotate
var rect={ x:100, y:100, width:175, height:50 };

// draw the rectangle unrotated
ctx.fillRect( rect.x, rect.y, rect.width, rect.height );

// draw the rectangle rotated by 45 degrees (==PI/4 radians)
ctx.translate( rect.x+rect.width/2, rect.y+rect.height/2 );
ctx.rotate( Math.PI/4 );
ctx.translate( -rect.x-rect.width/2, -rect.y-rect.height/2 );
ctx.fillRect( rect.x, rect.y, rect.width, rect.height );

トランスフォーメーションの紹介

変換は、その点を移動、回転、スケーリングすることによって、与えられた点の開始位置を変更します。

  • 移動ポイントをdistanceXdistanceYます。
  • Rotation:点を回転点の周りにradian angle回転させます。 Html Canvasのデフォルトの回転点は、Canvasの左上の原点[x = 0、y = 0]です。しかし、翻訳を使用して回転点の位置を変更することができます。
  • Scaling:点の位置をscalingFactorXscalingFactorYでスケールします。 Html Canvasのデフォルトのスケーリングポイントは、Canvasの左上の原点[x = 0、y = 0]です。しかし、翻訳を使用してスケーリングポイントを再配置することができます。

また、剪定(スキュー)のようなあまり一般的でない変換を行うには、 context.transformを使用してキャンバスの変換マトリックスを直接設定します。

context.translate(75,25)ポイントを変換(==移動context.translate(75,25)

ここに画像の説明を入力

context.rotate(Math.PI/8)点を回転させる

ここに画像の説明を入力

context.scale(2,2)使って点をcontext.scale(2,2)

ここに画像の説明を入力

キャンバスは実際にキャンバス全体の座標系を変更することで変形を実現します。

  • context.translateはキャンバス[0,0]原点を左上隅から新しい位置に移動します。
  • context.rotateはキャンバス座標系全体を原点の周りに回転させます。
  • context.scaleは、キャンバス座標系全体を原点の周りでスケーリングします。キャンバス上のx、yごとのサイズを増やすと考えてください: every x*=scaleXevery y*=scaleYです。

キャンバス変換は永続的です。キャンバスの変換をデフォルトの状態(==完全に変換されていない状態)に戻すまで、新しい図面はすべて変換され続けます。次のようにしてデフォルトに戻すことができます。

// reset context transformations to the default (untransformed) state
context.setTransform(1,0,0,1,0,0);

変換された回転した縮尺の形状を追跡するための変換マトリックス

キャンバスは、あなたがすることができますcontext.translatecontext.rotatecontext.scale必要な位置&サイズであなたの形状を描画するために。

キャンバス自体は、変換行列を使用して効率的に変換を追跡します。

  • あなたはcontext.transformキャンバスの行列を変更することができます
  • 個々のtranslate, rotate & scaleコマンドでCanvasの行列を変更することができます
  • Canvasの行列をcontext.setTransformで完全に上書きできますが、
  • しかし、キャンバスの内部変換マトリックスを読むことはできません。これは書き込み専用です。

なぜ変換行列を使うのですか?

変換行列を使用すると、多くの個々の変換、回転、および拡大縮小を単一の簡単に再適用された行列に集約できます。

複雑なアニメーションでは、数十(または数百)の変形をシェイプに適用できます。変換マトリックスを使用することで、数十の変換を1行のコードで瞬時に再適用することができます。

いくつかの例では、

  • マウスが翻訳、回転、拡大縮小された図形の内側にあるかどうかをテストします

    ポイント(マウスなど)がパス形状内にあるかどうかをテストする組み込みのcontext.isPointInPathがありますが、この組み込みテストはマトリックスを使用したテストと比較して非常に遅いです。

    マウスがシェイプ内にあるかどうかを効率的にテストするには、ブラウザによって報告されたマウスの位置を取得し、シェイプが変形されたのと同じ方法で変形させる必要があります。あたかも図形が変形していないかのようにヒットテストを適用することができます。

  • 広範囲に変換、回転、拡大縮小された図形を再描画します。

    複数の.translate, .rotate, .scale個々の変換を再適用する代わりに、すべての集約された変換を1行のコードに適用することができます。

  • 平行移動したテストシェイプは、平行移動、回転&スケーリングされています。

    ジオメトリと三角法を使用して、変形された図形を構成する点を計算することはできますが、変換行列を使ってそれらの点を計算する方が速くなります。

変換行列 "クラス"

このコードはネイティブのcontext.translatecontext.rotatecontext.scale変換コマンドを反映しています。ネイティブのキャンバスマトリックスとは異なり、このマトリックスは読みやすく再利用可能です。

メソッド:

  • translaterotatescale変換コマンドを使用して、変換を行列に供給することができます。マトリックスは、集約された変換を効率的に保持します。

  • setContextTransformはコンテキストを取り、そのコンテキストの行列をこの変換行列と等しく設定します。これにより、この行列に格納されているすべての変換がコンテキストに効率的に再適用されます。

  • resetContextTransformは、コンテキストの変換をデフォルト状態(== resetContextTransformリセットします。

  • getTransformedPointは、変換されていない座標点を取得し、変換された点に変換します。

  • getScreenPointは変換された座標点をとり、変換されていない点に変換します。

  • getMatrixは、集約された変換を行列配列の形で返します。

コード:

var TransformationMatrix=( function(){
    // private
    var self;
    var m=[1,0,0,1,0,0];
    var reset=function(){ var m=[1,0,0,1,0,0]; }
    var multiply=function(mat){
        var m0=m[0]*mat[0]+m[2]*mat[1];
        var m1=m[1]*mat[0]+m[3]*mat[1];
        var m2=m[0]*mat[2]+m[2]*mat[3];
        var m3=m[1]*mat[2]+m[3]*mat[3];
        var m4=m[0]*mat[4]+m[2]*mat[5]+m[4];
        var m5=m[1]*mat[4]+m[3]*mat[5]+m[5];
        m=[m0,m1,m2,m3,m4,m5];
    }
    var screenPoint=function(transformedX,transformedY){
        // invert
        var d =1/(m[0]*m[3]-m[1]*m[2]);
        im=[ m[3]*d, -m[1]*d, -m[2]*d, m[0]*d, d*(m[2]*m[5]-m[3]*m[4]), d*(m[1]*m[4]-m[0]*m[5]) ];
        // point
        return({
            x:transformedX*im[0]+transformedY*im[2]+im[4],
            y:transformedX*im[1]+transformedY*im[3]+im[5]
        });
    }
    var transformedPoint=function(screenX,screenY){
        return({
            x:screenX*m[0] + screenY*m[2] + m[4],
            y:screenX*m[1] + screenY*m[3] + m[5]
        });    
    }
    // public
    function TransformationMatrix(){
        self=this;
    }
    // shared methods
    TransformationMatrix.prototype.translate=function(x,y){
        var mat=[ 1, 0, 0, 1, x, y ];
        multiply(mat);
    };
    TransformationMatrix.prototype.rotate=function(rAngle){
        var c = Math.cos(rAngle);
        var s = Math.sin(rAngle);
        var mat=[ c, s, -s, c, 0, 0 ];    
        multiply(mat);
    };
    TransformationMatrix.prototype.scale=function(x,y){
        var mat=[ x, 0, 0, y, 0, 0 ];        
        multiply(mat);
    };
    TransformationMatrix.prototype.skew=function(radianX,radianY){
        var mat=[ 1, Math.tan(radianY), Math.tan(radianX), 1, 0, 0 ];
        multiply(mat);
    };
    TransformationMatrix.prototype.reset=function(){
        reset();
    }
    TransformationMatrix.prototype.setContextTransform=function(ctx){
        ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
    }
    TransformationMatrix.prototype.resetContextTransform=function(ctx){
        ctx.setTransform(1,0,0,1,0,0);
    }
    TransformationMatrix.prototype.getTransformedPoint=function(screenX,screenY){
        return(transformedPoint(screenX,screenY));
    }
    TransformationMatrix.prototype.getScreenPoint=function(transformedX,transformedY){
        return(screenPoint(transformedX,transformedY));
    }
    TransformationMatrix.prototype.getMatrix=function(){
        var clone=[m[0],m[1],m[2],m[3],m[4],m[5]];
        return(clone);
    }
    // return public
    return(TransformationMatrix);
})();

デモ:

このデモでは、上記の変換行列「クラス」を使用して次のことを行います。

  • 矩形の変換行列を追跡(==保存)します。

  • コンテキスト変換コマンドを使用せずに、変換された矩形を再描画します。

  • 変換された矩形内でマウスがクリックされたかどうかを判定します。

コード:

<!doctype html>
<html>
<head>
<style>
    body{ background-color:white; }
    #canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    function reOffset(){
        var BB=canvas.getBoundingClientRect();
        offsetX=BB.left;
        offsetY=BB.top;        
    }
    var offsetX,offsetY;
    reOffset();
    window.onscroll=function(e){ reOffset(); }
    window.onresize=function(e){ reOffset(); }

    // Transformation Matrix "Class"
    
    var TransformationMatrix=( function(){
        // private
        var self;
        var m=[1,0,0,1,0,0];
        var reset=function(){ var m=[1,0,0,1,0,0]; }
        var multiply=function(mat){
            var m0=m[0]*mat[0]+m[2]*mat[1];
            var m1=m[1]*mat[0]+m[3]*mat[1];
            var m2=m[0]*mat[2]+m[2]*mat[3];
            var m3=m[1]*mat[2]+m[3]*mat[3];
            var m4=m[0]*mat[4]+m[2]*mat[5]+m[4];
            var m5=m[1]*mat[4]+m[3]*mat[5]+m[5];
            m=[m0,m1,m2,m3,m4,m5];
        }
        var screenPoint=function(transformedX,transformedY){
            // invert
            var d =1/(m[0]*m[3]-m[1]*m[2]);
            im=[ m[3]*d, -m[1]*d, -m[2]*d, m[0]*d, d*(m[2]*m[5]-m[3]*m[4]), d*(m[1]*m[4]-m[0]*m[5]) ];
            // point
            return({
                x:transformedX*im[0]+transformedY*im[2]+im[4],
                y:transformedX*im[1]+transformedY*im[3]+im[5]
            });
        }
        var transformedPoint=function(screenX,screenY){
            return({
                x:screenX*m[0] + screenY*m[2] + m[4],
                y:screenX*m[1] + screenY*m[3] + m[5]
            });    
        }
        // public
        function TransformationMatrix(){
            self=this;
        }
        // shared methods
        TransformationMatrix.prototype.translate=function(x,y){
            var mat=[ 1, 0, 0, 1, x, y ];
            multiply(mat);
        };
        TransformationMatrix.prototype.rotate=function(rAngle){
            var c = Math.cos(rAngle);
            var s = Math.sin(rAngle);
            var mat=[ c, s, -s, c, 0, 0 ];    
            multiply(mat);
        };
        TransformationMatrix.prototype.scale=function(x,y){
            var mat=[ x, 0, 0, y, 0, 0 ];        
            multiply(mat);
        };
        TransformationMatrix.prototype.skew=function(radianX,radianY){
            var mat=[ 1, Math.tan(radianY), Math.tan(radianX), 1, 0, 0 ];
            multiply(mat);
        };
        TransformationMatrix.prototype.reset=function(){
            reset();
        }
        TransformationMatrix.prototype.setContextTransform=function(ctx){
            ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
        }
        TransformationMatrix.prototype.resetContextTransform=function(ctx){
            ctx.setTransform(1,0,0,1,0,0);
        }
        TransformationMatrix.prototype.getTransformedPoint=function(screenX,screenY){
            return(transformedPoint(screenX,screenY));
        }
        TransformationMatrix.prototype.getScreenPoint=function(transformedX,transformedY){
            return(screenPoint(transformedX,transformedY));
        }
        TransformationMatrix.prototype.getMatrix=function(){
            var clone=[m[0],m[1],m[2],m[3],m[4],m[5]];
            return(clone);
        }
        // return public
        return(TransformationMatrix);
    })();

    // DEMO starts here

    // create a rect and add a transformation matrix
    // to track it's translations, rotations & scalings
    var rect={x:30,y:30,w:50,h:35,matrix:new TransformationMatrix()};

    // draw the untransformed rect in black
    ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
    // Demo: label
    ctx.font='11px arial';
    ctx.fillText('Untransformed Rect',rect.x,rect.y-10);

    // transform the canvas & draw the transformed rect in red
    ctx.translate(100,0);
    ctx.scale(2,2);
    ctx.rotate(Math.PI/12);
    // draw the transformed rect
    ctx.strokeStyle='red';
    ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
    ctx.font='6px arial';
    // Demo: label
    ctx.fillText('Same Rect: Translated, rotated & scaled',rect.x,rect.y-6);
    // reset the context to untransformed state
    ctx.setTransform(1,0,0,1,0,0);

    // record the transformations in the matrix
    var m=rect.matrix;
    m.translate(100,0);
    m.scale(2,2);
    m.rotate(Math.PI/12);

    // use the rect's saved transformation matrix to reposition, 
    //     resize & redraw the rect
    ctx.strokeStyle='blue';
    drawTransformedRect(rect);

    // Demo: instructions
    ctx.font='14px arial';
    ctx.fillText('Demo: click inside the blue rect',30,200);

    // redraw a rect based on it's saved transformation matrix
    function drawTransformedRect(r){
        // set the context transformation matrix using the rect's saved matrix
        m.setContextTransform(ctx);
        // draw the rect (no position or size changes needed!)
        ctx.strokeRect( r.x, r.y, r.w, r.h );
        // reset the context transformation to default (==untransformed);
        m.resetContextTransform(ctx);
    }

    // is the point in the transformed rectangle?
    function isPointInTransformedRect(r,transformedX,transformedY){
        var p=r.matrix.getScreenPoint(transformedX,transformedY);
        var x=p.x;
        var y=p.y;
        return(x>r.x && x<r.x+r.w && y>r.y && y<r.y+r.h);
    } 

    // listen for mousedown events
    canvas.onmousedown=handleMouseDown;
    function handleMouseDown(e){
        // tell the browser we're handling this event
        e.preventDefault();
        e.stopPropagation();
        // get mouse position
        mouseX=parseInt(e.clientX-offsetX);
        mouseY=parseInt(e.clientY-offsetY);
        // is the mouse inside the transformed rect?
        if(isPointInTransformedRect(rect,mouseX,mouseY)){
            alert('You clicked in the transformed Rect');
        }
    }

    // Demo: redraw transformed rect without using
    //       context transformation commands
    function drawTransformedRect(r,color){
        var m=r.matrix;
        var tl=m.getTransformedPoint(r.x,r.y);
        var tr=m.getTransformedPoint(r.x+r.w,r.y);
        var br=m.getTransformedPoint(r.x+r.w,r.y+r.h);
        var bl=m.getTransformedPoint(r.x,r.y+r.h);
        ctx.beginPath();
        ctx.moveTo(tl.x,tl.y);
        ctx.lineTo(tr.x,tr.y);
        ctx.lineTo(br.x,br.y);
        ctx.lineTo(bl.x,bl.y);
        ctx.closePath();
        ctx.strokeStyle=color;
        ctx.stroke();
    }

}); // end window.onload
</script>
</head>
<body>
    <canvas id="canvas" width=512 height=250></canvas>
</body>
</html>


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow