startDecode method

  1. @override
DecodeInfo? startDecode(
  1. List<int> data
)
override

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 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':
        if (_info == null) {
          _info = InternalPngInfo();
        }

        final txtData = _input.readBytes(chunkSize).toUint8List();
        for (var i = 0, l = txtData.length; i < l; ++i) {
          if (txtData[i] == 0) {
            var key = latin1.decode(txtData.sublist(0, i));
            var 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 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 '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 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;
}