如何在 Flutter 中渲染单个像素?

cre*_*not 10 android dart flutter

设置

我正在使用自定义RenderBox绘制。
canvas下面的代码对象来自PaintingContextpaint方法

画画

我试图使个别像素使用Canvas.drawRect
我应该指出,这些有时比它们实际占据的屏幕上的像素更大,有时更小。

for (int i = 0; i < width * height; i++) {
  // in this case the rect size is 1
  canvas.drawRect(
      Rect.fromLTWH(index % (width * height), 
          (index / (width * height)).floor(), 1, 1), Paint()..color = colors[i]);
}
Run Code Online (Sandbox Code Playgroud)

贮存

我将像素存储为List<List<Color>>(colors在上面的代码中)。我之前尝试过不同的嵌套列表,但它们在性能方面没有造成任何明显的差异。当使用图像填充列表时,我的 Android Emulator 测试设备上的内存会增加282.7MB999x999。请注意,它只是暂时增加了282.7MB。大约半分钟后,增加量下降153.6MB并保持在那里(没有任何用户交互)。

渲染

分辨率为 时999x999,上面的代码导致GPU 最大值250.1 ms/frameUI 最大值1835.9 ms/frame,这显然是不可接受的。尝试绘制999x999图像时,UI 会冻结两秒钟,考虑到 4k 视频在同一设备上流畅运行,这应该是小菜一碟(我猜)。

中央处理器

我不确定如何使用 Android 分析器正确跟踪它,但是在填充或更改列表时,即绘制像素(上述指标也是如此),CPU 使用率从0%60%. 以下是 AVD 性能设置:

原因

我不知道从哪里开始,因为我什至不确定我的代码的哪一部分导致了冻结。是内存占用吗?还是图纸本身?
我一般会怎么做?我究竟做错了什么?我应该如何存储这些像素。

努力

我已经尝试了很多但根本没有帮助,所以我将尝试只指出最值得注意的:

  • 我试图转换List<List<Color>>Imagedart:ui希望利用Canvas.drawImage。为了做到这一点,我尝试对自己的 PNG 进行编码,但我无法渲染多于一行的. 但是,看起来这不会提高性能。在尝试转换9999x9999图像时,我遇到了内存不足异常。现在,我想知道视频是如何渲染的,因为任何 4k 视频9999x9999如果在内存中只有几秒钟,那么它很容易比图像占用更多的内存。

  • 我尝试实施该image软件包。然而,我在完成它之前停下来,因为我注意到它并不打算在 Flutter 中使用,而是在 HTML 中使用。使用它我不会得到任何东西。

  • 这对于我将得出的以下结论非常重要:我尝试只绘制而不存储像素,即Random.nextInt用于生成随机颜色。在尝试随机生成999x999图像时,这导致GPU 最大值1824.7 ms/framesUI 最大值2362.7 ms/frame,这甚至更糟,尤其是在 GPU 部门。

结论

这是我在尝试使用Canvas.drawImage:进行渲染失败之前得出的结论,Canvas.drawRect不是为此任务制作的,因为它甚至无法绘制简单的图像。

你如何在 Flutter 中做到这一点?

笔记

  • 这基本上是我两个月前试图问的问题(是的,我一直在努力解决这个问题),但我认为当时我没有正确表达自己,而且我对实际问题的了解更少曾是。

  • 我可以正确渲染的最高分辨率大约是10k像素。我至少需要1m.

  • 我认为放弃 Flutter 转而使用 Native 可能是我唯一的选择。但是,我想相信我只是完全错误地解决了这个问题。我花了大约三个月的时间试图弄清楚这一点,但我没有找到任何可以引导我到任何地方的东西。

cre*_*not 8

解决方案

dart:ui有一个功能可以Image轻松地将像素转换为:decodeImageFromPixels

当我创建这个答案时,我根本没有意识到这一点,这就是我写“替代”部分的原因。

选择

感谢@pslinkBMP在我写完之后提醒我我未能编码我自己的PNG.
我以前研究过它,但我认为它在没有足够文档的情况下看起来很复杂。现在,我找到了这篇很好的文章,它解释了必要的BMP标头,并通过复制“BMP 文件格式”维基百科文章中的示例 2实现了32 位 BGRA(ARGB,但 BGRA 是默认掩码的顺序)。我浏览了所有来源,但找不到此示例的原始来源。也许维基百科文章的作者自己写的。

结果

使用Canvas.drawImage我的999x999像素从转换为图像BMP字节列表中,我得到一个GPU最大9.9 ms/frameUI最大7.1 ms/frame这是真棒

|  ms/frame |  Before (Canvas.drawRect) | After (Canvas.drawImage) |
|-----------|---------------------------|--------------------------|
|  GPU max  | 1824.7                    | 9.9                      |
|  UI max   | 2362.7                    | 7.1                      |
Run Code Online (Sandbox Code Playgroud)

结论

类似的画布操作Canvas.drawRect不应该这样使用。

指示

首先,这是非常简单的,但是,您需要正确填充字节列表,否则,您将收到数据格式不正确并且看不到结果的错误,这可能会非常令人沮丧。

您需要在绘制之前准备好图像因为您无法async在绘制调用中使用操作。

在代码中,您需要使用 aCodec将字节列表转换为图像。

final list = [
            0x42, 0x4d, // 'B', 'M'
            ...];
// make sure that you either know the file size, data size and data offset beforehand
//        or that you edit these bytes afterwards

final Uint8List bytes = Uint8List.fromList(list);

final Codec codec = await instantiateImageCodec(bytes));

final Image image = (await codec.getNextFrame()).image;
Run Code Online (Sandbox Code Playgroud)

您需要将此图像传递给您的绘图小部件,例如使用FutureBuilder.
现在,您可以Canvas.drawImage在绘制调用中使用。