mirror of https://github.com/SixLabors/ImageSharp
8 changed files with 256 additions and 42 deletions
@ -0,0 +1,37 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors.Normalization; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a luminance histogram equilization to the image.
|
||||
|
/// </summary>
|
||||
|
public class AutoLevelProcessor : HistogramEqualizationProcessor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="AutoLevelProcessor"/> class.
|
||||
|
/// It uses the exact minimum and maximum values found in the luminance channel, as the BlackPoint and WhitePoint to linearly stretch the colors
|
||||
|
/// (and histogram) of the image.
|
||||
|
/// </summary>
|
||||
|
/// <param name="luminanceLevels">The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
|
||||
|
/// or 65536 for 16-bit grayscale images.</param>
|
||||
|
/// <param name="clipHistogram">Indicating whether to clip the histogram bins at a specific value.</param>
|
||||
|
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
|
||||
|
public AutoLevelProcessor( |
||||
|
int luminanceLevels, |
||||
|
bool clipHistogram, |
||||
|
int clipLimit) |
||||
|
: base(luminanceLevels, clipHistogram, clipLimit) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) |
||||
|
=> new AutoLevelProcessor<TPixel>( |
||||
|
configuration, |
||||
|
this.LuminanceLevels, |
||||
|
this.ClipHistogram, |
||||
|
this.ClipLimit, |
||||
|
source, |
||||
|
sourceRectangle); |
||||
|
} |
||||
@ -0,0 +1,136 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Buffers; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors.Normalization; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a luminance histogram equalization to the image.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
internal class AutoLevelProcessor<TPixel> : HistogramEqualizationProcessor<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="AutoLevelProcessor{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
|
||||
|
/// <param name="luminanceLevels">
|
||||
|
/// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
|
||||
|
/// or 65536 for 16-bit grayscale images.
|
||||
|
/// </param>
|
||||
|
/// <param name="clipHistogram">Indicating whether to clip the histogram bins at a specific value.</param>
|
||||
|
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
|
||||
|
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
|
||||
|
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
|
||||
|
public AutoLevelProcessor( |
||||
|
Configuration configuration, |
||||
|
int luminanceLevels, |
||||
|
bool clipHistogram, |
||||
|
int clipLimit, |
||||
|
Image<TPixel> source, |
||||
|
Rectangle sourceRectangle) |
||||
|
: base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnFrameApply(ImageFrame<TPixel> source) |
||||
|
{ |
||||
|
MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; |
||||
|
int numberOfPixels = source.Width * source.Height; |
||||
|
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); |
||||
|
|
||||
|
using IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean); |
||||
|
|
||||
|
// Build the histogram of the grayscale levels.
|
||||
|
var grayscaleOperation = new GrayscaleLevelsRowOperation<TPixel>(interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels); |
||||
|
ParallelRowIterator.IterateRows( |
||||
|
this.Configuration, |
||||
|
interest, |
||||
|
in grayscaleOperation); |
||||
|
|
||||
|
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 = 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
|
||||
|
var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin); |
||||
|
ParallelRowIterator.IterateRows( |
||||
|
this.Configuration, |
||||
|
interest, |
||||
|
in cdfOperation); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A <see langword="struct"/> implementing the cdf application levels logic for <see cref="GlobalHistogramEqualizationProcessor{TPixel}"/>.
|
||||
|
/// </summary>
|
||||
|
private readonly struct CdfApplicationRowOperation : IRowOperation |
||||
|
{ |
||||
|
private readonly Rectangle bounds; |
||||
|
private readonly IMemoryOwner<int> cdfBuffer; |
||||
|
private readonly Buffer2D<TPixel> source; |
||||
|
private readonly int luminanceLevels; |
||||
|
private readonly float numberOfPixelsMinusCdfMin; |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public CdfApplicationRowOperation( |
||||
|
Rectangle bounds, |
||||
|
IMemoryOwner<int> cdfBuffer, |
||||
|
Buffer2D<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(int y) |
||||
|
{ |
||||
|
ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); |
||||
|
var sourceAccess = new PixelAccessor<TPixel>(this.source); |
||||
|
Span<TPixel> pixelRow = sourceAccess.GetRowSpan(y); |
||||
|
int levels = this.luminanceLevels; |
||||
|
float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; |
||||
|
|
||||
|
for (int x = 0; x < this.bounds.Width; x++) |
||||
|
{ |
||||
|
// TODO: We should bulk convert here.
|
||||
|
ref TPixel pixel = ref pixelRow[x]; |
||||
|
var vector = pixel.ToVector4() * levels; |
||||
|
|
||||
|
uint originalX = (uint)MathF.Round(vector.X); |
||||
|
float scaledX = Unsafe.Add(ref cdfBase, originalX) / noOfPixelsMinusCdfMin; |
||||
|
uint originalY = (uint)MathF.Round(vector.Y); |
||||
|
float scaledY = Unsafe.Add(ref cdfBase, originalY) / noOfPixelsMinusCdfMin; |
||||
|
uint originalZ = (uint)MathF.Round(vector.Z); |
||||
|
float scaledZ = Unsafe.Add(ref cdfBase, originalZ) / noOfPixelsMinusCdfMin; |
||||
|
pixel.FromVector4(new Vector4(scaledX, scaledY, scaledZ, vector.W)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Buffers; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors.Normalization; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A <see langword="struct"/> implementing the grayscale levels logic as <see cref="IRowOperation"/>.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct GrayscaleLevelsRowOperation<TPixel> : IRowOperation |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly Rectangle bounds; |
||||
|
private readonly IMemoryOwner<int> histogramBuffer; |
||||
|
private readonly Buffer2D<TPixel> source; |
||||
|
private readonly int luminanceLevels; |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public GrayscaleLevelsRowOperation( |
||||
|
Rectangle bounds, |
||||
|
IMemoryOwner<int> histogramBuffer, |
||||
|
Buffer2D<TPixel> source, |
||||
|
int luminanceLevels) |
||||
|
{ |
||||
|
this.bounds = bounds; |
||||
|
this.histogramBuffer = histogramBuffer; |
||||
|
this.source = source; |
||||
|
this.luminanceLevels = luminanceLevels; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public void Invoke(int y) |
||||
|
{ |
||||
|
ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); |
||||
|
Span<TPixel> pixelRow = this.source.DangerousGetRowSpan(y); |
||||
|
int levels = this.luminanceLevels; |
||||
|
|
||||
|
for (int x = 0; x < this.bounds.Width; x++) |
||||
|
{ |
||||
|
// TODO: We should bulk convert here.
|
||||
|
var vector = pixelRow[x].ToVector4(); |
||||
|
int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); |
||||
|
Interlocked.Increment(ref Unsafe.Add(ref histogramBase, luminance)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:25041d2dafe6c01cfec0ae3c2ec15046accd44c02b737a4cfa464ad5f61d01af |
||||
|
size 14107709 |
||||
Loading…
Reference in new issue