encode static method
Encodes an image into Sixel escape sequences.
The image is first quantized to at most maxColors colors using the
image package's built-in quantizer, producing a high-quality palette
via octree quantization. This avoids the naive first-come-first-served
palette allocation that silently degrades color accuracy when an image
has more than 256 unique colors.
image is the image to encode.
maxColors is the maximum palette size (1–256, default 256).
Returns a string containing the complete Sixel escape sequence.
Implementation
static String encode(img.Image image, {int maxColors = maxPaletteSize}) {
if (maxColors < 1 || maxColors > maxPaletteSize) {
throw ArgumentError('maxColors must be between 1 and $maxPaletteSize');
}
final width = image.width;
final height = image.height;
if (width <= 0 || height <= 0) return '';
// Quantize the image to a limited palette. This uses octree quantization
// which produces much better results than the previous first-come-first-
// served approach that silently mapped overflow colors to register 0.
final quantized = img.quantize(
image,
numberOfColors: maxColors,
method: img.QuantizeMethod.octree,
);
// Build the palette and per-pixel index map from the quantized image.
final palette = <int, _RgbColor>{};
final indexedPixels = List<int>.filled(width * height, 0);
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
final pixel = quantized.getPixel(x, y);
final r = pixel.r.toInt();
final g = pixel.g.toInt();
final b = pixel.b.toInt();
// Pack RGB into a single int for palette lookup.
final packed = (r << 16) | (g << 8) | b;
if (!palette.containsKey(packed)) {
palette[packed] = _RgbColor(r, g, b);
}
indexedPixels[y * width + x] = packed;
}
}
// Assign sequential register indices to each unique color.
final registerMap = <int, int>{};
var nextRegister = 0;
for (final packed in palette.keys) {
registerMap[packed] = nextRegister++;
}
// Map each pixel to its register index.
final pixelRegisters = List<int>.filled(width * height, 0);
for (var i = 0; i < indexedPixels.length; i++) {
pixelRegisters[i] = registerMap[indexedPixels[i]] ?? 0;
}
final buffer = StringBuffer();
// DCS (Device Control String) start with Sixel introducer.
buffer.write('\x1bPq');
// Write color definitions: #<register>;2;<r%>;<g%>;<b%>
// RGB values are scaled from 0–255 to 0–100 percentages.
for (final entry in palette.entries) {
final reg = registerMap[entry.key]!;
final color = entry.value;
final rPct = (color.r * 100 / 255).round();
final gPct = (color.g * 100 / 255).round();
final bPct = (color.b * 100 / 255).round();
buffer.write('#$reg;2;$rPct;$gPct;$bPct');
}
// Encode image data in 6-row strips (bands).
_encodeImageData(buffer, pixelRegisters, width, height, nextRegister);
// String Terminator.
buffer.write('\x1b\\');
return buffer.toString();
}