diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs
index af3a336a4..68c1474be 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs
@@ -17,14 +17,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images.
/// Indicating whether to clip the histogram bins at a specific value.
- /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
/// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100.
public AdaptiveHistogramEqualizationProcessor(
int luminanceLevels,
bool clipHistogram,
- float clipLimitPercentage,
+ int clipLimit,
int numberOfTiles)
- : base(luminanceLevels, clipHistogram, clipLimitPercentage)
+ : base(luminanceLevels, clipHistogram, clipLimit)
{
this.NumberOfTiles = numberOfTiles;
}
@@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
return new AdaptiveHistogramEqualizationProcessor(
this.LuminanceLevels,
this.ClipHistogram,
- this.ClipLimitPercentage,
+ this.ClipLimit,
this.NumberOfTiles,
source,
sourceRectangle);
diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs
index 4cda4030f..e3960035e 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs
@@ -30,18 +30,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images.
/// Indicating whether to clip the histogram bins at a specific value.
- /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
/// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100.
/// The source for the current processor instance.
/// The source area to process for the current processor instance.
public AdaptiveHistogramEqualizationProcessor(
int luminanceLevels,
bool clipHistogram,
- float clipLimitPercentage,
+ int clipLimit,
int tiles,
Image source,
Rectangle sourceRectangle)
- : base(luminanceLevels, clipHistogram, clipLimitPercentage, source, sourceRectangle)
+ : base(luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle)
{
Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles));
Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles));
@@ -512,7 +512,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
if (processor.ClipHistogramEnabled)
{
- processor.ClipHistogram(histogram, processor.ClipLimitPercentage, this.pixelsInTile);
+ processor.ClipHistogram(histogram, processor.ClipLimit);
}
Unsafe.Add(ref cdfMinBase, cdfX) = processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1);
diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs
index 3ff001c52..632cfcd59 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs
@@ -16,14 +16,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images.
/// Indicating whether to clip the histogram bins at a specific value.
- /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
/// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100.
public AdaptiveHistogramEqualizationSlidingWindowProcessor(
int luminanceLevels,
bool clipHistogram,
- float clipLimitPercentage,
+ int clipLimit,
int numberOfTiles)
- : base(luminanceLevels, clipHistogram, clipLimitPercentage)
+ : base(luminanceLevels, clipHistogram, clipLimit)
{
this.NumberOfTiles = numberOfTiles;
}
@@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
return new AdaptiveHistogramEqualizationSlidingWindowProcessor(
this.LuminanceLevels,
this.ClipHistogram,
- this.ClipLimitPercentage,
+ this.ClipLimit,
this.NumberOfTiles,
source,
sourceRectangle);
diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
index 24ac5ccef..f2f11cbfe 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
@@ -29,18 +29,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images.
/// Indicating whether to clip the histogram bins at a specific value.
- /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
/// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100.
/// The source for the current processor instance.
/// The source area to process for the current processor instance.
public AdaptiveHistogramEqualizationSlidingWindowProcessor(
int luminanceLevels,
bool clipHistogram,
- float clipLimitPercentage,
+ int clipLimit,
int tiles,
Image source,
Rectangle sourceRectangle)
- : base(luminanceLevels, clipHistogram, clipLimitPercentage, source, sourceRectangle)
+ : base(luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle)
{
Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles));
Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles));
@@ -210,7 +210,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
// Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration.
histogram.CopyTo(histogramCopy);
- this.ClipHistogram(histogramCopy, this.ClipLimitPercentage, swInfos.PixelInTile);
+ this.ClipHistogram(histogramCopy, this.ClipLimit);
}
// Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value.
diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs
index dab101fcc..0666b21bf 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs
@@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
///
/// The number of luminance levels.
/// A value indicating whether to clip the histogram bins at a specific value.
- /// The histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
- public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage)
- : base(luminanceLevels, clipHistogram, clipLimitPercentage)
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
+ public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit)
+ : base(luminanceLevels, clipHistogram, clipLimit)
{
}
@@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
return new GlobalHistogramEqualizationProcessor(
this.LuminanceLevels,
this.ClipHistogram,
- this.ClipLimitPercentage,
+ this.ClipLimit,
source,
sourceRectangle);
}
diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs
index 6ae688247..8aaa5403d 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs
@@ -31,16 +31,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// or 65536 for 16-bit grayscale images.
///
/// Indicating whether to clip the histogram bins at a specific value.
- /// Histogram clip limit in percent of the total pixels. Histogram bins which exceed this limit, will be capped at this value.
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
/// The source for the current processor instance.
/// The source area to process for the current processor instance.
public GlobalHistogramEqualizationProcessor(
int luminanceLevels,
bool clipHistogram,
- float clipLimitPercentage,
+ int clipLimit,
Image source,
Rectangle sourceRectangle)
- : base(luminanceLevels, clipHistogram, clipLimitPercentage, source, sourceRectangle)
+ : base(luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle)
{
}
@@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
Span histogram = histogramBuffer.GetSpan();
if (this.ClipHistogramEnabled)
{
- this.ClipHistogram(histogram, this.ClipLimitPercentage, numberOfPixels);
+ this.ClipHistogram(histogram, this.ClipLimit);
}
// Calculate the cumulative distribution function, which will map each input pixel to a new value.
diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs
index 8ddb4834d..b55b725a6 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs
@@ -20,7 +20,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
///
/// Gets or sets the number of different luminance levels. Typical values are 256 for 8-bit grayscale images
- /// or 65536 for 16-bit grayscale images. Defaults to 256.
+ /// or 65536 for 16-bit grayscale images.
+ /// Defaults to 256.
///
public int LuminanceLevels { get; set; } = 256;
@@ -32,14 +33,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public bool ClipHistogram { get; set; } = false;
///
- /// Gets or sets the histogram clip limit in percent of the total pixels in a tile. Histogram bins which exceed this limit, will be capped at this value.
- /// Defaults to 0.035f.
+ /// Gets or sets the histogram clip limit. Adaptive histogram equalization may cause noise to be amplified in near constant
+ /// regions. To reduce this problem, histogram bins which exceed a given limit will be capped at this value. The exceeding values
+ /// will be redistributed equally to all other bins. The clipLimit depends on the size of the tiles the image is split into
+ /// and therefore the image size itself.
+ /// Defaults to 350.
///
- public float ClipLimitPercentage { get; set; } = 0.035f;
+ /// For more information, see also: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE
+ public int ClipLimit { get; set; } = 350;
///
- /// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. Defaults to 10.
+ /// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization.
+ /// Defaults to 8.
///
- public int NumberOfTiles { get; set; } = 10;
+ public int NumberOfTiles { get; set; } = 8;
}
}
diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
index 01a687ac5..4273e9375 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
@@ -17,12 +17,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images.
/// Indicates, if histogram bins should be clipped.
- /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
- protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage)
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
+ protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit)
{
this.LuminanceLevels = luminanceLevels;
this.ClipHistogram = clipHistogram;
- this.ClipLimitPercentage = clipLimitPercentage;
+ this.ClipLimit = clipLimit;
}
///
@@ -36,9 +36,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public bool ClipHistogram { get; }
///
- /// Gets the histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
+ /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
///
- public float ClipLimitPercentage { get; }
+ public int ClipLimit { get; }
///
public abstract IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle)
@@ -60,14 +60,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
processor = new GlobalHistogramEqualizationProcessor(
options.LuminanceLevels,
options.ClipHistogram,
- options.ClipLimitPercentage);
+ options.ClipLimit);
break;
case HistogramEqualizationMethod.AdaptiveTileInterpolation:
processor = new AdaptiveHistogramEqualizationProcessor(
options.LuminanceLevels,
options.ClipHistogram,
- options.ClipLimitPercentage,
+ options.ClipLimit,
options.NumberOfTiles);
break;
@@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
processor = new AdaptiveHistogramEqualizationSlidingWindowProcessor(
options.LuminanceLevels,
options.ClipHistogram,
- options.ClipLimitPercentage,
+ options.ClipLimit,
options.NumberOfTiles);
break;
@@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
processor = new GlobalHistogramEqualizationProcessor(
options.LuminanceLevels,
options.ClipHistogram,
- options.ClipLimitPercentage);
+ options.ClipLimit);
break;
}
diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
index f8515ece6..6e4c16de7 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
@@ -26,24 +26,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images.
/// Indicates, if histogram bins should be clipped.
- /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
/// The source for the current processor instance.
/// The source area to process for the current processor instance.
protected HistogramEqualizationProcessor(
int luminanceLevels,
bool clipHistogram,
- float clipLimitPercentage,
+ int clipLimit,
Image source,
Rectangle sourceRectangle)
: base(source, sourceRectangle)
{
Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels));
- Guard.MustBeGreaterThan(clipLimitPercentage, 0F, nameof(clipLimitPercentage));
+ Guard.MustBeGreaterThan(clipLimit, 1, nameof(clipLimit));
this.LuminanceLevels = luminanceLevels;
this.luminanceLevelsFloat = luminanceLevels;
this.ClipHistogramEnabled = clipHistogram;
- this.ClipLimitPercentage = clipLimitPercentage;
+ this.ClipLimit = clipLimit;
}
///
@@ -57,9 +57,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public bool ClipHistogramEnabled { get; }
///
- /// Gets the histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
+ /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
///
- public float ClipLimitPercentage { get; }
+ public int ClipLimit { get; }
///
/// Calculates the cumulative distribution function.
@@ -96,11 +96,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// the values over the clip limit to all other bins equally.
///
/// The histogram to apply the clipping.
- /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
- /// The numbers of pixels inside the tile.
- public void ClipHistogram(Span histogram, float clipLimitPercentage, int pixelCount)
+ /// Histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
+ public void ClipHistogram(Span histogram, int clipLimit)
{
- int clipLimit = (int)MathF.Round(pixelCount * clipLimitPercentage);
int sumOverClip = 0;
ref int histogramBase = ref MemoryMarshal.GetReference(histogram);
@@ -114,6 +112,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
}
}
+ // Redistribute the clipped pixels over all bins of the histogram.
int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0;
if (addToEachBin > 0)
{
@@ -122,6 +121,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
Unsafe.Add(ref histogramBase, i) += addToEachBin;
}
}
+
+ int residual = sumOverClip - (addToEachBin * this.LuminanceLevels);
+ if (residual != 0)
+ {
+ int residualStep = Math.Max(this.LuminanceLevels / residual, 1);
+ for (int i = 0; i < this.LuminanceLevels && residual > 0; i += residualStep, residual--)
+ {
+ ref int histogramLevel = ref Unsafe.Add(ref histogramBase, i);
+ histogramLevel++;
+ }
+ }
}
///
diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs
index c71232524..32da38621 100644
--- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs
@@ -121,8 +121,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization
/// See: https://github.com/SixLabors/ImageSharp/pull/984
///
[Theory]
- [WithTestPatternImages(110, 110, PixelTypes.Rgba32)]
- [WithTestPatternImages(170, 170, PixelTypes.Rgba32)]
+ [WithTestPatternImages(110, 110, PixelTypes.Rgb24)]
+ [WithTestPatternImages(170, 170, PixelTypes.Rgb24)]
public void Issue984(TestImageProvider provider)
where TPixel : struct, IPixel
{
@@ -133,10 +133,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization
Method = HistogramEqualizationMethod.AdaptiveTileInterpolation,
LuminanceLevels = 256,
ClipHistogram = true,
+ ClipLimit = 5,
NumberOfTiles = 10
};
image.Mutate(x => x.HistogramEqualization(options));
image.DebugSave(provider);
+ image.CompareToReferenceOutput(ValidatorComparer, provider);
}
}
}
diff --git a/tests/Images/External b/tests/Images/External
index 99a2bc523..468e39ad2 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit 99a2bc523cd4eb00e37af20d1b2088fa11564c57
+Subproject commit 468e39ad25c9c2f38d5a16d603ec09f11d1fe0a2