blob: 716c7c0cc3385582a2dc0ab138780752ef64a012 [file] [log] [blame]
import 'dart:typed_data';
import '../../image_exception.dart';
import '../../formats/decode_info.dart';
import '../../hdr/hdr_image.dart';
import '../../hdr/hdr_slice.dart';
import '../../util/input_buffer.dart';
import 'exr_channel.dart';
import 'exr_compressor.dart';
import 'exr_part.dart';
class ExrImage extends DecodeInfo {
/// An EXR image has one or more parts, each of which contains a framebuffer.
List<InternalExrPart> _parts = [];
ExrImage(List<int> bytes) {
InputBuffer input = InputBuffer(bytes);
int magic = input.readUint32();
if (magic != MAGIC) {
throw new ImageException('File is not an OpenEXR image file.');
}
version = input.readByte();
if (version != EXR_VERSION) {
throw new ImageException('Cannot read version $version image files.');
}
flags = input.readUint24();
if (!_supportsFlags(flags)) {
throw new ImageException('The file format version number\'s flag field '
'contains unrecognized flags.');
}
if (!_isMultiPart()) {
ExrPart part = InternalExrPart(_isTiled(), input);
if (part.isValid) {
_parts.add(part as InternalExrPart);
}
} else {
while (true) {
ExrPart part = InternalExrPart(_isTiled(), input);
if (!part.isValid) {
break;
}
_parts.add(part as InternalExrPart);
}
}
if (_parts.isEmpty) {
throw new ImageException('Error reading image header');
}
for (InternalExrPart part in _parts) {
part.readOffsets(input);
}
_readImage(input);
}
List<ExrPart> get parts => _parts;
int get numFrames => 1;
/// Parse just enough of the file to identify that it's an EXR image.
static bool isValidFile(List<int> bytes) {
InputBuffer input = InputBuffer(bytes);
int magic = input.readUint32();
if (magic != MAGIC) {
return false;
}
int version = input.readByte();
if (version != EXR_VERSION) {
return false;
}
int flags = input.readUint24();
if (!_supportsFlags(flags)) {
return false;
}
return true;
}
int numParts() => _parts.length;
ExrPart getPart(int i) => _parts[i];
bool _isTiled() {
return (flags & TILED_FLAG) != 0;
}
bool _isMultiPart() {
return flags & MULTI_PART_FILE_FLAG != 0;
}
/*bool _isNonImage() {
return flags & NON_IMAGE_FLAG != 0;
}*/
static bool _supportsFlags(int flags) {
return (flags & ~ALL_FLAGS) == 0;
}
void _readImage(InputBuffer input) {
//final bool multiPart = _isMultiPart();
for (int pi = 0; pi < _parts.length; ++pi) {
InternalExrPart part = _parts[pi];
HdrImage framebuffer = part.framebuffer;
for (int ci = 0; ci < part.channels.length; ++ci) {
ExrChannel ch = part.channels[ci];
if (!framebuffer.hasChannel(ch.name)) {
width = part.width;
height = part.height;
framebuffer.addSlice(
new HdrSlice(ch.name, part.width, part.height, ch.type));
}
}
if (part.tiled) {
_readTiledPart(pi, input);
} else {
_readScanlinePart(pi, input);
}
}
}
void _readTiledPart(int pi, InputBuffer input) {
InternalExrPart part = _parts[pi];
final bool multiPart = _isMultiPart();
HdrImage framebuffer = part.framebuffer;
ExrCompressor compressor = part.compressor;
List<Uint32List> offsets = part.offsets;
//Uint32List fbi = Uint32List(part.channels.length);
InputBuffer imgData = InputBuffer.from(input);
for (int ly = 0, l = 0; ly < part.numYLevels; ++ly) {
for (int lx = 0; lx < part.numXLevels; ++lx, ++l) {
for (int ty = 0, oi = 0; ty < part.numYTiles[ly]; ++ty) {
for (int tx = 0; tx < part.numXTiles[lx]; ++tx, ++oi) {
// TODO support sub-levels (for rip/mip-mapping).
if (l != 0) {
break;
}
int offset = offsets[l][oi];
imgData.offset = offset;
if (multiPart) {
int p = imgData.readUint32();
if (p != pi) {
throw new ImageException('Invalid Image Data');
}
}
int tileX = imgData.readUint32();
int tileY = imgData.readUint32();
/*int levelX =*/ imgData.readUint32();
/*int levelY =*/ imgData.readUint32();
int dataSize = imgData.readUint32();
InputBuffer data = imgData.readBytes(dataSize);
int ty = tileY * part.tileHeight;
int tx = tileX * part.tileWidth;
int tileWidth = compressor.decodedWidth;
int tileHeight = compressor.decodedHeight;
if (tx + tileWidth > width) {
tileWidth = width - tx;
}
if (ty + tileHeight > height) {
tileHeight = height - ty;
}
Uint8List uncompressedData;
if (compressor != null) {
uncompressedData = compressor.uncompress(
data, tx, ty, part.tileWidth, part.tileHeight);
tileWidth = compressor.decodedWidth;
tileHeight = compressor.decodedHeight;
} else {
uncompressedData = data.toUint8List();
}
int si = 0;
int len = uncompressedData.length;
int numChannels = part.channels.length;
//int lineCount = 0;
for (int yi = 0; yi < tileHeight && ty < height; ++yi, ++ty) {
for (int ci = 0; ci < numChannels; ++ci) {
ExrChannel ch = part.channels[ci];
Uint8List slice = framebuffer[ch.name].getBytes();
if (si >= len) {
break;
}
int tx = tileX * part.tileWidth;
for (int xx = 0; xx < tileWidth; ++xx, ++tx) {
for (int bi = 0; bi < ch.size; ++bi) {
if (tx < part.width && ty < part.height) {
int di = (ty * part.width + tx) * ch.size + bi;
slice[di] = uncompressedData[si++];
} else {
si++;
}
}
}
}
}
}
}
}
}
}
void _readScanlinePart(int pi, InputBuffer input) {
InternalExrPart part = _parts[pi];
final bool multiPart = _isMultiPart();
HdrImage framebuffer = part.framebuffer;
ExrCompressor compressor = part.compressor;
List<int> offsets = part.offsets[0];
//int scanLineMin = part.top;
//int scanLineMax = part.bottom;
int linesInBuffer = part.linesInBuffer;
//int minY = part.top;
//int maxY = minY + part.linesInBuffer - 1;
Uint32List fbi = Uint32List(part.channels.length);
//int total = 0;
//int xx = 0;
int yy = 0;
InputBuffer imgData = InputBuffer.from(input);
for (int offset in offsets) {
imgData.offset = offset;
if (multiPart) {
int p = imgData.readUint32();
if (p != pi) {
throw new ImageException('Invalid Image Data');
}
}
/*int y =*/ imgData.readInt32();
int dataSize = imgData.readInt32();
InputBuffer data = imgData.readBytes(dataSize);
Uint8List uncompressedData;
if (compressor != null) {
uncompressedData = compressor.uncompress(data, 0, yy);
} else {
uncompressedData = data.toUint8List();
}
int si = 0;
int len = uncompressedData.length;
int numChannels = part.channels.length;
//int lineCount = 0;
for (int yi = 0; yi < linesInBuffer && yy < height; ++yi, ++yy) {
si = part.offsetInLineBuffer[yy];
if (si >= len) {
break;
}
for (int ci = 0; ci < numChannels; ++ci) {
ExrChannel ch = part.channels[ci];
Uint8List slice = framebuffer[ch.name].getBytes();
if (si >= len) {
break;
}
for (int xx = 0; xx < part.width; ++xx) {
for (int bi = 0; bi < ch.size; ++bi) {
slice[fbi[ci]++] = uncompressedData[si++];
}
}
}
}
}
}
int version;
int flags;
/// The MAGIC number is stored in the first four bytes of every
/// OpenEXR image file. This can be used to quickly test whether
/// a given file is an OpenEXR image file (see isImfMagic(), below).
static const int MAGIC = 20000630;
/// Value that goes into VERSION_NUMBER_FIELD.
static const int EXR_VERSION = 2;
/// File is tiled
static const int TILED_FLAG = 0x000002;
/// File contains long attribute or channel names
static const int LONG_NAMES_FLAG = 0x000004;
/// File has at least one part which is not a regular scanline image or
/// regular tiled image (that is, it is a deep format).
static const int NON_IMAGE_FLAG = 0x000008;
/// File has multiple parts.
static const int MULTI_PART_FILE_FLAG = 0x000010;
/// Bitwise OR of all supported flags.
static const int ALL_FLAGS = TILED_FLAG | LONG_NAMES_FLAG;
}