From 5c89f6c89b4fb884ed67b92a7523fdc6084beaa3 Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 30 Jun 2018 15:46:22 +0200 Subject: [PATCH 01/15] added first attempt of histogram equalization --- .../HistogramEqualizationExtension.cs | 23 +++++++ .../HistogramEqualizationProcessor.cs | 64 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/ImageSharp/Processing/Contrast/HistogramEqualizationExtension.cs create mode 100644 src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs diff --git a/src/ImageSharp/Processing/Contrast/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/Contrast/HistogramEqualizationExtension.cs new file mode 100644 index 000000000..1cd29f8b4 --- /dev/null +++ b/src/ImageSharp/Processing/Contrast/HistogramEqualizationExtension.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Contrast +{ + /// + /// Adds extension that allows applying an HistogramEqualization to the image. + /// + public static class HistogramEqualizationExtension + { + /// + /// Equalizes the histogram of an image to increases the global contrast. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) + where TPixel : struct, IPixel + => source.ApplyProcessor(new HistogramEqualizationProcessor()); + } +} diff --git a/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs new file mode 100644 index 000000000..2bacb98ce --- /dev/null +++ b/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Contrast +{ + internal class HistogramEqualizationProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + var rgb = default(Rgb24); + int numberOfPixels = source.Width * source.Height; + + // build the histogram of the grayscale levels + int luminanceLevels = 256; + int[] histogram = new int[luminanceLevels]; + for (int y = 0; y < source.Height; y++) + { + Span row = source.GetPixelRowSpan(y); + for (int x = 0; x < source.Width; x++) + { + TPixel sourcePixel = row[x]; + sourcePixel.ToRgb24(ref rgb); + + // 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]++; + } + } + + // calculate the cumulative distribution function + double[] cdf = new double[luminanceLevels]; + double sum = 0.0d; + for (int i = 0; i < histogram.Length; i++) + { + double p = (double)histogram[i] / numberOfPixels; + sum += p; + cdf[i] = sum; + } + + // apply the cdf to each pixel of the image + for (int y = 0; y < source.Height; y++) + { + Span row = source.GetPixelRowSpan(y); + for (int x = 0; x < source.Width; x++) + { + TPixel sourcePixel = row[x]; + sourcePixel.ToRgb24(ref rgb); + int luminance = (int)((.2126F * rgb.R) + (.7152F * rgb.G) + (.0722F * rgb.B)); + byte luminanceEqualized = (byte)(cdf[luminance] * luminance); + + row[x].PackFromRgba32(new Rgba32(luminanceEqualized, luminanceEqualized, luminanceEqualized)); + } + } + } + } +} From 68d13b6ecb48f781e2fa76849e677ac361536151 Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 30 Jun 2018 16:25:14 +0200 Subject: [PATCH 02/15] added HistogramEqualizationTest --- .../HistogramEqualizationProcessor.cs | 23 ++++++- .../Contrast/HistogramEqualizationTests.cs | 66 +++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs diff --git a/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs index 2bacb98ce..23a2d30ba 100644 --- a/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs @@ -35,6 +35,26 @@ namespace SixLabors.ImageSharp.Processing.Contrast } } + int min = luminanceLevels - 1; + for (int i = 0; i < histogram.Length - 1; i++) + { + if (histogram[i] != 0) + { + min = i; + break; + } + } + + int max = 0; + for (int i = histogram.Length - 1; i > 0; i--) + { + if (histogram[i] != 0) + { + max = i; + break; + } + } + // calculate the cumulative distribution function double[] cdf = new double[luminanceLevels]; double sum = 0.0d; @@ -54,8 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Contrast TPixel sourcePixel = row[x]; sourcePixel.ToRgb24(ref rgb); int luminance = (int)((.2126F * rgb.R) + (.7152F * rgb.G) + (.0722F * rgb.B)); - byte luminanceEqualized = (byte)(cdf[luminance] * luminance); - + byte luminanceEqualized = (byte)(cdf[luminance] * (luminanceLevels - 1)); row[x].PackFromRgba32(new Rgba32(luminanceEqualized, luminanceEqualized, luminanceEqualized)); } } diff --git a/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs new file mode 100644 index 000000000..b5c584a55 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Contrast; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Contrast +{ + public class HistogramEqualizationTests + { + [Fact] + public void HistogramEqualizationTest() + { + // arrange + byte[] pixels = new byte[] + { + 52, 55, 61, 59, 70, 61, 76, 61, + 62, 59, 55, 104, 94, 85, 59, 71, + 63, 65, 66, 113, 144, 104, 63, 72, + 64, 70, 70, 126, 154, 109, 71, 69, + 67, 73, 68, 106, 122, 88, 68, 68, + 68, 79, 60, 79, 77, 66, 58, 75, + 69, 85, 64, 58, 55, 61, 65, 83, + 70, 87, 69, 68, 65, 73, 78, 90 + }; + var image = new Image(8, 8); + for (int y = 0; y < 8; y++) + { + for (int x = 0; x < 8; x++) + { + byte luminance = pixels[y * 8 + x]; + image[x, y] = new Rgba32(luminance, luminance, luminance); + } + } + + byte[] expected = new byte[] + { + 0, 12, 53, 32, 146, 53, 174, 53, + 57, 32, 12, 227, 219, 202, 32, 154, + 65, 85, 93, 239, 251, 227, 65, 158, + 73, 146, 146, 247, 255, 235, 154, 130, + 97, 166, 117, 231, 243, 210, 117, 117, + 117, 190, 36, 190, 178, 93, 20, 170, + 130, 202, 73, 20, 12, 53, 85, 194, + 146, 206, 130, 117, 85, 166, 182, 215 + }; + + // act + image.Mutate(x => x.HistogramEqualization()); + + // assert + for (int y = 0; y < 8; y++) + { + for (int x = 0; x < 8; x++) + { + Rgba32 actual = image[x, y]; + int diff = expected[y * 8 + x] - actual.R; + Assert.True(diff == 0); + int foo = 2; + } + } + } + } +} From bbe0e59c9b447364efde4bc389a19c1c4ccf1fe6 Mon Sep 17 00:00:00 2001 From: popow Date: Sun, 1 Jul 2018 13:29:46 +0200 Subject: [PATCH 03/15] changed the cdf to be the cumulative histogram --- .../HistogramEqualizationProcessor.cs | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs index 23a2d30ba..f23382d20 100644 --- a/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs @@ -35,37 +35,34 @@ namespace SixLabors.ImageSharp.Processing.Contrast } } - int min = luminanceLevels - 1; - for (int i = 0; i < histogram.Length - 1; i++) + // calculate the cumulative distribution function which will be the cumulative histogram + int[] cdf = new int[luminanceLevels]; + int histSum = 0; + for (int i = 0; i < histogram.Length; i++) { - if (histogram[i] != 0) - { - min = i; - break; - } + histSum += histogram[i]; + cdf[i] = histSum; } - int max = 0; - for (int i = histogram.Length - 1; i > 0; i--) + int cdfMin = 0; + for (int i = 0; i < histogram.Length; i++) { if (histogram[i] != 0) { - max = i; + cdfMin = cdf[i]; break; } } - // calculate the cumulative distribution function - double[] cdf = new double[luminanceLevels]; - double sum = 0.0d; + int[] lut = new int[luminanceLevels]; for (int i = 0; i < histogram.Length; i++) { - double p = (double)histogram[i] / numberOfPixels; - sum += p; - cdf[i] = sum; + lut[i] = cdf[i] - cdfMin; } // apply the cdf to each pixel of the image + double numberOfPixelsMinusCdfMin = (double)(numberOfPixels - cdfMin); + int luminanceLevelsMinusOne = luminanceLevels - 1; for (int y = 0; y < source.Height; y++) { Span row = source.GetPixelRowSpan(y); @@ -74,8 +71,9 @@ namespace SixLabors.ImageSharp.Processing.Contrast TPixel sourcePixel = row[x]; sourcePixel.ToRgb24(ref rgb); int luminance = (int)((.2126F * rgb.R) + (.7152F * rgb.G) + (.0722F * rgb.B)); - byte luminanceEqualized = (byte)(cdf[luminance] * (luminanceLevels - 1)); - row[x].PackFromRgba32(new Rgba32(luminanceEqualized, luminanceEqualized, luminanceEqualized)); + double luminanceEqualized = (lut[luminance] / numberOfPixelsMinusCdfMin) * luminanceLevelsMinusOne; + luminanceEqualized = Math.Round(luminanceEqualized); + row[x].PackFromRgba32(new Rgba32((byte)luminanceEqualized, (byte)luminanceEqualized, (byte)luminanceEqualized)); } } } From 72a4ee48281263ef342cb980bc10ddaf95f5b214 Mon Sep 17 00:00:00 2001 From: popow Date: Sun, 1 Jul 2018 19:09:00 +0200 Subject: [PATCH 04/15] added support for 16 bit greyscale --- .../HistogramEqualizationExtension.cs | 2 +- .../HistogramEqualizationProcessor.cs | 56 +++++++++++++++---- .../Contrast/HistogramEqualizationTests.cs | 17 +++--- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Processing/Contrast/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/Contrast/HistogramEqualizationExtension.cs index 1cd29f8b4..a7b59cedd 100644 --- a/src/ImageSharp/Processing/Contrast/HistogramEqualizationExtension.cs +++ b/src/ImageSharp/Processing/Contrast/HistogramEqualizationExtension.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Contrast /// /// The pixel format. /// The image this method extends. - /// The . + /// A histogram equalized grayscale image. public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) where TPixel : struct, IPixel => source.ApplyProcessor(new HistogramEqualizationProcessor()); diff --git a/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs index f23382d20..df1737455 100644 --- a/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs @@ -9,17 +9,23 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Contrast { + /// + /// Applies a global histogram equalization to the image. + /// + /// The pixel format. internal class HistogramEqualizationProcessor : ImageProcessor where TPixel : struct, IPixel { /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - var rgb = default(Rgb24); + var rgb48 = default(Rgb48); + var rgb24 = default(Rgb24); int numberOfPixels = source.Width * source.Height; + bool is16bitPerChannel = typeof(TPixel) == typeof(Rgb48) || typeof(TPixel) == typeof(Rgba64); // build the histogram of the grayscale levels - int luminanceLevels = 256; + int luminanceLevels = is16bitPerChannel ? 65536 : 256; int[] histogram = new int[luminanceLevels]; 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++) { TPixel sourcePixel = row[x]; - sourcePixel.ToRgb24(ref rgb); - - // Convert to grayscale using ITU-R Recommendation BT.709 if required - int luminance = (int)((.2126F * rgb.R) + (.7152F * rgb.G) + (.0722F * rgb.B)); + int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); 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 histSum = 0; 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++) { 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; 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)); + } } } } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The pixel to get the luminance from + /// Flag indicates, if its 16 bits per channel, otherwise its 8 + /// Will store the pixel values in case of 8 bit per channel + /// Will store the pixel values in case of 16 bit per channel + 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; + } } } diff --git a/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs index b5c584a55..db2282ccd 100644 --- a/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs @@ -37,14 +37,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Contrast byte[] expected = new byte[] { - 0, 12, 53, 32, 146, 53, 174, 53, - 57, 32, 12, 227, 219, 202, 32, 154, - 65, 85, 93, 239, 251, 227, 65, 158, - 73, 146, 146, 247, 255, 235, 154, 130, - 97, 166, 117, 231, 243, 210, 117, 117, - 117, 190, 36, 190, 178, 93, 20, 170, - 130, 202, 73, 20, 12, 53, 85, 194, - 146, 206, 130, 117, 85, 166, 182, 215 + 0, 12, 53, 32, 146, 53, 174, 53, + 57, 32, 12, 227, 219, 202, 32, 154, + 65, 85, 93, 239, 251, 227, 65, 158, + 73, 146, 146, 247, 255, 235, 154, 130, + 97, 166, 117, 231, 243, 210, 117, 117, + 117, 190, 36, 190, 178, 93, 20, 170, + 130, 202, 73, 20, 12, 53, 85, 194, + 146, 206, 130, 117, 85, 166, 182, 215 }; // act @@ -58,7 +58,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Contrast Rgba32 actual = image[x, y]; int diff = expected[y * 8 + x] - actual.R; Assert.True(diff == 0); - int foo = 2; } } } From bba2116666fe6fde3f399511d9317f417f2dd5b3 Mon Sep 17 00:00:00 2001 From: popow Date: Mon, 2 Jul 2018 15:34:31 +0200 Subject: [PATCH 05/15] SixLabors.ImageSharp.Processing.Contrast -> SixLabors.ImageSharp.Processing.Normalization --- .../HistogramEqualizationExtension.cs | 2 +- .../HistogramEqualizationProcessor.cs | 2 +- .../Processing/Contrast/HistogramEqualizationTests.cs | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) rename src/ImageSharp/Processing/{Contrast => Normalization}/HistogramEqualizationExtension.cs (94%) rename src/ImageSharp/Processing/{Contrast => Normalization}/HistogramEqualizationProcessor.cs (98%) diff --git a/src/ImageSharp/Processing/Contrast/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/Normalization/HistogramEqualizationExtension.cs similarity index 94% rename from src/ImageSharp/Processing/Contrast/HistogramEqualizationExtension.cs rename to src/ImageSharp/Processing/Normalization/HistogramEqualizationExtension.cs index a7b59cedd..b40064515 100644 --- a/src/ImageSharp/Processing/Contrast/HistogramEqualizationExtension.cs +++ b/src/ImageSharp/Processing/Normalization/HistogramEqualizationExtension.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Contrast +namespace SixLabors.ImageSharp.Processing.Normalization { /// /// Adds extension that allows applying an HistogramEqualization to the image. diff --git a/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs similarity index 98% rename from src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs rename to src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs index df1737455..a1f37b039 100644 --- a/src/ImageSharp/Processing/Contrast/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs @@ -7,7 +7,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Contrast +namespace SixLabors.ImageSharp.Processing.Normalization { /// /// Applies a global histogram equalization to the image. diff --git a/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs index db2282ccd..b989fcf85 100644 --- a/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Contrast; +using SixLabors.ImageSharp.Processing.Normalization; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Contrast @@ -56,8 +56,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Contrast for (int x = 0; x < 8; x++) { Rgba32 actual = image[x, y]; - int diff = expected[y * 8 + x] - actual.R; - Assert.True(diff == 0); + Assert.Equal(expected[y * 8 + x], actual.R); + Assert.Equal(expected[y * 8 + x], actual.G); + Assert.Equal(expected[y * 8 + x], actual.B); } } } From 75bc37c423264e9c888f917cb51efbeb4c2897ba Mon Sep 17 00:00:00 2001 From: popow Date: Mon, 2 Jul 2018 15:50:05 +0200 Subject: [PATCH 06/15] using memoryAllocator --- .../Normalization/HistogramEqualizationProcessor.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs index a1f37b039..75bd2d256 100644 --- a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs @@ -5,6 +5,7 @@ using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Normalization @@ -21,12 +22,13 @@ namespace SixLabors.ImageSharp.Processing.Normalization { var rgb48 = default(Rgb48); var rgb24 = default(Rgb24); + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; int numberOfPixels = source.Width * source.Height; bool is16bitPerChannel = typeof(TPixel) == typeof(Rgb48) || typeof(TPixel) == typeof(Rgba64); // build the histogram of the grayscale levels int luminanceLevels = is16bitPerChannel ? 65536 : 256; - int[] histogram = new int[luminanceLevels]; + Span histogram = memoryAllocator.Allocate(luminanceLevels, clear: true).GetSpan(); for (int y = 0; y < source.Height; y++) { Span row = source.GetPixelRowSpan(y); @@ -39,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Normalization } // calculate the cumulative distribution function (which will be the cumulative histogram) - int[] cdf = new int[luminanceLevels]; + Span cdf = memoryAllocator.Allocate(luminanceLevels, clear: true).GetSpan(); int histSum = 0; for (int i = 0; i < histogram.Length; i++) { @@ -47,6 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Normalization cdf[i] = histSum; } + // get the first none zero value of the cumulative histogram int cdfMin = 0; for (int i = 0; i < histogram.Length; i++) { @@ -57,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Normalization } } - int[] lut = new int[luminanceLevels]; + Span lut = memoryAllocator.Allocate(luminanceLevels, clear: true).GetSpan(); for (int i = 0; i < histogram.Length; i++) { lut[i] = cdf[i] - cdfMin; From edbae214d0de2c68b9226ec5a3b003b937378ad1 Mon Sep 17 00:00:00 2001 From: popow Date: Mon, 2 Jul 2018 15:57:58 +0200 Subject: [PATCH 07/15] removed unnecessary allocation of the lut, using cdf instead --- .../Normalization/HistogramEqualizationProcessor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs index 75bd2d256..0036b5dc9 100644 --- a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs @@ -60,10 +60,10 @@ namespace SixLabors.ImageSharp.Processing.Normalization } } - Span lut = memoryAllocator.Allocate(luminanceLevels, clear: true).GetSpan(); + // creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop for (int i = 0; i < histogram.Length; i++) { - lut[i] = cdf[i] - cdfMin; + cdf[i] = cdf[i] - cdfMin; } // apply the cdf to each pixel of the image @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Normalization TPixel sourcePixel = row[x]; int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); - double luminanceEqualized = (lut[luminance] / numberOfPixelsMinusCdfMin) * luminanceLevelsMinusOne; + double luminanceEqualized = (cdf[luminance] / numberOfPixelsMinusCdfMin) * luminanceLevelsMinusOne; luminanceEqualized = Math.Round(luminanceEqualized); if (is16bitPerChannel) From a09fa9c61859f4ef239cd3d1ed1450f90705cd49 Mon Sep 17 00:00:00 2001 From: popow Date: Mon, 2 Jul 2018 18:30:17 +0200 Subject: [PATCH 08/15] moved test to Normalization folder --- .../{Contrast => Normalization}/HistogramEqualizationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/ImageSharp.Tests/Processing/{Contrast => Normalization}/HistogramEqualizationTests.cs (97%) diff --git a/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs similarity index 97% rename from tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs rename to tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs index b989fcf85..7a750ff8b 100644 --- a/tests/ImageSharp.Tests/Processing/Contrast/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Normalization; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Contrast +namespace SixLabors.ImageSharp.Tests.Processing.Normalization { public class HistogramEqualizationTests { From 4adb58e49ee4935426f4029cc6194802d27bf69c Mon Sep 17 00:00:00 2001 From: popow Date: Mon, 2 Jul 2018 18:34:19 +0200 Subject: [PATCH 09/15] fixed rounding issue in calculating the luminance --- .../Normalization/HistogramEqualizationProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs index 0036b5dc9..aa1526c87 100644 --- a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs @@ -106,12 +106,12 @@ namespace SixLabors.ImageSharp.Processing.Normalization if (is16bitPerChannel) { sourcePixel.ToRgb48(ref rgb48); - luminance = (int)((.2126F * rgb48.R) + (.7152F * rgb48.G) + (.0722F * rgb48.B)); + luminance = Convert.ToInt32((.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)); + luminance = Convert.ToInt32((.2126F * rgb24.R) + (.7152F * rgb24.G) + (.0722F * rgb24.B)); } return luminance; From ce96775d33b8dd05f3154850e842e166fd752faa Mon Sep 17 00:00:00 2001 From: popow Date: Tue, 3 Jul 2018 11:02:55 +0200 Subject: [PATCH 10/15] using GetPixelSpan instead of GetPixelRowSpan --- .../HistogramEqualizationProcessor.cs | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs index aa1526c87..633761352 100644 --- a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs @@ -29,15 +29,12 @@ namespace SixLabors.ImageSharp.Processing.Normalization // build the histogram of the grayscale levels int luminanceLevels = is16bitPerChannel ? 65536 : 256; Span histogram = memoryAllocator.Allocate(luminanceLevels, clear: true).GetSpan(); - for (int y = 0; y < source.Height; y++) + Span pixels = source.GetPixelSpan(); + for (int i = 0; i < pixels.Length; i++) { - Span row = source.GetPixelRowSpan(y); - for (int x = 0; x < source.Width; x++) - { - TPixel sourcePixel = row[x]; - int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); - histogram[luminance]++; - } + TPixel sourcePixel = pixels[i]; + int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); + histogram[luminance]++; } // calculate the cumulative distribution function (which will be the cumulative histogram) @@ -69,25 +66,21 @@ namespace SixLabors.ImageSharp.Processing.Normalization // apply the cdf to each pixel of the image double numberOfPixelsMinusCdfMin = (double)(numberOfPixels - cdfMin); int luminanceLevelsMinusOne = luminanceLevels - 1; - for (int y = 0; y < source.Height; y++) + for (int i = 0; i < pixels.Length; i++) { - Span row = source.GetPixelRowSpan(y); - for (int x = 0; x < source.Width; x++) - { - TPixel sourcePixel = row[x]; + TPixel sourcePixel = pixels[i]; - int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); - double luminanceEqualized = (cdf[luminance] / numberOfPixelsMinusCdfMin) * luminanceLevelsMinusOne; - luminanceEqualized = Math.Round(luminanceEqualized); + int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); + double luminanceEqualized = (cdf[luminance] / numberOfPixelsMinusCdfMin) * luminanceLevelsMinusOne; + luminanceEqualized = Math.Round(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)); - } + if (is16bitPerChannel) + { + pixels[i].PackFromRgb48(new Rgb48((ushort)luminanceEqualized, (ushort)luminanceEqualized, (ushort)luminanceEqualized)); + } + else + { + pixels[i].PackFromRgba32(new Rgba32((byte)luminanceEqualized, (byte)luminanceEqualized, (byte)luminanceEqualized)); } } } From 6d78ea9420c66cad6dfdba921c25530acec0b010 Mon Sep 17 00:00:00 2001 From: popow Date: Tue, 3 Jul 2018 12:20:43 +0200 Subject: [PATCH 11/15] allocating cdf and histogram buffer with a using statement --- .../HistogramEqualizationProcessor.cs | 79 ++++++++++++------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs index 633761352..6558f4889 100644 --- a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs @@ -26,19 +26,57 @@ namespace SixLabors.ImageSharp.Processing.Normalization int numberOfPixels = source.Width * source.Height; bool is16bitPerChannel = typeof(TPixel) == typeof(Rgb48) || typeof(TPixel) == typeof(Rgba64); + Span pixels = source.GetPixelSpan(); + // build the histogram of the grayscale levels int luminanceLevels = is16bitPerChannel ? 65536 : 256; - Span histogram = memoryAllocator.Allocate(luminanceLevels, clear: true).GetSpan(); - Span pixels = source.GetPixelSpan(); - for (int i = 0; i < pixels.Length; i++) + using (IBuffer histogramBuffer = memoryAllocator.AllocateClean(luminanceLevels)) + using (IBuffer cdfBuffer = memoryAllocator.AllocateClean(luminanceLevels)) { - TPixel sourcePixel = pixels[i]; - int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); - histogram[luminance]++; + Span histogram = histogramBuffer.GetSpan(); + for (int i = 0; i < pixels.Length; i++) + { + TPixel sourcePixel = pixels[i]; + int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); + histogram[luminance]++; + } + + // calculate the cumulative distribution function, which will map each input pixel to a new value + Span cdf = cdfBuffer.GetSpan(); + int cdfMin = this.CaluclateCdf(cdf, histogram); + + // apply the cdf to each pixel of the image + double numberOfPixelsMinusCdfMin = (double)(numberOfPixels - cdfMin); + int luminanceLevelsMinusOne = luminanceLevels - 1; + for (int i = 0; i < pixels.Length; i++) + { + TPixel sourcePixel = pixels[i]; + + int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); + double luminanceEqualized = (cdf[luminance] / numberOfPixelsMinusCdfMin) * luminanceLevelsMinusOne; + luminanceEqualized = Math.Round(luminanceEqualized); + + if (is16bitPerChannel) + { + pixels[i].PackFromRgb48(new Rgb48((ushort)luminanceEqualized, (ushort)luminanceEqualized, (ushort)luminanceEqualized)); + } + else + { + pixels[i].PackFromRgba32(new Rgba32((byte)luminanceEqualized, (byte)luminanceEqualized, (byte)luminanceEqualized)); + } + } } + } - // calculate the cumulative distribution function (which will be the cumulative histogram) - Span cdf = memoryAllocator.Allocate(luminanceLevels, clear: true).GetSpan(); + /// + /// Calculate the cumulative distribution function + /// + /// The array holding the cdf + /// The histogram of the input image + /// The first none zero value of the cdf + private int CaluclateCdf(Span cdf, Span histogram) + { + // calculate the cumulative histogram int histSum = 0; for (int i = 0; i < histogram.Length; i++) { @@ -50,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing.Normalization int cdfMin = 0; for (int i = 0; i < histogram.Length; i++) { - if (histogram[i] != 0) + if (cdf[i] != 0) { cdfMin = cdf[i]; break; @@ -60,29 +98,10 @@ namespace SixLabors.ImageSharp.Processing.Normalization // creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop for (int i = 0; i < histogram.Length; i++) { - cdf[i] = cdf[i] - cdfMin; + cdf[i] = Math.Max(0, cdf[i] - cdfMin); } - // apply the cdf to each pixel of the image - double numberOfPixelsMinusCdfMin = (double)(numberOfPixels - cdfMin); - int luminanceLevelsMinusOne = luminanceLevels - 1; - for (int i = 0; i < pixels.Length; i++) - { - TPixel sourcePixel = pixels[i]; - - int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); - double luminanceEqualized = (cdf[luminance] / numberOfPixelsMinusCdfMin) * luminanceLevelsMinusOne; - luminanceEqualized = Math.Round(luminanceEqualized); - - if (is16bitPerChannel) - { - pixels[i].PackFromRgb48(new Rgb48((ushort)luminanceEqualized, (ushort)luminanceEqualized, (ushort)luminanceEqualized)); - } - else - { - pixels[i].PackFromRgba32(new Rgba32((byte)luminanceEqualized, (byte)luminanceEqualized, (byte)luminanceEqualized)); - } - } + return cdfMin; } /// From 8b08b71d004c8f6be4f24547aef3b8529b8065c8 Mon Sep 17 00:00:00 2001 From: popow Date: Tue, 3 Jul 2018 13:46:15 +0200 Subject: [PATCH 12/15] using Vector4 to calculate the luminance and set the pixel value --- .../HistogramEqualizationProcessor.cs | 39 +++++-------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs index 6558f4889..ac6d23725 100644 --- a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; @@ -20,8 +21,6 @@ namespace SixLabors.ImageSharp.Processing.Normalization /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - var rgb48 = default(Rgb48); - var rgb24 = default(Rgb24); MemoryAllocator memoryAllocator = configuration.MemoryAllocator; int numberOfPixels = source.Width * source.Height; bool is16bitPerChannel = typeof(TPixel) == typeof(Rgb48) || typeof(TPixel) == typeof(Rgba64); @@ -37,7 +36,7 @@ namespace SixLabors.ImageSharp.Processing.Normalization for (int i = 0; i < pixels.Length; i++) { TPixel sourcePixel = pixels[i]; - int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); + int luminance = this.GetLuminance(sourcePixel, luminanceLevels); histogram[luminance]++; } @@ -47,23 +46,14 @@ namespace SixLabors.ImageSharp.Processing.Normalization // apply the cdf to each pixel of the image double numberOfPixelsMinusCdfMin = (double)(numberOfPixels - cdfMin); - int luminanceLevelsMinusOne = luminanceLevels - 1; for (int i = 0; i < pixels.Length; i++) { TPixel sourcePixel = pixels[i]; - int luminance = this.GetLuminance(sourcePixel, is16bitPerChannel, ref rgb24, ref rgb48); - double luminanceEqualized = (cdf[luminance] / numberOfPixelsMinusCdfMin) * luminanceLevelsMinusOne; - luminanceEqualized = Math.Round(luminanceEqualized); + int luminance = this.GetLuminance(sourcePixel, luminanceLevels); + double luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin; - if (is16bitPerChannel) - { - pixels[i].PackFromRgb48(new Rgb48((ushort)luminanceEqualized, (ushort)luminanceEqualized, (ushort)luminanceEqualized)); - } - else - { - pixels[i].PackFromRgba32(new Rgba32((byte)luminanceEqualized, (byte)luminanceEqualized, (byte)luminanceEqualized)); - } + pixels[i].PackFromVector4(new Vector4((float)luminanceEqualized)); } } } @@ -108,23 +98,12 @@ namespace SixLabors.ImageSharp.Processing.Normalization /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. /// /// The pixel to get the luminance from - /// Flag indicates, if its 16 bits per channel, otherwise its 8 - /// Will store the pixel values in case of 8 bit per channel - /// Will store the pixel values in case of 16 bit per channel - private int GetLuminance(TPixel sourcePixel, bool is16bitPerChannel, ref Rgb24 rgb24, ref Rgb48 rgb48) + /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) + private int GetLuminance(TPixel sourcePixel, int luminanceLevels) { // Convert to grayscale using ITU-R Recommendation BT.709 - int luminance; - if (is16bitPerChannel) - { - sourcePixel.ToRgb48(ref rgb48); - luminance = Convert.ToInt32((.2126F * rgb48.R) + (.7152F * rgb48.G) + (.0722F * rgb48.B)); - } - else - { - sourcePixel.ToRgb24(ref rgb24); - luminance = Convert.ToInt32((.2126F * rgb24.R) + (.7152F * rgb24.G) + (.0722F * rgb24.B)); - } + var vector = sourcePixel.ToVector4(); + int luminance = Convert.ToInt32(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * luminanceLevels); return luminance; } From d5cc405212426afcef3301151668a75accebd431 Mon Sep 17 00:00:00 2001 From: popow Date: Wed, 4 Jul 2018 20:37:00 +0200 Subject: [PATCH 13/15] luminance levels is now a parameter of the constructor, defaults to 65536 --- .../HistogramEqualizationExtension.cs | 6 ++-- .../HistogramEqualizationProcessor.cs | 28 ++++++++++++++----- .../HistogramEqualizationTests.cs | 8 ++++-- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Processing/Normalization/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/Normalization/HistogramEqualizationExtension.cs index b40064515..aa3c86d24 100644 --- a/src/ImageSharp/Processing/Normalization/HistogramEqualizationExtension.cs +++ b/src/ImageSharp/Processing/Normalization/HistogramEqualizationExtension.cs @@ -15,9 +15,11 @@ namespace SixLabors.ImageSharp.Processing.Normalization /// /// 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.Defaults to 65536. /// A histogram equalized grayscale image. - public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) + public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source, int luminanceLevels = 65536) where TPixel : struct, IPixel - => source.ApplyProcessor(new HistogramEqualizationProcessor()); + => source.ApplyProcessor(new HistogramEqualizationProcessor(luminanceLevels)); } } diff --git a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs index ac6d23725..782df4762 100644 --- a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs @@ -18,25 +18,39 @@ namespace SixLabors.ImageSharp.Processing.Normalization internal class HistogramEqualizationProcessor : ImageProcessor 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.Defaults to 65536. + public HistogramEqualizationProcessor(int luminanceLevels = 65536) + { + Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); + + this.LuminanceLevels = luminanceLevels; + } + + /// + /// Gets the luminance levels. + /// + public int LuminanceLevels { get; } + /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { MemoryAllocator memoryAllocator = configuration.MemoryAllocator; int numberOfPixels = source.Width * source.Height; - bool is16bitPerChannel = typeof(TPixel) == typeof(Rgb48) || typeof(TPixel) == typeof(Rgba64); - Span pixels = source.GetPixelSpan(); // build the histogram of the grayscale levels - int luminanceLevels = is16bitPerChannel ? 65536 : 256; - using (IBuffer histogramBuffer = memoryAllocator.AllocateClean(luminanceLevels)) - using (IBuffer cdfBuffer = memoryAllocator.AllocateClean(luminanceLevels)) + 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, luminanceLevels); + int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels); histogram[luminance]++; } @@ -50,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Normalization { TPixel sourcePixel = pixels[i]; - int luminance = this.GetLuminance(sourcePixel, luminanceLevels); + int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels); double luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin; pixels[i].PackFromVector4(new Vector4((float)luminanceEqualized)); diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs index 7a750ff8b..2fc49db8a 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -10,8 +10,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization { public class HistogramEqualizationTests { - [Fact] - public void HistogramEqualizationTest() + [Theory] + [InlineData(256)] + [InlineData(65536)] + public void HistogramEqualizationTest(int luminanceLevels) { // arrange byte[] pixels = new byte[] @@ -48,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization }; // act - image.Mutate(x => x.HistogramEqualization()); + image.Mutate(x => x.HistogramEqualization(luminanceLevels)); // assert for (int y = 0; y < 8; y++) From 2b7f982120534887c68839407af727b8dfdce759 Mon Sep 17 00:00:00 2001 From: popow Date: Sun, 15 Jul 2018 10:36:46 +0200 Subject: [PATCH 14/15] moved extension to the processing namespace and the processor accordingly into Processors namespace --- .../{Normalization => }/HistogramEqualizationExtension.cs | 5 +++-- .../Normalization/HistogramEqualizationProcessor.cs | 7 +++---- .../Processing/Normalization/HistogramEqualizationTests.cs | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) rename src/ImageSharp/Processing/{Normalization => }/HistogramEqualizationExtension.cs (85%) rename src/ImageSharp/Processing/{ => Processors}/Normalization/HistogramEqualizationProcessor.cs (95%) diff --git a/src/ImageSharp/Processing/Normalization/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs similarity index 85% rename from src/ImageSharp/Processing/Normalization/HistogramEqualizationExtension.cs rename to src/ImageSharp/Processing/HistogramEqualizationExtension.cs index aa3c86d24..b472a2750 100644 --- a/src/ImageSharp/Processing/Normalization/HistogramEqualizationExtension.cs +++ b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs @@ -2,8 +2,9 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Normalization; -namespace SixLabors.ImageSharp.Processing.Normalization +namespace SixLabors.ImageSharp.Processing { /// /// Adds extension that allows applying an HistogramEqualization to the image. @@ -16,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Normalization /// 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.Defaults to 65536. + /// or 65536 for 16-bit grayscale images. Defaults to 65536. /// A histogram equalized grayscale image. public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source, int luminanceLevels = 65536) where TPixel : struct, IPixel diff --git a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs similarity index 95% rename from src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs rename to src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index 782df4762..fdd439a33 100644 --- a/src/ImageSharp/Processing/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs @@ -5,11 +5,10 @@ using System; using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; using SixLabors.Memory; using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Normalization +namespace SixLabors.ImageSharp.Processing.Processors.Normalization { /// /// Applies a global histogram equalization to the image. @@ -22,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Normalization /// 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.Defaults to 65536. + /// or 65536 for 16-bit grayscale images. Defaults to 65536. public HistogramEqualizationProcessor(int luminanceLevels = 65536) { Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); @@ -117,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing.Normalization { // Convert to grayscale using ITU-R Recommendation BT.709 var vector = sourcePixel.ToVector4(); - int luminance = Convert.ToInt32(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * luminanceLevels); + int luminance = Convert.ToInt32(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1)); return luminance; } diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs index 2fc49db8a..ebecfec5a 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Normalization; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Normalization From ade60bd284c868d1fcab62b80b2499ea9084df18 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 16 Jul 2018 23:57:39 +1000 Subject: [PATCH 15/15] Cleanup and remove double cast. --- .../HistogramEqualizationExtension.cs | 18 +++++++-- .../HistogramEqualizationProcessor.cs | 38 ++++++++++--------- .../HistogramEqualizationTests.cs | 7 ++-- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Processing/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs index b472a2750..8dabfcc05 100644 --- a/src/ImageSharp/Processing/HistogramEqualizationExtension.cs +++ b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs @@ -7,19 +7,29 @@ using SixLabors.ImageSharp.Processing.Processors.Normalization; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extension that allows applying an HistogramEqualization to the image. + /// Adds extension that allow the adjustment of the contrast of an image via its histogram. /// public static class HistogramEqualizationExtension { + /// + /// Equalizes the histogram of an image to increases the global contrast using 65536 luminance levels. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) + where TPixel : struct, IPixel + => HistogramEqualization(source, 65536); + /// /// Equalizes the histogram of an image to increases the global 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. Defaults to 65536. - /// A histogram equalized grayscale image. - public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source, int luminanceLevels = 65536) + /// or 65536 for 16-bit grayscale images. + /// The . + public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source, int luminanceLevels) where TPixel : struct, IPixel => source.ApplyProcessor(new HistogramEqualizationProcessor(luminanceLevels)); } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index fdd439a33..ba56e392f 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -21,8 +22,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// 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. Defaults to 65536. - public HistogramEqualizationProcessor(int luminanceLevels = 65536) + /// or 65536 for 16-bit grayscale images. + public HistogramEqualizationProcessor(int luminanceLevels) { Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); @@ -30,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization } /// - /// Gets the luminance levels. + /// Gets the number of luminance levels. /// public int LuminanceLevels { get; } @@ -41,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int numberOfPixels = source.Width * source.Height; Span pixels = source.GetPixelSpan(); - // build the histogram of the grayscale levels + // Build the histogram of the grayscale levels. using (IBuffer histogramBuffer = memoryAllocator.AllocateClean(this.LuminanceLevels)) using (IBuffer cdfBuffer = memoryAllocator.AllocateClean(this.LuminanceLevels)) { @@ -53,33 +54,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization histogram[luminance]++; } - // calculate the cumulative distribution function, which will map each input pixel to a new value + // Calculate the cumulative distribution function, which will map each input pixel to a new value. Span cdf = cdfBuffer.GetSpan(); - int cdfMin = this.CaluclateCdf(cdf, histogram); + int cdfMin = this.CalculateCdf(cdf, histogram); - // apply the cdf to each pixel of the image - double numberOfPixelsMinusCdfMin = (double)(numberOfPixels - cdfMin); + // 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); - double luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin; + float luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin; - pixels[i].PackFromVector4(new Vector4((float)luminanceEqualized)); + pixels[i].PackFromVector4(new Vector4(luminanceEqualized)); } } } /// - /// Calculate the cumulative distribution function + /// Calculates the cumulative distribution function. /// - /// The array holding the cdf - /// The histogram of the input image - /// The first none zero value of the cdf - private int CaluclateCdf(Span cdf, Span histogram) + /// The array holding the cdf. + /// The histogram of the input image. + /// The first none zero value of the cdf. + private int CalculateCdf(Span cdf, Span histogram) { - // calculate the cumulative histogram + // Calculate the cumulative histogram int histSum = 0; for (int i = 0; i < histogram.Length; i++) { @@ -87,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization cdf[i] = histSum; } - // get the first none zero value of the cumulative histogram + // Get the first none zero value of the cumulative histogram int cdfMin = 0; for (int i = 0; i < histogram.Length; i++) { @@ -98,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization } } - // creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop + // Creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop for (int i = 0; i < histogram.Length; i++) { cdf[i] = Math.Max(0, cdf[i] - cdfMin); @@ -112,6 +113,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// 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)] private 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 ebecfec5a..1595ed32c 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization [InlineData(65536)] public void HistogramEqualizationTest(int luminanceLevels) { - // arrange + // Arrange byte[] pixels = new byte[] { 52, 55, 61, 59, 70, 61, 76, 61, @@ -26,6 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization 69, 85, 64, 58, 55, 61, 65, 83, 70, 87, 69, 68, 65, 73, 78, 90 }; + var image = new Image(8, 8); for (int y = 0; y < 8; y++) { @@ -48,10 +49,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization 146, 206, 130, 117, 85, 166, 182, 215 }; - // act + // Act image.Mutate(x => x.HistogramEqualization(luminanceLevels)); - // assert + // Assert for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++)