web-dev-qa-db-fra.com

Plusieurs zones de découpage sur la toile Fabric.js

Pour faire Photo Collage Maker , j'utilise la structure js qui possède une fonction de découpage basée sur les objets. Cette fonctionnalité est excellente, mais l’image à l’intérieur de cette zone de découpage ne peut pas être mise à l’échelle, déplacée ou pivotée. Je souhaite que la région de découpage à position fixe et l’image puissent être positionnées à l’intérieur de la zone de découpage fixe comme le souhaite l’utilisateur. 

J'ai googlé et trouve la solution très proche 

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

Plusieurs zones de découpage sur le tissu js canvas

où l'image d'une région de découpage apparaît dans une autre région de découpage. Comment puis-je éviter cela ou existe-t-il un autre moyen d'y parvenir en utilisant le tissu js.

16
ep4f

Cela peut être accompli avec Fabric en utilisant la propriété clipTo, mais vous devez «inverser» les transformations (échelle et rotation), dans la fonction clipTo.

Lorsque vous utilisez la propriété clipTo dans Fabric, la mise à l'échelle et la rotation sont appliquées after l'écrêtage, ce qui signifie que l'écrêtage est mis à l'échelle et pivoté avec l'image. Vous devez y remédier en appliquant le reverse exact des transformations dans la fonction de propriété clipTo.

Ma solution consiste à faire en sorte que Fabric.Rect serve d’espace réservé pour la région du clip (cela présente des avantages, car vous pouvez utiliser Fabric pour déplacer l’objet et, par conséquent, la région du clip.

Veuillez noter que ma solution utilise la bibliothèque d’utilitaires Lo-Dash , en particulier pour _.bind() (voir le code pour le contexte).

Exemple Fiddle


Panne

1. Initialiser le tissu

Nous voulons d’abord notre toile, bien sûr:

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

2. Clip région

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

Nous attribuons à ces objets Rect une propriété name, clipFor, afin que les fonctions clipTo puissent trouver celle par laquelle ils veulent être découpés:

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

Il n’est pas have d’être un objet réel pour la région de clip, mais cela facilite la gestion, car vous pouvez le déplacer à l’aide de Fabric.

3. Fonction de coupure

Nous définissons la fonction qui sera utilisée séparément par les propriétés clipTo des images pour éviter la duplication de code:

Comme la propriété angle de l'objet Image est stockée en degrés, nous l'utilisons pour le convertir en radians. 

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

findByClipName() est une fonction pratique qui utilise Lo-Dash , pour rechercher la propriété avec la propriété clipFor de l'objet Image à découper (par exemple, dans l'image ci-dessous, name sera 'pug'):

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

Et voici la partie qui fait le travail:

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: Voir ci-dessous l'explication de l'utilisation de this dans la fonction ci-dessus.

4. Objet fabric.Image utilisant clipByName()

Enfin, l'image peut être instanciée et utilisée pour utiliser la fonction clipByName comme ceci:

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';

Que fait _.bind()?

Notez que la référence est encapsulée dans la fonction _.bind() .

J'utilise _.bind() pour les deux raisons suivantes:

  1. Nous devons passer un objet Image de référence à clipByName()
  2. La propriété clipTo est transmise au contexte de la toile, pas à l'objet.

En principe, _.bind() vous permet de créer une version de la fonction qui utilise l'objet que vous spécifiez comme contexte this.

  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

J'ai peaufiné la solution de @natchiketa car le positionnement de la région de clip ne se plaçait pas correctement et il était désordonné lors de la rotation. Mais tout semble être bon maintenant. Découvrez ce violon modifié: http://jsfiddle.net/PromInc/ZxYCP/

Les seuls changements réels ont été apportés à la fonction clibByName de l'étape 3 du code fourni par @natchiketa. C'est la fonction mise à jour:

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

Deux prises mineures que j'ai trouvées:

  1. Ajouter un trait à l’objet de coupure semble faire perdre quelques pixels à . J'ai essayé de compenser le positionnement, mais après Rotation, cela ajouterait 2 pixels au bas et au côté droit. Donc J'ai choisi de l'enlever complètement.
  2. De temps en temps, lorsque vous faites pivoter l'image, celle-ci se termine par un espacement de 1 px sur les côtés aléatoires de la coupure.
9
PromInc

Mise à jour de la réponse Promlnc.

Vous devez remplacer l'ordre des transformations de contexte afin d'effectuer un découpage correct.

  1. traduction
  2. mise à l'échelle
  3. rotation

Sinon, vous obtiendrez un objet mal écrêté - si vous redimensionnez sans conserver le rapport de format (modification d'une seule dimension).

 bad

Code (69-72):

ctx.translate( ctxLeft, ctxTop );

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

Remplacer pour:

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

Voir ceci: http://jsfiddle.net/ZxYCP/185/

Résultat correct:

 good

UPDATE 1:

J'ai développé une fonctionnalité à découper par polygone. http://jsfiddle.net/ZxYCP/198/

 poly

5
l00k

Cela peut être fait beaucoup plus facilement. Fabric fournit render une méthode pour découper par un autre contexte d’objets.

Commander ce violon . J'ai vu cela sur un commentaire ici .

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

            clippingRect.render(ctx);

            ctx.restore();
        };
2
Kerem Demirer

Comme j'ai testé tous les violons ci-dessus, ils ont un bug. C'est lorsque vous inverserez les valeurs X et Y ensemble, les limites de découpage seront fausses. De plus, afin de ne pas effectuer tous les calculs pour placer les images à la bonne position, vous devez spécifier origineX = 'centre' et origineY = 'centre' pour elles.

Voici une mise à jour de la fonction de découpage au code original de @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();
}

S'il vous plaît vérifier mis à jour violon

1
Observer

Mettre à jour les réponses des gars précédents.

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

Nous sommes maintenant en mesure de dimensionner la zone de découpage sans aucun doute.

0
Newbie

Avec la dernière mise à jour de la matrice 1.6.0-rc.1, vous pouvez incliner l’image en maintenant la touche Maj enfoncée et en faisant glisser l’axe du milieu.

J'ai du mal à inverser l'inclinaison pour que la zone de découpage reste la même. J'ai essayé de suivre le code pour essayer de l'inverser, mais cela n'a pas fonctionné.

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

Démo: http://jsfiddle.net/uimos/bntepzLL/5/

0
Jowin Yip