fromRgbaBytes static method
Extracts a ColorPalette from raw RGBA pixel bytes.
Accepts the bytes and dimensions directly instead of a ui.Image, which
keeps the whole palette subsystem pure bytes: it works identically on
every platform, including web, and callers can produce the bytes from
any source — a decoder, a canvas snapshot, or a plain generator.
maxSamplePixels caps how many pixels are analyzed: larger images are
subsampled with a uniform stride, which preserves the dominant colors
while keeping extraction fast. region limits sampling to a normalized
sub-rectangle of the image (values 0..1).
Throws ArgumentError when the bounds are empty or the byte count
does not match width * height * 4.
Implementation
static Future<ColorPalette> fromRgbaBytes(
Uint8List rgbaBytes, {
required int width,
required int height,
int maxColors = 64,
int desiredColors = 16,
int maxSamplePixels = 128 * 128,
Rect? region,
PaletteWeights weights = const PaletteWeights(),
}) async {
if (width <= 0 || height <= 0) {
throw ArgumentError(
'width and height must be positive, got $width x $height',
);
}
final expected = width * height * 4;
if (rgbaBytes.length < expected) {
throw ArgumentError.value(
rgbaBytes,
'rgbaBytes',
'has ${rgbaBytes.length} bytes but $width*$height*4 = $expected '
'were required',
);
}
// Normalized sampling region (defaults to the full image).
final rect = (region ?? Rect.zero).normalize();
final x0 = (rect.left * width).floor().clamp(0, width - 1);
final x1 = (rect.right * width).ceil().clamp(1, width);
final y0 = (rect.top * height).floor().clamp(0, height - 1);
final y1 = (rect.bottom * height).ceil().clamp(1, height);
final regionWidth = x1 - x0;
final regionHeight = y1 - y0;
final regionPixelCount = regionWidth * regionHeight;
// Uniform stride so that the number of sampled pixels stays close to
// maxSamplePixels without ever exceeding the actual region size.
final stride = regionPixelCount <= maxSamplePixels
? 1
: (regionPixelCount / maxSamplePixels).ceil();
final pixels = <int>[];
for (var y = y0; y < y1; y += stride) {
for (var x = x0; x < x1; x += stride) {
final offset = (y * width + x) * 4;
final r = rgbaBytes[offset];
final g = rgbaBytes[offset + 1];
final b = rgbaBytes[offset + 2];
pixels.add(0xFF000000 | (r << 16) | (g << 8) | b);
}
}
return extract(
pixels,
maxColors: maxColors,
desiredColors: desiredColors,
weights: weights,
);
}