diff --git a/src/ImageSharp/Processing/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs
index 8dabfcc05..3a279cc5f 100644
--- a/src/ImageSharp/Processing/HistogramEqualizationExtension.cs
+++ b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs
@@ -12,25 +12,47 @@ namespace SixLabors.ImageSharp.Processing
public static class HistogramEqualizationExtension
{
///
- /// Equalizes the histogram of an image to increases the global contrast using 65536 luminance levels.
+ /// Equalizes the histogram of an image to increases the contrast.
///
/// The pixel format.
/// The image this method extends.
/// The .
public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source)
where TPixel : struct, IPixel
- => HistogramEqualization(source, 65536);
+ => HistogramEqualization(source, new HistogramEqualizationOptions());
///
- /// Equalizes the histogram of an image to increases the global contrast.
+ /// Equalizes the histogram of an image to increases the contrast.
///
/// The pixel format.
/// The image this method extends.
- /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
- /// or 65536 for 16-bit grayscale images.
+ /// The histogram equalization options to use.
/// The .
- public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source, int luminanceLevels)
+ public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source, HistogramEqualizationOptions options)
where TPixel : struct, IPixel
- => source.ApplyProcessor(new HistogramEqualizationProcessor(luminanceLevels));
+ => source.ApplyProcessor(GetProcessor(options));
+
+ private static HistogramEqualizationProcessor GetProcessor(HistogramEqualizationOptions options)
+ where TPixel : struct, IPixel
+ {
+ HistogramEqualizationProcessor processor;
+
+ switch (options.Method)
+ {
+ case HistogramEqualizationMethod.Global:
+ processor = new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit);
+ break;
+
+ case HistogramEqualizationMethod.Adaptive:
+ processor = new AdaptiveHistEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.GridSize);
+ break;
+
+ default:
+ processor = new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit);
+ break;
+ }
+
+ return processor;
+ }
}
}
diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs
index 3d2ef02d5..218d2680f 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs
@@ -22,18 +22,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
///
/// Initializes a new instance of the class.
///
- /// The grid size of the adaptive histogram equalization.
- /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
/// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images.
- public AdaptiveHistEqualizationProcessor(int gridSize, int clipLimit, int luminanceLevels)
- : base(luminanceLevels)
+ /// 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.
+ /// The grid size of the adaptive histogram equalization.
+ public AdaptiveHistEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit, int gridSize)
+ : base(luminanceLevels, clipHistogram, clipLimit)
{
Guard.MustBeGreaterThan(gridSize, 8, nameof(gridSize));
- Guard.MustBeGreaterThan(clipLimit, 1, nameof(clipLimit));
this.GridSize = gridSize;
- this.ClipLimit = clipLimit;
}
///
@@ -41,11 +40,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
///
public int GridSize { get; }
- ///
- /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
- ///
- public int ClipLimit { get; }
-
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
{
@@ -82,23 +76,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
for (int y = 0; y < source.Height; y++)
{
- // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration
- histogram.CopyTo(histogramCopy);
- this.ClipHistogram(histogramCopy, this.ClipLimit);
+ if (this.ClipHistogramEnabled)
+ {
+ // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration.
+ histogram.CopyTo(histogramCopy);
+ this.ClipHistogram(histogramCopy, this.ClipLimit);
+ }
+
+ // Calculate the cumulative distribution function, which will map each input pixel in the current grid to a new value.
cdf.Clear();
- int cdfMin = this.CalculateCdf(cdf, histogramCopy);
- double numberOfPixelsMinusCdfMin = (double)(pixelsInGrid - cdfMin);
+ int cdfMin = this.ClipHistogramEnabled ? this.CalculateCdf(cdf, histogramCopy) : this.CalculateCdf(cdf, histogram);
+ float numberOfPixelsMinusCdfMin = pixelsInGrid - cdfMin;
// Map the current pixel to the new equalized value
int luminance = this.GetLuminance(source[x, y], this.LuminanceLevels);
- double luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin;
+ float luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin;
targetPixels[x, y].PackFromVector4(new Vector4((float)luminanceEqualized));
- // Remove top most row from the histogram, mirroring rows which exceeds the borders
+ // Remove top most row from the histogram, mirroring rows which exceeds the borders.
Span rowSpan = this.GetPixelRow(source, x - halfGridSize, y - halfGridSize, this.GridSize);
this.RemovePixelsFromHistogram(rowSpan, histogram, this.LuminanceLevels);
- // Add new bottom row to the histogram, mirroring rows which exceeds the borders
+ // Add new bottom row to the histogram, mirroring rows which exceeds the borders.
rowSpan = this.GetPixelRow(source, x - halfGridSize, y + halfGridSize, this.GridSize);
this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels);
}
@@ -162,32 +161,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
return source.GetPixelRowSpan(y).Slice(start: x, length: gridSize);
}
- ///
- /// AHE tends to over amplify the contrast in near-constant regions of the image, since the histogram in such regions is highly concentrated.
- /// Clipping the histogram is meant to reduce this effect, by cutting of histogram bin's which exceed a certain amount and redistribute
- /// the values over the clip limit to all other bins equally.
- ///
- /// The histogram to apply the clipping.
- /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
- private void ClipHistogram(Span histogram, int clipLimit)
- {
- int sumOverClip = 0;
- for (int i = 0; i < histogram.Length; i++)
- {
- if (histogram[i] > clipLimit)
- {
- sumOverClip += histogram[i] - clipLimit;
- histogram[i] = clipLimit;
- }
- }
-
- int addToEachBin = (int)Math.Floor(sumOverClip / (double)this.LuminanceLevels);
- for (int i = 0; i < histogram.Length; i++)
- {
- histogram[i] += addToEachBin;
- }
- }
-
///
/// Adds a row of grey values to the histogram.
///
diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs
new file mode 100644
index 000000000..423840097
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Memory;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Normalization
+{
+ ///
+ /// Applies a global histogram equalization to the image.
+ ///
+ /// The pixel format.
+ internal class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
+ /// 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.
+ public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit)
+ : base(luminanceLevels, clipHistogram, clipLimit)
+ {
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ {
+ MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
+ int numberOfPixels = source.Width * source.Height;
+ Span pixels = source.GetPixelSpan();
+
+ // Build the histogram of the grayscale levels.
+ using (IBuffer histogramBuffer = memoryAllocator.AllocateClean(this.LuminanceLevels))
+ using (IBuffer cdfBuffer = memoryAllocator.AllocateClean(this.LuminanceLevels))
+ {
+ Span histogram = histogramBuffer.GetSpan();
+ for (int i = 0; i < pixels.Length; i++)
+ {
+ TPixel sourcePixel = pixels[i];
+ int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels);
+ histogram[luminance]++;
+ }
+
+ if (this.ClipHistogramEnabled)
+ {
+ this.ClipHistogram(histogram, this.ClipLimit);
+ }
+
+ // Calculate the cumulative distribution function, which will map each input pixel to a new value.
+ Span cdf = cdfBuffer.GetSpan();
+ int cdfMin = this.CalculateCdf(cdf, histogram);
+
+ // Apply the cdf to each pixel of the image
+ float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin;
+ for (int i = 0; i < pixels.Length; i++)
+ {
+ TPixel sourcePixel = pixels[i];
+
+ int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels);
+ float luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin;
+
+ pixels[i].PackFromVector4(new Vector4(luminanceEqualized));
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs
new file mode 100644
index 000000000..cae745c9b
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Processing.Processors.Normalization
+{
+ ///
+ /// Enumerates the different types of defined histogram equalization methods.
+ ///
+ public enum HistogramEqualizationMethod : int
+ {
+ ///
+ /// A global histogram equalization.
+ ///
+ Global,
+
+ ///
+ /// Adaptive histogram equalization.
+ ///
+ Adaptive
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs
new file mode 100644
index 000000000..20aa113ac
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Processing.Processors.Normalization
+{
+ ///
+ /// Data container providing the different options for the histogram equalization.
+ ///
+ public class HistogramEqualizationOptions
+ {
+ ///
+ /// Gets or sets the histogram equalization method to use. Defaults to global histogram equalization.
+ ///
+ public HistogramEqualizationMethod Method { get; set; } = HistogramEqualizationMethod.Global;
+
+ ///
+ /// Gets or sets the number of different luminance levels. Typical values are 256 for 8-bit grayscale images
+ /// or 65536 for 16-bit grayscale images. Defaults to 256.
+ ///
+ public int LuminanceLevels { get; set; } = 256;
+
+ ///
+ /// Gets or sets a value indicating whether to clip the histogram bins at a specific value. Defaults to true.
+ ///
+ public bool ClipHistogram { get; set; } = true;
+
+ ///
+ /// Gets or sets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
+ /// Defaults to 80.
+ ///
+ public int ClipLimit { get; set; } = 80;
+
+ ///
+ /// Gets or sets the size of the grid for the adaptive histogram equalization. Defaults to 32.
+ ///
+ public int GridSize { get; set; } = 32;
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
index ab4f8332b..298de1388 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
@@ -2,20 +2,15 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Numerics;
-using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Memory;
-using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
///
- /// Applies a global histogram equalization to the image.
+ /// Defines a processor that normalizes the histogram of an image.
///
/// The pixel format.
- internal class HistogramEqualizationProcessor : ImageProcessor
+ internal abstract class HistogramEqualizationProcessor : ImageProcessor
where TPixel : struct, IPixel
{
///
@@ -23,11 +18,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
///
/// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images.
- public HistogramEqualizationProcessor(int luminanceLevels)
+ /// Indicates, if histogram bins should be clipped.
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
+ protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit)
{
Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels));
+ Guard.MustBeGreaterThan(clipLimit, 1, nameof(clipLimit));
this.LuminanceLevels = luminanceLevels;
+ this.ClipHistogramEnabled = clipHistogram;
+ this.ClipLimit = clipLimit;
}
///
@@ -35,42 +35,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
///
public int LuminanceLevels { get; }
- ///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
- int numberOfPixels = source.Width * source.Height;
- Span pixels = source.GetPixelSpan();
-
- // Build the histogram of the grayscale levels.
- using (IBuffer histogramBuffer = memoryAllocator.AllocateClean(this.LuminanceLevels))
- using (IBuffer cdfBuffer = memoryAllocator.AllocateClean(this.LuminanceLevels))
- {
- Span histogram = histogramBuffer.GetSpan();
- for (int i = 0; i < pixels.Length; i++)
- {
- TPixel sourcePixel = pixels[i];
- int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels);
- histogram[luminance]++;
- }
-
- // Calculate the cumulative distribution function, which will map each input pixel to a new value.
- Span cdf = cdfBuffer.GetSpan();
- int cdfMin = this.CalculateCdf(cdf, histogram);
-
- // Apply the cdf to each pixel of the image
- float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin;
- for (int i = 0; i < pixels.Length; i++)
- {
- TPixel sourcePixel = pixels[i];
-
- int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels);
- float luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin;
+ ///
+ /// Gets a value indicating whether to clip the histogram bins at a specific value.
+ ///
+ public bool ClipHistogramEnabled { get; }
- pixels[i].PackFromVector4(new Vector4(luminanceEqualized));
- }
- }
- }
+ ///
+ /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
+ ///
+ public int ClipLimit { get; }
///
/// Calculates the cumulative distribution function.
@@ -108,12 +81,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
return cdfMin;
}
+ ///
+ /// AHE tends to over amplify the contrast in near-constant regions of the image, since the histogram in such regions is highly concentrated.
+ /// Clipping the histogram is meant to reduce this effect, by cutting of histogram bin's which exceed a certain amount and redistribute
+ /// the values over the clip limit to all other bins equally.
+ ///
+ /// The histogram to apply the clipping.
+ /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
+ protected void ClipHistogram(Span histogram, int clipLimit)
+ {
+ int sumOverClip = 0;
+ for (int i = 0; i < histogram.Length; i++)
+ {
+ if (histogram[i] > clipLimit)
+ {
+ sumOverClip += histogram[i] - clipLimit;
+ histogram[i] = clipLimit;
+ }
+ }
+
+ int addToEachBin = (int)Math.Floor(sumOverClip / (double)this.LuminanceLevels);
+ for (int i = 0; i < histogram.Length; i++)
+ {
+ histogram[i] += addToEachBin;
+ }
+ }
+
///
/// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
///
/// The pixel to get the luminance from
/// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)
- [MethodImpl(InliningOptions.ShortMethod)]
+ [System.Runtime.CompilerServices.MethodImpl(InliningOptions.ShortMethod)]
protected int GetLuminance(TPixel sourcePixel, int luminanceLevels)
{
// Convert to grayscale using ITU-R Recommendation BT.709
diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs
index 1595ed32c..86d343e42 100644
--- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs
@@ -3,6 +3,7 @@
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Processors.Normalization;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Normalization
@@ -50,7 +51,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization
};
// Act
- image.Mutate(x => x.HistogramEqualization(luminanceLevels));
+ image.Mutate(x => x.HistogramEqualization(new HistogramEqualizationOptions()
+ {
+ LuminanceLevels = luminanceLevels
+ }));
// Assert
for (int y = 0; y < 8; y++)