Suche…


Viele übersetzte, skalierte und gedrehte Bilder schnell zeichnen

In vielen Situationen möchten Sie ein Bild zeichnen, das gedreht, skaliert und übersetzt wird. Die Drehung sollte um die Bildmitte herum erfolgen. Dies ist der schnellste Weg auf der 2D-Leinwand. Diese Funktionen eignen sich gut für 2D-Spiele, bei denen die Erwartung besteht, dass alle 60 Sekunden ein paar hundert Bilder und sogar mehr als 1000 Bilder gerendert werden. (abhängig von der 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
}

Eine Variante kann auch den Alpha-Wert enthalten, der für Partikelsysteme nützlich ist.

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 ist wichtig zu wissen, dass beide Funktionen den Leinwandkontext in einem zufälligen Zustand belassen. Obwohl die Funktionen nicht von anderen Rendering-Funktionen betroffen sein werden. Wenn Sie mit dem Rendern von Bildern fertig sind, müssen Sie möglicherweise die Standardumwandlung wiederherstellen

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

Wenn Sie die Alpha-Version (zweites Beispiel) und dann die Standardversion verwenden, müssen Sie sicherstellen, dass der globale Alpha-Status wiederhergestellt wird

ctx.globalAlpha = 1;

Ein Beispiel für die Verwendung der obigen Funktionen zum Rendern einiger Partikel und einiger 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

Drehen Sie ein Bild oder einen Pfad um den Mittelpunkt

Geben Sie hier die Bildbeschreibung ein

Die Schritte 1 bis 5 unten ermöglichen, dass ein Bild oder eine Pfadform an eine beliebige Stelle auf der Leinwand verschoben und in einen beliebigen Winkel gedreht werden kann, ohne die ursprünglichen Punktkoordinaten der Bild- / Pfadform zu ändern.

  1. Verschieben Sie den Ursprung der Leinwand [0,0] zum Mittelpunkt der Form

    context.translate( shapeCenterX, shapeCenterY );
    
  2. Die Leinwand um den gewünschten Winkel drehen (im Bogenmaß)

    context.rotate( radianAngle );
    
  3. Bewegen Sie den Leinwandursprung zurück in die linke obere Ecke

     context.translate( -shapeCenterX, -shapeCenterY );
    
  4. Zeichnen Sie das Bild oder die Pfadform mit den ursprünglichen Koordinaten.

     context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
    
  5. Immer aufräumen! Setzen Sie den Transformationsstatus wieder auf den Wert vor # 1

  • Schritt 5, Option 1: Machen Sie jede Transformation in umgekehrter Reihenfolge rück

       // undo #3
       context.translate( shapeCenterX, shapeCenterY );
       // undo #2
       context.rotate( -radianAngle );
       // undo #1
       context.translate( -shapeCenterX, shapeCenterY );
    
  • Schritt 5, Option 2: Wenn sich die Leinwand vor dem Beginn von Schritt 1 in einem nicht transformierten Zustand befunden hat (Standardeinstellung), können Sie die Auswirkungen der Schritte 1 bis 3 rückgängig machen, indem Sie alle Transformationen auf ihren Standardstatus zurücksetzen

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

Beispielcode Demo:

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

Einführung in Transformationen

Transformationen ändern die Startposition eines bestimmten Punkts, indem dieser Punkt verschoben, gedreht und skaliert wird.

  • Übersetzung: Verschiebt einen Punkt um eine distanceX und eine distanceY .
  • Rotation: Dreht einen Punkt um einen radian angle um seinen Rotationspunkt. Der Standarddrehpunkt in Html Canvas ist der obere linke Ursprung [x = 0, y = 0] des Canvas. Sie können den Drehpunkt jedoch mit Hilfe von Übersetzungen neu positionieren.
  • Skalierung: scalingFactorX die Position eines scalingFactorX um einen scalingFactorX und einen scalingFactorY vom Skalierungspunkt aus. Der Standard-Skalierungspunkt in Html Canvas ist der linke obere Ursprung [x = 0, y = 0] des Canvas. Sie können den Skalierungspunkt jedoch mit Übersetzungen neu positionieren.

Sie können auch weniger gebräuchliche Transformationen durchführen, z. B. das Scheren (Verziehen), indem Sie die Transformationsmatrix der context.transform direkt mit context.transform .

Einen Punkt mit context.translate(75,25) übersetzen (== verschieben context.translate(75,25)

Geben Sie hier die Bildbeschreibung ein

Einen Punkt mit context.rotate(Math.PI/8)

Geben Sie hier die Bildbeschreibung ein

context.scale(2,2) einen Punkt mit context.scale(2,2)

Geben Sie hier die Bildbeschreibung ein

Canvas erzielt tatsächlich Transformationen, indem das gesamte Koordinatensystem der Canvas geändert wird.

  • context.translate verschiebt den Ursprung der Leinwand [0,0] von der oberen linken Ecke an eine neue Position.
  • context.rotate dreht das gesamte Leinwandkoordinatensystem um den Ursprung.
  • context.scale skaliert das gesamte Leinwandkoordinatensystem um den Ursprung. every x*=scaleX dass die Größe jedes x, y auf der Leinwand every x*=scaleX : every x*=scaleX und every y*=scaleY .

Canvas-Transformationen sind dauerhaft. Alle neuen Zeichnungen werden solange transformiert, bis Sie die Umwandlung der Zeichenfläche auf den Standardstatus zurücksetzen (== völlig untransformiert). Sie können den Standard auf folgende Weise zurücksetzen:

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

Eine Transformationsmatrix zum Verfolgen der übersetzten, gedrehten und skalierten Form (en)

Mit Canvas können Sie context.translate , context.rotate und context.scale context.translate , um Ihre Form in der context.scale Position und Größe zu zeichnen.

Canvas selbst verwendet eine Transformationsmatrix, um Transformationen effizient zu verfolgen.

  • Sie können die Matrix von Canvas mit context.transform
  • Sie können die Canvas-Matrix mit einzelnen Befehlen zum translate, rotate & scale Skalieren ändern
  • Sie können die Canvas-Matrix vollständig mit context.setTransform überschreiben,
  • Sie können die interne Transformationsmatrix von Canvas jedoch nicht lesen - sie ist nur schreibgeschützt.

Warum eine Transformationsmatrix verwenden?

Mit einer Transformationsmatrix können Sie viele einzelne Translationen, Rotationen und Skalierungen in einer einzigen, einfach erneut anzuwendenden Matrix zusammenfassen.

In komplexen Animationen können Sie Dutzende (oder Hunderte) von Transformationen auf eine Form anwenden. Durch die Verwendung einer Transformationsmatrix können Sie diese Dutzende von Transformationen (fast) sofort mit einer einzigen Codezeile erneut anwenden.

Einige Beispiele verwenden:

  • Testen Sie, ob sich die Maus in einer Form befindet, die Sie übersetzt, gedreht und skaliert haben

    Es gibt einen eingebauten context.isPointInPath , der prüft, ob sich ein Punkt (z. B. die Maus) innerhalb einer context.isPointInPath befindet. Dieser integrierte Test ist jedoch im Vergleich zum Testen mit einer Matrix sehr langsam.

    Um effizient zu testen, ob sich die Maus innerhalb einer Form befindet, muss die vom Browser gemeldete Mausposition auf dieselbe Weise wie die Form transformiert werden. Dann können Sie Trefferprüfungen anwenden, als ob die Form nicht transformiert wurde.

  • Zeichnen Sie eine Form, die umfangreich übersetzt, gedreht und skaliert wurde.

    Anstatt einzelne Transformationen mit mehreren .translate, .rotate, .scale Sie alle aggregierten Transformationen in einer einzigen Codezeile anwenden.

  • Kollisionstestformen, die übersetzt, gedreht und skaliert wurden

    Sie können Geometrie und Trigonometrie verwenden, um die Punkte zu berechnen, aus denen sich transformierte Formen zusammensetzen. Es ist jedoch schneller, diese Transformationsmatrix mit einer Transformationsmatrix zu berechnen.

Eine Transformationsmatrix "Klasse"

Dieser Code spiegelt die context.scale Umwandlungsbefehle context.translate , context.rotate und context.scale . Im Gegensatz zur nativen Canvas-Matrix ist diese Matrix lesbar und wiederverwendbar.

Methoden:

  • translate , rotate , scale Spiegel die Kontexttransformationsbefehle und lassen Sie Transformationen in die Matrix ernähren. Die Matrix hält die aggregierten Transformationen effizient.

  • setContextTransform nimmt einen Kontext und setzt die Matrix dieses Kontexts dieser Transformationsmatrix gleich. Dadurch werden alle in dieser Matrix gespeicherten Transformationen wieder effizient auf den Kontext angewendet.

  • resetContextTransform setzt die Umwandlung des Kontexts in den Standardzustand zurück (== untransformiert).

  • getTransformedPoint nimmt einen nicht transformierten Koordinatenpunkt und konvertiert ihn in einen transformierten Punkt.

  • getScreenPoint nimmt einen transformierten Koordinatenpunkt und konvertiert ihn in einen nicht transformierten Punkt.

  • getMatrix gibt die aggregierten Transformationen in Form eines Matrix-Arrays zurück.

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:

Diese Demo verwendet die Transformationsmatrix "Klasse", um:

  • Verfolgung (= Speichern) der Transformationsmatrix eines Rechtecks.

  • Zeichnen Sie das transformierte Rechteck neu, ohne Kontextbefehle zu verwenden.

  • Testen Sie, ob die Maus in das transformierte Rechteck geklickt hat.

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow