How do I display the cropped part using react-image-crop- It appears as a black image of the cropped portion

1.6k Views Asked by At
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 nowenter image description here.

Appears as a black image. How do i fix this?

1

There are 1 best solutions below

2
On BEST ANSWER

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