Search code examples
reactjsreact-image-cropimage-cropper

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


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?


Solution

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