diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs index 167a10579c..232bb1b0a7 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs @@ -45,89 +45,80 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - MemoryAllocator memoryAllocator = configuration.MemoryAllocator; int numberOfPixels = source.Width * source.Height; - int tileWidth = Convert.ToInt32(Math.Ceiling(source.Width / (double)this.Tiles)); int tileHeight = Convert.ToInt32(Math.Ceiling(source.Height / (double)this.Tiles)); int pixelsInTile = tileWidth * tileHeight; int halfTileWidth = tileWidth / 2; int halfTileHeight = tileHeight / 2; - using (System.Buffers.IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (System.Buffers.IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - { - Span histogram = histogramBuffer.GetSpan(); - Span cdf = cdfBuffer.GetSpan(); + // The image is split up into tiles. For each tile the cumulative distribution function will be calculated. + CdfData[,] cdfData = this.CalculateLookupTables(source, configuration, this.Tiles, this.Tiles, tileWidth, tileHeight); - // The image is split up into tiles. For each tile the cumulative distribution function will be calculated. - CdfData[,] cdfData = this.CalculateLookupTables(source, histogram, cdf, this.Tiles, this.Tiles, tileWidth, tileHeight); + var tileYStartPositions = new List<(int y, int cdfY)>(); + int cdfY = 0; + for (int y = halfTileHeight; y < source.Height - halfTileHeight; y += tileHeight) + { + tileYStartPositions.Add((y, cdfY)); + cdfY++; + } - var tileYStartPositions = new List<(int y, int cdfY)>(); - int cdfY = 0; - for (int y = halfTileHeight; y < source.Height - halfTileHeight; y += tileHeight) - { - tileYStartPositions.Add((y, cdfY)); - cdfY++; - } + Parallel.ForEach(tileYStartPositions, new ParallelOptions() { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }, (tileYStartPosition) => + { + int cdfX = 0; + int tileX = 0; + int tileY = 0; + int y = tileYStartPosition.y; - Parallel.ForEach(tileYStartPositions, new ParallelOptions() { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }, (tileYStartPosition) => + cdfX = 0; + for (int x = halfTileWidth; x < source.Width - halfTileWidth; x += tileWidth) { - int cdfX = 0; - int tileX = 0; - int tileY = 0; - int y = tileYStartPosition.y; - - cdfX = 0; - for (int x = halfTileWidth; x < source.Width - halfTileWidth; x += tileWidth) + tileY = 0; + int yEnd = Math.Min(y + tileHeight, source.Height); + int xEnd = Math.Min(x + tileWidth, source.Width); + for (int dy = y; dy < yEnd; dy++) { - tileY = 0; - int yEnd = Math.Min(y + tileHeight, source.Height); - int xEnd = Math.Min(x + tileWidth, source.Width); - for (int dy = y; dy < yEnd; dy++) + Span pixelRow = source.GetPixelRowSpan(dy); + tileX = 0; + for (int dx = x; dx < xEnd; dx++) { - Span pixelRow = source.GetPixelRowSpan(dy); - tileX = 0; - for (int dx = x; dx < xEnd; dx++) - { - float luminanceEqualized = this.InterpolateBetweenFourTiles(source[dx, dy], cdfData, tileX, tileY, cdfX, tileYStartPosition.cdfY, tileWidth, tileHeight, pixelsInTile); - pixelRow[dx].FromVector4(new Vector4(luminanceEqualized)); - tileX++; - } - - tileY++; + float luminanceEqualized = this.InterpolateBetweenFourTiles(source[dx, dy], cdfData, tileX, tileY, cdfX, tileYStartPosition.cdfY, tileWidth, tileHeight, pixelsInTile); + pixelRow[dx].FromVector4(new Vector4(luminanceEqualized)); + tileX++; } - cdfX++; + tileY++; } - }); - Span pixels = source.GetPixelSpan(); + cdfX++; + } + }); + + Span pixels = source.GetPixelSpan(); - // fix left column - this.ProcessBorderColumn(source, pixels, cdfData, 0, tileWidth, tileHeight, xStart: 0, xEnd: halfTileWidth); + // fix left column + this.ProcessBorderColumn(source, pixels, cdfData, 0, tileWidth, tileHeight, xStart: 0, xEnd: halfTileWidth); - // fix right column - this.ProcessBorderColumn(source, pixels, cdfData, this.Tiles - 1, tileWidth, tileHeight, xStart: source.Width - halfTileWidth, xEnd: source.Width); + // fix right column + this.ProcessBorderColumn(source, pixels, cdfData, this.Tiles - 1, tileWidth, tileHeight, xStart: source.Width - halfTileWidth, xEnd: source.Width); - // fix top row - this.ProcessBorderRow(source, pixels, cdfData, 0, tileWidth, tileHeight, yStart: 0, yEnd: halfTileHeight); + // fix top row + this.ProcessBorderRow(source, pixels, cdfData, 0, tileWidth, tileHeight, yStart: 0, yEnd: halfTileHeight); - // fix bottom row - this.ProcessBorderRow(source, pixels, cdfData, this.Tiles - 1, tileWidth, tileHeight, yStart: source.Height - halfTileHeight, yEnd: source.Height); + // fix bottom row + this.ProcessBorderRow(source, pixels, cdfData, this.Tiles - 1, tileWidth, tileHeight, yStart: source.Height - halfTileHeight, yEnd: source.Height); - // left top corner - this.ProcessCornerTile(source, pixels, cdfData[0, 0], xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, pixelsInTile: pixelsInTile); + // left top corner + this.ProcessCornerTile(source, pixels, cdfData[0, 0], xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, pixelsInTile: pixelsInTile); - // left bottom corner - this.ProcessCornerTile(source, pixels, cdfData[0, this.Tiles - 1], xStart: 0, xEnd: halfTileWidth, yStart: source.Height - halfTileHeight, yEnd: source.Height, pixelsInTile: pixelsInTile); + // left bottom corner + this.ProcessCornerTile(source, pixels, cdfData[0, this.Tiles - 1], xStart: 0, xEnd: halfTileWidth, yStart: source.Height - halfTileHeight, yEnd: source.Height, pixelsInTile: pixelsInTile); - // right top corner - this.ProcessCornerTile(source, pixels, cdfData[this.Tiles - 1, 0], xStart: source.Width - halfTileWidth, xEnd: source.Width, yStart: 0, yEnd: halfTileHeight, pixelsInTile: pixelsInTile); + // right top corner + this.ProcessCornerTile(source, pixels, cdfData[this.Tiles - 1, 0], xStart: source.Width - halfTileWidth, xEnd: source.Width, yStart: 0, yEnd: halfTileHeight, pixelsInTile: pixelsInTile); - // right bottom corner - this.ProcessCornerTile(source, pixels, cdfData[this.Tiles - 1, this.Tiles - 1], xStart: source.Width - halfTileWidth, xEnd: source.Width, yStart: source.Height - halfTileHeight, yEnd: source.Height, pixelsInTile: pixelsInTile); - } + // right bottom corner + this.ProcessCornerTile(source, pixels, cdfData[this.Tiles - 1, this.Tiles - 1], xStart: source.Width - halfTileWidth, xEnd: source.Width, yStart: source.Height - halfTileHeight, yEnd: source.Height, pixelsInTile: pixelsInTile); } /// @@ -286,59 +277,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization return luminanceEqualized; } - /// - /// Calculates the lookup tables for each tile of the image. - /// - /// The input image for which the tiles will be calculated. - /// Histogram buffer. - /// Buffer for calculating the cumulative distribution function. - /// Number of tiles in the X Direction. - /// Number of tiles in Y Direction - /// Width in pixels of one tile. - /// Height in pixels of one tile. - /// All lookup tables for each tile in the image. - private CdfData[,] CalculateLookupTables(ImageFrame source, Span histogram, Span cdf, int numTilesX, int numTilesY, int tileWidth, int tileHeight) - { - var cdfData = new CdfData[numTilesX, numTilesY]; - int pixelsInTile = tileWidth * tileHeight; - int tileX = 0; - int tileY = 0; - for (int y = 0; y < source.Height; y += tileHeight) - { - tileX = 0; - for (int x = 0; x < source.Width; x += tileWidth) - { - histogram.Clear(); - cdf.Clear(); - int ylimit = Math.Min(y + tileHeight, source.Height); - int xlimit = Math.Min(x + tileWidth, source.Width); - for (int dy = y; dy < ylimit; dy++) - { - for (int dx = x; dx < xlimit; dx++) - { - int luminace = this.GetLuminance(source[dx, dy], this.LuminanceLevels); - histogram[luminace]++; - } - } - - if (this.ClipHistogramEnabled) - { - this.ClipHistogram(histogram, this.ClipLimitPercentage, pixelsInTile); - } - - int cdfMin = this.CalculateCdf(cdf, histogram, histogram.Length - 1); - var currentCdf = new CdfData(cdf.ToArray(), cdfMin); - cdfData[tileX, tileY] = currentCdf; - - tileX++; - } - - tileY++; - } - - return cdfData; - } - /// /// Bilinear interpolation between four tiles. /// @@ -366,6 +304,73 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization return left + ((right - left) * t); } + /// + /// Calculates the lookup tables for each tile of the image. + /// + /// The input image for which the tiles will be calculated. + /// The configuration. + /// Number of tiles in the X Direction. + /// Number of tiles in Y Direction. + /// Width in pixels of one tile. + /// Height in pixels of one tile. + /// All lookup tables for each tile in the image. + private CdfData[,] CalculateLookupTables(ImageFrame source, Configuration configuration, int numTilesX, int numTilesY, int tileWidth, int tileHeight) + { + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; + var cdfData = new CdfData[numTilesX, numTilesY]; + int pixelsInTile = tileWidth * tileHeight; + + var tileYStartPositions = new List<(int y, int cdfY)>(); + int cdfY = 0; + for (int y = 0; y < source.Height; y += tileHeight) + { + tileYStartPositions.Add((y, cdfY)); + cdfY++; + } + + Parallel.ForEach(tileYStartPositions, new ParallelOptions() { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }, (tileYStartPosition) => + { + using (System.Buffers.IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + using (System.Buffers.IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + { + int cdfX = 0; + int y = tileYStartPosition.y; + for (int x = 0; x < source.Width; x += tileWidth) + { + Span histogram = histogramBuffer.GetSpan(); + Span cdf = cdfBuffer.GetSpan(); + histogram.Clear(); + cdf.Clear(); + int ylimit = Math.Min(y + tileHeight, source.Height); + int xlimit = Math.Min(x + tileWidth, source.Width); + for (int dy = y; dy < ylimit; dy++) + { + for (int dx = x; dx < xlimit; dx++) + { + int luminace = this.GetLuminance(source[dx, dy], this.LuminanceLevels); + histogram[luminace]++; + } + } + + if (this.ClipHistogramEnabled) + { + this.ClipHistogram(histogram, this.ClipLimitPercentage, pixelsInTile); + } + + int cdfMin = this.CalculateCdf(cdf, histogram, histogram.Length - 1); + var currentCdf = new CdfData(cdf.ToArray(), cdfMin); + cdfData[cdfX, tileYStartPosition.cdfY] = currentCdf; + + cdfX++; + } + + cdfY++; + } + }); + + return cdfData; + } + /// /// Lookup table for remapping the grey values of one tile. /// diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationSWProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationSWProcessor.cs index c96b590248..66775520f2 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationSWProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationSWProcessor.cs @@ -178,8 +178,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// Adds a row of grey values to the histogram. /// - /// The grey values to add - /// The histogram + /// The grey values to add. + /// The histogram. /// The number of different luminance levels. /// The maximum index where a value was changed. private int AddPixelsToHistogram(Span greyValues, Span histogram, int luminanceLevels) @@ -201,8 +201,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// Removes a row of grey values from the histogram. /// - /// The grey values to remove - /// The histogram + /// The grey values to remove. + /// The histogram. /// The number of different luminance levels. /// The current maximum index of the histogram. /// The (maybe changed) maximum index of the histogram.