imcodec 0.3.2 copy "imcodec: ^0.3.2" to clipboard
imcodec: ^0.3.2 copied to clipboard

Focused BMP, GIF, JPEG, JPEG XL, PNG, QOI, TGA, TIFF, and WebP codecs for RGBA images in Flutter.

Imcodec #

Imcodec is a focused Flutter image codec for BMP, GIF, JPEG, JPEG XL, PNG, QOI, TGA, TIFF, and WebP. It keeps a small straight-alpha RGBA image model and exposes synchronous pure-Dart encoders and decoders, which makes expensive conversions suitable for Isolate.run.

Note

Animated GIF, JPEG XL, PNG, and WebP inputs currently return their first visible frame. Decoding preserves straight alpha and hidden RGB values where the source format carries them.

Usage #

import 'dart:isolate';
import 'dart:typed_data';

import 'package:imcodec/imcodec.dart' as img;

Future<Uint8List> exportWebP(
  Uint8List straightRgba,
  int width,
  int height,
) => Isolate.run(() {
  final img.Image image = img.Image.fromBytes(
    width: width,
    height: height,
    bytes: straightRgba.buffer,
    bytesOffset: straightRgba.offsetInBytes,
    numChannels: 4,
    order: img.ChannelOrder.rgba,
  );
  return img.encodeWebP(image);
});

The encoder entry points mirror the subset used by almost any image editor:

final Uint8List png = img.encodePng(image);
final Uint8List png8 = img.encodePng8(
  image,
  options: const img.IndexedColorOptions(
    colorCount: 64,
    ditherAmount: 75,
  ),
);
final Uint8List gif = img.encodeGif(image); // one palette-indexed frame
final Uint8List jpeg = img.encodeJpg(image, quality: 90);
final Uint8List jpegXl = img.encodeJpegXl(image); // lossless Modular
final Uint8List quickJxl = img.encodeJpegXl(image, effort: img.JpegXlEffort.fast);
final Uint8List webp = img.encodeWebP(image); // lossless VP8L
final Uint8List lossyWebP = img.encodeWebP(image, quality: 82);
final Uint8List bmp = img.encodeBmp(image);
final Uint8List tga = img.encodeTga(image); // RLE by default
final Uint8List qoi = img.encodeQoi(image);
final Uint8List tiff = img.encodeTiff(image); // PackBits by default

Decode supported data with format detection or a format-specific function:

final img.Image decoded = img.decodeImage(encodedBytes);
final img.Image png = img.decodePng(pngBytes);

Editors that must retain authored precision or process channels can use the metadata-aware API. It keeps PNG/TIFF 16-bit samples, TIFF float32 samples, CMYK JPEG/TIFF channels, and embedded ICC payloads without changing the small RGBA8 Image API used by existing callers:

final img.DecodedImageMetadata? metadata = img.inspectImage(encodedBytes);
if (metadata?.requiresExactDecoding ?? false) {
  final img.DecodedImage decoded = img.decodeImageData(encodedBytes);
  // decoded.bytes contains straight RGB+A or CMYK+A samples in native depth.
}

Unsigned 16-bit and float32 samples in DecodedImage.bytes are little-endian. inspectImage bounds ICC decompression through maxIccProfileBytes.

Spreading encoding across isolates #

encodeWith is part of RasterCodec and RasterEncoder, so every codec accepts a runner and encodeImageWith dispatches on format just like encodeImage. Use runSequentially to run every task on the current isolate, onIsolates to run one isolate per job, or onBoundedIsolates to cap the number of concurrent isolates:

final Uint8List jpeg = await img.encodeJpgWith(
  img.onBoundedIsolates,
  image,
  quality: 90,
);

JPEG transforms MCU bands independently, JPEG XL spreads its modular groups and context work, PNG filters row bands independently, and lossless WebP selects and applies predictor-block bands independently. Lossy WebP currently encodes inline. JPEG, PNG, and WebP keep small images inline because isolate startup and byte transfer would cost more than the work saved. Their parallel output is byte-for-byte identical to synchronous output.

BMP, GIF, TGA, and TIFF are dominated by inexpensive byte shuffling, quantization, or run-length passes, while QOI carries state from every pixel to the next. Measurements show that moving their buffers between isolates is slower, so these formats accept a runner for API consistency but intentionally encode inline.

decodeImage defaults to a 100-million-pixel allocation limit. Supply a lower maxPixels value when input comes from an untrusted source.

maxPixels alone no longer bounds memory once samples are kept natively: a CMYK float32 pixel needs 20 bytes where an RGBA8 pixel needs 4. The metadata-aware functions therefore also take maxDecodedBytes, which defaults to the 400 MB an RGBA8 image of defaultMaxPixels pixels would occupy, and is checked from container metadata before any pixel buffer is allocated.

The format classes can also be used through dart:convert:

final img.PngCodec codec = img.PngCodec(level: 7);
final Uint8List encoded = codec.encoder.convert(image);
final img.Image decoded = codec.decoder.convert(encoded);

Each RasterCodec composes a RasterEncoder and a RasterDecoder. The shared defaultMaxPixels constant (100 million) is used unless a lower maxPixels limit is supplied to a codec or decoding helper.

Format behavior #

A documentation on the behavior and implementation of formats in available in docs/formats.md.

The JPEG and WebP encoder implementations contain code derived from the MIT licensed Dart image package.

The JPEG XL implementation is adapted from koni_jxl, released by Jonathan Urzúa under the MIT License. Its decoding logic includes work derived from the MIT licensed JXLatte project.

0
likes
0
points
969
downloads

Publisher

verified publisherfocale-editor.app

Weekly Downloads

Focused BMP, GIF, JPEG, JPEG XL, PNG, QOI, TGA, TIFF, and WebP codecs for RGBA images in Flutter.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, zcodec

More

Packages that depend on imcodec