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

问题描述 投票:0回答:1

Setup

我正在使用自定义RenderBox绘制。 下面代码中的canvas对象来自PaintingContext in the paint method

Drawing

我试图通过使用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]);
}

Storage

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

Rendering

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

CPU

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

Cause

我不知道从哪里开始,因为我甚至不确定我的代码的哪一部分会导致冻结。它是内存使用情况吗?还是绘图本身? 我一般会如何处理这个问题?我究竟做错了什么?我应该如何存储这些像素。

Efforts

我已经尝试了很多,根本没有帮助,我会尽力指出最值得注意的:

  • 我尝试将List<List<Color>>转换为Image from the dart:ui library,希望使用Canvas.drawImage。为了做到这一点,我尝试编码我自己的PNG,但I have not been able to render more than a single row。但是,它看起来并不会提升性能。当试图转换9999x9999图像时,我遇到了内存不足异常。现在,我想知道视频是如何呈现的,因为任何4k视频都会比9999x9999图像容易占用更多内存,如果它在内存中的几秒钟。
  • 我尝试实现image包。但是,我在完成它之前停止了,因为我注意到它不是用于Flutter而是用于HTML。我不会使用它获得任何东西。
  • 这个对于我将要绘制的以下结论非常重要:我试图在不存储像素的情况下进行绘制,即使用Random.nextInt生成随机颜色。当试图随机生成999x999图像时,这导致GPU最大值为1824.7 ms/frames,UI最大值为2362.7 ms/frame,这更糟糕,尤其是在GPU部门。

Conclusion

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

你是如何在Flutter做到这一点的?

Notes

  • 这基本上是what I tried to ask over two months ago(是的,我一直试图解决这个问题很长时间),但我认为我当时没有正确地表达自己,而且我甚至不知道实际问题是什么。
  • 我可以正确渲染的最高分辨率是围绕10k像素。我至少需要1m
  • 我在想放弃Flutter和为本地人做准备可能是我唯一的选择。但是,我想相信我只是完全错误地解决了这个问题。我花了大约三个月的时间试图解决这个问题,但我找不到任何可以引导我的事情。
android dart flutter
1个回答
1
投票

感谢@pslink在我写完之后提醒我BMP我没有编码我自己的PNG。 我以前曾调查过它,但我认为如果没有足够的文档,它看起来很复杂。现在,我发现this nice article解释了必要的BMP标头,并通过复制Example 2 from the "BMP file format" Wikipedia article实现了32位BGRA(ARGB,但BGRA是默认掩码的顺序)。我浏览了所有来源,但找不到此示例的原始来源。也许维基百科文章的作者自己写了这篇文章。

结果

使用Canvas.drawImage和我的999x999像素转换为BMP字节列表中的图像,我获得了GPU最大值9.9 ms/frame和UI最大值7.1 ms/frame,这太棒了!

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

结论

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

说明

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

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

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

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;

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

© www.soinside.com 2019 - 2024. All rights reserved.