From 34891da46b32fa87bb4183112c6cfa2fda2015c8 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 17 Sep 2022 14:18:34 +0200 Subject: [PATCH] Add SyncChannels option --- .../Normalization/AutoLevelProcessor.cs | 11 ++- .../AutoLevelProcessor{TPixel}.cs | 86 +++++++++++++++++-- .../HistogramEqualizationOptions.cs | 7 ++ .../HistogramEqualizationProcessor.cs | 2 +- .../HistogramEqualizationTests.cs | 22 ++++- ...oReferenceOutput_Rgba32_forest_bridge.png} | 0 ...ToReferenceOutput_Rgba32_forest_bridge.png | 3 + 7 files changed, 119 insertions(+), 12 deletions(-) rename tests/Images/External/ReferenceOutput/HistogramEqualizationTests/{AutoLevel_CompareToReferenceOutput_Rgba32_forest_bridge.png => AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png} (100%) create mode 100644 tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png diff --git a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor.cs index b33e46ce3..2721efac6 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor.cs @@ -17,14 +17,22 @@ public class AutoLevelProcessor : HistogramEqualizationProcessor /// or 65536 for 16-bit grayscale images. /// Indicating whether to clip the histogram bins at a specific value. /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + /// Whether to apply a synchronized luminance value to each color channel. public AutoLevelProcessor( int luminanceLevels, bool clipHistogram, - int clipLimit) + int clipLimit, + bool syncChannels) : base(luminanceLevels, clipHistogram, clipLimit) { + this.SyncChannels = syncChannels; } + /// + /// Gets whether to apply a synchronized luminance value to each color channel. + /// + public bool SyncChannels { get; } + /// public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => new AutoLevelProcessor( @@ -32,6 +40,7 @@ public class AutoLevelProcessor : HistogramEqualizationProcessor this.LuminanceLevels, this.ClipHistogram, this.ClipLimit, + this.SyncChannels, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs index bdb2a500b..28a799a61 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs @@ -30,17 +30,25 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessorThe histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. /// The source for the current processor instance. /// The source area to process for the current processor instance. + /// Whether to apply a synchronized luminance value to each color channel. public AutoLevelProcessor( Configuration configuration, int luminanceLevels, bool clipHistogram, int clipLimit, + bool syncChannels, Image source, Rectangle sourceRectangle) : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) { + this.SyncChannels = syncChannels; } + /// + /// Gets whether to apply a synchronized luminance value to each color channel. + /// + private bool SyncChannels { get; } + /// protected override void OnFrameApply(ImageFrame source) { @@ -73,18 +81,78 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor + /// A implementing the cdf logic for synchronized color channels. + /// + private readonly struct SynchronizedChannelsRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly IMemoryOwner cdfBuffer; + private readonly Buffer2D source; + private readonly int luminanceLevels; + private readonly float numberOfPixelsMinusCdfMin; + + [MethodImpl(InliningOptions.ShortMethod)] + public SynchronizedChannelsRowOperation( + Rectangle bounds, + IMemoryOwner cdfBuffer, + Buffer2D source, + int luminanceLevels, + float numberOfPixelsMinusCdfMin) + { + this.bounds = bounds; + this.cdfBuffer = cdfBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; + this.numberOfPixelsMinusCdfMin = numberOfPixelsMinusCdfMin; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); + var sourceAccess = new PixelAccessor(this.source); + Span 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); + } + } } /// - /// A implementing the cdf application levels logic for . + /// A implementing the cdf logic for separate color channels. /// - private readonly struct CdfApplicationRowOperation : IRowOperation + private readonly struct SeperateChannelsRowOperation : IRowOperation { private readonly Rectangle bounds; private readonly IMemoryOwner cdfBuffer; @@ -93,7 +161,7 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor cdfBuffer, Buffer2D source, diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs index 634378842..1736a067a 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs @@ -42,4 +42,11 @@ public class HistogramEqualizationOptions /// Defaults to 8. /// public int NumberOfTiles { get; set; } = 8; + + /// + /// 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. + /// + public bool SyncChannels { get; set; } = true; } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index d493d1734..8a9056b1f 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs +++ b/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), }; diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs index 9ef69f76e..60e33835a 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs +++ b/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(TestImageProvider provider) + public void AutoLevel_SeparateChannels_CompareToReferenceOutput(TestImageProvider provider) where TPixel : unmanaged, IPixel { using (Image 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(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new HistogramEqualizationOptions + { + Method = HistogramEqualizationMethod.AutoLevel, + LuminanceLevels = 256, + SyncChannels = true }; image.Mutate(x => x.HistogramEqualization(options)); image.DebugSave(provider); diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_CompareToReferenceOutput_Rgba32_forest_bridge.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png similarity index 100% rename from tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_CompareToReferenceOutput_Rgba32_forest_bridge.png rename to tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png new file mode 100644 index 000000000..ff5b35a5f --- /dev/null +++ b/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