画布中的文本被下面的视频画布擦除

Posted

技术标签:

【中文标题】画布中的文本被下面的视频画布擦除【英文标题】:Text in canvas being wiped out with a video canvas underneath 【发布时间】:2021-08-22 04:55:23 【问题描述】:

注意:最小的工作示例here。点击change text按钮,可以看到文字出现一帧消失。

我想在我的视频中添加一个叠加层,所以我堆叠了两个画布并将文本填充到透明的顶部画布上。

但是,文本没有粘贴。它消失了。

为了测试我是否正确填充了文本,我尝试在下面使用黑色画布(没有视频)。

我只需要填充一次文本,文本就保留了。

但是,在下面的视频画布下,除非我在 requestAnimationFrame 中绘制文本,否则文本不会粘住,我相信这会在每一帧中绘制相同的文本,这是不必要的。

文本画布应该与视频画布分开。为什么在没有 requestAnimationFrame 的情况下会消失?

我该如何解决?

最小的工作示例here,但代码也如下所示。

import "./styles.css";
import  useEffect, useRef, useState  from "react";

export default function App() 
  const inputStreamRef = useRef();
  const videoRef = useRef();

  const canvasRef = useRef();
  const overlayCanvasRef = useRef();
  const [text, setText] = useState("Hello");

  function drawTextWithBackground() 
    const ctx = overlayCanvasRef.current.getContext("2d");
    ctx.clearRect(
      0,
      0,
      overlayCanvasRef.current.width,
      overlayCanvasRef.current.height
    );

    /// lets save current state as we make a lot of changes
    ctx.save();

    /// set font
    const font = "50px monospace";
    ctx.font = font;

    /// draw text from top - makes life easier at the moment
    ctx.textBaseline = "top";

    /// color for background
    ctx.fillStyle = "#FFFFFF";

    /// get width of text
    const textWidth = ctx.measureText(text).width;
    // Just note that this way of "measuring" height is not accurate.
    // You can measure height of a font by using a temporary div / span element and
    // get the calculated style from that when font and text is set for it.
    const textHeight = parseInt(font, 10);

    const textXPosition = canvasRef.current.width / 2 - textWidth / 2;
    const textYPosition = canvasRef.current.height - textHeight * 3;

    ctx.save();
    ctx.globalAlpha = 0.4;
    /// draw background rect assuming height of font
    ctx.fillRect(textXPosition, textYPosition, textWidth, textHeight);
    ctx.restore(); // this applies globalAlpha to just this?

    /// text color
    ctx.fillStyle = "#000";

    ctx.fillText(text, textXPosition, textYPosition);

    /// restore original state
    ctx.restore();
  

  function updateCanvas() 
    const ctx = canvasRef.current.getContext("2d");

    ctx.drawImage(
      videoRef.current,
      0,
      0,
      videoRef.current.videoWidth,
      videoRef.current.videoHeight
    );

    // QUESTION: Now the text won't stick unless I uncomment this below,
    // which runs drawTextWithBackground in requestAnimationFrame.
    // Previosuly, with just a black canvas underneath, I just needed to fillText once
    // and the text stayed.
    // The text canvas is supposed to be separate. Why is this getting wiped out without requestAnimationFrame?
    // drawTextWithBackground();

    requestAnimationFrame(updateCanvas);
  

  const CAMERA_CONSTRAINTS = 
    audio: true,
    video: 
      // the best (4k) resolution from camera
      width: 4096,
      height: 2160
    
  ;

  const enableCamera = async () => 
    inputStreamRef.current = await navigator.mediaDevices.getUserMedia(
      CAMERA_CONSTRAINTS
    );

    videoRef.current.srcObject = inputStreamRef.current;

    await videoRef.current.play();

    // We need to set the canvas height/width to match the video element.
    canvasRef.current.height = videoRef.current.videoHeight;
    canvasRef.current.width = videoRef.current.videoWidth;
    overlayCanvasRef.current.height = videoRef.current.videoHeight;
    overlayCanvasRef.current.width = videoRef.current.videoWidth;

    requestAnimationFrame(updateCanvas);
  ;
  useEffect(() => 
    enableCamera();
  );

  return (
    <div className="App">
      <video
        ref=videoRef
        style= visibility: "hidden", position: "absolute" 
      />
      <div style= position: "relative", width: "90vw" >
        <canvas
          style= width: "100%", top: 0, left: 0, position: "static" 
          ref=canvasRef
          
          
        />
        <canvas
          style=
            width: "100%",
            top: 0,
            left: 0,
            position: "absolute"
          
          ref=overlayCanvasRef
          
          
        />
      </div>
      <button
        onClick=() => 
          setText(text + "c");
          drawTextWithBackground();
        
      >
        change text
      </button>
    </div>
  );

【问题讨论】:

【参考方案1】:

当您从视频中更新画布时,画布中的所有像素都会使用视频中的像素进行更新。把它想象成一堵墙:

你在墙上画了“这是我的文字”。

然后你把整面墙漆成蓝色。 (你的文字不见了,墙只是蓝色的)。

如果您将整面墙涂成蓝色,然后重新绘制“这是我的文字”,您仍然会在蓝色墙上看到您的文字。

当你重画画布时,你必须重新画出你想出现在画布上的所有东西。如果视频“覆盖”了您的文字,您必须重新绘制文字。

https://github.com/dougsillars/chyronavideo 的工作示例在画布的每一帧上绘制一个 chyron(新闻故事底部的条)。

【讨论】:

谢谢,但正是出于这个原因,我在视频画布上使用了一个单独的画布作为文本。我仍然不确定为什么在视频画布上绘画会擦除我的文本画布。文本画布是透明的,位于顶部。 也许您应该为每个画布使用不同的变量:ctx1 和 ctx2 您只能定义一次 const,所以我认为它们都被绘制到其中一个画布上。【参考方案2】:

非常简单的修复

由于 React 更新状态的方式,画布正在被清除。

修复

最简单的解决方法是仅使用一个画布并将文本绘制到用于显示视频的画布上。

从工作示例中更改两个函数updateCanvasdrawTextWithBackground,如下所示。

请注意,我已经删除了 drawTextWithBackground 中的所有状态保存,因为画布状态由于无论如何都会发生反应而丢失,所以为什么要使用非常昂贵的状态堆栈函数。

  function drawTextWithBackground(ctx) 
    const can = ctx.canvas;
    const textH = 50;  
    ctx.font = textH + "px monospace";
    ctx.textBaseline = "top";
    ctx.textAlign = "center";
    ctx.fillStyle = "#FFFFFF66";     // alpha 0.4 as part of fillstyle
    ctx.fillStyle = "#000";
    const [W, X, Y] = [ctx.measureText(text).width, can.width, can.height - textH * 3];
    ctx.fillRect(X -  W / 2, Y, W, textH);
    ctx.fillText(text, X, Y);
  

  function updateCanvas() 
    const vid = videoRef.current;
    const ctx = canvasRef.current.getContext("2d");
    ctx.drawImage(vid, 0, 0, vid.videoWidth,  vid.videoHeight);
    drawTextWithBackground(ctx);
    requestAnimationFrame(updateCanvas);
  

【讨论】:

谢谢。您能否详细说明为什么这是由于“React 如何更新状态”?我的原始函数中的什么状态导致了这种情况? 顺便说一句,当我重构以从我的应用程序中提取画布组件时,它会自动解决。这可能是因为我从画布上分离了不必要的状态吗? @EricNa 影响 DOM 的状态更改可能会导致受影响的元素重新渲染(更改属性)。 React 将此 Reconciliation 称为 reactjs.org/docs/reconciliation.html。写入画布的宽度或高度属性将清除画布。

以上是关于画布中的文本被下面的视频画布擦除的主要内容,如果未能解决你的问题,请参考以下文章

重新填充画布擦除区域的区域

HTML5 drawimage 从视频到画布

来自视频帧的画布中的drawImage改变色调

Html画布视频流

HTML5:在带有控件的视频上放置画布

将暂停的视频帧捕获到画布的最佳方法