diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs index d13a0edf62..62f1d18b9c 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs @@ -64,14 +64,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization Span histogram = histogramBuffer.GetSpan(); Span histogramCopy = histogramBufferCopy.GetSpan(); Span cdf = cdfBuffer.GetSpan(); - - histogram.Clear(); + int maxHistIdx = 0; // Build the histogram of grayscale values for the current grid. for (int dy = -halfGridSize; dy < halfGridSize; dy++) { Span rowSpan = this.GetPixelRow(source, x - halfGridSize, dy, this.GridSize); - this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels); + int maxIdx = this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels); + if (maxIdx > maxHistIdx) + { + maxHistIdx = maxIdx; + } } for (int y = 0; y < source.Height; y++) @@ -79,12 +82,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization if (this.ClipHistogramEnabled) { // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration. - histogram.CopyTo(histogramCopy); + histogram.Slice(0, maxHistIdx).CopyTo(histogramCopy); this.ClipHistogram(histogramCopy, this.ClipLimitPercentage, pixelsInGrid); } // Calculate the cumulative distribution function, which will map each input pixel in the current grid to a new value. - int cdfMin = this.ClipHistogramEnabled ? this.CalculateCdf(cdf, histogramCopy) : this.CalculateCdf(cdf, histogram); + int cdfMin = this.ClipHistogramEnabled ? this.CalculateCdf(cdf, histogramCopy, maxHistIdx) : this.CalculateCdf(cdf, histogram, maxHistIdx); float numberOfPixelsMinusCdfMin = pixelsInGrid - cdfMin; // Map the current pixel to the new equalized value @@ -94,11 +97,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization // 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); + maxHistIdx = this.RemovePixelsFromHistogram(rowSpan, histogram, this.LuminanceLevels, maxHistIdx); // 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); + int maxIdx = this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels); + if (maxIdx > maxHistIdx) + { + maxHistIdx = maxIdx; + } } } }); @@ -127,34 +134,39 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization y = source.Height - diff - 1; } - // special cases for the left and the right border where GetPixelRowSpan can not be used + // Special cases for the left and the right border where GetPixelRowSpan can not be used if (x < 0) { - var rowPixels = new List(); + var rowPixels = new TPixel[gridSize]; + int idx = 0; for (int dx = x; dx < x + gridSize; dx++) { - rowPixels.Add(source[Math.Abs(dx), y]); + rowPixels[idx] = source[Math.Abs(dx), y]; + idx++; } - return rowPixels.ToArray(); + return rowPixels; } else if (x + gridSize > source.Width) { - var rowPixels = new List(); + var rowPixels = new TPixel[gridSize]; + int idx = 0; for (int dx = x; dx < x + gridSize; dx++) { if (dx >= source.Width) { int diff = dx - source.Width; - rowPixels.Add(source[dx - diff - 1, y]); + rowPixels[idx] = source[dx - diff - 1, y]; } else { - rowPixels.Add(source[dx, y]); + rowPixels[idx] = source[dx, y]; } + + idx++; } - return rowPixels.ToArray(); + return rowPixels; } return source.GetPixelRowSpan(y).Slice(start: x, length: gridSize); @@ -166,13 +178,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// The grey values to add /// The histogram /// The number of different luminance levels. - private void AddPixelsTooHistogram(Span greyValues, Span histogram, int luminanceLevels) + /// The maximum index where a value was changed. + private int AddPixelsTooHistogram(Span greyValues, Span histogram, int luminanceLevels) { - for (int i = 0; i < greyValues.Length; i++) + int maxIdx = 0; + for (int idx = 0; idx < greyValues.Length; idx++) { - int luminance = this.GetLuminance(greyValues[i], luminanceLevels); + int luminance = this.GetLuminance(greyValues[idx], luminanceLevels); histogram[luminance]++; + if (luminance > maxIdx) + { + maxIdx = luminance; + } } + + return maxIdx; } /// @@ -181,13 +201,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// The grey values to remove /// The histogram /// The number of different luminance levels. - private void RemovePixelsFromHistogram(Span greyValues, Span histogram, int luminanceLevels) + /// The current maximum index of the histogram. + /// The (maybe changed) maximum index of the histogram. + private int RemovePixelsFromHistogram(Span greyValues, Span histogram, int luminanceLevels, int maxHistIdx) { - for (int i = 0; i < greyValues.Length; i++) + for (int idx = 0; idx < greyValues.Length; idx++) { - int luminance = this.GetLuminance(greyValues[i], luminanceLevels); + int luminance = this.GetLuminance(greyValues[idx], luminanceLevels); histogram[luminance]--; + + // If the histogram at the maximum index has changed to 0, search for the next smaller value. + if (luminance == maxHistIdx && histogram[luminance] == 0) + { + for (int j = luminance; j >= 0; j--) + { + maxHistIdx = j; + if (histogram[j] != 0) + { + break; + } + } + } } + + return maxHistIdx; } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs index f7a4349106..cdf1f43c08 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization // Calculate the cumulative distribution function, which will map each input pixel to a new value. Span cdf = cdfBuffer.GetSpan(); - int cdfMin = this.CalculateCdf(cdf, histogram); + int cdfMin = this.CalculateCdf(cdf, histogram, histogram.Length - 1); // Apply the cdf to each pixel of the image float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs index 9a6502854f..6708a36998 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs @@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public int LuminanceLevels { get; set; } = 256; /// - /// Gets or sets a value indicating whether to clip the histogram bins at a specific value. Defaults to true. + /// Gets or sets a value indicating whether to clip the histogram bins at a specific value. Defaults to false. /// - public bool ClipHistogram { get; set; } = true; + public bool ClipHistogram { get; set; } = false; /// /// Gets or sets the histogram clip limit in percent of the total pixels in the grid. Histogram bins which exceed this limit, will be capped at this value. diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index bf46390328..4928a54674 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs @@ -50,12 +50,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// The array holding the cdf. /// The histogram of the input image. + /// Index of the maximum of the histogram. /// The first none zero value of the cdf. - protected int CalculateCdf(Span cdf, Span histogram) + protected int CalculateCdf(Span cdf, Span histogram, int maxIdx) { // Calculate the cumulative histogram int histSum = 0; - for (int i = 0; i < histogram.Length; i++) + for (int i = 0; i <= maxIdx; i++) { histSum += histogram[i]; cdf[i] = histSum; @@ -63,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization // Get the first none zero value of the cumulative histogram int cdfMin = 0; - for (int i = 0; i < histogram.Length; i++) + for (int i = 0; i <= maxIdx; i++) { if (cdf[i] != 0) { @@ -73,7 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization } // Creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop - for (int i = 0; i < histogram.Length; i++) + for (int i = 0; i <= maxIdx; i++) { cdf[i] = Math.Max(0, cdf[i] - cdfMin); }