Comment réduire l'utilisation de la mémoire de getImageData lors de l'utilisation de la toile pour éditer une vidéo?

J'ai essayé d'utiliser une toile pour éditer une vidéo en dessinant une toile hors écran, puis en utilisant getImageData pour faire un peu de travail, puis en la mettant sur mon canevas à l'écran. Cela fonctionne, mais même avec une petite vidéo de la résolution de 3,860, l'utilisation de la mémoire Chrome augmente jusqu'à ce qu'elle se bloque (environ dix minutes sur ma machine, moins avec une vidéo plus grande). La question semble être meilleure dans Firefox, mais souffre encore d'une grande utilisation de la mémoire.

Je me rends compte que chaque appel getImageData prend ~ 3 Mo de mémoire, mais même alors, j'ai l'impression qu'il devrait y avoir un moyen d'utiliser Chrome pour utiliser moins de 1 Go de mémoire. J'ai abaissé le taux d'intérêt, ce qui aide mais ne résout pas le problème. Est-ce que je peux faire pour s'assurer que la mémoire imageData est libérée de manière plus rapide?

<!DOCTYPE html> <meta charset='utf-8'> <html> <head> <title>Video Test</title> </head> <body> <canvas id='display'> </canvas> <script type='text/javascript'> var canvas = document.getElementById('display'); var context = canvas.getContext('2d'); var bCanvas = document.createElement('canvas'); var bContext = bCanvas.getContext('2d'); var video = document.createElement('video'); video.src = 'VIDEO HERE'; video.autoplay = true; video.loop = true; var last; var interval = 35; video.addEventListener('play', function() { canvas.width = video.videoWidth; canvas.height = video.videoHeight; bCanvas.width = video.videoWidth; bCanvas.height = video.videoHeight; last = performance.now(); window.requestAnimationFrame(draw); }, false); function draw(time) { if(time - last > interval) { bContext.drawImage(video,0,0,bCanvas.width,bCanvas.height); var imageData = bContext.getImageData(0,0,bCanvas.width,bCanvas.height); context.putImageData(imageData,0,0); last = time; } window.requestAnimationFrame(draw); } </script> </body> </html> 

Puisque ce que vous essayez d'atteindre est un effet clé chroma, vous pouvez effectuer la détection chromatique sur un cadre échantillonné en bas, régler la transparence et redessiner à une échelle normale sur le canevas de sortie.

Ensuite, grâce à la propriété globalCompositeOperation de votre contexte de sortie défini sur "destination-in" , vous ne pouvez dessiner que la partie non transparente de votre cadre d'origine, en gardant sa qualité d'origine:

 // Define our canvases var output = document.createElement('canvas'), ctx = output.getContext('2d'), buffer = output.cloneNode(), buf = buffer.getContext('2d'); document.body.insertBefore(output, changeSrc.previousElementSibling); var threshold = colorThreshold.value, color = hexToRgb(colorInp.value.split('#')[1]); var lastCall = 0; function draw() { requestAnimationFrame(draw); // if the video is still at the same frame, we don't need to process anything if (video.currentTime === lastCall) return; // video.pause(); lastCall = video.currentTime; // clear our output canvas ctx.clearRect(0, 0, output.width, output.height); ctx.drawImage(video, 0, 0, output.width, output.height); // draw a downsampled frame on the buffer canvas buf.drawImage(video, 0, 0, buffer.width, buffer.height); // get this downsampled canvas's imageData var image = buf.getImageData(0, 0, buffer.width, buffer.height), data = image.data; var t = threshold / 2; // loop through the imageData pixels for (var i = 0; i < data.length; i += 4) { // for a correct Chroma key, this should be improved if ((color[0] - t) <= data[i] && data[i] <= (color[0] + t) && (color[1] - t) <= data[i + 1] && data[i + 1] <= (color[1] + t) && (color[2] - t) <= data[i + 2] && data[i + 2] <= (color[2] + t)) { // set the alpha channel to 0 data[i + 3] = 0; } } // redraw our now-tranparent image on the buffer buf.putImageData(image, 0, 0); // set our context's gCO to destination-in ... ctx.globalCompositeOperation = 'destination-in'; // resample the buffer to a normal scale (bad quality) ctx.drawImage(buffer, 0, 0, output.width, output.height); // reset the context's gCO ctx.globalCompositeOperation = 'source-over'; } colorThreshold.addEventListener('change', function() { threshold = this.value; }); colorInp.addEventListener('change', function() { color = hexToRgb(this.value.split('#')[1]); }); cutQ.addEventListener('change', function() { buffer.width = (output.width / 100) * this.value; buffer.height = (output.height / 100) * this.value; }); video.addEventListener('loadedmetadata', function() { output.width = this.videoWidth; output.height = this.videoHeight; buffer.width = (output.width / 100) * cutQ.value; buffer.height = (output.height / 100) * cutQ.value; draw(); }); // convert our input's value to rgb function hexToRgb(hex) { var bigint = parseInt(hex, 16), r = (bigint >> 16) & 255, g = (bigint >> 8) & 255, b = bigint & 255; return [r, g, b]; } changeSrc.onclick = function(){ var c = video.src.indexOf('1080p')>0, next = c?'bch2j17v6ny4ako/movie720p.mp4':'f02uebtrgv5fsd3/movie720p.mp4'; video.src = video.src.split('/s/')[0]+'/s/'+next; var quals = ['720p', '1080p']; this.innerHTML = this.innerHTML.replace(quals[+!c],quals[+c]); } 
 canvas { width:100%; border: 1px solid; background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGUlEQVQYlWP4z8DwHx0zYANDQeEgcw5FCgHKJ2OdLLDjkAAAAABJRU5ErkJggg=='), repeat; } 
 Color to pick : <input type="color" value="#d2f26a" id="colorInp" /><br> Cut quality : <input type="range" min="10" max="100" step=".5" value="20" id="cutQ" /><br> Color Threshold : <input type="range" min="0" max="255" step=".5" value="150" id="colorThreshold" /><br> <video id="video" style="display:none" autoplay="" muted="true" loop="true" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4" crossorigin="anonymous"></video> <br><button id="changeSrc">load a 1880p version</button>