it-swarm.com.de

Mehrere Schnittbereiche auf der Fabric.js-Zeichenfläche

Für die Erstellung von Photo Collage Maker verwende ich Fabric-Js mit objektbasierter Beschneidungsfunktion. Diese Funktion ist großartig, aber das Bild in diesem Beschneidungsbereich kann nicht skaliert, verschoben oder gedreht werden. Ich möchte einen Ausschnittbereich mit fester Position und das Bild kann innerhalb des festgelegten Ausschnittbereichs positioniert werden, wie es der Benutzer wünscht.

Ich googelte und fand eine sehr nahe Lösung

var canvas = new fabric.Canvas('c');
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.rect(10,10,150,150);
ctx.rect(180,10,200,200);
ctx.closePath();
ctx.stroke();
ctx.clip();

Mehrere Schnittbereiche auf Stoff js Leinwand

wobei das Bild eines Beschneidungsbereichs in einem anderen Beschneidungsbereich erscheint. Wie kann ich das vermeiden oder gibt es eine andere Möglichkeit, dies mithilfe von Fabric Js zu erreichen?.

16
ep4f

Dies kann mit Fabric mithilfe der Eigenschaft clipTo erreicht werden. Sie müssen jedoch die Transformationen (Skalierung und Drehung) in der Funktion clipTo 'umkehren'.

Wenn Sie die Eigenschaft clipTo in Fabric verwenden, werden die Skalierung und Drehung nach des Ausschnitts angewendet. Dies bedeutet, dass der Ausschnitt mit dem Bild skaliert und gedreht wird. Sie müssen dem entgegenwirken, indem Sie das genaue reverse der Transformationen in der Eigenschaftsfunktion clipTo anwenden.

Meine Lösung besteht darin, einen Fabric.Rect als 'Platzhalter' für den Clip-Bereich zu verwenden (dies hat Vorteile, da Sie mit Fabric das Objekt und damit den Clip-Bereich verschieben können.

Bitte beachten Sie, dass meine Lösung die Lo-Dash Utility-Bibliothek verwendet, insbesondere für _.bind() (Kontext siehe Code).

Beispiel Fiddle


Nervenzusammenbruch

1. Fabric initialisieren

Zuerst wollen wir natürlich unsere Leinwand:

var canvas = new fabric.Canvas('c');

2. Clip Region

var clipRect1 = new fabric.Rect({
    originX: 'left',
    originY: 'top',
    left: 180,
    top: 10,
    width: 200,
    height: 200,
    fill: 'none',
    stroke: 'black',
    strokeWidth: 2,
    selectable: false
});

Wir geben diesen Rect-Objekten eine name-Eigenschaft, clipFor, damit die clipTo-Funktionen die finden können, von der sie abgeschnitten werden möchten:

clipRect1.set({
    clipFor: 'pug'
});
canvas.add(clipRect1);

muss kein tatsächliches Objekt für den Clip-Bereich sein, aber es erleichtert die Verwaltung, da Sie es mit Fabric verschieben können.

3. Clipping-Funktion

Wir definieren die Funktion, die von den clipTo -Eigenschaften der Bilder separat verwendet wird, um Code-Duplikate zu vermeiden:

Da die Eigenschaft angle des Image-Objekts in Grad gespeichert ist, wird sie hiermit in Bogenmaß konvertiert.

function degToRad(degrees) {
    return degrees * (Math.PI / 180);
}

findByClipName() ist eine praktische Funktion, die Lo-Dash verwendet, um die Eigenschaft mit der clipFor für das zu beschneidende Image-Objekt zu finden (in der folgenden Abbildung lautet name beispielsweise 'pug'):

function findByClipName(name) {
    return _(canvas.getObjects()).where({
            clipFor: name
        }).first()
}

Und das ist der Teil, der die Arbeit macht:

var clipByName = function (ctx) {
    var clipRect = findByClipName(this.clipName);
    var scaleXTo1 = (1 / this.scaleX);
    var scaleYTo1 = (1 / this.scaleY);
    ctx.save();
    ctx.translate(0,0);
    ctx.rotate(degToRad(this.angle * -1));
    ctx.scale(scaleXTo1, scaleYTo1);
    ctx.beginPath();
    ctx.rect(
        clipRect.left - this.left,
        clipRect.top - this.top,
        clipRect.width,
        clipRect.height
    );
    ctx.closePath();
    ctx.restore();
}

NOTE: Nachfolgend finden Sie Erläuterungen zur Verwendung von this in der obigen Funktion.

4. fabric.Image object using clipByName()

Schließlich kann das Bild instanziiert werden und die Funktion clipByName wie folgt verwenden:

var pugImg = new Image();
pugImg.onload = function (img) {    
    var pug = new fabric.Image(pugImg, {
        angle: 45,
        width: 500,
        height: 500,
        left: 230,
        top: 170,
        scaleX: 0.3,
        scaleY: 0.3,
        clipName: 'pug',
        clipTo: function(ctx) { 
            return _.bind(clipByName, pug)(ctx) 
        }
    });
    canvas.add(pug);
};
pugImg.src = 'http://fabricjs.com/lib/pug.jpg';

Was macht _.bind()?

Beachten Sie, dass die Referenz in die Funktion _.bind() eingeschlossen ist.

Ich benutze _.bind() aus den folgenden zwei Gründen:

  1. Wir müssen ein Referenzobjekt Image an clipByName() übergeben.
  2. Der Eigenschaft clipTo wird der Zeichenbereichskontext und nicht das Objekt übergeben.

Grundsätzlich können Sie mit _.bind() eine Version der Funktion erstellen, die das von Ihnen als this angegebene Objekt verwendet.

  1. http://lodash.com/docs#bind
  2. http://fabricjs.com/docs/fabric.Object.html#clipTo
  3. http://html5.litten.com/understanding-save-and-restore-for-the-canvas-context/
27
natchiketa

Ich habe die Lösung von @natchiketa optimiert, da die Positionierung des Clip-Bereichs nicht korrekt war und bei der Drehung alles wackelte. Aber jetzt scheint alles gut zu sein. Schauen Sie sich diese modifizierte Geige an: http://jsfiddle.net/PromInc/ZxYCP/

Die einzigen tatsächlichen Änderungen wurden in der Funktion clibByName von Schritt 3 des von @natchiketa bereitgestellten Codes vorgenommen. Dies ist die aktualisierte Funktion:

var clipByName = function (ctx) {
    this.setCoords();

    var clipRect = findByClipName(this.clipName);

    var scaleXTo1 = (1 / this.scaleX);
    var scaleYTo1 = (1 / this.scaleY);
    ctx.save();

    var ctxLeft = -( this.width / 2 ) + clipRect.strokeWidth;
    var ctxTop = -( this.height / 2 ) + clipRect.strokeWidth;
    var ctxWidth = clipRect.width - clipRect.strokeWidth + 1;
    var ctxHeight = clipRect.height - clipRect.strokeWidth + 1;

    ctx.translate( ctxLeft, ctxTop );

    ctx.rotate(degToRad(this.angle * -1));
    ctx.scale(scaleXTo1, scaleYTo1);
    ctx.beginPath();

    ctx.rect(
        clipRect.left - this.oCoords.tl.x,
        clipRect.top - this.oCoords.tl.y,
        ctxWidth,
        ctxHeight
    );
    ctx.closePath();
    ctx.restore();
}

Zwei kleinere Fänge habe ich gefunden:

  1. Das Hinzufügen eines Strichs zum Beschneidungsobjekt scheint die Dinge um einige Pixel zu verschieben. Ich habe versucht, die Positionierung zu kompensieren, aber dann fügte es beim Drehen 2 Pixel an der unteren und rechten Seite hinzu. Also habe ich mich dafür entschieden, es einfach komplett zu entfernen.
  2. Wenn Sie das Bild von Zeit zu Zeit drehen, wird es auf zufälligen Seiten des Ausschnitts einen Abstand von 1 Pixel haben.
9
PromInc

Update auf Promlnc-Antwort.

Sie müssen die Reihenfolge der Kontexttransformationen ersetzen, um einen ordnungsgemäßen Zuschnitt durchzuführen.

  1. Übersetzung
  2. skalierung
  3. drehung

Andernfalls erhalten Sie ein falsch zugeschnittenes Objekt, wenn Sie skalieren, ohne das Seitenverhältnis beizubehalten (nur eine Dimension zu ändern).

 bad

Code (69-72):

ctx.translate( ctxLeft, ctxTop );

ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);

Ersetzen:

ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.rotate(degToRad(this.angle * -1));

Siehe dies: http://jsfiddle.net/ZxYCP/185/

Richtiges Ergebnis:

 good

UPDATE 1:

Ich habe eine Funktion zum Ausschneiden nach Polygon entwickelt. http://jsfiddle.net/ZxYCP/198/

 poly

5
l00k

Dies kann viel einfacher gemacht werden. Fabric stellt die Methode render bereit, mit der der Kontext eines anderen Objekts ausgeschnitten werden kann.

Kasse diese Geige . Ich habe das auf einem Kommentar gesehen hier .

        obj.clipTo = function(ctx) {
            ctx.save();
            ctx.setTransform(1, 0, 0, 1, 0, 0);

            clippingRect.render(ctx);

            ctx.restore();
        };
2
Kerem Demirer

Da ich alle obigen Fiddles getestet habe, haben sie einen Fehler. Wenn Sie die X- und Y-Werte zusammen spiegeln, sind die Schnittgrenzen falsch. Um nicht alle Berechnungen zum Platzieren von Bildern an der richtigen Position durchzuführen, müssen Sie originX = 'center' und originY = 'center' für sie angeben.

Hier ist ein Update der Clipping-Funktion auf den Originalcode von @natchiketa

var clipByName = function (ctx) {
    var clipRect = findByClipName(this.clipName);
    var scaleXTo1 = (1 / this.scaleX);
    var scaleYTo1 = (1 / this.scaleY);
    ctx.save();
    ctx.translate(0,0);

    //logic for correct scaling
    if (this.getFlipY() && !this.getFlipX()){
      ctx.scale(scaleXTo1, -scaleYTo1);
    } else if (this.getFlipX() && !this.getFlipY()){
      ctx.scale(-scaleXTo1, scaleYTo1);
    } else if (this.getFlipX() && this.getFlipY()){
      ctx.scale(-scaleXTo1, -scaleYTo1);
    } else {
        ctx.scale(scaleXTo1, scaleYTo1);
    }
    //IMPORTANT!!! do rotation after scaling
    ctx.rotate(degToRad(this.angle * -1));
    ctx.beginPath();
    ctx.rect(
        clipRect.left - this.left,
        clipRect.top - this.top,
        clipRect.width,
        clipRect.height
    );
    ctx.closePath();
    ctx.restore();
}

Bitte überprüfen Sie aktualisierte Geige

1
Observer

Aktualisiere die Antworten der Vorgänger.

ctx.rect(
    clipRect.oCoords.tl.x - this.oCoords.tl.x - clipRect.strokeWidth,
    clipRect.oCoords.tl.y - this.oCoords.tl.y - clipRect.strokeWidth,
    clipRect.oCoords.tr.x - clipRect.oCoords.tl.x,
    clipRect.oCoords.bl.y - clipRect.oCoords.tl.y
);

Jetzt können wir den Beschneidungsbereich ohne Zweifel skalieren.

0
Newbie

Mit dem neuesten Update auf Fabric 1.6.0-rc.1 können Sie das Bild durch Halten der Umschalttaste und Ziehen der Mittelachse verzerren.

Ich habe Probleme beim Umkehren des Schräglaufs, damit der Beschneidungsbereich gleich bleibt. Ich habe versucht, folgenden Code umzukehren, aber es hat nicht funktioniert.

var skewXReverse = - this.skewX;
var skewYReverse = - this.skewY;

ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.transform(1, skewXReverse, skewYReverse, 1, 0, 0);
ctx.rotate(degToRad(this.angle * -1));

Demo: http://jsfiddle.net/uimos/bntepzLL/5/

0
Jowin Yip