mirror of https://github.com/SixLabors/ImageSharp
55 changed files with 1156 additions and 265 deletions
@ -0,0 +1,94 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.MetaData; |
|||
using SixLabors.ImageSharp.MetaData.Profiles.Exif; |
|||
|
|||
namespace SixLabors.ImageSharp.Common.Helpers |
|||
{ |
|||
/// <summary>
|
|||
/// Contains methods for converting values between unit scales.
|
|||
/// </summary>
|
|||
internal static class UnitConverter |
|||
{ |
|||
/// <summary>
|
|||
/// The number of centimeters in a meter.
|
|||
/// 1 cm is equal to exactly 0.01 meters.
|
|||
/// </summary>
|
|||
private const double CmsInMeter = 1 / 0.01D; |
|||
|
|||
/// <summary>
|
|||
/// The number of centimeters in an inch.
|
|||
/// 1 inch is equal to exactly 2.54 centimeters.
|
|||
/// </summary>
|
|||
private const double CmsInInch = 2.54D; |
|||
|
|||
/// <summary>
|
|||
/// The number of inches in a meter.
|
|||
/// 1 inch is equal to exactly 0.0254 meters.
|
|||
/// </summary>
|
|||
private const double InchesInMeter = 1 / 0.0254D; |
|||
|
|||
/// <summary>
|
|||
/// Scales the value from centimeters to meters.
|
|||
/// </summary>
|
|||
/// <param name="x">The value to scale.</param>
|
|||
/// <returns>The <see cref="double"/>.</returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static double CmToMeter(double x) => x * CmsInMeter; |
|||
|
|||
/// <summary>
|
|||
/// Scales the value from meters to centimeters.
|
|||
/// </summary>
|
|||
/// <param name="x">The value to scale.</param>
|
|||
/// <returns>The <see cref="double"/>.</returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static double MeterToCm(double x) => x / CmsInMeter; |
|||
|
|||
/// <summary>
|
|||
/// Scales the value from meters to inches.
|
|||
/// </summary>
|
|||
/// <param name="x">The value to scale.</param>
|
|||
/// <returns>The <see cref="double"/>.</returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static double MeterToInch(double x) => x / InchesInMeter; |
|||
|
|||
/// <summary>
|
|||
/// Scales the value from inches to meters.
|
|||
/// </summary>
|
|||
/// <param name="x">The value to scale.</param>
|
|||
/// <returns>The <see cref="double"/>.</returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static double InchToMeter(double x) => x * InchesInMeter; |
|||
|
|||
/// <summary>
|
|||
/// Scales the value from centimeters to inches.
|
|||
/// </summary>
|
|||
/// <param name="x">The value to scale.</param>
|
|||
/// <returns>The <see cref="double"/>.</returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static double CmToInch(double x) => x / CmsInInch; |
|||
|
|||
/// <summary>
|
|||
/// Scales the value from inches to centimeters.
|
|||
/// </summary>
|
|||
/// <param name="x">The value to scale.</param>
|
|||
/// <returns>The <see cref="double"/>.</returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static double InchToCm(double x) => x * CmsInInch; |
|||
|
|||
/// <summary>
|
|||
/// Converts an <see cref="ExifTag.ResolutionUnit"/> to a <see cref="PixelResolutionUnit"/>.
|
|||
/// </summary>
|
|||
/// <param name="profile">The EXIF profile containing the value.</param>
|
|||
/// <returns>The <see cref="PixelResolutionUnit"/></returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile) |
|||
{ |
|||
return profile.TryGetValue(ExifTag.ResolutionUnit, out ExifValue resolution) |
|||
? (PixelResolutionUnit)(byte)(((ushort)resolution.Value) - 1) // EXIF is 1, 2, 3
|
|||
: default; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Options for allocating buffers.
|
|||
/// </summary>
|
|||
public enum AllocationOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Indicates that the buffer should just be allocated.
|
|||
/// </summary>
|
|||
None, |
|||
|
|||
/// <summary>
|
|||
/// Indicates that the allocated buffer should be cleaned following allocation.
|
|||
/// </summary>
|
|||
Clean |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.MetaData |
|||
{ |
|||
/// <summary>
|
|||
/// Provides enumeration of available pixel density units.
|
|||
/// </summary>
|
|||
public enum PixelResolutionUnit : byte |
|||
{ |
|||
/// <summary>
|
|||
/// No units; width:height pixel aspect ratio.
|
|||
/// </summary>
|
|||
AspectRatio = 0, |
|||
|
|||
/// <summary>
|
|||
/// Pixels per inch (2.54 cm).
|
|||
/// </summary>
|
|||
PixelsPerInch = 1, |
|||
|
|||
/// <summary>
|
|||
/// Pixels per centimeter.
|
|||
/// </summary>
|
|||
PixelsPerCentimeter = 2, |
|||
|
|||
/// <summary>
|
|||
/// Pixels per meter (100 cm).
|
|||
/// </summary>
|
|||
PixelsPerMeter = 3 |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extension that allow the adjustment of the contrast of an image via its histogram.
|
|||
/// </summary>
|
|||
public static class HistogramEqualizationExtension |
|||
{ |
|||
/// <summary>
|
|||
/// Equalizes the histogram of an image to increases the global contrast using 65536 luminance levels.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext<TPixel> HistogramEqualization<TPixel>(this IImageProcessingContext<TPixel> source) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> HistogramEqualization(source, 65536); |
|||
|
|||
/// <summary>
|
|||
/// Equalizes the histogram of an image to increases the global contrast.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="luminanceLevels">The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
|
|||
/// or 65536 for 16-bit grayscale images.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext<TPixel> HistogramEqualization<TPixel>(this IImageProcessingContext<TPixel> source, int luminanceLevels) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.ApplyProcessor(new HistogramEqualizationProcessor<TPixel>(luminanceLevels)); |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Applies a global histogram equalization to the image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class HistogramEqualizationProcessor<TPixel> : ImageProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HistogramEqualizationProcessor{TPixel}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="luminanceLevels">The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
|
|||
/// or 65536 for 16-bit grayscale images.</param>
|
|||
public HistogramEqualizationProcessor(int luminanceLevels) |
|||
{ |
|||
Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); |
|||
|
|||
this.LuminanceLevels = luminanceLevels; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of luminance levels.
|
|||
/// </summary>
|
|||
public int LuminanceLevels { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|||
{ |
|||
MemoryAllocator memoryAllocator = configuration.MemoryAllocator; |
|||
int numberOfPixels = source.Width * source.Height; |
|||
Span<TPixel> pixels = source.GetPixelSpan(); |
|||
|
|||
// Build the histogram of the grayscale levels.
|
|||
using (IBuffer<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) |
|||
using (IBuffer<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) |
|||
{ |
|||
Span<int> 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<int> 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)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates the cumulative distribution function.
|
|||
/// </summary>
|
|||
/// <param name="cdf">The array holding the cdf.</param>
|
|||
/// <param name="histogram">The histogram of the input image.</param>
|
|||
/// <returns>The first none zero value of the cdf.</returns>
|
|||
private int CalculateCdf(Span<int> cdf, Span<int> 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; |
|||
} |
|||
|
|||
/// <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="luminanceLevels">The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)</param>
|
|||
[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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Helpers |
|||
{ |
|||
public class UnitConverterHelperTests |
|||
{ |
|||
[Fact] |
|||
public void InchToFromMeter() |
|||
{ |
|||
const double expected = 96D; |
|||
double actual = UnitConverter.InchToMeter(expected); |
|||
actual = UnitConverter.MeterToInch(actual); |
|||
|
|||
Assert.Equal(expected, actual, 15); |
|||
} |
|||
|
|||
[Fact] |
|||
public void InchToFromCm() |
|||
{ |
|||
const double expected = 96D; |
|||
double actual = UnitConverter.InchToCm(expected); |
|||
actual = UnitConverter.CmToInch(actual); |
|||
|
|||
Assert.Equal(expected, actual, 15); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CmToFromMeter() |
|||
{ |
|||
const double expected = 96D; |
|||
double actual = UnitConverter.CmToMeter(expected); |
|||
actual = UnitConverter.MeterToCm(actual); |
|||
|
|||
Assert.Equal(expected, actual, 15); |
|||
} |
|||
} |
|||
} |
|||
@ -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<Rgba32>(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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 404 B |
|
After Width: | Height: | Size: 344 B |
Loading…
Reference in new issue