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(List<int> data) {
_input = InputBuffer(data, bigEndian: true);
final pngHeader = _input.readBytes(8);
const PNG_HEADER = [137, 80, 78, 71, 13, 10, 26, 10];
for (var i = 0; i < 8; ++i) {
if (pngHeader[i] != PNG_HEADER[i]) {
return null;
}
}
while (true) {
final inputPos = _input.position;
var chunkSize = _input.readUint32();
final chunkType = _input.readString(4);
switch (chunkType) {
case 'IHDR':
final hdr = InputBuffer.from(_input.readBytes(chunkSize));
final List<int> hdrBytes = hdr.toUint8List();
_info = InternalPngInfo();
_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 (![GRAYSCALE, RGB, INDEXED, GRAYSCALE_ALPHA, RGBA]
.contains(_info!.colorType)) {
return null;
}
if (_info!.filterMethod != 0) {
return null;
}
switch (_info!.colorType) {
case GRAYSCALE:
if (![1, 2, 4, 8, 16].contains(_info!.bits)) {
return null;
}
break;
case RGB:
if (![8, 16].contains(_info!.bits)) {
return null;
}
break;
case INDEXED:
if (![1, 2, 4, 8].contains(_info!.bits)) {
return null;
}
break;
case GRAYSCALE_ALPHA:
if (![8, 16].contains(_info!.bits)) {
return null;
}
break;
case 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 '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 PngFrame frame = InternalPngFrame();
_info!.frames.add(frame);
frame.sequenceNumber = _input.readUint32();
frame.width = _input.readUint32();
frame.height = _input.readUint32();
frame.xOffset = _input.readUint32();
frame.yOffset = _input.readUint32();
frame.delayNum = _input.readUint16();
frame.delayDen = _input.readUint16();
frame.dispose = _input.readByte();
frame.blend = _input.readByte();
_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 == 3) {
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]!;
_info!.backgroundColor = Color.fromRgb(r, g, b);
} else if (_info!.colorType == 0 || _info!.colorType == 4) {
/*int gray =*/ _input.readUint16();
chunkSize -= 2;
} else if (_info!.colorType == 2 || _info!.colorType == 6) {
/*int r =*/ _input.readUint16();
/*int g =*/
_input.readUint16();
/*int b =*/
_input.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:
_input.skip(chunkSize);
_input.skip(4); // CRC
break;
}
if (chunkType == 'IEND') {
break;
}
if (_input.isEOS) {
return null;
}
}
return _info;
}