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