Ricerca…


Disegnare rapidamente molte immagini tradotte, ridimensionate e ruotate

Ci sono molte situazioni in cui si desidera disegnare un'immagine che viene ruotata, ridimensionata e tradotta. La rotazione dovrebbe avvenire attorno al centro dell'immagine. Questo è il modo più rapido per farlo sulla tela 2D. Queste funzioni sono molto adatte ai giochi 2D in cui l'aspettativa è di rendere alcune centinaia persino fino a 1000+ immagini ogni 60esimo di secondo. (dipende dall'hardware)

// 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
}

Una variante può anche includere il valore alfa che è utile per i sistemi di particelle.

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
}

È importante notare che entrambe le funzioni lasciano il contesto della tela in uno stato casuale. Anche se le funzioni non saranno influenzate, altri renderanno il mio essere. Una volta terminato il rendering delle immagini, potrebbe essere necessario ripristinare la trasformazione predefinita

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

Se si utilizza la versione alpha (secondo esempio) e quindi la versione standard, è necessario assicurarsi che lo stato alfa globale venga ripristinato

ctx.globalAlpha = 1;

Un esempio di utilizzo delle funzioni di cui sopra per rendere alcune particelle e alcune immagini

// 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

Ruota un'immagine o un percorso attorno al suo punto centrale

inserisci la descrizione dell'immagine qui

I passaggi da 1 a 5 qui sotto consentono a qualsiasi immagine o forma del percorso di essere spostati ovunque nell'area di disegno e ruotati su qualsiasi angolo senza modificare le coordinate del punto originale dell'immagine / percorso.

  1. Sposta l'origine della tela [0,0] al punto centrale della forma

    context.translate( shapeCenterX, shapeCenterY );
    
  2. Ruota la tela dell'angolo desiderato (in radianti)

    context.rotate( radianAngle );
    
  3. Sposta l'origine della tela nell'angolo in alto a sinistra

     context.translate( -shapeCenterX, -shapeCenterY );
    
  4. Disegna l'immagine o la forma del percorso usando le sue coordinate originali.

     context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
    
  5. Pulisci sempre! Impostare lo stato di trasformazione su dove era prima del # 1

  • Passaggio n. 5, opzione n. 1: annullare ogni trasformazione nell'ordine inverso

       // undo #3
       context.translate( shapeCenterX, shapeCenterY );
       // undo #2
       context.rotate( -radianAngle );
       // undo #1
       context.translate( -shapeCenterX, shapeCenterY );
    
  • Passaggio n. 5, opzione n. 2: se la tela era in uno stato non trasformato (impostazione predefinita) prima di iniziare il passaggio n. 1, è possibile annullare gli effetti dei passaggi n. 1-3 ripristinando tutte le trasformazioni al loro stato predefinito

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

Esempio di demo del codice:

// 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 );

Introduzione alle trasformazioni

Le trasformazioni alterano la posizione di partenza di un dato punto spostando, ruotando e ridimensionando quel punto.

  • Traslazione: sposta un punto di una distanceX e una distanceX distanceY
  • Rotazione: ruota un punto di un radian angle attorno al suo punto di rotazione. Il punto di rotazione predefinito in Html Canvas è l'origine in alto a sinistra [x = 0, y = 0] della tela. Ma puoi riposizionare il punto di rotazione usando le traduzioni.
  • Ridimensionamento: consente di ridimensionare la posizione di un punto mediante un scalingFactorX di scala e un scalingFactorY di scala dal punto di ridimensionamento. Il punto di ridimensionamento predefinito in Html Canvas è l'origine in alto a sinistra [x = 0, y = 0] della tela. Ma puoi riposizionare il punto di ridimensionamento usando le traduzioni.

È anche possibile eseguire trasformazioni meno comuni, come la cesoiatura (inclinazione), impostando direttamente la matrice di trasformazione della tela utilizzando context.transform .

Traduci (== sposta) un punto con context.translate(75,25)

inserisci la descrizione dell'immagine qui

Ruota un punto con context.rotate(Math.PI/8)

inserisci la descrizione dell'immagine qui

Ridimensiona un punto con context.scale(2,2)

inserisci la descrizione dell'immagine qui

Canvas realizza effettivamente le trasformazioni alterando l'intero sistema di coordinate della tela.

  • context.translate sposta l'origine del canvas [0,0] dall'angolo in alto a sinistra in una nuova posizione.
  • context.rotate ruoterà l'intero sistema di coordinate del canvas attorno all'origine.
  • context.scale ridimensiona l'intero sistema di coordinate del canvas attorno all'origine. Pensa a ciò aumentando la dimensione di ogni x, y sulla tela: every x*=scaleX e every y*=scaleY .

Le trasformazioni della tela sono persistenti. Tutti i nuovi disegni continueranno a essere trasformati fino a quando non si ripristinerà la trasformazione della tela al suo stato predefinito (== totalmente non trasformato). Puoi ripristinare i valori predefiniti con:

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

Una matrice di trasformazione per tracciare le forme tradotte, ruotate e ridimensionate

Canvas ti permette di context.translate , context.rotate e context.scale per disegnare la tua forma nella posizione e dimensione richiesta.

La tela stessa usa una matrice di trasformazione per tracciare efficientemente le trasformazioni.

  • Puoi cambiare la matrice di Canvas con context.transform
  • Puoi cambiare la matrice di Canvas con singoli comandi di translate, rotate & scale scalatura
  • Puoi sovrascrivere completamente la matrice di Canvas con context.setTransform ,
  • Ma non puoi leggere la matrice di trasformazione interna di Canvas: è di sola scrittura.

Perché usare una matrice di trasformazione?

Una matrice di trasformazione consente di aggregare molte singole traduzioni, rotazioni e ridimensionamenti in un'unica matrice facilmente riapplicabile.

Durante animazioni complesse è possibile applicare dozzine (o centinaia) di trasformazioni a una forma. Utilizzando una matrice di trasformazione è possibile (quasi) riapplicare istantaneamente quelle dozzine di trasformazioni con una singola riga di codice.

Alcuni esempi utilizza:

  • Verifica se il mouse si trova all'interno di una forma che hai tradotto, ruotato e ridimensionato

    Esiste un context.isPointInPath integrato che verifica se un punto (ad es. Il mouse) si trova all'interno di un percorso, ma questo test integrato è molto lento rispetto al test che utilizza una matrice.

    Testare in modo efficiente se il mouse si trova all'interno di una forma comporta prendere la posizione del mouse riportata dal browser e trasformarla nello stesso modo in cui la forma è stata trasformata. Quindi puoi applicare hit-test come se la forma non fosse stata trasformata.

  • Ridisegna una forma che è stata ampiamente tradotta, ruotata e ridimensionata.

    Invece di riapplicare le singole trasformazioni con più .translate, .rotate, .scale è possibile applicare tutte le trasformazioni aggregate in una singola riga di codice.

  • Forme di prova di collisione che sono state tradotte, ruotate e ridimensionate

    È possibile utilizzare la geometria e la trigonometria per calcolare i punti che costituiscono le forme trasformate, ma è più rapido utilizzare una matrice di trasformazione per calcolare quei punti.

Una matrice di trasformazione "Classe"

Questo codice rispecchia i comandi di trasformazione nativi context.translate , context.rotate , context.scale . A differenza della matrice tela nativa, questa matrice è leggibile e riutilizzabile.

metodi:

  • translate , rotate , scale i comandi di trasformazione del contesto e consenti di alimentare le trasformazioni nella matrice. La matrice contiene efficientemente le trasformazioni aggregate.

  • setContextTransform prende un contesto e imposta la matrice del contesto uguale a questa matrice di trasformazione. Questo riapplica in modo efficiente tutte le trasformazioni memorizzate in questa matrice al contesto.

  • resetContextTransform reimposta la trasformazione del contesto nel suo stato predefinito (== non trasformato).

  • getTransformedPoint prende un punto di coordinate non trasformato e lo converte in un punto trasformato.

  • getScreenPoint prende un punto di coordinate trasformato e lo converte in un punto non trasformato.

  • getMatrix restituisce le trasformazioni aggregate sotto forma di matrice matrice.

Codice:

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:

Questa demo utilizza la "Classe" di Transformation Matrix sopra per:

  • Traccia (== salva) la matrice di trasformazione di un rettangolo.

  • Ridisegna il rettangolo trasformato senza utilizzare i comandi di trasformazione del contesto.

  • Verifica se il mouse ha fatto clic all'interno del rettangolo trasformato.

Codice:

<!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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow