ditherImage function
- Image image, {
- Quantizer? quantizer,
- DitherKernel kernel = DitherKernel.floydSteinberg,
- @Deprecated('Use scanOrder: DitherScanOrder.serpentine instead. ' 'This parameter will be removed in a future release.') bool serpentine = false,
- DitherScanOrder scanOrder = DitherScanOrder.zigzag,
- double bayerStrength = 1.0,
Dither an image to reduce banding patterns when reducing the number of colors. Derived from http://jsbin.com/iXofIji/2/edit
quantizer is the color reducer used to map each pixel to the palette;
if null a NeuralQuantizer is built from image.
kernel selects the dithering algorithm: error-diffusion kernels
(e.g. DitherKernel.floydSteinberg) propagate quantization error to
neighbors, while the ordered Bayer kernels (DitherKernel.bayer2x2,
DitherKernel.bayer4x4, DitherKernel.bayer8x8) and blue noise
(DitherKernel.blueNoise) use a fixed position-based threshold matrix.
scanOrder selects the order in which pixels are visited by the
error-diffusion kernels (DitherScanOrder.raster,
DitherScanOrder.serpentine, the diagonal DitherScanOrder.zigzag, or
the space-filling DitherScanOrder.hilbert curve).
It has no effect on the Bayer kernels.
bayerStrength scales the dither offset and is only used for the Bayer
kernels; it is ignored by the error-diffusion kernels.
Implementation
img.Image ditherImage(
img.Image image, {
img.Quantizer? quantizer,
DitherKernel kernel = DitherKernel.floydSteinberg,
@Deprecated(
'Use scanOrder: DitherScanOrder.serpentine instead. '
'This parameter will be removed in a future release.',
)
bool serpentine = false,
DitherScanOrder scanOrder = DitherScanOrder.zigzag,
double bayerStrength = 1.0,
}) {
quantizer ??= img.NeuralQuantizer(image);
if (kernel == DitherKernel.none) {
return quantizer.getIndexImage(image);
}
final orderedMatrix = _orderedDitherMatrix(kernel);
if (orderedMatrix != null) {
return ditherImageOrdered(image, quantizer, orderedMatrix, bayerStrength);
}
final order = serpentine
// ignore: deprecated_member_use_from_same_package
? DitherScanOrder.serpentine
: scanOrder;
final q = quantizer;
final ds = _errorDiffusionKernels[kernel]!;
final height = image.height;
final width = image.width;
final palette = quantizer.palette;
final indexedImage = img.Image(
width: width,
height: height,
numChannels: 1,
palette: palette,
);
final imageCopy = image.clone();
// Quantizes the pixel at [x],[y] and diffuses its error to the neighbors.
// [direction] is the horizontal scan direction (1 or -1) and controls the
// order in which the kernel taps are applied.
void diffusePixel(int x, int y, int direction) {
// Get original color
final pc = imageCopy.getPixel(x, y);
final r1 = pc[0].toInt();
final g1 = pc[1].toInt();
final b1 = pc[2].toInt();
// Get converted color
final idx = q.getColorIndexRgb(r1, g1, b1);
indexedImage.setPixelIndex(x, y, idx);
final r2 = palette.get(idx, 0);
final g2 = palette.get(idx, 1);
final b2 = palette.get(idx, 2);
final er = r1 - r2;
final eg = g1 - g2;
final eb = b1 - b2;
if (er == 0 && eg == 0 && eb == 0) {
return;
}
final i0 = direction == 1 ? 0 : ds.length - 1;
final i1 = direction == 1 ? ds.length : 0;
for (var i = i0; i != i1; i += direction) {
final x1 = ds[i][1].toInt();
final y1 = ds[i][2].toInt();
if ((x1 + x) >= 0 &&
(x1 + x) < width &&
(y1 + y) >= 0 &&
(y1 + y) < height) {
final d = ds[i][0];
final nx = x + x1;
final ny = y + y1;
final p2 = imageCopy.getPixel(nx, ny);
p2
..r = p2.r + er * d
..g = p2.g + eg * d
..b = p2.b + eb * d;
}
}
}
if (order == DitherScanOrder.zigzag) {
// Walk the anti-diagonals x + y == d, alternating their direction.
final numDiagonals = width + height - 1;
for (var d = 0; d < numDiagonals; d++) {
final xMin = d < height ? 0 : d - height + 1;
final xMax = d < width ? d : width - 1;
if (d.isEven) {
for (var x = xMin; x <= xMax; x++) {
diffusePixel(x, d - x, 1);
}
} else {
for (var x = xMax; x >= xMin; x--) {
diffusePixel(x, d - x, 1);
}
}
}
return indexedImage;
}
if (order == DitherScanOrder.hilbert) {
// Walk pixels in Hilbert space-filling curve order.
// The curve is defined on a power-of-2 square; pixels outside the image
// bounds are simply skipped.
final maxDim = max(width, height);
var n = 1;
while (n < maxDim) {
n <<= 1;
}
final xy = [0, 0];
final total = n * n;
for (var d = 0; d < total; d++) {
_hilbertDtoXY(n, d, xy);
if (xy[0] < width && xy[1] < height) {
diffusePixel(xy[0], xy[1], 1);
}
}
return indexedImage;
}
final isSerpentine = order == DitherScanOrder.serpentine;
var direction = isSerpentine ? -1 : 1;
for (var y = 0; y < height; y++) {
if (isSerpentine) {
direction = direction * -1;
}
final x0 = direction == 1 ? 0 : width - 1;
final x1 = direction == 1 ? width : 0;
for (var x = x0; x != x1; x += direction) {
diffusePixel(x, y, direction);
}
}
return indexedImage;
}