startDecode method
Start decoding the data as an animation sequence, but don't actually process the frames until they are requested with decodeFrame.
Implementation
@override
DecodeInfo? startDecode(Uint8List data) {
_input = InputBuffer(data, bigEndian: true);
final pngHeader = _input.readBytes(8);
const expectedHeader = [137, 80, 78, 71, 13, 10, 26, 10];
for (var i = 0; i < 8; ++i) {
if (pngHeader[i] != expectedHeader[i]) {
return null;
}
}
while (true) {
final inputPos = _input.position;
var chunkSize = _input.readUint32();
final chunkType = _input.readString(4);
switch (chunkType) {
case 'tEXt':
final txtData = _input.readBytes(chunkSize).toUint8List();
final l = txtData.length;
for (var i = 0; i < l; ++i) {
if (txtData[i] == 0) {
final key = latin1.decode(txtData.sublist(0, i));
final text = latin1.decode(txtData.sublist(i + 1));
_info.textData[key] = text;
break;
}
}
_input.skip(4); //crc
break;
case 'IHDR':
final hdr = InputBuffer.from(_input.readBytes(chunkSize));
final Uint8List hdrBytes = hdr.toUint8List();
_info.width = hdr.readUint32();
_info.height = hdr.readUint32();
_info.bits = hdr.readByte();
_info.colorType = hdr.readByte();
_info.compressionMethod = hdr.readByte();
_info.filterMethod = hdr.readByte();
_info.interlaceMethod = hdr.readByte();
// Validate some of the info in the header to make sure we support
// the proposed image data.
if (!PngColorType.isValid(_info.colorType)) {
return null;
}
if (_info.filterMethod != 0) {
return null;
}
switch (_info.colorType) {
case PngColorType.grayscale:
if (![1, 2, 4, 8, 16].contains(_info.bits)) {
return null;
}
break;
case PngColorType.rgb:
if (![8, 16].contains(_info.bits)) {
return null;
}
break;
case PngColorType.indexed:
if (![1, 2, 4, 8].contains(_info.bits)) {
return null;
}
break;
case PngColorType.grayscaleAlpha:
if (![8, 16].contains(_info.bits)) {
return null;
}
break;
case PngColorType.rgba:
if (![8, 16].contains(_info.bits)) {
return null;
}
break;
}
final crc = _input.readUint32();
final computedCrc = _crc(chunkType, hdrBytes);
if (crc != computedCrc) {
throw ImageException('Invalid $chunkType checksum');
}
break;
case 'PLTE':
_info.palette = _input.readBytes(chunkSize).toUint8List();
final crc = _input.readUint32();
final computedCrc = _crc(chunkType, _info.palette as List<int>);
if (crc != computedCrc) {
throw ImageException('Invalid $chunkType checksum');
}
break;
case 'tRNS':
_info.transparency = _input.readBytes(chunkSize).toUint8List();
final crc = _input.readUint32();
final computedCrc = _crc(chunkType, _info.transparency!);
if (crc != computedCrc) {
throw ImageException('Invalid $chunkType checksum');
}
break;
case 'IEND':
// End of the image.
_input.skip(4); // CRC
break;
/*case 'eXif': // TODO: parse exif
{
final exifData = _input.readBytes(chunkSize);
final exif = ExifData.fromInputBuffer(exifData);
_input.skip(4); // CRC
break;
}*/
case 'gAMA':
if (chunkSize != 4) {
throw ImageException('Invalid gAMA chunk');
}
final gammaInt = _input.readUint32();
_input.skip(4); // CRC
// A gamma of 1.0 doesn't have any affect, so pretend we didn't get
// a gamma in that case.
if (gammaInt != 100000) {
_info.gamma = gammaInt / 100000.0;
}
break;
case 'IDAT':
_info.idat.add(inputPos);
_input.skip(chunkSize);
_input.skip(4); // CRC
break;
case 'acTL': // Animation control chunk
_info.numFrames = _input.readUint32();
_info.repeat = _input.readUint32();
_input.skip(4); // CRC
break;
case 'fcTL': // Frame control chunk
final frame = InternalPngFrame(
sequenceNumber: _input.readUint32(),
width: _input.readUint32(),
height: _input.readUint32(),
xOffset: _input.readUint32(),
yOffset: _input.readUint32(),
delayNum: _input.readUint16(),
delayDen: _input.readUint16(),
dispose: PngDisposeMode.values[_input.readByte()],
blend: PngBlendMode.values[_input.readByte()]);
_info.frames.add(frame);
_input.skip(4); // CRC
break;
case 'fdAT':
/*int sequenceNumber =*/ _input.readUint32();
final frame = _info.frames.last as InternalPngFrame;
frame.fdat.add(inputPos);
_input.skip(chunkSize - 4);
_input.skip(4); // CRC
break;
case 'bKGD':
if (_info.colorType == PngColorType.indexed) {
final paletteIndex = _input.readByte();
chunkSize--;
final p3 = paletteIndex * 3;
final r = _info.palette![p3]!;
final g = _info.palette![p3 + 1]!;
final b = _info.palette![p3 + 2]!;
if (_info.transparency != null) {
final isTransparent = _info.transparency!.contains(paletteIndex);
_info.backgroundColor =
ColorRgba8(r, g, b, isTransparent ? 0 : 255);
} else {
_info.backgroundColor = ColorRgb8(r, g, b);
}
} else if (_info.colorType == PngColorType.grayscale ||
_info.colorType == PngColorType.grayscaleAlpha) {
/*int gray =*/ _input.readUint16();
chunkSize -= 2;
} else if (_info.colorType == PngColorType.rgb ||
_info.colorType == PngColorType.rgba) {
/*int r =*/ _input
..readUint16()
/*int g =*/
..readUint16()
/*int b =*/
..readUint16();
chunkSize -= 24;
}
if (chunkSize > 0) {
_input.skip(chunkSize);
}
_input.skip(4); // CRC
break;
case 'iCCP':
_info.iccpName = _input.readString();
_info.iccpCompression = _input.readByte(); // 0: deflate
chunkSize -= _info.iccpName.length + 2;
final profile = _input.readBytes(chunkSize);
_info.iccpData = profile.toUint8List();
_input.skip(4); // CRC
break;
default:
//print('Skipping $chunkType');
_input.skip(chunkSize);
_input.skip(4); // CRC
break;
}
if (chunkType == 'IEND') {
break;
}
if (_input.isEOS) {
return null;
}
}
return _info;
}