Fabric JS 2.4.1 如何使用 clipPath 裁剪已缩放为小于或大于 1:1 比例的图像

Posted

技术标签:

【中文标题】Fabric JS 2.4.1 如何使用 clipPath 裁剪已缩放为小于或大于 1:1 比例的图像【英文标题】:Fabric JS 2.4.1 How to use clipPath to crop image that has been scaled smaller or larger then 1:1 Ratio 【发布时间】:2019-03-13 16:49:46 【问题描述】:

在调整目标图像的大小后,在计算蒙版矩形的位置和大小时,我一直试图找出我做错了什么。

下面是fabric JS的文档:

clipPath :fabric.Object 一个fabricObject,没有描边,用它们的形状定义一个剪裁区域。当对象被渲染时,clipPath 对象被用黑色填充,并且上下文被放置在对象 cacheCanvas 的中心。如果您希望 clipPath 的 0,0 与对象中心对齐,请使用 clipPath.originX/Y 到 'center' 类型: 织物.对象 资源: fabric.js,第 12991 行

当图像未调整大小(比例为 1:1 X 和 Y)时,我的代码可以完美运行。在名为 rescaleMask 的代码函数中,我尝试将掩码定位到零中心 X 和 Y,当我在一张方格纸上手动运行我的数学时,它似乎数学是正确的。显然,我不知道有一件事情会导致定位以不同的方式关闭,具体取决于执行裁剪的象限。这里有很多代码,但重要的是掩码是动态创建的,而不是硬编码的。问题一定出在 rescaleMask 函数中,因此希望可以忽略其余代码。

我创建了一个带有编号的正方形的测试图像图,我将通过单击蒙版按钮来裁剪它,用鼠标左键在其中一个框周围绘制一个矩形,然后单击裁剪按钮。在创建蒙版和裁剪之前调整图像大小时会出现此问题。

这是测试图片:

这是一个 jsfiddle fabric Creating rect with a mouse dynamic js 2.4.1 sent as fix #4

<canvas id="c"   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】:

您需要考虑target.scaleXtarget.scaleY 作为掩码。

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"   style="border:1px solid #ccc"></canvas>
<button id="mask">Mask</button>
<button id="crop">Crop</button>

【讨论】:

完美地解决了它。我在不同的地方玩过 mask.scaleXY 和 target.scaleXY ,但我的结果没有看到任何变化,因为我将它们部署在错误的地方。谢谢你说得这么清楚,马吕斯。我相信这也会帮助其他人理解。

以上是关于Fabric JS 2.4.1 如何使用 clipPath 裁剪已缩放为小于或大于 1:1 比例的图像的主要内容,如果未能解决你的问题,请参考以下文章

canvas库fabric.js踩坑

Fabric JS 2.4.1 ClipPath Crop 不适用于动态创建的矩形蒙版

如何使用 Fabric.js 将图像上传到画布?

如何使用 fabric.js 禁用画布元素的缩放点?

如何使用 fabric.js 在画布上画一条线

如何将 Fabric.js 与 React 一起使用?