Browse Source

Add SyncChannels option

pull/2235/head
Ynse Hoornenborg 3 years ago
parent
commit
34891da46b
  1. 11
      src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor.cs
  2. 86
      src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs
  3. 7
      src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs
  4. 2
      src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
  5. 22
      tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs
  6. 0
      tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png
  7. 3
      tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png

11
src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor.cs

@ -17,14 +17,22 @@ public class AutoLevelProcessor : HistogramEqualizationProcessor
/// 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="syncChannels">Whether to apply a synchronized luminance value to each color channel.</param>
public AutoLevelProcessor(
int luminanceLevels,
bool clipHistogram,
int clipLimit)
int clipLimit,
bool syncChannels)
: base(luminanceLevels, clipHistogram, clipLimit)
{
this.SyncChannels = syncChannels;
}
/// <summary>
/// Gets whether to apply a synchronized luminance value to each color channel.
/// </summary>
public bool SyncChannels { get; }
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new AutoLevelProcessor<TPixel>(
@ -32,6 +40,7 @@ public class AutoLevelProcessor : HistogramEqualizationProcessor
this.LuminanceLevels,
this.ClipHistogram,
this.ClipLimit,
this.SyncChannels,
source,
sourceRectangle);
}

86
src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs

@ -30,17 +30,25 @@ internal class AutoLevelProcessor<TPixel> : HistogramEqualizationProcessor<TPixe
/// <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>
/// <param name="syncChannels">Whether to apply a synchronized luminance value to each color channel.</param>
public AutoLevelProcessor(
Configuration configuration,
int luminanceLevels,
bool clipHistogram,
int clipLimit,
bool syncChannels,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle)
{
this.SyncChannels = syncChannels;
}
/// <summary>
/// Gets whether to apply a synchronized luminance value to each color channel.
/// </summary>
private bool SyncChannels { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
@ -73,18 +81,78 @@ internal class AutoLevelProcessor<TPixel> : HistogramEqualizationProcessor<TPixe
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);
if (this.SyncChannels)
{
var cdfOperation = new SynchronizedChannelsRowOperation(interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin);
ParallelRowIterator.IterateRows(
this.Configuration,
interest,
in cdfOperation);
}
else
{
var cdfOperation = new SeperateChannelsRowOperation(interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin);
ParallelRowIterator.IterateRows(
this.Configuration,
interest,
in cdfOperation);
}
}
/// <summary>
/// A <see langword="struct"/> implementing the cdf logic for synchronized color channels.
/// </summary>
private readonly struct SynchronizedChannelsRowOperation : 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 SynchronizedChannelsRowOperation(
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();
int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels);
float scaledLuminance = Unsafe.Add(ref cdfBase, luminance) / noOfPixelsMinusCdfMin;
float scalingFactor = scaledLuminance * levels / luminance;
Vector4 scaledVector = new Vector4(scalingFactor * vector.X, scalingFactor * vector.Y, scalingFactor * vector.Z, vector.W);
pixel.FromVector4(scaledVector);
}
}
}
/// <summary>
/// A <see langword="struct"/> implementing the cdf application levels logic for <see cref="GlobalHistogramEqualizationProcessor{TPixel}"/>.
/// A <see langword="struct"/> implementing the cdf logic for separate color channels.
/// </summary>
private readonly struct CdfApplicationRowOperation : IRowOperation
private readonly struct SeperateChannelsRowOperation : IRowOperation
{
private readonly Rectangle bounds;
private readonly IMemoryOwner<int> cdfBuffer;
@ -93,7 +161,7 @@ internal class AutoLevelProcessor<TPixel> : HistogramEqualizationProcessor<TPixe
private readonly float numberOfPixelsMinusCdfMin;
[MethodImpl(InliningOptions.ShortMethod)]
public CdfApplicationRowOperation(
public SeperateChannelsRowOperation(
Rectangle bounds,
IMemoryOwner<int> cdfBuffer,
Buffer2D<TPixel> source,

7
src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs

@ -42,4 +42,11 @@ public class HistogramEqualizationOptions
/// Defaults to 8.
/// </summary>
public int NumberOfTiles { get; set; } = 8;
/// <summary>
/// Gets or sets a value indicating whether to synchronize the scaling factor over all color channels.
/// This parameter is only applicable to AutoLevel and is ignored for all others.
/// Defaults to true.
/// </summary>
public bool SyncChannels { get; set; } = true;
}

2
src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs

@ -61,7 +61,7 @@ public abstract class HistogramEqualizationProcessor : IImageProcessor
=> new AdaptiveHistogramEqualizationSlidingWindowProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.NumberOfTiles),
HistogramEqualizationMethod.AutoLevel
=> new AutoLevelProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit),
=> new AutoLevelProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.SyncChannels),
_ => new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit),
};

22
tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs

@ -136,7 +136,7 @@ public class HistogramEqualizationTests
[Theory]
[WithFile(TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, PixelTypes.Rgba32)]
public void AutoLevel_CompareToReferenceOutput<TPixel>(TestImageProvider<TPixel> provider)
public void AutoLevel_SeparateChannels_CompareToReferenceOutput<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
@ -145,6 +145,26 @@ public class HistogramEqualizationTests
{
Method = HistogramEqualizationMethod.AutoLevel,
LuminanceLevels = 256,
SyncChannels = false
};
image.Mutate(x => x.HistogramEqualization(options));
image.DebugSave(provider);
image.CompareToReferenceOutput(ValidatorComparer, provider, extension: "png");
}
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, PixelTypes.Rgba32)]
public void AutoLevel_SynchronizedChannels_CompareToReferenceOutput<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
var options = new HistogramEqualizationOptions
{
Method = HistogramEqualizationMethod.AutoLevel,
LuminanceLevels = 256,
SyncChannels = true
};
image.Mutate(x => x.HistogramEqualization(options));
image.DebugSave(provider);

0
tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_CompareToReferenceOutput_Rgba32_forest_bridge.png → tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png

3
tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dca9b5b890d3a79b0002b7093d254d484ada4207e5010d1f0c6248d4dd6e22db
size 13909894
Loading…
Cancel
Save