提高动画图像在浏览器中的渲染性能
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 背景。图片标签未呈现。
问题
- 为什么使用
picture
元素的解决方案 #3 不起作用?
- 为什么带有 CSS 背景的解决方案 #2 比带有图像标签的解决方案 #1 慢得多。为什么它会跳过一些帧并且没有 GPU 加速?
- 有没有办法通过更改 HTML 或控制器中的某些内容来减少 CPU 上的负载,例如在控制器中切换到异步流或将图像转换为视频流?
更新
刷新 img
URL 似乎存在更大的问题。看起来从 ASP 控制器返回的 FileStreamResult
被图像锁定了,所以每次我请求图像更新时,比如
- /来源?1
- /source/2
- /来源?3
...浏览器创建并缓存此图像,而 .NET 无法释放此资源,从而导致大量内存增加。
找到解决方案 - 使用 Motion JPEG 作为 img
或 iframe
标签的来源。使用 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" />
通过示例解释此概念的链接。
- https://en.wikipedia.org/wiki/Motion_JPEG
- https://blog.green.web.za/2019/11/23/mjpeg-in-asp-net-core.html
- https://www.codeproject.com/Articles/371955/Motion-JPEG-Streaming-Server
Real-time 图表应用实现为可重复使用的 Blazor 控件。
我正在试验在浏览器中呈现图像的不同方式。这个想法是创建自定义 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 背景。图片标签未呈现。
问题
- 为什么使用
picture
元素的解决方案 #3 不起作用? - 为什么带有 CSS 背景的解决方案 #2 比带有图像标签的解决方案 #1 慢得多。为什么它会跳过一些帧并且没有 GPU 加速?
- 有没有办法通过更改 HTML 或控制器中的某些内容来减少 CPU 上的负载,例如在控制器中切换到异步流或将图像转换为视频流?
更新
刷新 img
URL 似乎存在更大的问题。看起来从 ASP 控制器返回的 FileStreamResult
被图像锁定了,所以每次我请求图像更新时,比如
- /来源?1
- /source/2
- /来源?3
...浏览器创建并缓存此图像,而 .NET 无法释放此资源,从而导致大量内存增加。
找到解决方案 - 使用 Motion JPEG 作为 img
或 iframe
标签的来源。使用 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" />
通过示例解释此概念的链接。
- https://en.wikipedia.org/wiki/Motion_JPEG
- https://blog.green.web.za/2019/11/23/mjpeg-in-asp-net-core.html
- https://www.codeproject.com/Articles/371955/Motion-JPEG-Streaming-Server
Real-time 图表应用实现为可重复使用的 Blazor 控件。