提高动画图像在浏览器中的渲染性能

Improve rendering performance of animated image in browser

我正在试验在浏览器中呈现图像的不同方式。这个想法是创建自定义 HTML 元素,能够将一组图像或帧显示为连续动画。图像将在服务器端生成并流式传输到浏览器客户端。最简单的方法似乎是使用 img 标记持续更新其 src 属性。为此,我订阅了 onload 事件,一旦加载了图像,我就用随机时间戳更新图像 URL ,它会自动发送新请求,创建看起来像动画的无限循环。该解决方案非常简单且有效,但会影响 CPU 性能并且不使用任何 GPU 加速。

控制器用作图像源

[ApiController]
public class StreamController : ControllerBase
{
  [Route("/source")]
  public ActionResult Get()
  {
    var o = new Random();
    var pos = o.Next(50, 150);
    var map = new SKBitmap(250, 250);
    var canvas = new SKCanvas(map);
    var paint = new SKPaint
    {
      Color = SKColors.Black,
      Style = SKPaintStyle.Fill,
      FilterQuality = SKFilterQuality.High
    };

    canvas.DrawCircle(pos, pos, 20, paint);

    var image = SKImage.FromBitmap(map);
    var ms = new MemoryStream();

    image.Encode(SKEncodedImageFormat.Png, 100).SaveTo(ms);
    ms.Position = 0;

    return new FileStreamResult(ms, "image/png");
  }
}

HTML 页

@page "/"

<!-- Solution #1 : SRC attribute -->

<img 
  width="250" 
  height="250" 
  @onload="OnLoad" 
  src="@Source" />

<!-- Solution #2 : background style URL -->

<div 
  style="width: 250px; height: 250px; background: url(@Source)">
</div>

<!-- Solution #3 : SRC of the picture element - does not work -->

<picture style="width: 250px; height: 250px">
  <source srcset="@Source" type="image/png" media="(min-width:250px)">
</picture>

@code 
{
  private Random _generator = new();
  public string Source { get; set; } = "/source";

  public void OnLoad()
  {
    // Animate by creating infinite loop of HTTP calls 
    // updating image source right after loading the previous one

    var uid = Guid.NewGuid().ToString("N");
    var num = _generator.Next();
    var stamp = DateTime.Now.Ticks;
    
    Source = $"/source?{ uid }-{ num }-{ stamp }";
  }
}

结局

红色边框是HTML动画。蓝色边框是 CSS 背景。图片标签未呈现。

https://youtu.be/gk8Z-LrKxLE

问题

  1. 为什么使用 picture 元素的解决方案 #3 不起作用?
  2. 为什么带有 CSS 背景的解决方案 #2 比带有图像标签的解决方案 #1 慢得多。为什么它会跳过一些帧并且没有 GPU 加速?
  3. 有没有办法通过更改 HTML 或控制器中的某些内容来减少 CPU 上的负载,例如在控制器中切换到异步流或将图像转换为视频流?

更新

刷新 img URL 似乎存在更大的问题。看起来从 ASP 控制器返回的 FileStreamResult 被图像锁定了,所以每次我请求图像更新时,比如

...浏览器创建并缓存此图像,而 .NET 无法释放此资源,从而导致大量内存增加。

找到解决方案 - 使用 Motion JPEG 作为 imgiframe 标签的来源。使用 video 标签也应该是可能的。似乎大多数浏览器(如果不是全部)都支持通过 HTTP 作为 multipart/mixed 内容发送的图像流,其中该流中的每个图像只是一组由一些分隔符分隔的字节。

Motion JPEG 格式

通过 HTTP 发送以下内容将使浏览器等待流中的连续数据。

HTTP/1.1 200 OK\r\n
Content-Type: multipart/x-mixed-replace; boundary=--SomeBoundary\r\n

生成图像的服务器端应保持流打开,例如使用无限循环。此循环将生成新图像,将它们转换为适当的格式,如 JPEG,甚至更好优化的 WEBP,并将它们作为一组字节发送到 HTTP 流。

--SomeBoundary\r\n
Content-Type: image/jpeg\r\n
Content-Length: 100\r\n\r\n 
<Image-Bytes-Go-Here>
\r\n

因此,要显示包含 3 帧的视频,生成的 HTTP 响应将如下所示。

HTTP/1.1 200 OK\r\n
Content-Type: multipart/x-mixed-replace; boundary=--SomeBoundary\r\n

--SomeBoundary\r\n
Content-Type: image/jpeg\r\n
Content-Length: 100\r\n\r\n 
<Image-Bytes-Go-Here>\r\n

--SomeBoundary\r\n
Content-Type: image/jpeg\r\n
Content-Length: 100\r\n\r\n 
<Image-Bytes-Go-Here>\r\n

--SomeBoundary\r\n
Content-Type: image/jpeg\r\n
Content-Length: 100\r\n\r\n 
<Image-Bytes-Go-Here>\r\n

优化

  • Simple shapes could be drawn with pure CSS
  • 使用 SkiaSharp canvas
  • 可以更好地呈现具有大量动态形状的主视图
  • SkiSharp 中的文本呈现是 somewhat slow。 SVG 或普通 HTML 可能是这里的最佳选择

内存泄漏

下一个问题是,当 img 标记通过 HTTP 请求带有图像的 HTTP 流时,内存量不断增加。

<img src="http://127.0.0.1/stream" />

令人惊讶的是,只需将 img 标记替换为 iframe 即可解决内存泄漏问题,这很可能是因为框架和浏览器 windows 不像 [=15] 那样积极地缓存图像=] 标记或至少此缓存正在更有效地清理。

<iframe src="http://127.0.0.1/stream" />

通过示例解释此概念的链接。

Real-time 图表应用实现为可重复使用的 Blazor 控件。