diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs
index b7d27f6356..a79b5b8dac 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs
@@ -21,13 +21,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
///
/// Initializes a new instance of the class.
///
+ /// The grid size of the adaptive histogram equalization.
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
/// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images.
- public AdaptiveHistEqualizationProcessor(int luminanceLevels)
+ public AdaptiveHistEqualizationProcessor(int gridSize, int clipLimit, int luminanceLevels)
: base(luminanceLevels)
{
+ this.GridSize = gridSize;
+ this.ClipLimit = clipLimit;
}
+ ///
+ /// Gets the size of the grid for the adaptive histogram equalization.
+ ///
+ public int GridSize { get; }
+
+ ///
+ /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
+ ///
+ public int ClipLimit { get; }
+
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
{
@@ -35,10 +49,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
int numberOfPixels = source.Width * source.Height;
Span pixels = source.GetPixelSpan();
- int gridSize = 64;
- int pixelsInGrid = gridSize * gridSize;
- int contrastLimit = 5;
- int halfGridSize = gridSize / 2;
+ int pixelsInGrid = this.GridSize * this.GridSize;
+ int halfGridSize = this.GridSize / 2;
using (IBuffer histogramBuffer = memoryAllocator.AllocateClean(this.LuminanceLevels))
using (IBuffer histogramBufferCopy = memoryAllocator.AllocateClean(this.LuminanceLevels))
using (IBuffer cdfBuffer = memoryAllocator.AllocateClean(this.LuminanceLevels))
@@ -53,25 +65,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
histogram.Clear();
// Build the histogram of grayscale values for the current grid.
- for (int dy = 0; dy < gridSize; dy++)
+ for (int dy = -halfGridSize; dy < halfGridSize; dy++)
{
- Span rowSpan = this.GetPixelRow(source, x - halfGridSize, dy, gridSize);
+ Span rowSpan = this.GetPixelRow(source, x - halfGridSize, dy, this.GridSize);
this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels);
}
- for (int y = 1; y < source.Height; y++)
+ for (int y = 0; y < source.Height; y++)
{
- // Remove top most row from the histogram, mirroring rows which exceeds the borders
- Span rowSpan = this.GetPixelRow(source, x - halfGridSize, y - halfGridSize - 1, gridSize);
- this.RemovePixelsFromHistogram(rowSpan, histogram, this.LuminanceLevels);
-
- // Add new bottom row to the histogram, mirroring rows which exceeds the borders
- rowSpan = this.GetPixelRow(source, x - halfGridSize, y + halfGridSize - 1, gridSize);
- this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels);
-
// 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, gridSize, contrastLimit);
+ this.ClipHistogram(histogramCopy, this.ClipLimit);
cdf.Clear();
int cdfMin = this.CalculateCdf(cdf, histogramCopy);
double numberOfPixelsMinusCdfMin = (double)(pixelsInGrid - cdfMin);
@@ -80,6 +84,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
int luminance = this.GetLuminance(pixels[(y * source.Width) + x], this.LuminanceLevels);
double luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin;
targetPixels[x, y].PackFromVector4(new Vector4((float)luminanceEqualized));
+
+ // Remove top most row from the histogram, mirroring rows which exceeds the borders
+ Span rowSpan = this.GetPixelRow(source, x - halfGridSize, y - halfGridSize, this.GridSize);
+ this.RemovePixelsFromHistogram(rowSpan, histogram, this.LuminanceLevels);
+
+ // Add new bottom row to the histogram, mirroring rows which exceeds the borders
+ rowSpan = this.GetPixelRow(source, x - halfGridSize, y + halfGridSize, this.GridSize);
+ this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels);
}
}
@@ -145,14 +157,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// Clipping the histogram is meant to reduce this effect, by cutting of histogram bin's which exceed a certain amount and redistribute
/// the values over the clip limit to all other bins equally.
///
- /// The histogram to apply the clipping
- /// The grid size of the AHE
- /// The contrast limit. Defaults to 5.
- /// The number of pixels redistributed to all other bins
- private int ClipHistogram(Span histogram, int gridSize, int contrastLimit = 5)
+ /// The histogram to apply the clipping.
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
+ /// The number of pixels redistributed to all other bins.
+ private int ClipHistogram(Span histogram, int clipLimit)
{
- int clipLimit = (contrastLimit * (gridSize * gridSize)) / this.LuminanceLevels;
-
int sumOverClip = 0;
for (int i = 0; i < histogram.Length; i++)
{
@@ -173,7 +182,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
// Re-Applying the clipping until this effect no longer occurs.
while (addToEachBin > 1)
{
- addToEachBin = this.ClipHistogram(histogram, gridSize, contrastLimit);
+ addToEachBin = this.ClipHistogram(histogram, clipLimit);
}
return addToEachBin;