javascript - 如何缓存或预渲染 Canvas 动画

标签 javascript html5-canvas html5-video requestanimationframe

我正在使用 Canvas 对内嵌 html5 视频进行一些轻微的修改(颜色键控)。现在,这个视频是一个循环,我认为有更多的最佳方法,而不是每次都评估和重新绘制循环视频。我可以对 a) 预渲染整个动画或 b) 缓存原始循环进行哪些优化(如果有的话),以便不再需要进行处理/评估。我发现不断让它运行,CPU/内存使用效率完全低下。

我当前正在使用以下代码(注意,我使用的是 Vue.js,因此假设所有当前函数和变量分配都已正常工作):

loadVideo() {
	
	this.video = document.getElementById('globe');
	this.c1 = document.getElementById("c1");
	this.ctx1 = this.c1.getContext("2d");
	let that = this;
	
	this.video.addEventListener("play", function() {
		that.vWidth = that.video.videoWidth / 2;
		that.vHeight = that.video.videoHeight / 2;
		that.fpsInterval = 1000 / 120;
		that.then = Date.now();
		that.startTime = that.then;
		that.computeFrame();
	}, false);
	
}

computeFrame() {
	
	requestAnimationFrame(this.computeFrame);
	this.now = Date.now();
	this.elapsed = this.now - this.then;
	
	if (this.elapsed > this.fpsInterval) {
		this.ctx1.canvas.width = this.video.offsetWidth;
		this.ctx1.canvas.height = this.video.offsetHeight;
		if (this.video.offsetWidth > 0 && this.video.offsetHeight > 0) {
			this.then = this.now - (this.elapsed % this.fpsInterval);
			this.ctx1.drawImage(this.video, 0, 0, this.ctx1.canvas.width, this.ctx1.canvas.height);
			let frame = this.ctx1.getImageData(0, 0, this.ctx1.canvas.width, this.ctx1.canvas.height);
			let l = frame.data.length / 4;
			let primaryColor = this.ctx1.getImageData(0, 0, 8, 8).data;
			let primaryR = primaryColor[60];
			let primaryG = primaryColor[61];
			let primaryB = primaryColor[62];
			for (let i = 0; i < l; i++) {
				let r = frame.data[i * 4 + 0];
				let g = frame.data[i * 4 + 1];
				let b = frame.data[i * 4 + 2];
				if (r == primaryR && g == primaryG && b == primaryB) {
					frame.data[i * 4 + 1] = 255;
					frame.data[i * 4 + 2] = 0;
					frame.data[i * 4 + 3] = 0;
				}
			}
			this.ctx1.putImageData(frame, 0, 0);
		}
	}
	
}

loadVideo();

最佳答案

您可以使用 MediaRecorder记录您的 cavnas 返回的视频流 captureStream()方法来录制第一遍,然后直接在循环中读取录制的视频

btn.onclick = e => {
  // initialise the <video>
  const vid = document.createElement('video');
  vid.muted = true;
  vid.crossOrigin = true;
  vid.src = "https://upload.wikimedia.org/wikipedia/commons/transcoded/a/a4/BBH_gravitational_lensing_of_gw150914.webm/BBH_gravitational_lensing_of_gw150914.webm.480p.webm";
  vid.playbackRate = 2;
  vid.onplaying = startProcessing;
  vid.play();
  btn.remove();
  log.textContent = 'fetching';
};

function startProcessing(evt) {
  // when video is playing
  const vid = evt.target;
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = vid.videoWidth;
  canvas.height = vid.videoHeight;
  // show the canvas for first round
  document.body.appendChild(canvas);
  // force first frame
  anim();
  
  const chunks = [];   // we'll store our recorder's data here
  const canvasStream = canvas.captureStream();
  const vp = 'video/webm; codecs="vp',
    vp8 = vp + '8"',
    vp9 = vp + '9"';
  const recorder = new MediaRecorder(canvasStream, {
    mimeType: MediaRecorder.isTypeSupported(vp9) ? vp9:vp8,
    videoBitsPerSeconds: 5000000
  });
  // every time new data is available
  recorder.ondataavailable = evt => chunks.push(evt.data);
  // record until the video ends
  vid.onended = evt => {
    recorder.stop();
  };
  recorder.onstop = exportVid;

  recorder.start();
  log.textContent = "recording";
  
  function anim() {
    if(vid.paused) {
      console.log('stop drawing');
      return;
    }
    ctx.drawImage(vid, 0, 0);
    applyFilter(ctx);
    requestAnimationFrame(anim);
  }
  
  function exportVid() {
    // concatenate all our chunks in a single Blob
    const blob = new Blob(chunks);
    const url = URL.createObjectURL(blob);
    // we reuse the same <video> (for Safari autoplay)
    vid.onplaying = vid.onended = null;
    vid.src = url;
    vid.loop = true;
    vid.playbackRate = 1;
    log.textContent = "playing";

vid.play().then(()=> canvas.replaceWith(vid));
  }
}


function applyFilter(ctx) {
  const img = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
  const data = new Uint32Array(img.data.buffer);
  for(let i=0; i<data.length; i++) {
    if(data[i] < 0xFF111111) data[i] = ((Math.random()*0xFFFFFF) + 0xFF000000 | 0);
  }
  ctx.putImageData(img, 0, 0);
}
canvas,video{
  max-height: 100vh;
  max-width: 100vw;
}
<button id="btn">start</button><pre id="log"></pre>

关于javascript - 如何缓存或预渲染 Canvas 动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55331026/

相关文章:

javascript - if/else 语句中的语法错误

javascript - 向 AngularJS 中的输入字段添加前缀值

javascript - 如何在 Javascript/HTML5 中将 Uint16Array 中的数据显示为图像?

javascript - 在 PDF.js 中,如何隐藏 Canvas 并以完全不透明的方式显示底层文本?

javascript - 移动对象以使用 Canvas 单击 X 和 Y

video - 将媒体源扩展与原始视频帧一起使用

javascript - 覆盖 UTM 参数

javascript - 动态文档对象模型 (DOM) 的后退按钮(历史记录)

javascript - 如何将 HTMLAudioElement 当前播放位置重置为 0?

javascript - 带有 javascript 循环的 HTML5 视频