import 'dart:math'; | |
import 'dart:typed_data'; | |
import '../../image_exception.dart'; | |
import '../../internal/internal.dart'; | |
import '../../hdr/hdr_image.dart'; | |
import '../../util/input_buffer.dart'; | |
import 'exr_attribute.dart'; | |
import 'exr_channel.dart'; | |
import 'exr_compressor.dart'; | |
class ExrPart { | |
/// The framebuffer for this exr part. | |
HdrImage framebuffer = HdrImage(); | |
/// The channels present in this part. | |
List<ExrChannel> channels = []; | |
/// The extra attributes read from the part header. | |
Map<String, ExrAttribute> attributes = {}; | |
/// The display window (see the openexr documentation). | |
List<int> displayWindow; | |
/// The data window (see the openexr documentation). | |
List<int> dataWindow; | |
/// width of the data window | |
int width; | |
/// Height of the data window | |
int height; | |
double pixelAspectRatio = 1.0; | |
double screenWindowCenterX = 0.0; | |
double screenWindowCenterY = 0.0; | |
double screenWindowWidth = 1.0; | |
Float32List chromaticities; | |
ExrPart(this._tiled, InputBuffer input) { | |
//_type = _tiled ? ExrPart.TYPE_TILE : ExrPart.TYPE_SCANLINE; | |
while (true) { | |
String name = input.readString(); | |
if (name == null || name.isEmpty) { | |
break; | |
} | |
String type = input.readString(); | |
int size = input.readUint32(); | |
InputBuffer value = input.readBytes(size); | |
attributes[name] = ExrAttribute(name, type, size, value); | |
switch (name) { | |
case 'channels': | |
while (true) { | |
ExrChannel channel = ExrChannel(value); | |
if (!channel.isValid) { | |
break; | |
} | |
channels.add(channel); | |
} | |
break; | |
case 'chromaticities': | |
chromaticities = Float32List(8); | |
chromaticities[0] = value.readFloat32(); | |
chromaticities[1] = value.readFloat32(); | |
chromaticities[2] = value.readFloat32(); | |
chromaticities[3] = value.readFloat32(); | |
chromaticities[4] = value.readFloat32(); | |
chromaticities[5] = value.readFloat32(); | |
chromaticities[6] = value.readFloat32(); | |
chromaticities[7] = value.readFloat32(); | |
break; | |
case 'compression': | |
_compressionType = value.readByte(); | |
if (_compressionType > 7) { | |
throw new ImageException('EXR Invalid compression type'); | |
} | |
break; | |
case 'dataWindow': | |
dataWindow = [ | |
value.readInt32(), | |
value.readInt32(), | |
value.readInt32(), | |
value.readInt32() | |
]; | |
width = (dataWindow[2] - dataWindow[0]) + 1; | |
height = (dataWindow[3] - dataWindow[1]) + 1; | |
break; | |
case 'displayWindow': | |
displayWindow = [ | |
value.readInt32(), | |
value.readInt32(), | |
value.readInt32(), | |
value.readInt32() | |
]; | |
break; | |
case 'lineOrder': | |
//_lineOrder = value.readByte(); | |
break; | |
case 'pixelAspectRatio': | |
pixelAspectRatio = value.readFloat32(); | |
break; | |
case 'screenWindowCenter': | |
screenWindowCenterX = value.readFloat32(); | |
screenWindowCenterY = value.readFloat32(); | |
break; | |
case 'screenWindowWidth': | |
screenWindowWidth = value.readFloat32(); | |
break; | |
case 'tiles': | |
_tileWidth = value.readUint32(); | |
_tileHeight = value.readUint32(); | |
int mode = value.readByte(); | |
_tileLevelMode = mode & 0xf; | |
_tileRoundingMode = (mode >> 4) & 0xf; | |
break; | |
case 'type': | |
String s = value.readString(); | |
if (s == 'deepscanline') { | |
//this._type = TYPE_DEEP_SCANLINE; | |
} else if (s == 'deeptile') { | |
//this._type = TYPE_DEEP_TILE; | |
} else { | |
throw new ImageException('EXR Invalid type: $s'); | |
} | |
break; | |
default: | |
break; | |
} | |
} | |
if (_tiled) { | |
_numXLevels = _calculateNumXLevels(left, right, top, bottom); | |
_numYLevels = _calculateNumYLevels(left, right, top, bottom); | |
if (_tileLevelMode != RIPMAP_LEVELS) { | |
_numYLevels = 1; | |
} | |
_numXTiles = List<int>(_numXLevels); | |
_numYTiles = List<int>(_numYLevels); | |
_calculateNumTiles( | |
_numXTiles, _numXLevels, left, right, _tileWidth, _tileRoundingMode); | |
_calculateNumTiles( | |
_numYTiles, _numYLevels, top, bottom, _tileHeight, _tileRoundingMode); | |
_bytesPerPixel = _calculateBytesPerPixel(); | |
_maxBytesPerTileLine = _bytesPerPixel * _tileWidth; | |
//_tileBufferSize = _maxBytesPerTileLine * _tileHeight; | |
_compressor = ExrCompressor( | |
_compressionType, this, _maxBytesPerTileLine, _tileHeight); | |
_offsets = List<Uint32List>(_numXLevels * _numYLevels); | |
for (int ly = 0, l = 0; ly < _numYLevels; ++ly) { | |
for (int lx = 0; lx < _numXLevels; ++lx, ++l) { | |
_offsets[l] = Uint32List(_numXTiles[lx] * _numYTiles[ly]); | |
} | |
} | |
} else { | |
_bytesPerLine = Uint32List(height + 1); | |
for (ExrChannel ch in channels) { | |
int nBytes = ch.size * width ~/ ch.xSampling; | |
for (int y = 0; y < height; ++y) { | |
if ((y + top) % ch.ySampling == 0) { | |
_bytesPerLine[y] += nBytes; | |
} | |
} | |
} | |
int maxBytesPerLine = 0; | |
for (int y = 0; y < height; ++y) { | |
maxBytesPerLine = max(maxBytesPerLine, _bytesPerLine[y]); | |
} | |
_compressor = ExrCompressor(_compressionType, this, maxBytesPerLine); | |
_linesInBuffer = _compressor.numScanLines(); | |
//_lineBufferSize = maxBytesPerLine * _linesInBuffer; | |
_offsetInLineBuffer = Uint32List(_bytesPerLine.length); | |
int offset = 0; | |
for (int i = 0; i <= _bytesPerLine.length - 1; ++i) { | |
if (i % _linesInBuffer == 0) { | |
offset = 0; | |
} | |
_offsetInLineBuffer[i] = offset; | |
offset += _bytesPerLine[i]; | |
} | |
int numOffsets = ((height + _linesInBuffer) ~/ _linesInBuffer) - 1; | |
_offsets = [new Uint32List(numOffsets)]; | |
} | |
} | |
int get left => dataWindow[0]; | |
int get top => dataWindow[1]; | |
int get right => dataWindow[2]; | |
int get bottom => dataWindow[3]; | |
/// Was this part successfully decoded? | |
bool get isValid => width != null; | |
int _calculateNumXLevels(int minX, int maxX, int minY, int maxY) { | |
int num = 0; | |
switch (_tileLevelMode) { | |
case ONE_LEVEL: | |
num = 1; | |
break; | |
case MIPMAP_LEVELS: | |
int w = maxX - minX + 1; | |
int h = maxY - minY + 1; | |
num = _roundLog2(max(w, h), _tileRoundingMode) + 1; | |
break; | |
case RIPMAP_LEVELS: | |
int w = maxX - minX + 1; | |
num = _roundLog2(w, _tileRoundingMode) + 1; | |
break; | |
default: | |
throw new ImageException("Unknown LevelMode format."); | |
} | |
return num; | |
} | |
int _calculateNumYLevels(int minX, int maxX, int minY, int maxY) { | |
int num = 0; | |
switch (_tileLevelMode) { | |
case ONE_LEVEL: | |
num = 1; | |
break; | |
case MIPMAP_LEVELS: | |
int w = (maxX - minX) + 1; | |
int h = (maxY - minY) + 1; | |
num = _roundLog2(max(w, h), _tileRoundingMode) + 1; | |
break; | |
case RIPMAP_LEVELS: | |
int h = (maxY - minY) + 1; | |
num = _roundLog2(h, _tileRoundingMode) + 1; | |
break; | |
default: | |
throw new ImageException('Unknown LevelMode format.'); | |
} | |
return num; | |
} | |
int _roundLog2(int x, int rmode) { | |
return (rmode == ROUND_DOWN) ? _floorLog2(x) : _ceilLog2(x); | |
} | |
int _floorLog2(int x) { | |
int y = 0; | |
while (x > 1) { | |
y += 1; | |
x >>= 1; | |
} | |
return y; | |
} | |
int _ceilLog2(int x) { | |
int y = 0; | |
int r = 0; | |
while (x > 1) { | |
if (x & 1 != 0) { | |
r = 1; | |
} | |
y += 1; | |
x >>= 1; | |
} | |
return y + r; | |
} | |
int _calculateBytesPerPixel() { | |
int bytesPerPixel = 0; | |
for (ExrChannel ch in channels) { | |
bytesPerPixel += ch.size; | |
} | |
return bytesPerPixel; | |
} | |
void _calculateNumTiles(List<int> numTiles, int numLevels, int min, int max, | |
int size, int rmode) { | |
for (int i = 0; i < numLevels; i++) { | |
numTiles[i] = (_levelSize(min, max, i, rmode) + size - 1) ~/ size; | |
} | |
} | |
int _levelSize(int _min, int _max, int l, int rmode) { | |
if (l < 0) { | |
throw new ImageException('Argument not in valid range.'); | |
} | |
int a = (_max - _min) + 1; | |
int b = (1 << l); | |
int size = a ~/ b; | |
if (rmode == ROUND_UP && size * b < a) { | |
size += 1; | |
} | |
return max(size, 1); | |
} | |
static const int TYPE_SCANLINE = 0; | |
static const int TYPE_TILE = 1; | |
static const int TYPE_DEEP_SCANLINE = 2; | |
static const int TYPE_DEEP_TILE = 3; | |
static const int INCREASING_Y = 0; | |
static const int DECREASING_Y = 1; | |
static const int RANDOM_Y = 2; | |
static const int ONE_LEVEL = 0; | |
static const int MIPMAP_LEVELS = 1; | |
static const int RIPMAP_LEVELS = 2; | |
static const int ROUND_DOWN = 0; | |
static const int ROUND_UP = 1; | |
//int _type; | |
//int _lineOrder = INCREASING_Y; | |
int _compressionType = ExrCompressor.NO_COMPRESSION; | |
List<Uint32List> _offsets; | |
Uint32List _bytesPerLine; | |
ExrCompressor _compressor; | |
int _linesInBuffer; | |
//int _lineBufferSize; | |
Uint32List _offsetInLineBuffer; | |
bool _tiled; | |
int _tileWidth; | |
int _tileHeight; | |
int _tileLevelMode; | |
int _tileRoundingMode; | |
List<int> _numXTiles; | |
List<int> _numYTiles; | |
int _numXLevels; | |
int _numYLevels; | |
int _bytesPerPixel; | |
int _maxBytesPerTileLine; | |
//int _tileBufferSize; | |
} | |
@internal | |
class InternalExrPart extends ExrPart { | |
InternalExrPart(bool tiled, InputBuffer input) : super(tiled, input); | |
List<Uint32List> get offsets => _offsets; | |
ExrCompressor get compressor => _compressor; | |
int get linesInBuffer => _linesInBuffer; | |
Uint32List get offsetInLineBuffer => _offsetInLineBuffer; | |
bool get tiled => _tiled; | |
int get tileWidth => _tileWidth; | |
int get tileHeight => _tileHeight; | |
List<int> get numXTiles => _numXTiles; | |
List<int> get numYTiles => _numYTiles; | |
int get numXLevels => _numXLevels; | |
int get numYLevels => _numYLevels; | |
void readOffsets(InputBuffer input) { | |
if (_tiled) { | |
for (int i = 0; i < _offsets.length; ++i) { | |
for (int j = 0; j < _offsets[i].length; ++j) { | |
_offsets[i][j] = input.readUint64(); | |
} | |
} | |
} else { | |
int numOffsets = _offsets[0].length; | |
for (int i = 0; i < numOffsets; ++i) { | |
_offsets[0][i] = input.readUint64(); | |
} | |
} | |
} | |
} |