diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs
index 20543369fa..f03c3ff9b2 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs
@@ -2,11 +2,12 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Threading.Tasks;
-using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@@ -46,63 +47,71 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
{
- int numberOfPixels = source.Width * source.Height;
- int tileWidth = (int)MathF.Ceiling(source.Width / (float)this.Tiles);
- int tileHeight = (int)MathF.Ceiling(source.Height / (float)this.Tiles);
+ int sourceWidth = source.Width;
+ int sourceHeight = source.Height;
+ int numberOfPixels = sourceWidth * sourceHeight;
+ int tileWidth = (int)MathF.Ceiling(sourceWidth / (float)this.Tiles);
+ int tileHeight = (int)MathF.Ceiling(sourceHeight / (float)this.Tiles);
int pixelsInTile = tileWidth * tileHeight;
int halfTileWidth = tileWidth / 2;
int halfTileHeight = tileHeight / 2;
int luminanceLevels = this.LuminanceLevels;
// The image is split up into tiles. For each tile the cumulative distribution function will be calculated.
- using (var cdfData = new CdfTileData(configuration, sourceRectangle.Height, this.Tiles, this.Tiles, tileWidth, tileHeight, luminanceLevels))
+ using (var cdfData = new CdfTileData(configuration, sourceWidth, sourceHeight, this.Tiles, this.Tiles, tileWidth, tileHeight, luminanceLevels))
{
cdfData.CalculateLookupTables(source, this);
var tileYStartPositions = new List<(int y, int cdfY)>();
int cdfY = 0;
- for (int y = halfTileHeight; y < source.Height - halfTileHeight; y += tileHeight)
+ for (int y = halfTileHeight; y < sourceHeight - halfTileHeight; y += tileHeight)
{
tileYStartPositions.Add((y, cdfY));
cdfY++;
}
- Parallel.ForEach(
- tileYStartPositions,
+ Parallel.For(
+ 0,
+ tileYStartPositions.Count,
new ParallelOptions() { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism },
- tileYStartPosition =>
+ index =>
{
int cdfX = 0;
int tileX = 0;
int tileY = 0;
- int y = tileYStartPosition.y;
+ int y = tileYStartPositions[index].y;
+ int cdfYY = tileYStartPositions[index].cdfY;
+
+ // It's unfortunate that we have to do this per iteration.
+ ref TPixel sourceBase = ref source.GetPixelReference(0, 0);
cdfX = 0;
- for (int x = halfTileWidth; x < source.Width - halfTileWidth; x += tileWidth)
+ for (int x = halfTileWidth; x < sourceWidth - halfTileWidth; x += tileWidth)
{
tileY = 0;
- int yEnd = Math.Min(y + tileHeight, source.Height);
- int xEnd = Math.Min(x + tileWidth, source.Width);
+ int yEnd = Math.Min(y + tileHeight, sourceHeight);
+ int xEnd = Math.Min(x + tileWidth, sourceWidth);
for (int dy = y; dy < yEnd; dy++)
{
- Span pixelRow = source.GetPixelRowSpan(dy);
+ int dyOffSet = dy * sourceWidth;
tileX = 0;
for (int dx = x; dx < xEnd; dx++)
{
+ ref TPixel pixel = ref Unsafe.Add(ref sourceBase, dyOffSet + dx);
float luminanceEqualized = InterpolateBetweenFourTiles(
- source[dx, dy],
+ pixel,
cdfData,
this.Tiles,
this.Tiles,
tileX,
tileY,
cdfX,
- tileYStartPosition.cdfY,
+ cdfYY,
tileWidth,
tileHeight,
luminanceLevels);
- pixelRow[dx].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixelRow[dx].ToVector4().W));
+ pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
tileX++;
}
@@ -113,42 +122,42 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
}
});
- Span pixels = source.GetPixelSpan();
+ ref TPixel pixelsBase = ref source.GetPixelReference(0, 0);
// Fix left column
- ProcessBorderColumn(source, pixels, cdfData, 0, tileWidth, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels);
+ ProcessBorderColumn(ref pixelsBase, cdfData, 0, sourceWidth, sourceHeight, tileWidth, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels);
// Fix right column
int rightBorderStartX = ((this.Tiles - 1) * tileWidth) + halfTileWidth;
- ProcessBorderColumn(source, pixels, cdfData, this.Tiles - 1, tileWidth, tileHeight, xStart: rightBorderStartX, xEnd: source.Width, luminanceLevels);
+ ProcessBorderColumn(ref pixelsBase, cdfData, this.Tiles - 1, sourceWidth, sourceHeight, tileWidth, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels);
// Fix top row
- ProcessBorderRow(source, pixels, cdfData, 0, tileWidth, tileHeight, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
+ ProcessBorderRow(ref pixelsBase, cdfData, 0, sourceWidth, tileWidth, tileHeight, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
// Fix bottom row
int bottomBorderStartY = ((this.Tiles - 1) * tileHeight) + halfTileHeight;
- ProcessBorderRow(source, pixels, cdfData, this.Tiles - 1, tileWidth, tileHeight, yStart: bottomBorderStartY, yEnd: source.Height, luminanceLevels);
+ ProcessBorderRow(ref pixelsBase, cdfData, this.Tiles - 1, sourceWidth, tileWidth, tileHeight, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels);
// Left top corner
- ProcessCornerTile(source, pixels, cdfData, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
+ ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
// Left bottom corner
- ProcessCornerTile(source, pixels, cdfData, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: source.Height, luminanceLevels);
+ ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels);
// Right top corner
- ProcessCornerTile(source, pixels, cdfData, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: source.Width, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
+ ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
// Right bottom corner
- ProcessCornerTile(source, pixels, cdfData, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: source.Width, yStart: bottomBorderStartY, yEnd: source.Height, luminanceLevels);
+ ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels);
}
}
///
/// Processes the part of a corner tile which was previously left out. It consists of 1 / 4 of a tile and does not need interpolation.
///
- /// The source image.
- /// The output pixels.
+ /// The output pixels base reference.
/// The lookup table to remap the grey values.
+ /// The source image width.
/// The x-position in the CDF lookup map.
/// The y-position in the CDF lookup map.
/// X start position.
@@ -160,9 +169,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// or 65536 for 16-bit grayscale images.
///
private static void ProcessCornerTile(
- ImageFrame source,
- Span pixels,
+ ref TPixel pixelsBase,
CdfTileData cdfData,
+ int sourceWidth,
int cdfX,
int cdfY,
int xStart,
@@ -173,10 +182,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
for (int dy = yStart; dy < yEnd; dy++)
{
+ int dyOffSet = dy * sourceWidth;
for (int dx = xStart; dx < xEnd; dx++)
{
- float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, GetLuminance(source[dx, dy], luminanceLevels));
- pixels[(dy * source.Width) + dx].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, source[dx, dy].ToVector4().W));
+ ref TPixel pixel = ref Unsafe.Add(ref pixelsBase, dyOffSet + dx);
+ float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, GetLuminance(pixel, luminanceLevels));
+ pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
}
}
}
@@ -184,10 +195,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
///
/// Processes a border column of the image which is half the size of the tile width.
///
- /// The source image.
- /// The output pixels.
+ /// The output pixels reference.
/// The pre-computed lookup tables to remap the grey values for each tiles.
/// The X index of the lookup table to use.
+ /// The source image width.
+ /// The source image height.
/// The width of a tile.
/// The height of a tile.
/// X start position in the image.
@@ -197,10 +209,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// or 65536 for 16-bit grayscale images.
///
private static void ProcessBorderColumn(
- ImageFrame source,
- Span pixels,
+ ref TPixel pixelBase,
CdfTileData cdfData,
int cdfX,
+ int sourceWidth,
+ int sourceHeight,
int tileWidth,
int tileHeight,
int xStart,
@@ -211,17 +224,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
int halfTileHeight = tileHeight / 2;
int cdfY = 0;
- for (int y = halfTileHeight; y < source.Height - halfTileHeight; y += tileHeight)
+ for (int y = halfTileHeight; y < sourceHeight - halfTileHeight; y += tileHeight)
{
- int yLimit = Math.Min(y + tileHeight, source.Height - 1);
+ int yLimit = Math.Min(y + tileHeight, sourceHeight - 1);
int tileY = 0;
for (int dy = y; dy < yLimit; dy++)
{
+ int dyOffSet = dy * sourceWidth;
int tileX = halfTileWidth;
for (int dx = xStart; dx < xEnd; dx++)
{
- float luminanceEqualized = InterpolateBetweenTwoTiles(source[dx, dy], cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels);
- pixels[(dy * source.Width) + dx].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, source[dx, dy].ToVector4().W));
+ ref TPixel pixel = ref Unsafe.Add(ref pixelBase, dyOffSet + dx);
+ float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels);
+ pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
tileX++;
}
@@ -235,10 +250,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
///
/// Processes a border row of the image which is half of the size of the tile height.
///
- /// The source image.
- /// The output pixels.
+ /// The output pixels base reference.
/// The pre-computed lookup tables to remap the grey values for each tiles.
/// The Y index of the lookup table to use.
+ /// The source image width.
/// The width of a tile.
/// The height of a tile.
/// Y start position in the image.
@@ -248,10 +263,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// or 65536 for 16-bit grayscale images.
///
private static void ProcessBorderRow(
- ImageFrame source,
- Span pixels,
+ ref TPixel pixelBase,
CdfTileData cdfData,
int cdfY,
+ int sourceWidth,
int tileWidth,
int tileHeight,
int yStart,
@@ -262,17 +277,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
int halfTileHeight = tileHeight / 2;
int cdfX = 0;
- for (int x = halfTileWidth; x < source.Width - halfTileWidth; x += tileWidth)
+ for (int x = halfTileWidth; x < sourceWidth - halfTileWidth; x += tileWidth)
{
int tileY = 0;
for (int dy = yStart; dy < yEnd; dy++)
{
+ int dyOffSet = dy * sourceWidth;
int tileX = 0;
- int xLimit = Math.Min(x + tileWidth, source.Width - 1);
+ int xLimit = Math.Min(x + tileWidth, sourceWidth - 1);
for (int dx = x; dx < xLimit; dx++)
{
- float luminanceEqualized = InterpolateBetweenTwoTiles(source[dx, dy], cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels);
- pixels[(dy * source.Width) + dx].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, source[dx, dy].ToVector4().W));
+ ref TPixel pixel = ref Unsafe.Add(ref pixelBase, dyOffSet + dx);
+ float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels);
+ pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
tileX++;
}
@@ -396,10 +413,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
private sealed class CdfTileData : IDisposable
{
private readonly Configuration configuration;
+ private readonly MemoryAllocator memoryAllocator;
+
+ // Used for storing the minimum value for each CDF entry.
private readonly Buffer2D cdfMinBuffer2D;
+
+ // Used for storing the LUT for each CDF entry.
private readonly Buffer2D cdfLutBuffer2D;
- private readonly Buffer2D histogramBuffer2D;
private readonly int pixelsInTile;
+ private readonly int sourceWidth;
+ private readonly int sourceHeight;
private readonly int tileWidth;
private readonly int tileHeight;
private readonly int luminanceLevels;
@@ -407,6 +430,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public CdfTileData(
Configuration configuration,
+ int sourceWidth,
int sourceHeight,
int tileCountX,
int tileCountY,
@@ -415,10 +439,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
int luminanceLevels)
{
this.configuration = configuration;
- MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
+ this.memoryAllocator = configuration.MemoryAllocator;
this.luminanceLevels = luminanceLevels;
- this.cdfMinBuffer2D = memoryAllocator.Allocate2D(tileCountX, tileCountY);
- this.cdfLutBuffer2D = memoryAllocator.Allocate2D(tileCountX * luminanceLevels, tileCountY);
+ this.cdfMinBuffer2D = this.memoryAllocator.Allocate2D(tileCountX, tileCountY);
+ this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D(tileCountX * luminanceLevels, tileCountY);
+ this.sourceWidth = sourceWidth;
+ this.sourceHeight = sourceHeight;
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
this.pixelsInTile = tileWidth * tileHeight;
@@ -431,51 +457,59 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
this.tileYStartPositions.Add((y, cdfY));
cdfY++;
}
-
- // Use 2D to avoid rent/return per iteration.
- this.histogramBuffer2D = memoryAllocator.Allocate2D(luminanceLevels, this.tileYStartPositions.Count);
}
public void CalculateLookupTables(ImageFrame source, HistogramEqualizationProcessor processor)
{
+ int sourceWidth = this.sourceWidth;
+ int sourceHeight = this.sourceHeight;
+ int tileWidth = this.tileWidth;
+ int tileHeight = this.tileHeight;
+ int luminanceLevels = this.luminanceLevels;
+ MemoryAllocator memoryAllocator = this.memoryAllocator;
+
Parallel.For(
0,
this.tileYStartPositions.Count,
new ParallelOptions() { MaxDegreeOfParallelism = this.configuration.MaxDegreeOfParallelism },
index =>
{
- Span histogram = this.histogramBuffer2D.GetRowSpan(index);
-
int cdfX = 0;
int cdfY = this.tileYStartPositions[index].cdfY;
int y = this.tileYStartPositions[index].y;
- int endY = Math.Min(y + this.tileHeight, source.Height);
+ int endY = Math.Min(y + tileHeight, sourceHeight);
+ ref TPixel sourceBase = ref source.GetPixelReference(0, 0);
+ ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY));
- for (int x = 0; x < source.Width; x += this.tileWidth)
+ using (IMemoryOwner histogramBuffer = this.memoryAllocator.Allocate(luminanceLevels))
{
- histogram.Clear();
- Span cdf = this.GetCdfLutSpan(cdfX, index);
+ Span histogram = histogramBuffer.GetSpan();
- int xlimit = Math.Min(x + this.tileWidth, source.Width);
- for (int dy = y; dy < endY; dy++)
+ for (int x = 0; x < sourceWidth; x += tileWidth)
{
- Span sourceRowSpan = source.GetPixelRowSpan(dy);
+ histogram.Clear();
+ Span cdf = this.GetCdfLutSpan(cdfX, index);
- for (int dx = x; dx < xlimit; dx++)
+ int xlimit = Math.Min(x + tileWidth, sourceWidth);
+ for (int dy = y; dy < endY; dy++)
{
- int luminace = GetLuminance(sourceRowSpan[dx], this.luminanceLevels);
- histogram[luminace]++;
+ int dyOffset = dy * sourceWidth;
+ for (int dx = x; dx < xlimit; dx++)
+ {
+ int luminace = GetLuminance(Unsafe.Add(ref sourceBase, dyOffset + dx), luminanceLevels);
+ histogram[luminace]++;
+ }
}
- }
- if (processor.ClipHistogramEnabled)
- {
- processor.ClipHistogram(histogram, processor.ClipLimitPercentage, this.pixelsInTile);
- }
+ if (processor.ClipHistogramEnabled)
+ {
+ processor.ClipHistogram(histogram, processor.ClipLimitPercentage, this.pixelsInTile);
+ }
- this.cdfMinBuffer2D[cdfX, cdfY] = processor.CalculateCdf(cdf, histogram, histogram.Length - 1);
+ Unsafe.Add(ref cdfMinBase, cdfX) = processor.CalculateCdf(cdf, histogram, histogram.Length - 1);
- cdfX++;
+ cdfX++;
+ }
}
});
}
@@ -503,7 +537,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public void Dispose()
{
this.cdfMinBuffer2D.Dispose();
- this.histogramBuffer2D.Dispose();
this.cdfLutBuffer2D.Dispose();
}
}