diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs index e3c62f813a..b7d27f6356 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Collections.Generic; using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -47,32 +48,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization Span histogramCopy = histogramBufferCopy.GetSpan(); Span cdf = cdfBuffer.GetSpan(); - for (int x = halfGridSize; x < source.Width - halfGridSize; x++) + for (int x = 0; x < source.Width; x++) { histogram.Clear(); // Build the histogram of grayscale values for the current grid. for (int dy = 0; dy < gridSize; dy++) { - Span rowSpan = source.GetPixelRowSpan(dy).Slice(start: x - halfGridSize, length: gridSize); + Span rowSpan = this.GetPixelRow(source, x - halfGridSize, dy, gridSize); this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels); } for (int y = 1; y < source.Height; y++) { // Remove top most row from the histogram, mirroring rows which exceeds the borders - Span rowSpan = source.GetPixelRowSpan(Math.Abs(y - halfGridSize - 1)).Slice(start: x - halfGridSize, length: gridSize); + 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 - int rowPos = y + halfGridSize - 1; - if (rowPos >= source.Height) - { - int diff = rowPos - source.Height; - rowPos = source.Height - diff - 1; - } - - rowSpan = source.GetPixelRowSpan(rowPos).Slice(start: x - halfGridSize, length: gridSize); + 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 @@ -93,6 +87,59 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization } } + /// + /// Get the a pixel row at a given position with a length of the grid size. Mirrors pixels which exceeds the edges. + /// + /// The source image. + /// The x position. + /// The y position. + /// The grid size. + /// A pixel row of the length of the grid size. + private Span GetPixelRow(ImageFrame source, int x, int y, int gridSize) + { + if (y < 0) + { + y = Math.Abs(y); + } + else if (y >= source.Height) + { + int diff = y - source.Height; + y = source.Height - diff - 1; + } + + // special cases for the left and the right border where GetPixelRowSpan can not be used + if (x < 0) + { + var rowPixels = new List(); + for (int dx = x; dx < x + gridSize; dx++) + { + rowPixels.Add(source[Math.Abs(dx), y]); + } + + return rowPixels.ToArray(); + } + else if (x + gridSize > source.Width) + { + var rowPixels = new List(); + for (int dx = x; dx < x + gridSize; dx++) + { + if (dx >= source.Width) + { + int diff = dx - source.Width; + rowPixels.Add(source[dx - diff - 1, y]); + } + else + { + rowPixels.Add(source[dx, y]); + } + } + + return rowPixels.ToArray(); + } + + return source.GetPixelRowSpan(y).Slice(start: x, length: gridSize); + } + /// /// AHE tends to over amplify the contrast in near-constant regions of the image, since the histogram in such regions is highly concentrated. /// Clipping the histogram is meant to reduce this effect, by cutting of histogram bin's which exceed a certain amount and redistribute