Buscar..


Dibujar rápidamente muchas imágenes traducidas, escaladas y rotadas.

Hay muchas situaciones en las que desea dibujar una imagen que se gira, se escala y se traduce. La rotación debe ocurrir alrededor del centro de la imagen. Esta es la forma más rápida de hacerlo en el lienzo 2D. Estas funciones se adaptan bien a los juegos en 2D, donde la expectativa es generar unos cientos de imágenes de hasta más de 1000 imágenes cada 60 segundos. (Depende del 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 también puede incluir el valor alfa que es útil para los sistemas de partículas.

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
}

Es importante tener en cuenta que ambas funciones dejan el contexto del lienzo en un estado aleatorio. Aunque las funciones no se verán afectadas por otros renderizando mi ser. Cuando haya terminado de representar las imágenes, es posible que deba restaurar la transformación predeterminada

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

Si usa la versión alfa (segundo ejemplo) y luego la versión estándar, tendrá que asegurarse de que se restaure el estado alfa global

ctx.globalAlpha = 1;

Un ejemplo del uso de las funciones anteriores para representar algunas partículas y algunas imágenes.

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

Girar una imagen o camino alrededor de su punto central

introduzca la descripción de la imagen aquí

Los pasos 1 a 5 a continuación permiten que cualquier imagen o forma de trayectoria se mueva a cualquier lugar del lienzo y se gire a cualquier ángulo sin cambiar ninguna de las coordenadas del punto original de la imagen / forma de trayectoria.

  1. Mueva el origen del lienzo [0,0] al punto central de la forma

    context.translate( shapeCenterX, shapeCenterY );
    
  2. Gire el lienzo en el ángulo deseado (en radianes)

    context.rotate( radianAngle );
    
  3. Mueve el origen del lienzo de nuevo a la esquina superior izquierda

     context.translate( -shapeCenterX, -shapeCenterY );
    
  4. Dibuja la imagen o la forma del camino usando sus coordenadas originales.

     context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
    
  5. ¡Siempre limpia! Establecer el estado de transformación de nuevo a donde estaba antes de # 1

  • Paso # 5, Opción # 1: Deshacer todas las transformaciones en orden inverso

       // undo #3
       context.translate( shapeCenterX, shapeCenterY );
       // undo #2
       context.rotate( -radianAngle );
       // undo #1
       context.translate( -shapeCenterX, shapeCenterY );
    
  • Paso # 5, Opción # 2: Si el lienzo estaba en un estado sin transformar (el valor predeterminado) antes de comenzar el paso # 1, puede deshacer los efectos de los pasos # 1-3 restableciendo todas las transformaciones a su estado predeterminado

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

Código de ejemplo de demostración:

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

Introducción a las transformaciones

Las transformaciones alteran la posición inicial de un punto dado al mover, rotar y escalar ese punto.

  • Traducción: Mueve un punto por una distanceX y una distanceY .
  • Rotación: Gira un punto en un radian angle alrededor de su punto de rotación. El punto de rotación predeterminado en Lienzo HTML es el origen superior izquierdo [x = 0, y = 0] del Lienzo. Pero puedes reposicionar el punto de rotación usando traducciones.
  • Escalado: escala la posición de un punto mediante un scalingFactorX y scalingFactorY de scalingFactorY desde su punto de escala. El punto de escala predeterminado en el Lienzo HTML es el origen superior izquierdo [x = 0, y = 0] del Lienzo. Pero puedes reposicionar el punto de escala usando traducciones.

También puede hacer transformaciones menos comunes, como cizallamiento (sesgo), configurando directamente la matriz de transformación del lienzo usando context.transform .

Traducir (== mover) un punto con context.translate(75,25)

introduzca la descripción de la imagen aquí

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

introduzca la descripción de la imagen aquí

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

introduzca la descripción de la imagen aquí

Canvas realmente logra transformaciones al alterar todo el sistema de coordenadas del canvas.

  • context.translate moverá el origen del lienzo [0,0] desde la esquina superior izquierda a una nueva ubicación.
  • context.rotate rotará todo el sistema de coordenadas del lienzo alrededor del origen.
  • context.scale escalará todo el sistema de coordenadas del lienzo alrededor del origen. Piense en esto como un aumento del tamaño de cada x, y en el lienzo: every x*=scaleX y every y*=scaleY .

Las transformaciones del lienzo son persistentes. Todos los dibujos nuevos continuarán transformándose hasta que restablezca la transformación del lienzo a su estado predeterminado (== totalmente sin transformar). Puede restablecer la configuración predeterminada con:

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

Una matriz de transformación para realizar un seguimiento de las formas traducidas, rotadas y escaladas

Canvas le permite context.translate , context.rotate y context.scale para dibujar su forma en la posición y el tamaño que necesita.

Canvas utiliza una matriz de transformación para realizar un seguimiento eficiente de las transformaciones.

  • Puedes cambiar la matriz de Canvas con context.transform
  • Puede cambiar la matriz de Canvas con los comandos de translate, rotate & scale individuales
  • Puede sobrescribir completamente la matriz de Canvas con context.setTransform ,
  • Pero no puede leer la matriz de transformación interna de Canvas, es solo de escritura.

¿Por qué usar una matriz de transformación?

Una matriz de transformación le permite agregar muchas traducciones individuales, rotaciones y escalas en una matriz única y fácil de volver a aplicar.

Durante animaciones complejas, puede aplicar docenas (o cientos) de transformaciones a una forma. Al utilizar una matriz de transformación, puede (casi) volver a aplicar esas docenas de transformaciones con una sola línea de código.

Algunos ejemplos de uso:

  • Probar si el mouse está dentro de una forma que ha traducido, rotado y escalado

    Existe un context.isPointInPath incorporado que comprueba si un punto (por ejemplo, el mouse) está dentro de una forma de trayectoria, pero esta prueba incorporada es muy lenta en comparación con las pruebas que usan una matriz.

    Comprobar de manera eficiente si el mouse está dentro de una forma implica tomar la posición del mouse informada por el navegador y transformarla de la misma manera que se transformó la forma. Luego, puede aplicar la prueba de impacto como si la forma no se hubiera transformado.

  • Redibuje una forma que haya sido traducida, girada y escalada extensivamente.

    En lugar de volver a aplicar transformaciones individuales con múltiples .translate, .rotate, .scale , puede aplicar todas las transformaciones agregadas en una sola línea de código.

  • Formas de prueba de colisión que han sido traducidas, rotadas y escaladas.

    Puede usar geometría y trigonometría para calcular los puntos que conforman las formas transformadas, pero es más rápido usar una matriz de transformación para calcular esos puntos.

Una matriz de transformación "clase"

Este código refleja los comandos nativos de transformación context.translate , context.rotate , context.scale . A diferencia de la matriz del lienzo nativo, esta matriz es legible y reutilizable.

Métodos:

  • translate , rotate , scale los comandos de transformación de contexto y te permite alimentar transformaciones en la matriz. La matriz mantiene eficientemente las transformaciones agregadas.

  • setContextTransform toma un contexto y establece la matriz de ese contexto igual a esta matriz de transformación. Esto vuelve a aplicar de manera eficiente todas las transformaciones almacenadas en esta matriz al contexto.

  • resetContextTransform restablece la transformación del contexto a su estado predeterminado (== sin transformar).

  • getTransformedPoint toma un punto de coordenadas sin transformar y lo convierte en un punto transformado.

  • getScreenPoint toma un punto de coordenadas transformado y lo convierte en un punto sin transformar.

  • getMatrix devuelve las transformaciones agregadas en forma de matriz de matriz.

Código:

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);
})();

Manifestación:

Esta demostración utiliza la Matriz de transformación "Clase" anterior para:

  • Seguimiento (== guardar) la matriz de transformación de un rectángulo.

  • Redibuje el rectángulo transformado sin utilizar comandos de transformación de contexto.

  • Probar si el ratón ha hecho clic dentro del rectángulo transformado.

Código:

<!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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow