Zoeken…


Veel vertaalde, geschaalde en geroteerde afbeeldingen snel tekenen

Er zijn veel situaties waarin u een afbeelding wilt tekenen die is gedraaid, geschaald en vertaald. De rotatie moet plaatsvinden rond het midden van de afbeelding. Dit is de snelste manier om dit op het 2D-canvas te doen. Deze functies zijn zeer geschikt voor 2D-games waarbij de verwachting is om een paar honderd tot maximaal 1000 afbeeldingen per 60 seconden te maken. (afhankelijk van de 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
}

Een variant kan ook de alfawaarde bevatten die handig is voor deeltjessystemen.

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
}

Het is belangrijk op te merken dat beide functies de canvascontext in een willekeurige staat verlaten. Hoewel de functies niet worden beïnvloed, wordt mijn weergave anders. Wanneer u klaar bent met het renderen van afbeeldingen, moet u mogelijk de standaardtransformatie herstellen

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

Als u de alfaversie (tweede voorbeeld) en vervolgens de standaardversie gebruikt, moet u ervoor zorgen dat de globale alfastatus wordt hersteld

ctx.globalAlpha = 1;

Een voorbeeld van het gebruik van de bovenstaande functies om enkele deeltjes en enkele afbeeldingen weer te geven

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

Roteer een afbeelding of pad rond het middelpunt

voer hier de afbeeldingsbeschrijving in

Met stappen # 1-5 hieronder kan elke afbeelding of padvorm zowel naar elke plek op het canvas worden verplaatst als in elke hoek worden gedraaid zonder de oorspronkelijke puntcoördinaten van de afbeelding / padvorm te wijzigen.

  1. Verplaats de canvas [0,0] oorsprong naar het middelpunt van de vorm

    context.translate( shapeCenterX, shapeCenterY );
    
  2. Roteer het canvas met de gewenste hoek (in radialen)

    context.rotate( radianAngle );
    
  3. Verplaats de canvasoorsprong terug naar de linkerbovenhoek

     context.translate( -shapeCenterX, -shapeCenterY );
    
  4. Teken de afbeelding of padvorm met behulp van de oorspronkelijke coördinaten.

     context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
    
  5. Altijd opruimen! Zet de transformatiestatus terug naar waar deze vóór # 1 was

  • Stap # 5, Optie # 1: Maak elke transformatie ongedaan in de omgekeerde volgorde

       // undo #3
       context.translate( shapeCenterX, shapeCenterY );
       // undo #2
       context.rotate( -radianAngle );
       // undo #1
       context.translate( -shapeCenterX, shapeCenterY );
    
  • Stap # 5, Optie # 2: Als het canvas in een niet-getransformeerde staat was (de standaardinstelling) voordat u aan stap # 1 begon, kunt u de effecten van stappen # 1-3 ongedaan maken door alle transformaties terug te zetten naar hun standaardstatus

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

Voorbeeld van codedemo:

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

Inleiding tot transformaties

Transformaties veranderen de startpositie van een bepaald punt door dat punt te verplaatsen, roteren en schalen.

  • Vertaling: Verplaatst een punt met een distanceX en distanceY .
  • Rotatie: roteert een punt met een radian angle rond het rotatiepunt. Het standaard rotatiepunt in HTML Canvas is de oorsprong linksboven [x = 0, y = 0] van het Canvas. Maar u kunt het rotatiepunt verplaatsen met behulp van vertalingen.
  • Schalen: schaalt de positie van een punt door een scalingFactorX en scalingFactorY vanaf het scalingFactorY . Het standaard schaalpunt in Html Canvas is de oorsprong linksboven [x = 0, y = 0] van het Canvas. Maar u kunt het schaalpunt verplaatsen met behulp van vertalingen.

U kunt ook minder algemene transformaties uitvoeren, zoals knippen (scheeftrekken), door de transformatiematrix van het canvas rechtstreeks in te stellen met context.transform .

Een punt vertalen (== verplaatsen) met context.translate(75,25)

voer hier de afbeeldingsbeschrijving in

Een punt roteren met context.rotate(Math.PI/8)

voer hier de afbeeldingsbeschrijving in

Een punt schalen met context.scale(2,2)

voer hier de afbeeldingsbeschrijving in

Canvas realiseert eigenlijk transformaties door het hele coördinatensysteem van het canvas te wijzigen.

  • context.translate verplaatst de oorsprong van het canvas [0,0] vanuit de linkerbovenhoek naar een nieuwe locatie.
  • context.rotate roteert het hele canvascoördinatensysteem rond de oorsprong.
  • context.scale schaalt het hele canvascoördinatensysteem rond de oorsprong. Zie dit als het vergroten van de grootte van elke x, y op het canvas: every x*=scaleX en every y*=scaleY .

Canvastransformaties zijn persistent. Alle nieuwe tekeningen worden getransformeerd totdat u de transformatie van het canvas terugzet naar de standaardstatus (== volledig niet-getransformeerd). U kunt de standaardwaarden herstellen met:

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

Een transformatiematrix om vertaalde, geroteerde en geschaalde vorm (en) bij te houden

Met Canvas kunt u context.translate , context.rotate en context.scale om uw vorm te tekenen in de gewenste positie en grootte.

Canvas zelf gebruikt een transformatiematrix om transformaties efficiënt te volgen.

  • U kunt de matrix van Canvas wijzigen met context.transform
  • U kunt de matrix van Canvas wijzigen met afzonderlijke opdrachten voor translate, rotate & scale
  • Je kunt Canvas's matrix volledig overschrijven met context.setTransform ,
  • Maar je kunt de interne transformatiematrix van Canvas niet lezen - het is alleen-schrijven.

Waarom een transformatiematrix gebruiken?

Met een transformatiematrix kunt u veel individuele vertalingen, rotaties en schalen samenvoegen tot een enkele, eenvoudig opnieuw toe te passen matrix.

Tijdens complexe animaties kunt u tientallen (of honderden) transformaties op een vorm toepassen. Door een transformatiematrix te gebruiken, kunt u die tientallen transformaties (bijna) onmiddellijk opnieuw toepassen met een enkele coderegel.

Sommige voorbeelden gebruiken:

  • Test of de muis zich in een vorm bevindt die u hebt vertaald, gedraaid en geschaald

    Er is een ingebouwde context.isPointInPath die test of een punt (bijvoorbeeld de muis) zich in een padvorm bevindt, maar deze ingebouwde test is erg langzaam in vergelijking met testen met een matrix.

    Als u de muis efficiënt in een vorm wilt testen, moet u de door de browser gerapporteerde muispositie innemen en deze op dezelfde manier transformeren als de vorm. Vervolgens kunt u hit-testen toepassen alsof de vorm niet is getransformeerd.

  • Teken een vorm opnieuw die uitgebreid is vertaald, gedraaid en geschaald.

    In plaats van afzonderlijke transformaties opnieuw toe te passen met meerdere .translate, .rotate, .scale kunt u alle geaggregeerde transformaties in een enkele .translate, .rotate, .scale toepassen.

  • Botsingstestvormen die zijn vertaald, gedraaid en geschaald

    U kunt geometrie en trigonometrie gebruiken om de punten te berekenen waaruit getransformeerde vormen bestaan, maar het is sneller om een transformatiematrix te gebruiken om die punten te berekenen.

Een transformatiematrix "Klasse"

Deze code weerspiegelt de native transformatieopdrachten context.translate , context.rotate , context.scale . In tegenstelling tot de native canvas-matrix is deze matrix leesbaar en herbruikbaar.

methoden:

  • translate , rotate , scale spiegelt de contexttransformatieopdrachten en stelt u in staat transformaties in de matrix in te voeren. De matrix bevat efficiënt de geaggregeerde transformaties.

  • setContextTransform neemt een context en stelt de matrix van die context gelijk aan deze transformatiematrix. Hierdoor worden alle transformaties die in deze matrix zijn opgeslagen, opnieuw toegepast op de context.

  • resetContextTransform zet de transformatie van de context terug naar de standaardstatus (== niet-getransformeerd).

  • getTransformedPoint neemt een niet-getransformeerd coördinaatpunt en converteert het naar een getransformeerd punt.

  • getScreenPoint neemt een getransformeerd coördinaatpunt en converteert het naar een niet-getransformeerd punt.

  • getMatrix retourneert de geaggregeerde transformaties in de vorm van een matrixmatrix.

Code:

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:

Deze demo gebruikt de Transformation Matrix "Class" hierboven om:

  • Houd de transformatiematrix van een rechthoek bij (== opslaan).

  • Teken de getransformeerde rechthoek opnieuw zonder opdrachten voor contexttransformatie te gebruiken.

  • Test of de muis in de getransformeerde rechthoek heeft geklikt.

Code:

<!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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow