imcodec 0.4.4
imcodec: ^0.4.4 copied to clipboard
Focused BMP, GIF, JPEG, JPEG XL, OpenEXR, 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, OpenEXR,
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 gif = img.encodeGif(
image,
options: const img.GifEncodeOptions(
colorCount: 64,
ditherAmount: 75,
),
); // one palette-indexed frame
final Uint8List jpeg = img.encodeJpg(
image,
options: const img.JpegEncodeOptions(quality: 90),
);
final Uint8List jpegXl = img.encodeJpegXl(image); // lossless Modular
final Uint8List quickJxl = img.encodeJpegXl(
image,
options: const img.JpegXlEncodeOptions(effort: img.JpegXlEffort.fast),
);
final Uint8List openExr = img.encodeOpenExr(image); // scene-linear half float
final Uint8List webp = img.encodeWebP(image); // lossless VP8L
final Uint8List lossyWebP = img.encodeWebP(
image,
options: const img.WebPEncodeOptions(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,
options: const img.PngDecodeOptions(maxPixels: 25_000_000),
);
Editors that must retain authored precision or process channels can use the
metadata-aware API. It keeps PNG/TIFF 16-bit samples, TIFF/OpenEXR 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. It also
retains opaque EXIF, IPTC-IIM, and XMP packets where their container has a
standard representation: EXIF and XMP in PNG and WebP, EXIF/IPTC/XMP in JPEG,
and IPTC/XMP in TIFF. Each descriptive packet is copied into immutable bytes
and bounded independently through maxDescriptiveMetadataBytes; Imcodec does
not reinterpret or rewrite the packet contents.
OpenEXR decoding additionally bounds its floating-point output and expanded
scan-line working blocks through maxDecodedBytes. Applications with an
existing extended-sRGB float buffer can call encodeOpenExrFloat32Rgba to
retain values above display white.
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,
options: const img.JpegEncodeOptions(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 typed
decode-options object to a format-specific helper when a lower limit is needed.
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 at
RasterDecodeOptions.defaultMaxPixels would occupy, and is checked from
container metadata before any pixel buffer is allocated.
The format classes can also be used through dart:convert:
const img.PngCodec codec = img.PngCodec();
final Uint8List encoded = codec.encoder.convert(
image,
encodeOptions: const img.PngEncodeOptions(level: 7),
);
final img.Image decoded = codec.decode(
encoded,
decodeOptions: const img.PngDecodeOptions(maxPixels: 25_000_000),
);
Each RasterCodec composes an immutable RasterEncoder and RasterDecoder.
Compression and allocation choices are immutable per-operation options. The
shared RasterDecodeOptions.defaultMaxPixels value (100 million) is used
unless a lower limit is supplied to a 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.
Built for Focale, an advanced local image editor. Discover what these packages make possible in a real creative workflow.
