|
|
|
@ -49,62 +49,121 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization |
|
|
|
int numberOfPixels = source.Width * source.Height; |
|
|
|
var workingRect = new Rectangle(0, 0, source.Width, source.Height); |
|
|
|
|
|
|
|
using (IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) |
|
|
|
using (IMemoryOwner<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) |
|
|
|
using IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean); |
|
|
|
|
|
|
|
// Build the histogram of the grayscale levels
|
|
|
|
ParallelRowIterator.IterateRows( |
|
|
|
workingRect, |
|
|
|
this.Configuration, |
|
|
|
new GrayscaleLevelsRowIntervalAction(workingRect, histogramBuffer, source, this.LuminanceLevels)); |
|
|
|
|
|
|
|
Span<int> histogram = histogramBuffer.GetSpan(); |
|
|
|
if (this.ClipHistogramEnabled) |
|
|
|
{ |
|
|
|
this.ClipHistogram(histogram, this.ClipLimit); |
|
|
|
} |
|
|
|
|
|
|
|
using IMemoryOwner<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean); |
|
|
|
|
|
|
|
// Calculate the cumulative distribution function, which will map each input pixel to a new value.
|
|
|
|
int cdfMin = this.CalculateCdf( |
|
|
|
ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), |
|
|
|
ref MemoryMarshal.GetReference(histogram), |
|
|
|
histogram.Length - 1); |
|
|
|
|
|
|
|
float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; |
|
|
|
|
|
|
|
// Apply the cdf to each pixel of the image
|
|
|
|
ParallelRowIterator.IterateRows( |
|
|
|
workingRect, |
|
|
|
this.Configuration, |
|
|
|
new CdfApplicationRowIntervalAction(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// A <see langword="struct"/> implementing the grayscale levels logic for <see cref="GlobalHistogramEqualizationProcessor{TPixel}"/>.
|
|
|
|
/// </summary>
|
|
|
|
private readonly struct GrayscaleLevelsRowIntervalAction : IRowIntervalAction |
|
|
|
{ |
|
|
|
private readonly Rectangle bounds; |
|
|
|
private readonly IMemoryOwner<int> histogramBuffer; |
|
|
|
private readonly ImageFrame<TPixel> source; |
|
|
|
private readonly int luminanceLevels; |
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
public GrayscaleLevelsRowIntervalAction( |
|
|
|
in Rectangle bounds, |
|
|
|
IMemoryOwner<int> histogramBuffer, |
|
|
|
ImageFrame<TPixel> source, |
|
|
|
int luminanceLevels) |
|
|
|
{ |
|
|
|
// Build the histogram of the grayscale levels.
|
|
|
|
ParallelRowIterator.IterateRows( |
|
|
|
workingRect, |
|
|
|
this.Configuration, |
|
|
|
rows => |
|
|
|
{ |
|
|
|
ref int histogramBase = ref MemoryMarshal.GetReference(histogramBuffer.GetSpan()); |
|
|
|
for (int y = rows.Min; y < rows.Max; y++) |
|
|
|
{ |
|
|
|
ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); |
|
|
|
|
|
|
|
for (int x = 0; x < workingRect.Width; x++) |
|
|
|
{ |
|
|
|
int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.LuminanceLevels); |
|
|
|
Unsafe.Add(ref histogramBase, luminance)++; |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
Span<int> histogram = histogramBuffer.GetSpan(); |
|
|
|
if (this.ClipHistogramEnabled) |
|
|
|
this.bounds = bounds; |
|
|
|
this.histogramBuffer = histogramBuffer; |
|
|
|
this.source = source; |
|
|
|
this.luminanceLevels = luminanceLevels; |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
public void Invoke(in RowInterval rows) |
|
|
|
{ |
|
|
|
ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); |
|
|
|
for (int y = rows.Min; y < rows.Max; y++) |
|
|
|
{ |
|
|
|
this.ClipHistogram(histogram, this.ClipLimit); |
|
|
|
ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); |
|
|
|
|
|
|
|
for (int x = 0; x < this.bounds.Width; x++) |
|
|
|
{ |
|
|
|
int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels); |
|
|
|
Unsafe.Add(ref histogramBase, luminance)++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// A <see langword="struct"/> implementing the cdf application levels logic for <see cref="GlobalHistogramEqualizationProcessor{TPixel}"/>.
|
|
|
|
/// </summary>
|
|
|
|
private readonly struct CdfApplicationRowIntervalAction : IRowIntervalAction |
|
|
|
{ |
|
|
|
private readonly Rectangle bounds; |
|
|
|
private readonly IMemoryOwner<int> cdfBuffer; |
|
|
|
private readonly ImageFrame<TPixel> source; |
|
|
|
private readonly int luminanceLevels; |
|
|
|
private readonly float numberOfPixelsMinusCdfMin; |
|
|
|
|
|
|
|
// Calculate the cumulative distribution function, which will map each input pixel to a new value.
|
|
|
|
int cdfMin = this.CalculateCdf( |
|
|
|
ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), |
|
|
|
ref MemoryMarshal.GetReference(histogram), |
|
|
|
histogram.Length - 1); |
|
|
|
|
|
|
|
float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; |
|
|
|
|
|
|
|
// Apply the cdf to each pixel of the image
|
|
|
|
ParallelRowIterator.IterateRows( |
|
|
|
workingRect, |
|
|
|
this.Configuration, |
|
|
|
rows => |
|
|
|
{ |
|
|
|
ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); |
|
|
|
for (int y = rows.Min; y < rows.Max; y++) |
|
|
|
{ |
|
|
|
ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); |
|
|
|
|
|
|
|
for (int x = 0; x < workingRect.Width; x++) |
|
|
|
{ |
|
|
|
ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); |
|
|
|
int luminance = GetLuminance(pixel, this.LuminanceLevels); |
|
|
|
float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; |
|
|
|
pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
public CdfApplicationRowIntervalAction( |
|
|
|
in Rectangle bounds, |
|
|
|
IMemoryOwner<int> cdfBuffer, |
|
|
|
ImageFrame<TPixel> source, |
|
|
|
int luminanceLevels, |
|
|
|
float numberOfPixelsMinusCdfMin) |
|
|
|
{ |
|
|
|
this.bounds = bounds; |
|
|
|
this.cdfBuffer = cdfBuffer; |
|
|
|
this.source = source; |
|
|
|
this.luminanceLevels = luminanceLevels; |
|
|
|
this.numberOfPixelsMinusCdfMin = numberOfPixelsMinusCdfMin; |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
public void Invoke(in RowInterval rows) |
|
|
|
{ |
|
|
|
ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); |
|
|
|
for (int y = rows.Min; y < rows.Max; y++) |
|
|
|
{ |
|
|
|
ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); |
|
|
|
|
|
|
|
for (int x = 0; x < this.bounds.Width; x++) |
|
|
|
{ |
|
|
|
ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); |
|
|
|
int luminance = GetLuminance(pixel, this.luminanceLevels); |
|
|
|
float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin; |
|
|
|
pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|