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.
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).
Nous voulons d’abord notre toile, bien sûr:
var canvas = new fabric.Canvas('c');
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.
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.
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';
_.bind()
?Notez que la référence est encapsulée dans la fonction _.bind()
.
J'utilise _.bind()
pour les deux raisons suivantes:
Image
de référence à clipByName()
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
.
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:
Mise à jour de la réponse Promlnc.
Vous devez remplacer l'ordre des transformations de contexte afin d'effectuer un découpage correct.
Sinon, vous obtiendrez un objet mal écrêté - si vous redimensionnez sans conserver le rapport de format (modification d'une seule dimension).
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:
UPDATE 1:
J'ai développé une fonctionnalité à découper par polygone. http://jsfiddle.net/ZxYCP/198/
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();
};
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
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.
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));