Home > Back-end >  useRef always coming back as null and is not able to get .current value in image comparison
useRef always coming back as null and is not able to get .current value in image comparison

Time:01-19

I am trying to make a react component to compare two images using the pixel match library and I am stuck on the output image. The outputCanvasRef is always null so in the compareImages function it errors on the line with outputCanvas.width = image1.width;

import { useRef, useState } from "react";
import pixelmatch from "pixelmatch";

const Tab1 = () => {
  const [image1, setImage1] = useState(null);
  const [image2, setImage2] = useState(null);
  const [difference, setDifference] = useState(null);

  const canvas1Ref = useRef(null);
  const canvas2Ref = useRef(null);
  const outputCanvasRef = useRef(null);

  const handleFileChange = (setImage) => (event) => {
    const file = event.target.files[0];
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file);
    fileReader.onload = () => {
      const image = new Image();
      image.src = fileReader.result;
      image.onload = () => setImage(image);
    };
  };

  const compareImages = async () => {
    if (image1 && image2) {
      const canvas1 = canvas1Ref.current;
      const canvas2 = canvas2Ref.current;
      const outputCanvas = outputCanvasRef.current;
      canvas1.width = image1.width;
      canvas1.height = image1.height;
      canvas2.width = image2.width;
      canvas2.height = image2.height;
      outputCanvas.width = image1.width;
      outputCanvas.height = image1.height;

      const ctx1 = canvas1.getContext("2d");
      const ctx2 = canvas2.getContext("2d");
      const outputCtx = outputCanvas.getContext("2d");
      outputCtx.scale(0.5, 0.5);

      ctx1.drawImage(image1, 0, 0);
      ctx2.drawImage(image2, 0, 0);

      const diff = pixelmatch(
        ctx1.getImageData(0, 0, image1.width, image1.height),
        ctx2.getImageData(0, 0, image2.width, image2.height),
        null,
        image1.width,
        image1.height
      );

      outputCtx.putImageData(diff, 0, 0);
      setDifference(outputCanvas);
    }
  };
  

  return (
    <div>
      <input type="file" onChange={handleFileChange(setImage1)} />
      <input type="file" onChange={handleFileChange(setImage2)} />
      <button onClick={() => { compareImages() }}>Compare Images</button>
      <canvas ref={canvas1Ref} />
      <canvas ref={canvas2Ref} />
      {difference && <canvas ref={outputCanvasRef} />}
    </div>
  );
}

export default Tab1;

CodePudding user response:

Can use the spread operator like this {...props} to pass a conditional ref

  const itemProps = difference ? { ref: outputCanvasRef } : {};

  <canvas {...itemProps} />

CodePudding user response:

There are several things that went wrong in your implementation:

  • You need to render the output canvas without any condition so that it is always available for use in the comparison function.
  • the pixelmatch package requires that the images you want to compare always have the same width & height. To do this, you can use the width & height of the first image for both image 1 & image 2 canvas.
  • You need to pass the array buffer that is returned from getImageData call to the pixelmatch. The getImageData returns ImageData object. You can access the array buffer like this ImageData.data
  • You should pass an ImageData object's data field to the pixelmatch library's third parameter as argument so that the difference in pixels can be written in the ImageData object.

Solution



import { useRef, useState } from "react";
import pixelmatch from "pixelmatch";

const Tab1 = () => {
  const [image1, setImage1] = useState(null);
  const [image2, setImage2] = useState(null);
  const [difference, setDifference] = useState(null);

  const canvas1Ref = useRef(null);
  const canvas2Ref = useRef(null);
  const outputCanvasRef = useRef(null);

  const handleFileChange = (setImage) => (event) => {
    const file = event.target.files[0];
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file);
    fileReader.onload = () => {
      const image = new Image();
      image.src = fileReader.result;
      image.onload = () => setImage(image);
    };
  };

  const compareImages = async () => {
    if (image1 && image2) {
      const canvas1 = canvas1Ref.current;
      const canvas2 = canvas2Ref.current;
      const outputCanvas = outputCanvasRef.current;
      canvas1.width = image1.width;
      canvas1.height = image1.height;
      canvas2.width = image1.width;
      canvas2.height = image1.height;

      outputCanvas.width = image1.width;
      outputCanvas.height = image1.height;

      const ctx1 = canvas1.getContext("2d");
      const ctx2 = canvas2.getContext("2d");
      const outputCtx = outputCanvas.getContext("2d");
      const diff = outputCtx.createImageData(image1.width, image1.height);

      ctx1.drawImage(image1, 0, 0);
      ctx2.drawImage(image2, 0, 0);


      pixelmatch(
        ctx1.getImageData(0, 0, image1.width, image1.height).data,
        ctx2.getImageData(0, 0, image1.width, image1.height).data,
        diff.data,
        image1.width,
        image1.height
      );

      outputCtx.putImageData(diff, 0, 0);
      setDifference(diff);
    }
  };
  

  return (
    <div>
      <input type="file" onChange={handleFileChange(setImage1)} />
      <input type="file" onChange={handleFileChange(setImage2)} />
      <button onClick={() => { compareImages() }}>Compare Images</button>
      <canvas ref={canvas1Ref} />
      <canvas ref={canvas2Ref} />
      {<canvas ref={outputCanvasRef} />}
    </div>
  );
}

export default Tab1;

  • Related