如何缓存光栅化图像

How to cache image rasterized

我正在做其中一个停止拍摄滚动控制播放网站,比如索尼的 Be Moved

考虑到停止拍摄技术,我面临的问题是在浏览器将图像绘制到屏幕之前图像被光栅化所花费的时间。在手机上需要很多。可能调整图像大小占用了大部分 cpu,但我不确定。这就是我显示框架的方式:

<div 
  style="
    position: fixed;
    top:0; right:0; bottom:0; left:0;
    background-image: url(...);
    background-position: center;
    background-size: cover;
  "
></div>

问题:

有没有办法缓存图像的光栅化版本?也许 canvas 支持这个?这样,当我决定在屏幕上显示它时,它就准备好了。

现在,这是我知道如何缓存图像的唯一方法。

var image = new Image();
image.src = '...';

Canvas 绘图源是图像和视频对象(以及一些现在不相关的其他源)。因此,如果在初始下载和渲染期间发生了您不想要的延迟,那么 canvas 将花费更长的时间,因为传入的图像必须首先渲染到图像对象上,然后再次渲染到 canvas-- 两个步骤而不是一个。

您的答案不在 canvas 元素中,因此您又回到了通常的解决方案:通过降低图像质量(质量较差的 jpg)来减少下载的图像位数。

您还可以(如您所指出的)在 new Image 中预加载和缓存所有图像,以便在需要时立即使用它们。通常的成本适用:缓存图像的内存使用量增加,以及在下载所有必需图像时应用程序启动延迟。

参考评论 - 有一种方法可以预缓存视频帧。每个帧都将为位图使用一个完整的内存块(在任何情况下,预加载图像序列也是如此)。

缓存进程

  • 创建一个 "off-line" 视频元素
  • 设置视频源 preload 设置为 auto
  • 您需要知道帧速率(典型值:USA/Japan 为 30 fps,欧洲为 25 fps),据此计算时间增量,即。 1 / FPS.
  • 对每个 currentTime 更新使用 timeupdate 事件,因为设置当前时间是 异步

在视频中选择一个入点,缓存(由于事件循环,这可能需要一段时间),使用每个帧的 canvas 元素存储到帧缓冲区。然后在需要时播放缓冲区(这也使您能够向后播放视频,如下所示,浏览器尚不支持此功能)。

例子

此示例将从网络加载视频,将 90(3 秒 @ 30 fps)帧缓存到内存,然后在 window 中播放乒乓球序列(您看到的图像来自明显缓存):

var canvas = document.querySelector("canvas"),
    ctx = canvas.getContext("2d"),
    video = document.createElement("video"),
    frames = [],
    w = canvas.width, h = canvas.height;

video.addEventListener("canplay", cache);
video.preload = "auto";
video.src = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";

function cache() {
  this.removeEventListener("canplay", cache);         // remove to avoid recalls

  var fps = 30,                                       // assuming 30 FPS
      delta = 1 / fps,                                // time delta
      count = 0,                                      // current cached frame
      max = fps * 3,                                  // 3 seconds
      div = document.querySelector("div");            // just for info

  this.addEventListener("timeupdate", cacheFrame);    // time update is aync
  this.currentTime = 19;                              // start with initial time
  
  function cacheFrame() {
    div.innerHTML = "Caching frame: " + count;
    
    if (count++ < max) {
      
      // create canvas for frame-buffer;
      var canvas = document.createElement("canvas"),
          ctx = canvas.getContext("2d");

      canvas.width = this.videoWidth;                 // canvas size = video frame
      canvas.height = this.videoHeight;
      
      ctx.drawImage(video, 0, 0);                     // draw current frame
      frames.push(canvas);                            // store frame
      
      this.currentTime += delta;                      // update time, wait..
    }
    else {
      this.removeEventListener("timeupdate", cacheFrame); // remove!!
      play();                                         // play back cached sequence
    }
  }
}

// to demo the cached frames
function play() {
  var current = 0, max = frames.length, dlt = 1,
      div = document.querySelector("div"),
      toggle = false,
      mem = max * video.videoWidth * video.videoHeight * 4; // always RGBA
 
  mem = (mem / 1024) / 1024;                          //mb

  ctx.fillStyle = "red";

  (function loop() {
    toggle = !toggle;                                 // toggle FPS to 30 FPS
    requestAnimationFrame(loop);
    
    if (toggle) {
      div.innerHTML = "Playing frame: " + current + 
                      " (raw mem: " + mem.toFixed(1) + " mb)";

      ctx.drawImage(frames[current], 0, 0, w, h);     // using frame-buffer
      ctx.fillRect(0, 0, current/max * w, 3);
      
      current += dlt;
      if (!current || current === max-1) dlt = -dlt;  // pong-pong
    }
  })();
}
html, body {width:100%;height:100%}
body {margin:0; overflow:hidden;background:#aaa}
div {font:bold 20px monospace;padding:12px;color:#000}
canvas {z-index:-1;position:fixed;left:0;top:0;width:100%;height:100%;min-height:400px}
<div>Pre-loading video... wait for it, wait for it...</div>
<canvas width=600 height=360></canvas>