首頁 > 軟體

在React中用canvas對圖片標註的實現

2022-05-17 16:00:55

在稽核業務中難免會有需要對圖片進行標註的需求,本次用一個最小demo來演示如何對圖片進行矩形標註。

首先我們要理解canvas是一塊畫布,而這塊畫布需要在我們要標註的圖片上層,圖片和canvas外層的div用相對位置,內層的圖片和canvas用絕對位置,即可保證canvas重疊於圖片之上。如圖:

我們來看下canvas的初始化,在img、canvas中都有ref屬性,不同的是img的ref屬性直接就是一個useRef參照,而canvas中的ref是一個回撥函數。它在元件被載入或解除安裝時會立即執行,載入時ref回撥接收當前元件範例作為引數,解除安裝時ref回撥接收null作為引數。在initCanvas函數中,用canvas的ref參照承接了canvas節點,並且通過drawImage函數,初始化了一塊400*400的畫布,第一個引數為需要繪製到的上下文元素:

<img src={lancome} ref={imgInstance} className="App-logo" alt="logo" />
<canvas className="canvas" ref={initCanvas} width="400px" height="400px" />
const canvasRef = useRef(null);
const imgInstance = useRef(null);

const initCanvas =  useCallback((node) => {
      canvasRef.current = node;
      const context = node.getContext('2d');
      context.drawImage(imgInstance.current, 0, 0, 400, 400);
  }, []);

接下來,我們通過invalidLocations來儲存之前的標註位置資訊,addInvalidLocation函數是為了新增標註位置資訊。最需要注意的是我們在useEffect中所監聽的三個函數,startDraw、drawingDeal和drawingEnd。

滑鼠落下時,startDraw為起始點的x,y座標賦值,並且拖拽狀態位isDrawing置為true。滑鼠移動時,drawingDeal函數會邊通過clearRect函數更新畫布,邊根據滑鼠的最新位置通過highlightInvalid來更新標註,經過確定矩形位置大小,內容填充,描邊三個步驟來繪製出矩形。滑鼠擡起時,drawingEnd函數會通過addInvalidLocation函數新增標註位置,然後初始化引數。

const [invalidLocations, setInvalidLocations] = useState([]);

const addInvalidLocation = useCallback((newMark) => {
    setInvalidLocations([...invalidLocations, newMark]);
}, [invalidLocations])

const highlightInvalid = (context, x1, y1, x2, y2) => {
    context.beginPath();
    context.rect(x1, y1, x2 - x1, y2 - y1);
    context.fillStyle = 'rgba(255, 0, 0, 0.2)';
    context.fill();
    context.strokeStyle = '#FF0070';
    context.lineWidth = 1;
    context.stroke();
    console.log('drawing', x2, y2);
};

const clearRect = (drawContext) => {
    drawContext.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
};

useEffect(() => {
    const canvasElem = canvasRef.current;
    let x = 0; let y = 0;
    let isDrawing = false;
    const drawContext = canvasRef.current.getContext('2d');
    let canvasRect;
    const lastCursorPosition = {
      x: 0,
      y: 0,
    };
    const startDraw = (e) => {
      console.log(e.type, 'start');
      canvasRect = canvasRef.current.getBoundingClientRect();
      x = e.clientX - canvasRect.left;
      y = e.clientY - canvasRect.top;
      if (x < 0) x = 0;
      if (y < 0) y = 0;
      isDrawing = true;
    };
    const drawingDeal = (e) => {
      console.log(e.type, 'move');
      if (isDrawing) {
        const x1 = e.clientX - canvasRect.left;
        const y1 = e.clientY - canvasRect.top;
        clearRect(drawContext);
        highlightInvalid(drawContext, x, y, x1, y1);
        lastCursorPosition.x = x1;
        lastCursorPosition.y = y1;
      }
    };
    const drawingEnd = () => {
      if (isDrawing) {
        if (lastCursorPosition.x && lastCursorPosition.y) {
          const width = lastCursorPosition.x - x + 1;
          const height = lastCursorPosition.y - y + 1;
          addInvalidLocation({ x, y, width, height });
          lastCursorPosition.x = 0;
          lastCursorPosition.y = 0;
        }
        clearRect(drawContext);
        isDrawing = false;
        x = 0;
        y = 0;
      }
    };
    canvasElem.addEventListener('mousedown', startDraw);
    canvasElem.addEventListener('mousemove', drawingDeal);
    canvasElem.addEventListener('mouseup', drawingEnd);
    return () => {
      canvasElem.removeEventListener('mousedown', startDraw);
      canvasElem.removeEventListener('mousemove', drawingDeal);
      canvasElem.removeEventListener('mouseup', drawingEnd);
    };
}, [invalidLocations, addInvalidLocation]);

在新增完標註位置之後,模板中我們通過迭代返回絕對定位的div來實現已經標註過的矩形。

<div className="img-wrap">
    <img src={lancome} ref={imgInstance} className="App-logo" alt="logo" />
    <canvas className="canvas" ref={initCanvas} width="400px" height="400px" />
    {invalidLocations && invalidLocations.map((location, index) => { 
        const { width, height, x, y } = location;
            return <div
                     key={`${width}_${height}_${x}_${y}`}
                     tabIndex={-1}
                     className={'remark'}
                     style={{ width: `${width}px`, height: `${height}px`, left: `${x}px`, top: `${y}px` }}
            ></div>
    })}
</div>

最後效果:

到此這篇關於在React中用canvas對圖片標註的實現 的文章就介紹到這了,更多相關React canvas對圖片標註內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com