如何缓存或预渲染画布动画

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何缓存或预渲染画布动画相关的知识,希望对你有一定的参考价值。

我正在使用画布对内联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();
答案

您可以使用qazxsw poi记录画布qazxsw poi方法返回的视频流以记录第一遍,然后直接在循环中读取生成的录制视频 :

MediaRecorder
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);
}

以上是关于如何缓存或预渲染画布动画的主要内容,如果未能解决你的问题,请参考以下文章

Unity3D:画布上的 Sprite 渲染器与图像渲染器问题

如何将另一个矩阵雨代码动画添加到我的画布动画中?

在画布中同时渲染精灵表中的多个部分?

优化性能的方法有哪些

[UWP]缓存Lottie动画帧

reactjs重新渲染时画布闪烁