0

I have an ImageCropper function that works perfectly on Firefox, but not Chrome. On chrome, the blob file it produces is empty ie a black image.

Through debugging, I know that the sourceImage HTMLImageElement being passed into the ctx.drawImage() function contains the correct image / information (ie is not black/empty) and has a height and width.

Yet, the blob value in the callback to the canvas.toBlob() function, is black/empty

I cannot see where the canvas gets emptied between those two points. Has anyone fixed a similar issue before?

export type ImageCropperProps = {
    classNameCropArea?: string;
    imageToCrop: File;
    onImageCropped: (imageFile: File) => void;
    circularCrop?: boolean;
    aspect?: number; 
};

function ImageCropper(props: ImageCropperProps): JSX.Element {
    const { classNameCropArea, imageToCrop, onImageCropped, circularCrop, aspect} = props;

    const [percentCrop, setPercentCrop] = useState<PercentCrop>();
    const [pixelCrop, setPixelCrop] = useState<PixelCrop>();
    const [croppedImageSize, setCroppedImageSize] = useState<number>(0);

    const imageRef = useRef<HTMLImageElement>();

    useEffect(() => {
        pixelCrop && cropImage(pixelCrop);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [imageToCrop]);

    function onImageLoad(e: React.SyntheticEvent<HTMLImageElement>) {
        if (aspect && !percentCrop) {
            const { width, height } = e.currentTarget;
            setPercentCrop(centerAspectCrop(width, height, aspect));
        }
    }

    async function cropImage(crop: PixelCrop) {
        if (imageRef && crop.width && crop.height) {
            const croppedImage = await getCroppedImage(imageRef.current, crop);
            onImageCropped(croppedImage);
            setCroppedImageSize(croppedImage ? croppedImage.size / 1000000 : 0);
        }
    }

    function getCroppedImage(sourceImage: HTMLImageElement, cropConfig: PixelCrop): Promise<File> {
        const canvas = document.createElement('canvas');
        const scaleX = sourceImage.naturalWidth / sourceImage.width;
        const scaleY = sourceImage.naturalHeight / sourceImage.height;
        canvas.width = cropConfig.width * scaleX;
        canvas.height = cropConfig.height * scaleY;
        const ctx = canvas.getContext('2d');

        ctx.drawImage(
            sourceImage,
            cropConfig.x * scaleX,
            cropConfig.y * scaleY,
            cropConfig.width * scaleX,
            cropConfig.height * scaleY,
            0,
            0,
            cropConfig.width * scaleX,
            cropConfig.height * scaleY
        );

        return new Promise((resolve, reject) => {
            canvas.toBlob(
                (blob) => {
                    // returning an error
                    if (!blob) {
                        reject(new Error(t('settings:image-cropper.canvas-is-empty')));
                        return;
                    }
                    resolve(new File([blob], imageToCrop.name, { type: blob.type }));
                },
                'image/jpeg'
            );
        });
    }

    return (
        <div className="relative flex flex-col space-y-16 w-full">
            <div className="relative flex flex-col space-y-16">
                <ReactCrop
                    className={classnames(classNameCropArea, 'w-full h-auto selfcenter')}
                    crop={percentCrop}
                    aspect={aspect}
                    onChange={(pixelCrop, percentageCrop) => {
                        // setPixelCrop(pixelCrop);
                        setPercentCrop(percentageCrop);
                    }}
                    onComplete={(pixelCrop, percentageCrop) => {
                        setPixelCrop(pixelCrop);
                        setPercentCrop(percentageCrop);
                        cropImage(pixelCrop);
                    }}
                    circularCrop={circularCrop}
                    ruleOfThirds
                >
                    <img
                        className="w-full h-auto object-contain object-center"
                        alt="image to crop"
                        src={URL.createObjectURL(imageToCrop)}
                        onLoad={onImageLoad}
                        ref={imageRef}
                    />
                </ReactCrop>         
                </div>
            )}
        </div>
    );
}

Simon Gowing
  • 241
  • 1
  • 2
  • 10

0 Answers0