encode method

  1. @override
Uint8List encode(
  1. Image image, {
  2. bool singleFrame = false,
})
override

Encode an image to an image format. If singleFrame is true, only the one Image will be encoded; otherwise if image has animation, all frames of the image will be encoded if the encoder supports animation.

Implementation

@override
Uint8List encode(Image image, {bool singleFrame = false}) {
  final out = OutputBuffer();

  final nc = image.numChannels;
  var palette = image.palette;
  final format = image.format;

  if (format == Format.uint1 && nc == 1 && palette == null) {
    // add palette
    palette = PaletteUint8(2, 3)
      ..setRgb(0, 0, 0, 0)
      ..setRgb(1, 255, 255, 255);
  } else if (format == Format.uint1 && nc == 2) {
    // => uint2 palette
    image = image.convert(
        format: Format.uint2, numChannels: 1, withPalette: true);
    palette = image.palette;
  } else if (format == Format.uint1 && nc == 3 && palette == null) {
    // => uint4 palette
    image = image.convert(format: Format.uint4, withPalette: true);
    palette = image.palette;
  } else if (format == Format.uint1 && nc == 4) {
    // => uint8,4 - only 32bpp supports alpha
    image = image.convert(format: Format.uint8, numChannels: 4);
  } else if (format == Format.uint2 && nc == 1 && palette == null) {
    // => uint2 palette
    image = image.convert(format: Format.uint2, withPalette: true);
    palette = image.palette;
  } else if (format == Format.uint2 && nc == 2) {
    // => uint8 palette
    image = image.convert(format: Format.uint8, withPalette: true);
    palette = image.palette;
  } else if (format == Format.uint2 && nc == 3 && palette == null) {
    // => uint8 palette
    image = image.convert(format: Format.uint8, withPalette: true);
    palette = image.palette;
  } else if (format == Format.uint2 && nc == 4) {
    // => uint8 palette
    image = image.convert(format: Format.uint8, withPalette: true);
    palette = image.palette;
  } else if (format == Format.uint4 && nc == 1 && palette == null) {
    // => uint8 palette
    image = image.convert(format: Format.uint8, withPalette: true);
    palette = image.palette;
  } else if (format == Format.uint4 && nc == 2) {
    // => uint8,3
    image = image.convert(format: Format.uint8, numChannels: 3);
  } else if (format == Format.uint4 && nc == 3 && palette == null) {
    // => uint8,3
    image = image.convert(format: Format.uint8, numChannels: 3);
  } else if (format == Format.uint4 && nc == 4) {
    // => uint8,4
    image = image.convert(format: Format.uint8, numChannels: 4);
  } else if (format == Format.uint8 && nc == 1 && palette == null) {
    // => uint8 palette
    image = image.convert(format: Format.uint8, withPalette: true);
  } else if (format == Format.uint8 && nc == 2) {
    // => uint8,3
    image.convert(format: Format.uint8, numChannels: 3);
  } else if (image.isHdrFormat) {
    // => uint8,[3,4]
    image = image.convert(format: Format.uint8);
  } else if (image.hasPalette && image.numChannels == 4) {
    image = image.convert(numChannels: 4);
  }

  var bpp = image.bitsPerChannel * image.data!.numChannels;
  if (bpp == 12) {
    bpp = 16;
  }

  final compression =
      bpp > 8 ? BmpCompression.bitfields : BmpCompression.none;

  final imageStride = image.rowStride;
  final fileStride = ((image.width * bpp + 31) ~/ 32) * 4;
  final rowPaddingSize = fileStride - imageStride;
  final rowPadding =
      rowPaddingSize > 0 ? List<int>.filled(rowPaddingSize, 0xff) : null;

  final imageFileSize = fileStride * image.height;
  final headerInfoSize = bpp > 8 ? 124 : 40;
  final headerSize = headerInfoSize + 14;
  final paletteSize = (image.palette?.numColors ?? 0) * 4;
  final origImageOffset = headerSize + paletteSize;
  final imageOffset = origImageOffset;
  //final imageOffset = _roundToMultiple(origImageOffset);
  final gapSize = imageOffset - origImageOffset;
  final fileSize = imageFileSize + headerSize + paletteSize + gapSize;

  const sRgb = 0x73524742;

  out
    ..writeUint16(BmpFileHeader.signature)
    ..writeUint32(fileSize)
    ..writeUint32(0) // reserved
    ..writeUint32(imageOffset) // offset to image data
    ..writeUint32(headerInfoSize)
    ..writeUint32(image.width)
    ..writeUint32(image.height)
    ..writeUint16(1) // planes
    ..writeUint16(bpp) // bits per pixel
    ..writeUint32(compression.index) // compression
    ..writeUint32(imageFileSize)
    ..writeUint32(11811) // hr
    ..writeUint32(11811) // vr
    ..writeUint32(bpp == 8 ? 255 : 0) // totalColors
    ..writeUint32(bpp == 8 ? 255 : 0); // importantColors

  if (bpp > 8) {
    final blueMask = bpp == 16 ? 0xf : 0xff;
    final greenMask = bpp == 16 ? 0xf0 : 0xff00;
    final redMask = bpp == 16 ? 0xf00 : 0xff0000;
    final alphaMask = bpp == 16 ? 0xf000 : 0xff000000;

    out
      ..writeUint32(redMask) // redMask
      ..writeUint32(greenMask) // greenMask
      ..writeUint32(blueMask) // blueMask
      ..writeUint32(alphaMask) // alphaMask
      ..writeUint32(sRgb) // CSType
      ..writeUint32(0) // endpoints.red.x
      ..writeUint32(0) // endpoints.red.y
      ..writeUint32(0) // endpoints.red.z
      ..writeUint32(0) // endpoints.green.x
      ..writeUint32(0) // endpoints.green.y
      ..writeUint32(0) // endpoints.green.z
      ..writeUint32(0) // endpoints.blue.x
      ..writeUint32(0) // endpoints.blue.y
      ..writeUint32(0) // endpoints.blue.z
      ..writeUint32(0) // gammaRed
      ..writeUint32(0) // gammaGreen
      ..writeUint32(0) // gammaBlue
      ..writeUint32(2) // intent LCS_GM_GRAPHICS
      ..writeUint32(0) // profileData
      ..writeUint32(0) // profileSize
      ..writeUint32(0); // reserved
  }

  if (bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8) {
    if (palette != null) {
      //final palette = image.palette!;
      final l = palette.numColors;
      for (var pi = 0; pi < l; ++pi) {
        out
          ..writeByte(palette.getBlue(pi).toInt())
          ..writeByte(palette.getGreen(pi).toInt())
          ..writeByte(palette.getRed(pi).toInt())
          ..writeByte(0);
      }
    } else {
      if (bpp == 1) {
        out
          ..writeByte(0)
          ..writeByte(0)
          ..writeByte(0)
          ..writeByte(0)
          ..writeByte(255)
          ..writeByte(255)
          ..writeByte(255)
          ..writeByte(0);
      } else if (bpp == 2) {
        for (var pi = 0; pi < 4; ++pi) {
          final v = pi * 85;
          out
            ..writeByte(v)
            ..writeByte(v)
            ..writeByte(v)
            ..writeByte(0);
        }
      } else if (bpp == 4) {
        for (var pi = 0; pi < 16; ++pi) {
          final v = pi * 17;
          out
            ..writeByte(v)
            ..writeByte(v)
            ..writeByte(v)
            ..writeByte(0);
        }
      } else if (bpp == 8) {
        for (var pi = 0; pi < 256; ++pi) {
          out
            ..writeByte(pi)
            ..writeByte(pi)
            ..writeByte(pi)
            ..writeByte(0);
        }
      }
    }
  }

  // image data must be aligned to a 4 byte alignment. Pad the remaining
  // bytes until the image starts.
  var gap1 = gapSize;
  while (gap1-- > 0) {
    out.writeByte(0);
  }

  // Write image data
  if (bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8) {
    var offset = image.lengthInBytes - imageStride;
    final h = image.height;
    for (var y = 0; y < h; ++y) {
      final bytes = Uint8List.view(image.buffer, offset, imageStride);

      if (bpp == 1) {
        out.writeBytes(bytes);
      } else if (bpp == 2) {
        final l = bytes.length;
        for (var xi = 0; xi < l; ++xi) {
          final b = bytes[xi];
          final left = b >> 4;
          final right = b & 0x0f;
          final rb = (right << 4) | left;
          out.writeByte(rb);
        }
      } else if (bpp == 4) {
        final l = bytes.length;
        for (var xi = 0; xi < l; ++xi) {
          final b = bytes[xi];
          final b1 = b >> 4;
          final b2 = b & 0x0f;
          final rb = (b1 << 4) | b2;
          out.writeByte(rb);
        }
      } else {
        out.writeBytes(bytes);
      }

      if (rowPadding != null) {
        out.writeBytes(rowPadding);
      }

      offset -= imageStride;
    }

    return out.getBytes();
  }

  final hasAlpha = image.numChannels == 4;
  final h = image.height;
  final w = image.width;
  if (bpp == 16) {
    Pixel? p;
    for (var y = h - 1; y >= 0; --y) {
      p = image.getPixel(0, y, p);
      for (var x = 0; x < w; ++x) {
        out
          ..writeByte((p.g.toInt() << 4) | p.b.toInt())
          ..writeByte((p.a.toInt() << 4) | p.r.toInt());
        p.moveNext();
      }
      if (rowPadding != null) {
        out.writeBytes(rowPadding);
      }
    }
  } else {
    Pixel? p;
    for (var y = h - 1; y >= 0; --y) {
      p = image.getPixel(0, y, p);
      for (var x = 0; x < w; ++x) {
        out
          ..writeByte(p.b as int)
          ..writeByte(p.g as int)
          ..writeByte(p.r as int);
        if (hasAlpha) {
          out.writeByte(p.a as int);
        }
        p.moveNext();
      }
      if (rowPadding != null) {
        out.writeBytes(rowPadding);
      }
    }
  }

  return out.getBytes();
}