如何在使用canvas编辑视频时减少getImageData的内存使用量?

时间:2015-11-13 07:35:47

标签: javascript html5 memory canvas

我一直在尝试使用画布通过绘制到屏幕外的画布来编辑视频,然后使用getImageData做一些工作,然后将其放到我的屏幕画布上。它可以工作,但即使使用480x360的小视频,Chrome内存使用量也会不断增加,直到崩溃为止(我的机器需要大约十分钟,而视频大小则需要更少)。这个问题似乎在Firefox中更好,但仍然受到大量内存使用的影响。

我意识到每个getImageData调用需要大约3MB的内存,但即便如此,我觉得应该有办法让Chrome使用不到1GB的内存。我降低了帧速率,这有助于但不能解决问题。有什么办法可以确保以更及时的方式释放imageData内存吗?

<!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>

1 个答案:

答案 0 :(得分:1)

由于您要实现的是色度键效果,您可以对下采样帧进行色度检测,在其上设置透明度,然后在输出画布上以正常比例重新绘制。

然后,由于输出上下文的globalCompositeOperation属性设置为"destination-in",您只能绘制原始帧的非透明部分,保持其原始质量:

&#13;
&#13;
// Define our canvases
var output = document.createElement('canvas'),
  ctx = output.getContext('2d'),
  buffer = output.cloneNode(),
  buf = buffer.getContext('2d');

document.body.appendChild(output);

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('input', function() {
  threshold = this.value;
});
colorInp.addEventListener('input', function() {
  color = hexToRgb(this.value.split('#')[1]);
});
cutQ.addEventListener('input', 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];
}
&#13;
canvas {
  width:100%;
  border: 1px solid;
  background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGUlEQVQYlWP4z8DwHx0zYANDQeEgcw5FCgHKJ2OdLLDjkAAAAABJRU5ErkJggg=='), repeat;
}
&#13;
Color to pick :
<input type="color" value="#30f062" id="colorInp" /><br>
Cut quality :
<input type="range" min="10" max="100" step=".5" value="80" id="cutQ" /><br>
Color Threshold :
<input type="range" min="0" max="255" step=".5" value="166" id="colorThreshold" /><br>
<video id="video" style="position:absolute;visibility:hidden;z-index:-1" autoplay="" muted="true" loop="true" src="https://dl.dropboxusercontent.com/s/1jp0f76yvzuucj7/L0ckergn0me-PixieGreenScreen446_512kb.mp4" crossorigin="anonymous"><!--CC BY-NC-SA 2.0 http://chris.pirillo.com/--></video>
&#13;
&#13;
&#13;