Skip to content

Keep the adaptive threshold window non-empty for regions narrower than 16 pixels - #3229

Open
jafin wants to merge 1 commit into
SixLabors:mainfrom
jafin:jf/adaptive-threshold-narrow-regions
Open

jafin wants to merge 1 commit into
SixLabors:mainfrom
jafin:jf/adaptive-threshold-narrow-regions

Conversation

@jafin

@jafin jafin commented Oct 17, 2026 •

Copy link
Copy Markdown

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

Problem. AdaptiveThresholdProcessor sets the cluster size to the processed region's width divided by 16, truncated. For a region narrower than 16 pixels, the cluster size is 0. The averaging window is the integral-image range (x1, x2] x (y1, y2], and with a cluster size of 0 every window is empty (count == 0). The comparison luminance * 0 <= sum * limit is then always true, so every pixel takes the lower color. For example, a light 8x8 image with one dark pixel comes out all black. The same happens when a narrow rectangle is passed to AdaptiveThreshold(upper, lower, rectangle).

Change. The cluster size now has a minimum of 1, so regions narrower than 16 pixels use the same window as regions 16 to 31 pixels wide. Regions 16 pixels and wider get the same cluster size as before, so their output does not change. All existing exact-match reference outputs still pass.

Known limitation (not changed). When the cluster size is 1, the window for the last column and the last row is still empty, so those pixels always take the lower color. Regions 16 to 31 pixels wide already behave this way. The 30x30 Issue_2217 exact reference output depends on this behavior, so changing it would mean regenerating that reference image. A region only 1 pixel wide or tall also still gets an empty window.

Testing.

  • New tests in AdaptiveThresholdTests: AdaptiveThreshold_NarrowImage_SeparatesDarkPixel (3x3, 8x8, 15x15, 15x3) and AdaptiveThreshold_NarrowRectangle_SeparatesDarkPixel (an 8x50 rectangle in a 100x100 image). Each test fills the region with a light color, adds one dark pixel, and checks that only the dark pixel turns black. All five cases fail without the fix and pass with it.
  • Release build, net10.0: the Binarization, OilPaint, Transforms and Drawing/DrawImage tests all pass (752 passed, 4 skipped). Across the whole Processing, Drawing and ImageTests namespaces, 1595 passed, 2 were skipped and 1 failed. The failure was ImageTests.SaveAsync.SetEncoding, an IOException because ImageTests.Save.SetEncoding was writing the same SetEncoding.dat output file at the same time. Both tests pass when run on their own, and this change does not touch that code.

Fixes #3241

…n 16 pixels

The cluster size is the region width divided by 16, truncated, so it was 0
for regions narrower than 16 pixels. Every averaging window was then empty
and each pixel took the lower color. Use a cluster size of at least 1, as
regions 16 to 31 pixels wide already do. Wider regions are unchanged.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AdaptiveThreshold turns every pixel black when the image or rectangle is narrower than 16 pixels

1 participant