React Canvas缩放图片后getImageData仅返回rgba[0,0,0,0]问题求助
问题:图片缩放至Canvas尺寸后,
getImageData仅返回rgba[0,0,0,0],无法实现洪水填充 我尝试通过操作像素颜色实现floodfill(洪水填充)功能,但将图片缩放至Canvas尺寸后,context.getImageData仅返回rgba[0,0,0,0]。当Canvas尺寸与图片尺寸一致时,填充功能可正常获取正确像素颜色,期望实现:即使图片缩放至全屏尺寸的Canvas,也能获取正确的像素颜色。
我的React组件代码
/* 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;
已尝试的方案
- 在图片加载完成后执行填充函数
- 设置
ctx.imageSmoothingEnabled = false; - 测试当Canvas尺寸与图片尺寸一致时,填充功能可正常获取正确像素颜色
问题原因
Canvas的**绘制尺寸(width/height属性)和显示尺寸(CSS样式)**不匹配是核心问题:
- 你给Canvas添加了
w-full的CSS类,让它显示为全屏,但Canvas的默认绘制尺寸是300x150,并未跟随显示尺寸调整,导致图片被拉伸显示,但实际绘制的像素区域仍为默认尺寸,超出区域就是透明的rgba[0,0,0,0]。 - 你用
offsetWidth/offsetHeight获取的是显示尺寸,但getImageData是基于Canvas的绘制尺寸读取像素的,坐标不匹配就会读到透明区域。
解决步骤
1. 同步Canvas的绘制尺寸与显示尺寸
在图片加载的useEffect中,将Canvas的width/height属性设置为它的显示尺寸,确保绘制区域和显示区域一致:
useEffect(() => { const img = new Image(); img.src = img1; img.onload = () => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; // 关键:同步绘制尺寸和显示尺寸 const displayWidth = canvas.offsetWidth; const displayHeight = canvas.offsetHeight; canvas.width = displayWidth; canvas.height = displayHeight; ctx.imageSmoothingEnabled = false; // 保持像素清晰,可选 ctx.drawImage(img, 0, 0, displayWidth, displayHeight); contextRef.current = ctx; }; }, []);
2. 修复坐标边界判断
原_valid函数的边界判断漏掉了x=0或y=0的像素,修改为:
if (x >= 0 && x < width && y >= 0 && y < height) { // 后续逻辑 }
3. 完善useCallback依赖
原代码中useCallback的依赖为空数组,会导致闭包捕获旧值,需要补上相关依赖,比如_valid的依赖:
const _valid = useCallback((x: number, y: number) => { // 函数体 }, [canvasRef, contextRef]);
同理,_fill、_setPixel等函数也需要补充对应的依赖,避免闭包过期问题。
4. 可选:监听窗口resize(适配窗口变化)
如果需要适配窗口大小变化,添加resize监听重新绘制图片:
useEffect(() => { const handleResize = () => { const canvas = canvasRef.current; const ctx = contextRef.current; const img = new Image(); img.src = img1; img.onload = () => { if (!canvas || !ctx) return; const displayWidth = canvas.offsetWidth; const displayHeight = canvas.offsetHeight; canvas.width = displayWidth; canvas.height = displayHeight; ctx.drawImage(img, 0, 0, displayWidth, displayHeight); }; }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []);
内容的提问来源于stack exchange,提问作者pythonkr
相关产品推荐
相关产品推荐

