nightograph
nightograph

Reputation: 2249

How can I crop an image on the client side without losing resolution using React

I'm trying to crop an image in a react application. I'm currently using a library called react-image-crop but it seems the cropped image is exactly the same size of crop area on the browser, means if I resize my browser window, the same area of the image comes to be different size. is there any way to retain the resolution of the original image and be still able to crop on the client side? the picture below is the result of cropping of the same area of the picture in different browser sizes.

i'm pretty much using react-image-crop out of the box with following crop parameters

 const [crop, setCrop] = useState({unit: '%', width: 100, aspect: 16 / 9 });

enter image description here

Upvotes: 3

Views: 4121

Answers (4)

Krishna Pankhania
Krishna Pankhania

Reputation: 101

I had the same issue with the cropper. But then adding scaled height and width to canvas and drawImage method worked for me.

check below screenshots.

Image while cropping: Image while cropping Image resolution after crop: Image resolution after crop

const createCropPreview = async (image, crop, fileName) => {
    const scaleX = image.naturalWidth / image.width;
    const scaleY = image.naturalHeight / image.height;
    const canvas = document.createElement("canvas");

    canvas.width = Math.ceil(crop.width * scaleX);
    canvas.height = Math.ceil(crop.height * scaleY);

    const ctx = canvas.getContext("2d");

    ctx.drawImage(
      image,
      crop.x * scaleX,
      crop.y * scaleY,
      crop.width * scaleX,
      crop.height * scaleY,
      0,
      0,
      crop.width * scaleX,
      crop.height * scaleY
    );

    return new Promise((resolve, reject) => {
      canvas.toBlob((blob) => {
        if (blob) {
          blob.name = fileName;
          window.URL.revokeObjectURL(previewUrl);
          const obj = window.URL.createObjectURL(blob);
          setPreviewUrl(obj);
          props.setImg(obj, blob);
        }
      }, "image/jpeg");
    });
  };

Upvotes: 0

Nowbie
Nowbie

Reputation: 15

For me, this was what actually fixed (using hook). Basically i'm calculating canvas size taking into account the window resolution in physical pixels to the resolution in CSS pixels for the current display device.

const canvas = document.createElement('canvas')
const crop = completedCrop

const scaleX = image.naturalWidth / image.width
const scaleY = image.naturalHeight / image.height
const ctx = canvas.getContext('2d')
const pixelRatio = window.devicePixelRatio

canvas.width = scaleX * crop.width * pixelRatio
canvas.height = scaleY * crop.height * pixelRatio
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0)
ctx.imageSmoothingQuality = 'high'

ctx.drawImage(
  image,
  crop.x * scaleX,
  crop.y * scaleY,
  crop.width * scaleX,
  crop.height * scaleY,
  0,
  0,
  crop.width * scaleX,
  crop.height * scaleY
)

You can also export the canvas blob as png (lossless) rather than jpeg, which will provide you a better image.

canvas.toBlob(
  blob => {
    const img = URL.createObjectURL(blob)
    // do whatever you want...
  },
  'image/png',
  1
)

Upvotes: 0

nightograph
nightograph

Reputation: 2249

in the hook version of the demo, replacing the getResizedCanvas (and moving it inside the react module) fixes the problem

const getResizedCanvas = () => {
    const scaleX = imgRef.current.naturalWidth / imgRef.current.width;
    const scaleY = imgRef.current.naturalHeight / imgRef.current.height;
    const tmpCanvas = document.createElement("canvas");
    tmpCanvas.width = Math.ceil(crop.width*scaleX);
    tmpCanvas.height = Math.ceil(crop.height*scaleY);

    const ctx = tmpCanvas.getContext("2d");
    const image = imgRef.current;
    ctx.drawImage(
        image,
        crop.x * scaleX,
        crop.y * scaleY,
        crop.width * scaleX,
        crop.height * scaleY,
        0,
        0,
        crop.width*scaleX,
        crop.height*scaleY,
    );

    return tmpCanvas;
}

Upvotes: 0

Potamir
Potamir

Reputation: 339

Actually i have the same problem, i also use react-image-crop. My current solution is i create a modal with static size in px as a crop windows. so if the window size is smaller, the modal overflow will be activated.

more or less like this: enter image description here

so scroller will dynamically change based on window size and it doesn't affect the image size.

If there is a real and better solution maybe i'd also like to hear about it :)

Upvotes: 0

Related Questions