问题
I have been trying to figure out what I am doing wrong when calculation the position and size of the mask rectangle once the target image has been resized.
Below is the documentation from fabric JS:
clipPath :fabric.Object a fabricObject that, without stroke define a clipping area with their shape. filled in black the clipPath object gets used when the object has rendered, and the context is placed in the center of the object cacheCanvas. If you want 0,0 of a clipPath to align with an object center, use clipPath.originX/Y to 'center' Type: fabric.Object Source: fabric.js, line 12991
The code I have works perfectly when the image is not resized (scale 1:1 X & Y). In the code's function named rescaleMask I attempt to position the mask to a zero center X & Y and when I run my math manually on a piece of graph paper it appears the math is correct. Obviously there is a piece that I am unaware of that is causing the positioning to be off in different ways depending on the quadrant in which the crop is being performed. There is quite a bit of code here but it is important that the mask is created dynamically and not hard coded. The problem must be in the rescaleMask function so hopefully the rest of the code can be ignored.
I created a test image graph with numbered squares which I will crop by clicking the mask button, drawing a rectangle around one of the boxes with the mouse left button and then clicking the crop button. The problem occurs when you resize the image before creating the mask and cropping.
Here is the test image:
Here is a jsfiddle fabric Creating rect with a mouse dynamic js 2.4.1 sent as fix #4
<canvas id="c" width="500" height="500" style="border:1px solid #ccc"></canvas>
<button id="mask">Mask</button>
<button id="crop">Crop</button>
JS
var lastSelectedPicture = null;
var isInsertingCropRectangle = false;
var canvas = new fabric.Canvas('c', {
selection: true,
preserveObjectStacking: true,
height: 700,
width: 800
});
var crop_rect, isDown, origX, origY, mask, target;
var done = false;
var src = "https://stealth-apsvaw.streamhoster.com/fabric_js_2_4_1_crop_test/graph_paper_540.png";
fabric.Image.fromURL(src, function(img) {
img.selectable = true;
img.id = 'target';
img.top = 30;
img.left = 30;
canvas.add(img);
});
canvas.on('object:added', function(e) {
target = null;
mask = null;
canvas.forEachObject(function(obj) {
//alert(obj.get('id'));
var id = obj.get('id');
if (id === 'target') {
target = obj;
canvas.setActiveObject(obj);
}
if (id === 'mask') {
//alert(done);
//alert('mask');
mask = obj;
}
});
});
canvas.on('object:modified', function(e) {
e.target.setCoords();
canvas.renderAll();
});
//////////////////////////////////////////////////////////
// MASK
//////////////////////////////////////////////////////////
document.getElementById("mask").addEventListener("click", function() {
isInsertingCropRectangle = true;
canvas.discardActiveObject();
lastSelectedPicture.selectable = false;
lastSelectedPicture.setCoords();
lastSelectedPicture.dirty = true;
canvas.renderAll();
canvas.discardActiveObject();
isInsertingCropRectangle = true;
});
//////////////////////////////////////////////////////////
// CROP
//////////////////////////////////////////////////////////
document.getElementById("crop").addEventListener("click", function() {
if (target !== null && mask !== null) {
target.setCoords();
// Re-scale mask
mask = rescaleMask(target, mask);
mask.setCoords();
// Do the crop
target.clipPath = mask;
target.dirty=true;
canvas.setActiveObject(target);
canvas.bringToFront(target);
target.selectable = true;
canvas.remove(mask);
canvas.renderAll();
console.log(target);
}
});
//////////////////////////////////////////////////////////
// RE-SCALE MASK FOR CROPPING
// P R O B L E M I N T H I S F U N C T I O N
//////////////////////////////////////////////////////////
function rescaleMask(target, mask){
mask.scaleX = 1;
mask.scaleY = 1;
var targetCenterX = target.width * target.scaleX / 2;
var targetCenterY = target.height * target.scaleY / 2;
var maskOverlapX = mask.left - target.left;
var maskOverlapY = mask.top - target.top;
var centerBasedX = maskOverlapX - targetCenterX;
var centerBasedY = maskOverlapY - targetCenterY;
if( maskOverlapX >= targetCenterX){
centerBasedX = maskOverlapX - targetCenterX;
}
else{
centerBasedX = -(targetCenterX) + maskOverlapX;
}
if( maskOverlapY >= targetCenterY){
centerBasedY = maskOverlapY - targetCenterY;
}
else{
centerBasedY = -(targetCenterY) + maskOverlapY;
}
console.log('targetleft = '+target.left);
console.log('targettop = '+target.top);
console.log('targetCenterX = '+targetCenterX);
console.log('targetCenterY = '+targetCenterY);
console.log('maskleft = '+mask.left);
console.log('masktop = '+mask.top);
console.log('maskOverlapX = '+maskOverlapX);
console.log('maskOverlapY = '+maskOverlapY);
console.log('centerBasedX = '+centerBasedX);
console.log('centerBasedY = '+centerBasedY);
mask.left = centerBasedX;
mask.top = centerBasedY;
mask.originX = 'left';
mask.originY = 'top';
mask.setCoords();
mask.dirty=true;
canvas.renderAll();
//var newMask = mask;
return(mask);
}
canvas.on('mouse:down', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse down done = '+done);
if (done) {
canvas.renderAll();
return;
}
isDown = true;
var pointer = canvas.getPointer(o.e);
origX = pointer.x;
origY = pointer.y;
crop_rect = new fabric.Rect({
left: origX,
top: origY,
width: pointer.x - origX,
height: pointer.y - origY,
opacity: .3,
transparentCorners: false,
selectable: true,
id: 'mask'
});
canvas.add(crop_rect);
canvas.renderAll();
}
else{
}
});
canvas.on('mouse:move', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse move done = '+done);
if (done) {
canvas.renderAll();
return;
}
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
if (origX > pointer.x) {
crop_rect.set({
left: Math.abs(pointer.x)
});
}
if (origY > pointer.y) {
crop_rect.set({
top: Math.abs(pointer.y)
});
}
crop_rect.set({
width: Math.abs(origX - pointer.x)
});
crop_rect.set({
height: Math.abs(origY - pointer.y)
});
crop_rect.setCoords();
canvas.renderAll();
}
else{
}
});
canvas.on('mouse:up', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse up done = '+done);
if (done) {
canvas.renderAll();
return;
}
isDown = false;
crop_rect.set({
selectable: true
});
done = true;
}
else{
}
});
canvas.on('selection:created', function(event) {
console.log("canvas.on('selection:created'");
selectionChanged(event);
});
canvas.on('selection:updated', function(event) {
console.log("canvas.on('selection:updated'");
selectionChanged(event);
});
function selectionChanged(event){
console.log("selectionChanged");
console.log("selectionChanged type = "+event.target.type);
switch(event.target.type) {
case 'textbox':
break;
case 'image':
lastSelectedPicture = event.target;
break;
case 'rect':
break;
case 'group':
break;
default:
break;
}
}
回答1:
You need to take in consideration the target.scaleX
and target.scaleY
for mask.
var lastSelectedPicture = null;
var isInsertingCropRectangle = false;
canvas = new fabric.Canvas('c', {
selection: true,
preserveObjectStacking: true,
height: 700,
width: 800
});
var crop_rect, isDown, origX, origY, mask, target;
var done = false;
var src = "https://stealth-apsvaw.streamhoster.com/fabric_js_2_4_1_crop_test/graph_paper_540.png";
fabric.Image.fromURL(src, function(img) {
img.selectable = true;
img.id = 'target';
img.top = 30;
img.left = 30;
canvas.add(img);
});
canvas.on('object:added', function(e) {
target = null;
mask = null;
canvas.forEachObject(function(obj) {
//alert(obj.get('id'));
var id = obj.get('id');
if (id === 'target') {
target = obj;
canvas.setActiveObject(obj);
}
if (id === 'mask') {
//alert(done);
//alert('mask');
mask = obj;
}
});
});
canvas.on('object:modified', function(e) {
e.target.setCoords();
canvas.renderAll();
});
//////////////////////////////////////////////////////////
// MASK
//////////////////////////////////////////////////////////
document.getElementById("mask").addEventListener("click", function() {
isInsertingCropRectangle = true;
canvas.discardActiveObject();
lastSelectedPicture.selectable = false;
lastSelectedPicture.setCoords();
lastSelectedPicture.dirty = true;
canvas.renderAll();
canvas.discardActiveObject();
isInsertingCropRectangle = true;
});
//////////////////////////////////////////////////////////
// CROP
//////////////////////////////////////////////////////////
document.getElementById("crop").addEventListener("click", function() {
if (target !== null && mask !== null) {
target.setCoords();
// Re-scale mask
mask = rescaleMask(target, mask);
mask.setCoords();
// Do the crop
target.clipPath = mask;
target.dirty=true;
canvas.setActiveObject(target);
canvas.bringToFront(target);
target.selectable = true;
canvas.remove(mask);
canvas.renderAll();
console.log(target);
}
});
//////////////////////////////////////////////////////////
// RE-SCALE MASK FOR CROPPING
// P R O B L E M I N T H I S F U N C T I O N
//////////////////////////////////////////////////////////
function rescaleMask(target, mask){
mask.scaleX = 1;
mask.scaleY = 1;
mask.scaleX/=target.scaleX;
mask.scaleY/=target.scaleY;
var targetCenterX = target.width * target.scaleX / 2;
var targetCenterY = target.height * target.scaleY / 2;
var maskOverlapX = mask.left - target.left;
var maskOverlapY = mask.top - target.top;
var centerBasedX = maskOverlapX - targetCenterX;
var centerBasedY = maskOverlapY - targetCenterY;
if( maskOverlapX >= targetCenterX){
centerBasedX = (maskOverlapX - targetCenterX)/target.scaleX;
}
else{
centerBasedX = (-(targetCenterX) + maskOverlapX)/target.scaleX;
}
if( maskOverlapY >= targetCenterY){
centerBasedY = (maskOverlapY - targetCenterY)/target.scaleY;
}
else{
centerBasedY = (-(targetCenterY) + maskOverlapY)/target.scaleY;
}
console.log('targetleft = '+target.left);
console.log('targettop = '+target.top);
console.log('targetCenterX = '+targetCenterX);
console.log('targetCenterY = '+targetCenterY);
console.log('maskleft = '+mask.left);
console.log('masktop = '+mask.top);
console.log('maskOverlapX = '+maskOverlapX);
console.log('maskOverlapY = '+maskOverlapY);
console.log('centerBasedX = '+centerBasedX);
console.log('centerBasedY = '+centerBasedY);
mask.left = centerBasedX;
mask.top = centerBasedY;
mask.originX = 'left';
mask.originY = 'top';
mask.setCoords();
mask.dirty=true;
canvas.renderAll();
//var newMask = mask;
return(mask);
}
canvas.on('mouse:down', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse down done = '+done);
if (done) {
canvas.renderAll();
return;
}
isDown = true;
var pointer = canvas.getPointer(o.e);
origX = pointer.x;
origY = pointer.y;
crop_rect = new fabric.Rect({
left: origX,
top: origY,
width: pointer.x - origX,
height: pointer.y - origY,
opacity: .3,
transparentCorners: false,
selectable: true,
id: 'mask'
});
canvas.add(crop_rect);
canvas.renderAll();
}
else{
}
});
canvas.on('mouse:move', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse move done = '+done);
if (done) {
canvas.renderAll();
return;
}
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
if (origX > pointer.x) {
crop_rect.set({
left: Math.abs(pointer.x)
});
}
if (origY > pointer.y) {
crop_rect.set({
top: Math.abs(pointer.y)
});
}
crop_rect.set({
width: Math.abs(origX - pointer.x)
});
crop_rect.set({
height: Math.abs(origY - pointer.y)
});
crop_rect.setCoords();
canvas.renderAll();
}
else{
}
});
canvas.on('mouse:up', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse up done = '+done);
if (done) {
canvas.renderAll();
return;
}
isDown = false;
crop_rect.set({
selectable: true
});
done = true;
}
else{
}
});
canvas.on('selection:created', function(event) {
console.log("canvas.on('selection:created'");
selectionChanged(event);
});
canvas.on('selection:updated', function(event) {
console.log("canvas.on('selection:updated'");
selectionChanged(event);
});
function selectionChanged(event){
console.log("selectionChanged");
console.log("selectionChanged type = "+event.target.type);
switch(event.target.type) {
case 'textbox':
break;
case 'image':
lastSelectedPicture = event.target;
break;
case 'rect':
break;
case 'group':
break;
default:
break;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.1/fabric.min.js"></script>
<canvas id="c" width="500" height="500" style="border:1px solid #ccc"></canvas>
<button id="mask">Mask</button>
<button id="crop">Crop</button>
来源:https://stackoverflow.com/questions/52712035/fabric-js-2-4-1-how-to-use-clippath-to-crop-image-that-has-been-scaled-smaller-o