The Floyd-Steinberg dithering strategy fails to produce a dithered output when the target palette is 1-bit (black & white). Instead of the expected error diffusion pattern, the resulting image shows a hard threshold "cut-off" effect, where gray areas are converted to solid black or white blocks.
Example
Screenshot of original dashboard:
Undithered 1-Bit PNG:
Dithered 1-Bit PNG (suggested fix applied):
Steps to Reproduce
Select Floyd-Steinberg as the dithering method.
Select a 1-bit or 2-color grayscale palette.
Observe the output image: it lacks the characteristic "dotted" appearance of dithering in transition areas (gradients or light gray backgrounds).
Root Cause Analysis
The issue lies in the FloydSteinbergStrategy.ts implementation. For the colors === 2 case, the code uses either .out('-monochrome') or .out('-type', 'Bilevel') immediately after the dither command.
In ImageMagick, both -monochrome and -type Bilevel are "high-level" operators that often force a simple 50% thresholding algorithm, effectively discarding the error diffusion matrix calculated by the preceding -dither FloydSteinberg command.
Suggested Fix
The dithering should be handled by -colors 2 while keeping the image in a flexible colorspace during processing. The final bit-depth reduction is already handled by the configureOutputFormat utility later in the pipeline, so forcing Bilevel inside the strategy is counterproductive.
Modified Code in FloydSteinbergStrategy.ts:
if (colors === 2) {
// Binary (black & white) with error diffusion
return image.out('-dither', 'FloydSteinberg').out('-colors', '2') // instead of image.out('-dither', 'FloydSteinberg').out('-monochrome')
} else if (colors !== undefined && colors > 2) {
// Multi-level grayscale with error diffusion
return image
.out('-dither', 'FloydSteinberg')
.out('-colors', String(colors))
}
The Floyd-Steinberg dithering strategy fails to produce a dithered output when the target palette is 1-bit (black & white). Instead of the expected error diffusion pattern, the resulting image shows a hard threshold "cut-off" effect, where gray areas are converted to solid black or white blocks.
Example
Screenshot of original dashboard:
Undithered 1-Bit PNG:
Dithered 1-Bit PNG (suggested fix applied):
Steps to Reproduce
Select Floyd-Steinberg as the dithering method.
Select a 1-bit or 2-color grayscale palette.
Observe the output image: it lacks the characteristic "dotted" appearance of dithering in transition areas (gradients or light gray backgrounds).
Root Cause Analysis
The issue lies in the FloydSteinbergStrategy.ts implementation. For the colors === 2 case, the code uses either .out('-monochrome') or .out('-type', 'Bilevel') immediately after the dither command.
In ImageMagick, both -monochrome and -type Bilevel are "high-level" operators that often force a simple 50% thresholding algorithm, effectively discarding the error diffusion matrix calculated by the preceding -dither FloydSteinberg command.
Suggested Fix
The dithering should be handled by -colors 2 while keeping the image in a flexible colorspace during processing. The final bit-depth reduction is already handled by the configureOutputFormat utility later in the pipeline, so forcing Bilevel inside the strategy is counterproductive.
Modified Code in FloydSteinbergStrategy.ts: