'Fabric JS clipPath: how to fit the image to the canvas after cropping?

I implemented image cropping using FabricJS and clipPath property.

The question is how do I make the image fit the canvas after cropping? I want the cropped image to fill the canvas area but can't figure out whether it's possible to do using fabric js.

So I want the selected part of the image to fit the canvas size after the user clicks the Crop button:

Screenshot of the cropping example

About the code: I draw a rectangle on the canvas and the user can resize and move it. After that, the user can click the Crop button and get a cropped image. But the cropped part stays the same size it was on the original image whereas I want it to scale and fit the canvas.

Note: I tried to use methods like scaleToWidth. Additionally, I used absolutePositioned set to true for the selection rectangle. I tried to scale to image with this property set to false, but it didn't help.

Please do not suggest using cropX and cropY properties for cropping instead of clipPath as they don't work properly for rotated images.

HTML:

<button>Crop</button>
<canvas id="canvas" width="500" height="500"></canvas>

JS:

// crop button
var button = $("button");

// handle click
button.on("click", function(){

  let rect = new fabric.Rect({
    left: selectionRect.left,
    top: selectionRect.top,
    width: selectionRect.getScaledWidth(),
    height: selectionRect.getScaledHeight(),
    absolutePositioned: true
  });

  currentImage.clipPath = rect;

  canvas.remove(selectionRect);

  canvas.renderAll();
});

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

canvas.preserveObjectStacking = true;

var selectionRect;
var currentImage;

fabric.Image.fromURL('https://www.sheffield.ac.uk/polopoly_fs/1.512509!/image/DiamondBasement.jpg', img => {
  img.scaleToHeight(500);

  img.selectable = true;

  canvas.add(img);

  canvas.centerObject(img);

  currentImage = img;

  canvas.backgroundColor = "#333";

  addSelectionRect();

  canvas.setActiveObject(selectionRect);

  canvas.renderAll();
});

function addSelectionRect() {
  selectionRect = new fabric.Rect({
    fill: 'rgba(0,0,0,0.3)',
    originX: 'left',
    originY: 'top',
    stroke: 'black',
    opacity: 1,
    width: currentImage.width,
    height: currentImage.height,
    hasRotatingPoint: false,
    transparentCorners: false,
    cornerColor: 'white',
    cornerStrokeColor: 'black',
    borderColor: 'black',
  });

  selectionRect.scaleToWidth(300);
  canvas.centerObject(selectionRect);
  selectionRect.visible = true;
  canvas.add(selectionRect);
}

Here is my jsfiddle: https://jsfiddle.net/04nvdeb1/23/



Solution 1:[1]

Actually you don't have to use clipPath method. What you want is actually move the area you need to the corner, and change the size of canvas.

so the sodo code is as below:

fabricCanvas.setDimensions({
    width: cropRectObject.getScaledWidth(),
    height: cropRectObject.getScaledHeight()
})
imageObject.set({
    left: -cropRectObject.left,
    top: -cropRectObject.top
})
fabricCanvas.remove(cropRectObject);

tweaked your fiddle a bit to show this idea: https://jsfiddle.net/tgy320w4/4/

hope this could help.

Solution 2:[2]

I also faced this same issue for a few days. But i could not found any solution even i also found your SO question and github issue. I think clipPath is the best choice for clipping the image using a nice adjustable musk.

Demo Link

Github Repo Link

In the button callback function, you have to implement like this way

step 1: 1st you have to create an Image instance to hold the cropped image

let cropped = new Image();

Step 2: You have to use Canvas.toDataURL() to exports the canvas element to a dataurl image. Here wan want to export only the selection rectangle or mask rectangle, so we pass the mast rect left, top, width, and height

cropped.src = canvas.toDataURL({
                    left: rect.left,
                    top: rect.top,
                    width: rect.width,
                    height: rect.height,
                });

Step 3: I the last step after the image loaded we clear the canvas. create new object with our cropped image, and set some option and rerender the canvas

cropped.onload = function () {
                    canvas.clear();
                    image = new fabric.Image(cropped);
                    image.left = rect.left;
                    image.top = rect.top;
                    image.setCoords();
                    canvas.add(image);
                    canvas.renderAll();
                };

i actually marged Crop Functionality using FabricJs #soution2 with your problem.

Solution 3:[3]

const { width, height, left = 0, top = 0 } = rect;
if (width == null || height == null) return;

const zoom = canvas.getZoom();

// toExact is a prototype for precision
const nw = (width * zoom).toExact(1);
const nh = (height * zoom).toExact(1);
const nl = (left * zoom).toExact(1);
const nt = (top * zoom).toExact(1);

// Apply
canvas.clipPath = o;

// Size
canvas.setWidth(nw);
canvas.setHeight(nh);
canvas.absolutePan(new fabric.Point(nl, nt));

canvas.remove(rect);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rongrong Luo
Solution 2
Solution 3 Garry Xiao