一般情况下,考虑网络传输效率,会采用算法来压缩这个数据,故而你会看到有各种各样的图像压缩算法和文件格式。
你可能会问什么情况下会有需要直接去加载一张图的原始rgba数据?
这里举个简单例子:分块加载图片。将图片解码后,分割成一个个矩形区域,每个矩形就有一个 raw rgba 数据,将其交给Image渲染,这样做可以降低一定的GPU 内存压力,减少出现GPU OOM 或黑屏的概率。
要支持 raw rgba ,其实很简单,在 dart:ui
包下有个方法decodeImageFromPixels
可以直接使用,前提是需要有原始的二进制数据、宽、高。
import 'dart:ui';
Future decodeRawRgba(ByteData bytes, int width, int height) {
final Completer completer = Completer();
decodeImageFromPixels(
bytes.buffer.asUint8List(),
width,
height,
PixelFormat.rgba8888,
completer.complete,
);
return completer.future;
}
有了这个 Image
(dart:ui)对象就可以交给 RawImage
Widget 来加载了。但RawImage
太过于底层了,能不能只用 Image
Widget呢?因为需要复用 LoadingBuilder
这些逻辑。
当然可以。查看一下 Image
Widget 的构造函数就知道,我们需要一个 ImageProvider
,那么问题进一步简化到如何写一个ImageProvider
支持 raw rgba 数据。
实现一个 ImageProvider
,我们需要实现 load
这个关键方法。以MemoryImage
为例:
class MemoryImage extends ImageProvider {
@override
ImageStreamCompleter load(MemoryImage key, DecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode),
scale: key.scale,
debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})',
);
}
Future<ui.Codec> _loadAsync(MemoryImage key, DecoderCallback decode) {
return decode(bytes);
}
}
很显然,我们需要想一个方法构造出raw rgba 数据的 Codec
。
其实秘密就在 decodeImageFromPixels
这个方法实现里:
void decodeImageFromPixels(
Uint8List pixels,
int width,
int height,
PixelFormat format,
ImageDecoderCallback callback, {
int? rowBytes,
int? targetWidth,
int? targetHeight,
bool allowUpscaling = true,
}) {
if (targetWidth != null) {
assert(allowUpscaling || targetWidth <= width);
}
if (targetHeight != null) {
assert(allowUpscaling || targetHeight <= height);
}
ImmutableBuffer.fromUint8List(pixels)
.then((ImmutableBuffer buffer) {
final ImageDescriptor descriptor = ImageDescriptor.raw(
buffer,
width: width,
height: height,
rowBytes: rowBytes,
pixelFormat: format,
);
if (!allowUpscaling) {
if (targetWidth != null && targetWidth! > descriptor.width) {
targetWidth = descriptor.width;
}
if (targetHeight != null && targetHeight! > descriptor.height) {
targetHeight = descriptor.height;
}
}
descriptor
.instantiateCodec(
targetWidth: targetWidth,
targetHeight: targetHeight,
)
.then((Codec codec) => codec.getNextFrame())
.then((FrameInfo frameInfo) => callback(frameInfo.image));
});
}
先从数据构造出ImageDescriptor
,再把descriptor.instantiateCodec()
这一步抽出来就可以获取 raw rgba 数据的 Codec
,进而实现一个自己的RawImageProvider
了。
如: