startDecode method

  1. @override
DecodeInfo? startDecode(
  1. Uint8List 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(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;
}