react-image-crop icon indicating copy to clipboard operation
react-image-crop copied to clipboard

Canvas returns null after cropping image iOS

Open ghost opened this issue 1 year ago • 2 comments

I'm having some issues when cropping an image taken from an iOS device, specially iPhones. Apparently, the canvas width and height, after cropping, are greater than the original image and there's also a canvas size limit on iOS devices, thus, the canvas.toBlob returns null because there's no actual canvas.

I've also tried the sanbBox demo and the same issue occurs.

How could I solve it? is there a way to resize the dimensions so it fits in the canvas? maintaining the proportions of the crop area.

My code below:

  const getCroppedImg = () => {
    const { crop, image } = cropRef.current.getCroppedArea();

    return new Promise(resolve => {
      const canvas = document.createElement('canvas');

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

      if (!ctx) {
        throw new Error('No 2d context');
      }

      const scaleX = image.naturalWidth / image.width;
      const scaleY = image.naturalHeight / image.height;
      const pixelRatio = window.devicePixelRatio;

      if (crop.unit === '%') {
        canvas.width = (image.naturalWidth * crop.width) / 100;
        canvas.height = (image.naturalHeight * crop.height) / 100;
      } else {
        canvas.width = Math.floor(crop.width * scaleX * pixelRatio);
        canvas.height = Math.floor(crop.height * scaleY * pixelRatio);
        ctx.scale(pixelRatio, pixelRatio);
      }

      ctx.imageSmoothingQuality = 'high';

      const cropX = crop.x * scaleX;
      const cropY = crop.y * scaleY;

      const rotate = 0;

      const rotateRads = rotate * TO_RADIANS;

      const centerX = image.naturalWidth / 2;
      const centerY = image.naturalHeight / 2;

      ctx.save();

      ctx.translate(-cropX, -cropY);
      ctx.translate(centerX, centerY);
      ctx.rotate(rotateRads);
      ctx.scale(1, 1);
      ctx.translate(-centerX, -centerY);
      ctx.drawImage(
        image,
        0,
        0,
        image.naturalWidth,
        image.naturalHeight,
        0,
        0,
        image.naturalWidth,
        image.naturalHeight
      );

      ctx.restore();

      canvas.toBlob(blobFile => {
        const croppedImageUrl = URL.createObjectURL(blobFile);
        resolve({ file: blobFile, src: croppedImageUrl });
        URL.revokeObjectURL(croppedImageUrl);
      }, 'image/jpeg');
    });
  };

ghost avatar Jan 31 '24 20:01 ghost

I struggled all day with this problem and I found a solution. Basically, IOS limit the memory for a canvas, and pictures of the iphones always pass that limit. So, you have to compress the image that you are loading to the canvas. My function to select the image looks like this:

  import imageCompression from 'browser-image-compression'

  ...


  const onSelectFile = async (e1: React.ChangeEvent<HTMLInputElement>) => {
    setFieldTouched(name, true)
    const file = e1?.target?.files?.[0]

    if (!file) return

    const reader = new FileReader()

    reader.addEventListener('load', () => {
      const imgElement = new Image()
      const imgUrl = reader.result?.toString() || ''
      imgElement.src = imgUrl

      setShowCrop(true)
      setImgSrc(imgUrl)
    })

    // must compress to avoid iphone failing for canvas over sized
    const compressedBlob = await imageCompression(file as File, {
      maxSizeMB: 1,
      maxWidthOrHeight: 1400,
      useWebWorker: true,
    })

    const compressedFile = new File([compressedBlob], file.name, {
      type: file.type,
      lastModified: file.lastModified,
    })

    reader.readAsDataURL(compressedFile)
  }

imgUrl is the value of the img tag. It will be then enought compressed to be cropped by the canvas. By the way, since I did it, the crop is a lot faster for Android too. Canvas cropping is quite heavy.

PS. Remember that file compression degeneration is exponential. If you compress in backend too, the image can get a lot wrost.

massimopibiri avatar Apr 02 '24 22:04 massimopibiri

massimopibiri

Hi , thx it truly worked .

MohammadrasiM avatar Aug 12 '24 06:08 MohammadrasiM