8000 Canvas returns null after cropping image iOS · Issue #578 · dominictobias/react-image-crop · GitHub
[go: up one dir, main page]

Skip to content
10000

Canvas returns null after cropping image iOS #578

Open

Description

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');
    });
  };

Activity

  1. massimopibiri commented on Apr 2, 2024

    @massimopibiri

    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  (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.

  2. MohammadrasiM commented on Aug 12, 2024

    @MohammadrasiM

    massimopibiri

    Hi , thx it truly worked .

  3. igorsantos07 commented on Dec 23, 2025

    @igorsantos07

    To be more specific about the issue, iOS seems to refuse any image beyond 4096x4096, as reported by the canvas-size library docs. It also makes it explicit that there's not a single damn error message about what's going on with the canvas 🤡

    That's also a documented issue at MDN's canvas page.

    The MDN reference is linked from the above-mentioned browser-image-compression lib. Given the canvas restriction is actually about dimensions rather than bytes, we've successfully implemented an "if iOS" flag that scales down the image in case it's larger than 4096 at any side, an option already supported by the compression library.

    Thanks for the pointers, Massimo!


    I wonder if @dominictobias has anything that could be done at the cropper implementation. Maybe check dimensions and warn? Or maybe check how canvas-size checks for valid dimensions and implement such check when rendering the cropping canvas?
    Nonetheless, I don't think the fix should come from this lib, but instead from implementers; the cropper shouldn't be responsible for resizing the picture, at least not without explicit developer consent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0