decodeOpenExr function Assets and loading
Decodes an OpenEXR image from bytes to linear RGBA float pixels (alpha 1),
row-major, row 0 at the top (the equirect up pole).
Only 16-bit half-float channels are supported (the common format for HDRI panoramas); a 32-bit-float or uint EXR throws with a clear message rather than decoding to garbage. TODO(exr-float): support 32-bit-float channels (the underlying decoder reads them as 16 bits; needs an upstream fix or a custom float path).
When maxWidth is set and the source is wider, the result is
box-downsampled by an integer factor (averaged in linear space) so a very
large panorama is not kept at full resolution. Alpha is discarded (set to
1); an environment map is opaque.
Implementation
DecodedHdr decodeOpenExr(Uint8List bytes, {int? maxWidth}) {
_requireHalfChannels(bytes);
final image = img.decodeExr(bytes);
if (image == null) {
throw ExrFormatException('Not a decodable OpenEXR image');
}
final width = image.width;
final height = image.height;
// package:image decodes top-down (row 0 = top), matching the equirect up-pole
// convention, so the scanline order is preserved.
final pixels = Float32List(width * height * 4);
var i = 0;
// The iterator reuses one Pixel, so this stays allocation-light.
// TODO(exr-perf): read the float image buffer directly if this is a hot path.
for (final pixel in image) {
pixels[i] = pixel.r.toDouble();
pixels[i + 1] = pixel.g.toDouble();
pixels[i + 2] = pixel.b.toDouble();
pixels[i + 3] = 1.0;
i += 4;
}
final decoded = DecodedHdr(pixels, width, height);
if (maxWidth == null || width <= maxWidth) return decoded;
return boxDownsampleEquirect(decoded, maxWidth);
}