当图像重新缩放以适合画布时,织物 js 上的裁剪功能无法正常工作

Posted

技术标签:

【中文标题】当图像重新缩放以适合画布时,织物 js 上的裁剪功能无法正常工作【英文标题】:Crop function on fabric js not working properly when image is rescaled to fit canvas 【发布时间】:2021-07-10 14:13:22 【问题描述】:

github 仓库链接:Click here

我目前正在使用一个函数来裁剪图像,问题是:当我重新缩放图像以适合画布时,裁剪无法识别该更改。

图像未重新缩放以适合画布的示例:https://drive.google.com/file/d/1YFWz9AQDnjQmvmG0-la2gn9HfpYAT1Ak/view我在视频中所做的只是缩小图像。

图像重新缩放以适合画布时的示例:https://drive.google.com/file/d/1Bny4g-QtX63oIrrvd4G7SNSkp0RoBXNn/view

我的裁剪代码

var lastSelectedPicture = null;
var isInsertingCropRectangle = false;


var crop_rect, isDown, origX, origY, mask, target;
var done = false;

// FIXED BACKGROUND IMAGE
var src = "https://i.imgur.com/nnCUr4g.jpg";
fabric.Image.fromURL(src, function(img) 
  img.dirty = true;
  img.selectable = false;
  img.noScaleCache = false

  canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), 
    scaleX: canvas.getWidth() / img.getScaledWidth(),
    scaleY: canvas.getHeight() / img.getScaledHeight(),
  )

  canvas.add(img);
  canvas.renderAll();
);


// IMAGEM TO CUT
fabric.Image.fromURL(src, function(img) 
  img.selectable = true;
  img.id = 'target';
  img.borderColor = 'green'; 
  img.noScaleCache = false
  
  img.scaleX = canvas.width / img.getScaledWidth();
  img.scaleY = canvas.height / img.getScaledHeight();

  
  canvas.add(img);
  
  canvas.renderAll();
)

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) 
    // Re-scale mask 
    var topMask = mask.top
    var leftMask = mask.left

    target.top = topMask
    target.left = leftMask
    // Do the crop
    target.cropX = leftMask
    target.cropY = topMask
    
    canvas.renderAll()
    mask = rescaleMask(target, mask);
        canvas.renderAll()
    
    
    
    mask.setCoords()
    
    canvas.renderAll()
    
    target.width = mask.getScaledWidth()
    target.height = mask.getScaledHeight()
    
    target.dirty=true;
    canvas.setActiveObject(target);
    target.selectable = true;

    canvas.bringToFront(target);
    target.setCoords()

    canvas.remove(mask)
    canvas.renderAll();
  
);

//////////////////////////////////////////////////////////
// RE-SCALE MASK FOR CROPPING
//////////////////////////////////////////////////////////
function rescaleMask(target, mask)
  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;
  

  mask.left = centerBasedX;
  mask.top = centerBasedY;
  mask.originX = 'left';
  mask.originY = 'top';
  mask.setCoords();
  mask.dirty=true;
  canvas.renderAll();
  
  return mask;


canvas.on('mouse:down', function(o) 
    if( isInsertingCropRectangle == true )
    if (done) 
      canvas.renderAll();
      return;
    
    isDown = true;
    var pointer = canvas.getPointer(o.e);
    console.log(pointer);
    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',
      borderColor: 'blue'
    );
    canvas.add(crop_rect);
    canvas.renderAll();
  
);

canvas.on('mouse:move', function(o) 
    if( isInsertingCropRectangle == true )
    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 )
    if (done) 
      canvas.renderAll();
      return;
    
    isDown = false;

    crop_rect.set(
      selectable: true
    );
    done = true;
  
  else
  
  
);

    canvas.on('selection:created', function(event) 
        selectionChanged(event);
  );
    
    canvas.on('selection:updated', function(event) 
        selectionChanged(event);
    );

    function selectionChanged(event)
    switch(event.target.type) 
      case 'textbox':
        break;
        case 'image':
          lastSelectedPicture = event.target;
                break;
            case 'rect':
                break;
            case 'group':
                break;
            default:
                break;
        
        
    

【问题讨论】:

【参考方案1】:

我找到了解决办法

基本上,我所做的是在对象上设置调整大小的图像的 scaleX 和 scaleY,然后获取这些值并将它们划分为cropX 和cropY 部分,如下所示:

target.cropX = leftMask / scaleImage.x
target.cropY = topMask / scaleImage.y

完整代码

var lastSelectedPicture = null;
var isInsertingCropRectangle = false;


var crop_rect, isDown, origX, origY, mask, target;
var done = false;

// IMAGEM DE PLANO DE FUNDO
var src = "https://i.imgur.com/nnCUr4g.jpg";
fabric.Image.fromURL(src, function(img) 
  img.dirty = true;
  img.selectable = false;
  img.noScaleCache = false

  canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), 
    scaleX: canvas.getWidth() / img.getScaledWidth(),
    scaleY: canvas.getHeight() / img.getScaledHeight(),
  )

  canvas.add(img);
  canvas.renderAll();
);


// IMAGEM A SER RECORTADA
var scaleImage;
fabric.Image.fromURL(src, function(img) 
  img.selectable = false;
  img.lockMovementX = true;
  img.lockMovementY = true;
  img.id = 'target';
  img.borderColor = 'green'; 

  img.scaleX = canvas.width / img.getScaledWidth();
  img.scaleY = canvas.height / img.getScaledHeight();

  scaleImage = 
    x: img.scaleX,
    y: img.scaleY
  ;

  canvas.add(img);
  
  canvas.renderAll();
)

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
//////////////////////////////////////////////////////////
function maskImage() 
  isInsertingCropRectangle = true;
  canvas.discardActiveObject();
  lastSelectedPicture.selectable = false;
  lastSelectedPicture.setCoords();
  lastSelectedPicture.dirty = true;
  canvas.renderAll();
  canvas.discardActiveObject();
  isInsertingCropRectangle = true;


//////////////////////////////////////////////////////////
// CROP
//////////////////////////////////////////////////////////
function cropImage() 
  if (target !== null && mask !== null) 
    console.log("before", target);
    console.log("beforemask", mask);
    var topMask = mask.top
    var leftMask = mask.left

    target.top = topMask
    target.left = leftMask
    // Do the crop
    target.cropX = leftMask / scaleImage.x
    target.cropY = topMask / scaleImage.y
    mask = rescaleMask(target, mask);
        canvas.renderAll()
    
    mask.setCoords()
    
    canvas.renderAll()
    
    target.width = mask.getScaledWidth()
    target.height = mask.getScaledHeight()
    
    target.dirty=true;
    canvas.setActiveObject(target);
    target.selectable = true;
    target.lockMovementX = false;
    target.lockMovementY = false;

    canvas.bringToFront(target);
    target.setCoords()

    canvas.remove(mask)
    canvas.renderAll();
  


//////////////////////////////////////////////////////////
// SET ICON CROP
//////////////////////////////////////////////////////////

function setIconCrop(action, img) 
  img.src = (action === 'crop') ? './assets/img/tesoura.svg'  : './assets/img/crop.svg' ;


let actionCrop = 'mask';
document.getElementById("cropImage").addEventListener("click", function() 
  if (actionCrop === 'mask') 
    actionCrop = 'crop';
    maskImage();
   else if(actionCrop === 'crop') 
    actionCrop = 'mask';
    cropImage();
  

  setIconCrop(actionCrop, this);
);

//////////////////////////////////////////////////////////
// RE-SCALE MASK FOR CROPPING
//////////////////////////////////////////////////////////
function rescaleMask(target, mask)
  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;
  

  mask.left = centerBasedX;
  mask.top = centerBasedY;
  mask.originX = 'left';
  mask.originY = 'top';
  mask.setCoords();
  mask.dirty=true;
  canvas.renderAll();
  
  return mask;


canvas.on('mouse:down', function(o) 
    if( isInsertingCropRectangle == true )
    if (done) 
      canvas.renderAll();
      return;
    
    isDown = true;
    var pointer = canvas.getPointer(o.e);
    console.log(pointer);
    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',
      borderColor: 'blue'
    );
    canvas.add(crop_rect);
    canvas.renderAll();
  
);

canvas.on('mouse:move', function(o) 
    if( isInsertingCropRectangle == true )
    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();
  
);

canvas.on('mouse:up', function(o) 
    if( isInsertingCropRectangle == true )
    if (done) 
      canvas.renderAll();
      return;
    
    isDown = false;

    crop_rect.set(
      selectable: true
    );
    done = true;
    console.log(crop_rect);
  
  else
  
  
);

    canvas.on('selection:created', function(event) 
        selectionChanged(event);
  );
    
    canvas.on('selection:updated', function(event) 
        selectionChanged(event);
    );

    function selectionChanged(event)
    switch(event.target.type) 
      case 'textbox':
        break;
        case 'image':
          lastSelectedPicture = event.target;
                break;
            case 'rect':
                break;
            case 'group':
                break;
            default:
                break;
        
        
    

【讨论】:

以上是关于当图像重新缩放以适合画布时,织物 js 上的裁剪功能无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章

我们如何在 php 中使用织物 Js 对画布上的图像执行相同的操作(缩放和旋转)?

使用 JCrop 重新缩放大图像

如何在织物画布中使用背景图像获取绘图路径中的图像?

Fabric JS clipPath:裁剪后如何使图像适合画布?

在画布中设置背景图像(织物js)

使用 FabricJS 裁剪图像