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;