react canvas api, getImageData returns only rgba [0,0,0,0]

63 Views Asked by At

I am trying to make floodfill function by manipulating the pixel color. When i scale the image to the canvas size, context.getImageData returns only rgba[0,0,0,0].

/* library */
import { useCallback, useEffect, useRef } from "react";
import { useSelector } from "react-redux";
/* module from local */
import { RootState } from "../../store";
import img1 from "../../test.png";

interface srcProps {
  src: string;
}

export function ImageCanvas({ src }: srcProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const contextRef = useRef<CanvasRenderingContext2D | null>(null);
  const awareness = useSelector((state: RootState) => state.yjs.awareness);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    canvas.addEventListener(
      "touchmove",
      (e) => {
        e.preventDefault();
      },
      { passive: false }
    );
  }, []);

  useEffect(() => {
    const img = new Image();
    img.src = img1;
    img.onload = () => {
      const canvas = canvasRef.current;
      if (!canvas) return;
      const ctx = canvas.getContext("2d");
      if (ctx === null) return;
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      contextRef.current = ctx;
    };
  }, []);

  const _valid = useCallback((x: number, y: number) => {
    console.log("Enter _valid");
    const width = canvasRef.current?.offsetWidth as number;
    const height = canvasRef.current?.offsetHeight as number;
    console.log(`width: ${width}, height: ${height}`);

    if (x > 0 && x < width && y > 0 && y < height) {
      const imageData = contextRef.current?.getImageData(x, y, 1, 1);
      console.log(imageData?.data);
      if (
        imageData?.data[0] === 255 &&
        imageData?.data[1] === 255 &&
        imageData?.data[2] === 255
      ) {
        console.log("_valid => Success");
        return true;
      } else {
        console.log("_valid => valid Failed!!");
        return false;
      }
    }
  }, []);

  const _isPixel = useCallback((x: number, y: number) => {
    console.log("Enter _isPixel");
    const imageData = contextRef.current?.getImageData(x, y, 1, 1);
    if (imageData) {
      return imageData.data.length > 0;
    } else {
      return false;
    }
  }, []);

  const _setPixel = useCallback((x: number, y: number) => {
    console.log("Enter _setPixel");
    const imageData = contextRef.current?.getImageData(x, y, 1, 1);
    const pixelData = imageData?.data;

    let color = awareness.getLocalState().color;
    if (color === "black") {
      color = "rgba(223,154,36)";
    }

    const values = color.match(/\d+/g).map(Number);
    console.log("This is pixel data => _setPixel");
    console.log(pixelData);

    if (pixelData) {
      pixelData[0] = values[0];
      pixelData[1] = values[1];
      pixelData[2] = values[2];
      pixelData[3] = 255;
    }
    if (imageData) {
      contextRef.current?.putImageData(imageData, x, y);
    }
  }, []);

  const _fill = useCallback((x: number, y: number) => {
    const fillStack = [];

    fillStack.push([x, y]);

    while (fillStack.length > 0) {
      const [x, y]: any = fillStack.pop();

      if (_valid(x, y)) {
        console.log("It is valid");
      } else {
        console.log("NOT!! Valid");
        continue;
      }
      if (_isPixel(x, y)) {
        console.log("This is pixel");
      } else {
        console.log("NOT!! pixel");
        continue;
      }
      _setPixel(x, y);

      fillStack.push([x + 1, y]);
      fillStack.push([x - 1, y]);
      fillStack.push([x, y + 1]);
      fillStack.push([x, y - 1]);
    }
  }, []);

  const handlePointerDown = useCallback((e: React.PointerEvent<any>) => {
    _fill(e.nativeEvent.offsetX, e.nativeEvent.offsetY);
  }, []);

  return (
    <canvas
      ref={canvasRef}
      onPointerDown={handlePointerDown}
      style={{ touchAction: "none" }}
      className={"w-full"}
    />
  );
}

export default ImageCanvas;

enter image description here

What i tried:

  1. runs fill function after the image is loaded
  2. set ctx.imageSmoothingEnabled = false;
  3. test if the canvas size is equal to the image (then the fill function works because i can get the right pixel color)

What i expect: i want to get the right pixel color, even the image is scale to the canvas size which is full screen size.

0

There are 0 best solutions below