Skip to content

v4.0.0

Compare
Choose a tag to compare
@ValentinH ValentinH released this 29 Nov 21:18
· 119 commits to main since this release

馃帀 Better rotation handling 馃帀

Thanks to an awesome work by @trurl-master, we are now handling rotation properly when computing the output image coordinate. 馃帀
in addition, the example code for generating the cropped image is now more efficient and should reduce the number of issues on iOS devices like reported in #91.

New initialCroppedAreaPercentages prop

initialCroppedAreaPercentages is similar to initialCroppedAreaPixels but more accurate as there is no rounding.

It minimizes the losses of save/restore operations. They're still present on some occasions as the JS floating-point operations accuracy is limited, but it allows to preserve the crop for much longer. initialCroppedAreaPixels still can be used, but it's a bit risky.

鈿狅笍 Breaking changes 鈿狅笍

TL;DR: If you were not using the rotation prop, there should be no breaking changes.

The croppedArea and croppedAreaPixels output by onCropComplete() have changed when using the Cropper with a rotation different than 0.

Migration guide

The main impact this major version has is on the croppedAreaPixels when used together with the rotation. As it now generates bounding box relative values, you will need to remake the part that generates the final image, taking that into account.

Here's an example implementation (codesandbox):

First, calculate the bounding box size:

const image = await createImage(...)

const rotRad = getRadianAngle(rotation)
const bBoxWidth =
  Math.abs(Math.cos(rotRad) * image.width) +
  Math.abs(Math.sin(rotRad) * image.height)
const bBoxHeight =
  Math.abs(Math.sin(rotRad) * image.width) +
  Math.abs(Math.cos(rotRad) * image.height)

Second, create a canvas element with the size of the bounding box:

const canvas = document.createElement('canvas')

canvas.width = bBoxWidth
canvas.height = bBoxHeight

Then, rotate the canvas context around the center of the canvas:

const ctx = canvas.getContext('2d')

ctx.translate(bBoxWidth / 2, bBoxHeight / 2)
ctx.rotate(rotRad)

Set context to point to the top-left corner of the rotated image:

ctx.translate(-image.width / 2, -image.height / 2)

Draw the image onto the rotated context:

ctx.drawImage(image, 0, 0)

What you have in the canvas at this point is a rotated image inside its bounding box. Now, you just need to extract the result using croppedAreaPixels and repaint it on a canvas with the size of the final image:

const data = ctx.getImageData(
  croppedAreaPixels.x,
  croppedAreaPixels.y,
  croppedAreaPixels.width,
  croppedAreaPixels.height
)

// set the canvas size to the final image size - this will clear existing context
canvas.width = croppedAreaPixels.width
canvas.height = croppedAreaPixels.height

// paste the extracted image at the top left corner
ctx.putImageData(data, 0, 0)

croppedArea

If you somehow managed to use croppedArea with rotation before - you're smarter than me. If not - now is the time. The approach is basically the same as with croppedAreaPixels - you just need to convert percentages to pixels based on the image you're cropping

Here's how you can use percentages server-side and crop using sharp (codesandbox):

Rotate the image:

const image = sharp(...);

await image.rotate(rotation);

Get rotated image bounding box size (basically you need to create a new image out of the rotated one, otherwise the metadata won't change):

const meta = await sharp(await image.toBuffer()).metadata();

Alternatively, you can calculate the bounding box size using the same method we used for the client-side crop

Convert percentages to pixels:

const cropInfo = {
  top: Math.round((croppedArea.y / 100) * meta.height),
  left: Math.round((croppedArea.x / 100) * meta.width),
  width: Math.round((croppedArea.width / 100) * meta.width),
  height: Math.round((croppedArea.height / 100) * meta.height)
}

Extract the image:

const myCroppedImage = await image.extract(cropInfo)

Commits

New Contributors

Full Changelog: ricardo-ch/react-easy-crop@v3.5.3...v4.0.0