decode static method

Future<Image?> decode(
  1. UPdfDocument document,
  2. UPdfStream stream, {
  3. UPdfDict? resources,
  4. Color fillColor = const Color(0xFF000000),
})

Implementation

static Future<ui.Image?> decode(UPdfDocument document, UPdfStream stream, {UPdfDict? resources, Color fillColor = const Color(0xFF000000)}) async {
  try {
    final UPdfDict dict = stream.dict;
    final int width = ((await document.resolve(dict["Width"] ?? dict["W"])) as num?)?.toInt() ?? 0;
    final int height = ((await document.resolve(dict["Height"] ?? dict["H"])) as num?)?.toInt() ?? 0;
    if (width <= 0 || height <= 0 || width * height > 80000000) return null;
    final bool isMask = (await document.resolve(dict["ImageMask"] ?? dict["IM"])) as bool? ?? false;
    final int bits = isMask ? 1 : (((await document.resolve(dict["BitsPerComponent"] ?? dict["BPC"])) as num?)?.toInt() ?? 8);
    final UPdfDecodedStream decoded = await document.decodeStream(stream);
    final List<double> decodeArray = <double>[];
    final Object? decodeObject = await document.resolve(dict["Decode"] ?? dict["D"]);
    if (decodeObject is List<Object?>) {
      for (final Object? entry in decodeObject) {
        final Object? value = await document.resolve(entry);
        if (value is num) decodeArray.add(value.toDouble());
      }
    }
    Uint8List samples = decoded.bytes;
    UPdfColorSpace space = isMask ? UPdfColorSpace.deviceGray : await UPdfColorSpace.load(document, dict["ColorSpace"] ?? dict["CS"], resources: resources);
    final String? filter = decoded.imageFilter;
    if (filter == "DCTDecode" || filter == "DCT" || filter == "JPXDecode") {
      final ui.Image? direct = await fromEncoded(samples);
      if (direct != null) return await _applyMask(document, direct, dict, resources);
      if (filter == "JPXDecode") return null;
      return null;
    }
    if (filter == "JBIG2Decode") {
      Uint8List? globals;
      final UPdfDict? parms = decoded.imageParms;
      final Object? globalsObject = await document.resolve(parms?["JBIG2Globals"]);
      if (globalsObject is UPdfStream) globals = (await document.decodeStream(globalsObject)).bytes;
      final Uint8List? packed = UJbig2.decode(samples, width: width, height: height, globals: globals);
      if (packed == null) return null;
      samples = packed;
      space = UPdfColorSpace.deviceGray;
    }
    if (filter == "CCITTFaxDecode" || filter == "CCF") {
      final UPdfDict? parms = decoded.imageParms;
      final int k = ((await document.resolve(parms?["K"])) as num?)?.toInt() ?? 0;
      final int columns = ((await document.resolve(parms?["Columns"])) as num?)?.toInt() ?? 1728;
      final int rowCount = ((await document.resolve(parms?["Rows"])) as num?)?.toInt() ?? height;
      final bool blackIs1 = (await document.resolve(parms?["BlackIs1"])) as bool? ?? false;
      final bool align = (await document.resolve(parms?["EncodedByteAlign"])) as bool? ?? false;
      samples = UPdfCcittDecoder(columns: columns, rows: rowCount, k: k, blackIs1: blackIs1, byteAlign: align).decode(samples);
      space = UPdfColorSpace.deviceGray;
    }
    final int componentCount = isMask ? 1 : space.components;
    final int maxValue = (1 << bits) - 1;
    final int rowBits = width * componentCount * bits;
    final int rowBytes = (rowBits + 7) >> 3;
    final Uint8List rgba = Uint8List(width * height * 4);
    final bool indexed = space.family == "Indexed";
    final List<double> values = List<double>.filled(componentCount, 0);
    final Map<int, int> cache = <int, int>{};
    for (int y = 0; y < height; y++) {
      final int rowStart = y * rowBytes;
      if (rowStart >= samples.length) break;
      for (int x = 0; x < width; x++) {
        int key = 0;
        for (int c = 0; c < componentCount; c++) {
          final int bitOffset = (x * componentCount + c) * bits;
          int raw = 0;
          if (bits == 8) {
            final int index = rowStart + (bitOffset >> 3);
            raw = index < samples.length ? samples[index] : 0;
          } else if (bits == 16) {
            final int index = rowStart + (bitOffset >> 3);
            raw = index + 1 < samples.length ? samples[index] : 0;
          } else {
            for (int b = 0; b < bits; b++) {
              final int bit = bitOffset + b;
              final int index = rowStart + (bit >> 3);
              final int value = index < samples.length ? (samples[index] >> (7 - (bit & 7))) & 1 : 0;
              raw = (raw << 1) | value;
            }
          }
          if (bits == 16) raw = raw & 0xFF;
          double normalized = indexed ? raw.toDouble() : raw / (bits == 16 ? 255 : maxValue);
          if (decodeArray.length >= (c + 1) * 2) {
            final double low = decodeArray[c * 2];
            final double high = decodeArray[c * 2 + 1];
            normalized = indexed ? low + raw * (high - low) / maxValue : low + normalized * (high - low);
          }
          values[c] = normalized;
          key = (key << 8) | (raw & 0xFF);
        }
        final int offset = (y * width + x) * 4;
        if (isMask) {
          final bool paint = values[0] < 0.5;
          rgba[offset] = (fillColor.r * 255).round();
          rgba[offset + 1] = (fillColor.g * 255).round();
          rgba[offset + 2] = (fillColor.b * 255).round();
          rgba[offset + 3] = paint ? 255 : 0;
          continue;
        }
        final int? cached = componentCount <= 2 ? cache[key] : null;
        final int argb = cached ?? space.color(values).toARGB32();
        if (componentCount <= 2 && cached == null) cache[key] = argb;
        rgba[offset] = (argb >> 16) & 0xFF;
        rgba[offset + 1] = (argb >> 8) & 0xFF;
        rgba[offset + 2] = argb & 0xFF;
        rgba[offset + 3] = 255;
      }
    }
    await _applyAlpha(document, rgba, width, height, dict, resources);
    return await fromPixels(rgba, width, height);
  } on Object {
    return null;
  }
}