Как кэшировать или предварительно визуализировать анимацию холста - PullRequest
0 голосов
/ 25 марта 2019

Я нахожусь в процессе небольшого изменения (цветовой маркировки) встроенного html5-видео с использованием canvas.Теперь это видео представляет собой цикл, и я думаю, что есть более оптимальные способы, чем циклическое видео, которое оценивается и перерисовывается каждый раз.Какие оптимизации, если таковые имеются, я могу сделать, чтобы а) предварительно визуализировать всю анимацию или б) кэшировать исходный цикл, чтобы больше не требовалось никакой обработки / оценки.Я обнаружил, что я совершенно неэффективен в использовании процессора / памяти, постоянно позволяя ему работать.

В настоящее время я использую следующий код (обратите внимание, я использую 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();

1 Ответ

1 голос
/ 25 марта 2019

Вы можете использовать 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);
}
canvas,video{
  max-height: 100vh;
  max-width: 100vw;
}
...