diff --git a/src/ImageSharp/Processing/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs
new file mode 100644
index 0000000000..8dabfcc05c
--- /dev/null
+++ b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Processors.Normalization;
+
+namespace SixLabors.ImageSharp.Processing
+{
+ ///
+ /// 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.
+ /// 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
new file mode 100644
index 0000000000..ba56e392f4
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
@@ -0,0 +1,126 @@
+// Copyright (c) Six Labors and contributors.
+// 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.
+ ///
+ /// The pixel format.
+ 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.
+ public HistogramEqualizationProcessor(int luminanceLevels)
+ {
+ Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels));
+
+ this.LuminanceLevels = luminanceLevels;
+ }
+
+ ///
+ /// Gets the number of 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;
+ 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;
+
+ pixels[i].PackFromVector4(new Vector4(luminanceEqualized));
+ }
+ }
+ }
+
+ ///
+ /// 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 CalculateCdf(Span cdf, Span histogram)
+ {
+ // Calculate the cumulative histogram
+ int histSum = 0;
+ for (int i = 0; i < histogram.Length; i++)
+ {
+ histSum += histogram[i];
+ cdf[i] = histSum;
+ }
+
+ // Get the first none zero value of the cumulative histogram
+ int cdfMin = 0;
+ for (int i = 0; i < histogram.Length; i++)
+ {
+ if (cdf[i] != 0)
+ {
+ cdfMin = cdf[i];
+ break;
+ }
+ }
+
+ // 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);
+ }
+
+ return cdfMin;
+ }
+
+ ///
+ /// 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)]
+ private int GetLuminance(TPixel sourcePixel, int luminanceLevels)
+ {
+ // 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 - 1));
+
+ return luminance;
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs
new file mode 100644
index 0000000000..1595ed32cc
--- /dev/null
+++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Processing.Normalization
+{
+ public class HistogramEqualizationTests
+ {
+ [Theory]
+ [InlineData(256)]
+ [InlineData(65536)]
+ public void HistogramEqualizationTest(int luminanceLevels)
+ {
+ // 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(luminanceLevels));
+
+ // Assert
+ for (int y = 0; y < 8; y++)
+ {
+ for (int x = 0; x < 8; x++)
+ {
+ Rgba32 actual = image[x, y];
+ Assert.Equal(expected[y * 8 + x], actual.R);
+ Assert.Equal(expected[y * 8 + x], actual.G);
+ Assert.Equal(expected[y * 8 + x], actual.B);
+ }
+ }
+ }
+ }
+}