Sök…


Rita många översatta, skalade och roterade bilder snabbt

Det finns många situationer där du vill rita en bild som roteras, skalas och översätts. Rotationen ska ske runt mitten av bilden. Det här är det snabbaste sättet på 2D-duken. Dessa funktioner är väl lämpade för 2D-spel där förväntningarna är att göra några hundra till och med upp till 1000+ bilder var 60: e sekund. (beroende på hårdvaran)

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

En variant kan också inkludera alfavärdet som är användbart för partikelsystem.

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
}

Det är viktigt att notera att båda funktionerna lämnar canvas-sammanhanget i slumpmässigt tillstånd. Även om funktionerna inte kommer att påverkas andra gör min be. När du är klar med att rendera bilder kan du behöva återställa standardtransformeringen

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

Om du använder alfa-versionen (andra exemplet) och sedan standardversionen måste du se till att det globala alfa-tillståndet återställs

ctx.globalAlpha = 1;

Ett exempel på att använda ovanstående funktioner för att göra vissa partiklar och några få bilder

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

Rotera en bild eller sökväg runt dess mittpunkt

ange bildbeskrivning här

Steg 1-5 nedan tillåter att alla bilder eller banformer flyttas var som helst på duken och roteras till valfri vinkel utan att ändra någon av bildens / banformens ursprungliga punktkoordinater.

  1. Flytta duken [0,0] ursprung till formens mittpunkt

    context.translate( shapeCenterX, shapeCenterY );
    
  2. Rotera duken med önskad vinkel (i radianer)

    context.rotate( radianAngle );
    
  3. Flytta tillbaka duken till det övre vänstra hörnet

     context.translate( -shapeCenterX, -shapeCenterY );
    
  4. Rita bilden eller banformen med sina ursprungliga koordinater.

     context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
    
  5. Rengör alltid upp! Ställ omvandlingstillståndet tillbaka till det som var före nr 1

  • Steg 5, alternativ 1: Ångra varje transformation i omvänd ordning

       // undo #3
       context.translate( shapeCenterX, shapeCenterY );
       // undo #2
       context.rotate( -radianAngle );
       // undo #1
       context.translate( -shapeCenterX, shapeCenterY );
    
  • Steg 5, alternativ # 2: Om duken var i ett otransformerat tillstånd (standard) innan du börjar steg 1, kan du ångra effekterna av steg # 1-3 genom att återställa alla transformationer till deras standardtillstånd

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

Exempel på koddemo:

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

Introduktion till transformationer

Transformationer förändrar en given punkts startposition genom att flytta, rotera och skala den punkten.

  • Översättning: Flyttar en punkt på distanceX och distanceY .
  • Rotation: Roterar en punkt med en radian angle runt dess rotationspunkt. Standardrotationspunkten i Html Canvas är det övre vänstra ursprunget [x = 0, y = 0] på Canvas. Men du kan flytta om rotationspunkten med översättningar.
  • Skalning: scalingFactorX en scalingFactorX position med en scalingFactorX och scalingFactorY från dess skalningspunkt. Standardskalningspunkten i Html Canvas är det övre vänstra ursprunget [x = 0, y = 0] på Canvas. Men du kan flytta skalningspunkten med översättningar.

Du kan också göra mindre vanliga transformationer, som skjuvning (skevning), genom att direkt ställa in transformationsmatrisen på duken med context.transform .

Översätt (== flytta) en punkt med context.translate(75,25)

ange bildbeskrivning här

Rotera en punkt med context.rotate(Math.PI/8)

ange bildbeskrivning här

Skala en punkt med context.scale(2,2)

ange bildbeskrivning här

Canvas uppnår faktiskt transformationer genom att förändra dukens hela koordinatsystem.

  • context.translate kommer att flytta canvas [0,0] ursprung från det övre vänstra hörnet till en ny plats.
  • context.rotate roterar hela canvas-koordinatsystemet runt ursprunget.
  • context.scale kommer att skala hela canvas-koordinatsystemet runt ursprunget. Tänk på detta som att öka storleken på varje x, y på duken: every x*=scaleX och every y*=scaleY .

Kanvastransformationer är ihållande. Alla nya ritningar kommer att fortsätta att transformeras tills du återställer kanvasens omvandling till sitt standardläge (== helt otransformerad). Du kan återställa till standard med:

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

En transformationsmatris för att spåra översatta, roterade och skalade former

Canvas gör att du kan context.translate , context.rotate och context.scale för att rita din form i den position och storlek du behöver.

Canvas själv använder en transformationsmatris för att effektivt spåra transformationer.

  • Du kan ändra Canvas matris med context.transform
  • Du kan ändra Canvas matrix med individuella kommandon för translate, rotate & scale
  • Du kan helt skriva över Canvas matris med context.setTransform ,
  • Men du kan inte läsa Canvas interna transformationsmatris - det är skrivskyddat.

Varför använda en transformationsmatris?

En transformationsmatris gör att du kan sammanställa många enskilda översättningar, rotationer och skalningar till en enda, lätt applicerad matris.

Under komplexa animationer kan du tillämpa dussintals (eller hundratals) transformationer till en form. Genom att använda en transformationsmatris kan du (nästan) omedelbart applicera om de dussintals transformationerna med en enda kodrad.

Några exempel använder:

  • Testa om musen är i en form som du har översatt, roterat och skalat

    Det finns en inbyggd context.isPointInPath som testar om en punkt (t.ex. musen) är i en banform, men detta inbyggda test är mycket långsamt jämfört med tester med en matris.

    Att testa effektivt om musen är i en form innebär att ta muspositionen som rapporterats av webbläsaren och omvandla den på samma sätt som formen transformerades. Sedan kan du tillämpa hit-testning som om formen inte förvandlats.

  • Rita om en form som i stor utsträckning har översatts, roterats och skalas.

    Istället för att tillämpa enskilda transformationer på .translate, .rotate, .scale med flera .translate, .rotate, .scale du tillämpa alla aggregerade transformationer i en enda kodrad.

  • Kollisionstestformer som har översatts, roterats och skalats

    Du kan använda geometri & trigonometri för att beräkna poäng som utgör transformerade former, men det är snabbare att använda en transformationsmatris för att beräkna dessa punkter.

En transformationsmatris "klass"

Den här koden speglar de ursprungliga context.translate , context.rotate , context.scale transformationskommandona. Till skillnad från den inhemska dukmatrisen är denna matris läsbar och återanvändbar.

metoder:

  • translate , rotate , scale spegla kontexttransformationskommandon och låt dig mata transformationer till matrisen. Matrisen håller effektivt de aggregerade transformationerna.

  • setContextTransform tar ett sammanhang och ställer in den sammanhangets matris lika med denna transformationsmatris. Detta applicerar effektivt igenom alla transformationer som är lagrade i denna matris till sammanhanget.

  • resetContextTransform återställer kontextens omvandling till dess standardtillstånd (== otransformerad).

  • getTransformedPoint tar en otransformerad koordinatpunkt och omvandlar den till en transformerad punkt.

  • getScreenPoint tar en transformerad koordinatpunkt och omvandlar den till en otransformerad punkt.

  • getMatrix returnerar de aggregerade transformationerna i form av en matrisgrupp.

Koda:

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:

Den här demonstrationen använder Transformation Matrix "Class" ovan för att:

  • Spåra (== spara) en rektangels transformationsmatris.

  • Rita om den transformerade rektangeln utan att använda kommandon för omvandlingsomvandling.

  • Testa om musen har klickat inuti den transformerade rektangeln.

Koda:

<!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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow