function getCroppedImg(){
console.log('inside getCroppedImg')
const canvas = document.createElement("canvas");
const image = document.createElement("image");
const scaleX = image.naturalWidth / image.width;
const scaleY = image.naturalHeight / image.height;
canvas.width = crop.width;
canvas.height = crop.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width,
crop.height
);
const base64Image = canvas.toDataURL("image/jpeg", 1);
setResult(base64Image);
};
const [srcImg, setSrcImg] = useState(null);
const [image, setImage] = useState(null);
const [crop, setCrop] = useState({aspect: 16 / 9});
const [result, setResult] = useState(null);
const handleImage = event => {
setSrcImg(URL.createObjectURL(event.target.files[0]));
console.log(event.target.files[0]);
};
return (
<div>
{srcImg && (
<div>
<ReactCrop src={srcImg} onLoad={setImage} crop={crop} onChange={(crop) => setCrop(crop)}>
<img src={srcImg}/>
</ReactCrop>
<button className="cropButton" onClick={getCroppedImg}>crop</button>
</div>)}
{result && (
<div>
<img src={result} alt="cropped image"/>
</div>
=)}
</div>)
Above is the function that is being called when clicked on crop button. But it is returning a black image. I want to display it using "result". Basically I am trying to input a image, on click the crop button the cropped portion should be displayed in the {result}
. I don't mind if it comes as a preview as well, like dynamic cropped preview.
Edit : I have updated the code. And this is what I get now.
Appears as a black image. How do i fix this?
Nice library to deal with images
So, according to their official docs https://www.npmjs.com/package/react-image-crop there's no built-in way to display cropped area "How can I generate a crop preview in the browser? This isn't part of the library"
On the other hand they provided live example with "crop preview" implementation: https://codesandbox.io/s/react-image-crop-demo-with-react-hooks-y831o?file=/src/App.tsx:3989-4188
From my side I decided to simplify a bit work with crop preview-s and implemented additional component for this purpose. I used code example above as a reference.
import { useEffect, useRef } from 'react';
import { canvasPreview } from './canvasPreview';
export default function CropPreview({ img, crop }) {
const canvasRef = useRef(null);
useEffect(() => {
if (!crop?.width || !crop?.height || !img || !canvasRef.current) {
return;
}
canvasPreview(img, canvasRef.current, crop, 1, 0);
}, [img, crop]);
if (!!crop && !!img) {
return <canvas ref={canvasRef} />;
}
}
And it's usage:
import { useRef, useState } from 'react';
import CropPreview from './CropPreview';
import ReactCrop from 'react-image-crop';
function App() {
const imgRef = useRef(null);
const [crop, setCrop] = useState();
const [completedCrop, setCompletedCrop] = useState();
return (
<>
<div style={{ maxWidth: '500px' }}>
<ReactCrop
crop={crop}
onChange={setCrop}
onComplete={(c) => setCompletedCrop(c)}
>
<img
crossOrigin="anonymous"
ref={imgRef}
src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/1024px-Image_created_with_a_mobile_phone.png"
/>
</ReactCrop>
</div>
<CropPreview img={imgRef.current} crop={completedCrop} />
</>
);
}
export default App;
canvasPreview.js was taken "as it is" from live example above, only some typescript-specific pieces of code were removed (I created this component without TS):
const TO_RADIANS = Math.PI / 180;
export async function canvasPreview(
image,
canvas,
crop,
scale = 1,
rotate = 0
) {
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;
// devicePixelRatio slightly increases sharpness on retina devices
// at the expense of slightly slower render times and needing to
// size the image back down if you want to download/upload and be
// true to the images natural size.
const pixelRatio = window.devicePixelRatio;
// const pixelRatio = 1
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 rotateRads = rotate * TO_RADIANS;
const centerX = image.naturalWidth / 2;
const centerY = image.naturalHeight / 2;
ctx.save();
// 5) Move the crop origin to the canvas origin (0,0)
ctx.translate(-cropX, -cropY);
// 4) Move the origin to the center of the original position
ctx.translate(centerX, centerY);
// 3) Rotate around the origin
ctx.rotate(rotateRads);
// 2) Scale the image
ctx.scale(scale, scale);
// 1) Move the center of the image to the origin (0,0)
ctx.translate(-centerX, -centerY);
ctx.drawImage(
image,
0,
0,
image.naturalWidth,
image.naturalHeight,
0,
0,
image.naturalWidth,
image.naturalHeight
);
ctx.restore();
}
With this solution you'll be able to display current crop on a canvas.
For me not clear a bit why you're attempting to draw current crop initially on canvas and then from canvas on image. But if for some reason that's important - code piece like following would "transfer" crop from canvas to image:
canvasRef.current.toBlob((blob) => {
const url = URL.createObjectURL(blob);
const image = document.createElement('img');
document.body.appendChild(image);
image.onload = function () {
URL.revokeObjectURL(url);
};
image.src = url;
});