Skip to content

Dithering broken for 1-bit / Grayscale-2 palettes #48

@Neverhaver

Description

@Neverhaver

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:

Image

Undithered 1-Bit PNG:

Image

Dithered 1-Bit PNG (suggested fix applied):

Image

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))
      }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions