|
|
@ -9,17 +9,23 @@ using SixLabors.Primitives; |
|
|
|
|
|
|
|
|
namespace SixLabors.ImageSharp.Processing.Contrast |
|
|
namespace SixLabors.ImageSharp.Processing.Contrast |
|
|
{ |
|
|
{ |
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Applies a global histogram equalization to the image.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
internal class HistogramEqualizationProcessor<TPixel> : ImageProcessor<TPixel> |
|
|
internal class HistogramEqualizationProcessor<TPixel> : ImageProcessor<TPixel> |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
{ |
|
|
{ |
|
|
/// <inheritdoc/>
|
|
|
/// <inheritdoc/>
|
|
|
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|
|
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|
|
{ |
|
|
{ |
|
|
var rgb = default(Rgb24); |
|
|
var rgb48 = default(Rgb48); |
|
|
|
|
|
var rgb24 = default(Rgb24); |
|
|
int numberOfPixels = source.Width * source.Height; |
|
|
int numberOfPixels = source.Width * source.Height; |
|
|
|
|
|
bool is16bitPerChannel = typeof(TPixel) == typeof(Rgb48) || typeof(TPixel) == typeof(Rgba64); |
|
|
|
|
|
|
|
|
// build the histogram of the grayscale levels
|
|
|
// build the histogram of the grayscale levels
|
|
|
int luminanceLevels = 256; |
|
|
int luminanceLevels = is16bitPerChannel ? 65536 : 256; |
|
|
int[] histogram = new int[luminanceLevels]; |
|
|
int[] histogram = new int[luminanceLevels]; |
|
|
for (int y = 0; y < source.Height; y++) |
|
|
for (int y = 0; y < source.Height; y++) |
|
|
{ |
|
|
{ |
|
|
@ -27,15 +33,12 @@ namespace SixLabors.ImageSharp.Processing.Contrast |
|
|
for (int x = 0; x < source.Width; x++) |
|
|
for (int x = 0; x < source.Width; x++) |
|
|
{ |
|
|
{ |
|
|
TPixel sourcePixel = row[x]; |
|
|
TPixel sourcePixel = row[x]; |
|
|
sourcePixel.ToRgb24(ref rgb); |
|
|
int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); |
|
|
|
|
|
|
|
|
// Convert to grayscale using ITU-R Recommendation BT.709 if required
|
|
|
|
|
|
int luminance = (int)((.2126F * rgb.R) + (.7152F * rgb.G) + (.0722F * rgb.B)); |
|
|
|
|
|
histogram[luminance]++; |
|
|
histogram[luminance]++; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// calculate the cumulative distribution function which will be the cumulative histogram
|
|
|
// calculate the cumulative distribution function (which will be the cumulative histogram)
|
|
|
int[] cdf = new int[luminanceLevels]; |
|
|
int[] cdf = new int[luminanceLevels]; |
|
|
int histSum = 0; |
|
|
int histSum = 0; |
|
|
for (int i = 0; i < histogram.Length; i++) |
|
|
for (int i = 0; i < histogram.Length; i++) |
|
|
@ -69,13 +72,46 @@ namespace SixLabors.ImageSharp.Processing.Contrast |
|
|
for (int x = 0; x < source.Width; x++) |
|
|
for (int x = 0; x < source.Width; x++) |
|
|
{ |
|
|
{ |
|
|
TPixel sourcePixel = row[x]; |
|
|
TPixel sourcePixel = row[x]; |
|
|
sourcePixel.ToRgb24(ref rgb); |
|
|
|
|
|
int luminance = (int)((.2126F * rgb.R) + (.7152F * rgb.G) + (.0722F * rgb.B)); |
|
|
int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); |
|
|
double luminanceEqualized = (lut[luminance] / numberOfPixelsMinusCdfMin) * luminanceLevelsMinusOne; |
|
|
double luminanceEqualized = (lut[luminance] / numberOfPixelsMinusCdfMin) * luminanceLevelsMinusOne; |
|
|
luminanceEqualized = Math.Round(luminanceEqualized); |
|
|
luminanceEqualized = Math.Round(luminanceEqualized); |
|
|
row[x].PackFromRgba32(new Rgba32((byte)luminanceEqualized, (byte)luminanceEqualized, (byte)luminanceEqualized)); |
|
|
|
|
|
|
|
|
if (is16bitPerChannel) |
|
|
|
|
|
{ |
|
|
|
|
|
row[x].PackFromRgb48(new Rgb48((ushort)luminanceEqualized, (ushort)luminanceEqualized, (ushort)luminanceEqualized)); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
row[x].PackFromRgba32(new Rgba32((byte)luminanceEqualized, (byte)luminanceEqualized, (byte)luminanceEqualized)); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sourcePixel">The pixel to get the luminance from</param>
|
|
|
|
|
|
/// <param name="is16bitPerChannel">Flag indicates, if its 16 bits per channel, otherwise its 8</param>
|
|
|
|
|
|
/// <param name="rgb24">Will store the pixel values in case of 8 bit per channel</param>
|
|
|
|
|
|
/// <param name="rgb48">Will store the pixel values in case of 16 bit per channel</param>
|
|
|
|
|
|
private int GetLuminance(TPixel sourcePixel, bool is16bitPerChannel, ref Rgb24 rgb24, ref Rgb48 rgb48) |
|
|
|
|
|
{ |
|
|
|
|
|
// Convert to grayscale using ITU-R Recommendation BT.709
|
|
|
|
|
|
int luminance; |
|
|
|
|
|
if (is16bitPerChannel) |
|
|
|
|
|
{ |
|
|
|
|
|
sourcePixel.ToRgb48(ref rgb48); |
|
|
|
|
|
luminance = (int)((.2126F * rgb48.R) + (.7152F * rgb48.G) + (.0722F * rgb48.B)); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
sourcePixel.ToRgb24(ref rgb24); |
|
|
|
|
|
luminance = (int)((.2126F * rgb24.R) + (.7152F * rgb24.G) + (.0722F * rgb24.B)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return luminance; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|