Browse Source

New overloads for binary threshold operations. The new argument ColorComponent defines the scalar color component to be used for threshold comparison: Luminance, Saturation or MaxChroma. Luminance is default and identical to previous versions. Saturation is the HSL saturation component. MaxChroma is calculated as the maximum of YCbCr chroma value, i.e. Cb and Cr distance from achromatic value. Background: This component shall discriminate colorful parts from achromatic parts in human perception. Very dark pixels, which are perceived as near black, can have high HSL saturation values if e.g. (rgb)==(4,0,0); this would definitely not be perceived as colorful by a human. The MaxChroma component will calculate them low.

pull/1555/head
Franz Häring 5 years ago
parent
commit
2d3ab4ebd5
  1. 38
      .gitattributes
  2. 9
      src/ImageSharp/ColorSpaces/YCbCr.cs
  3. 83
      src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs
  4. 57
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
  5. 62
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
  6. 96
      tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs
  7. 73
      tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs
  8. 1
      tests/ImageSharp.Tests/TestImages.cs
  9. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png
  10. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png
  11. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.25.png
  12. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.75.png
  13. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png
  14. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png
  15. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.25.png
  16. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.75.png
  17. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png
  18. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png
  19. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.25.png
  20. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.75.png
  21. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png
  22. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png
  23. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.25.png
  24. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.75.png
  25. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png
  26. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png
  27. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.25.png
  28. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.75.png
  29. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png
  30. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png
  31. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.25.png
  32. BIN
      tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.75.png
  33. BIN
      tests/Images/Input/Png/colors-saturation-lightness.png

38
.gitattributes

@ -2,9 +2,14 @@
# Set default behavior to:
# treat as text and
# normalize to Unix-style line endings
###############################################################################
* text eol=lf
###############################################################################
# Set explicit file behavior to:
# treat as text and
# normalize to Unix-style line endings
###############################################################################
*.asm text eol=lf
*.c text eol=lf
*.clj text eol=lf
@ -49,19 +54,39 @@
*.txt text eol=lf
*.vb text eol=lf
*.yml text eol=lf
###############################################################################
# Set explicit file behavior to:
# treat as text
# normalize to Unix-style line endings and
# diff as csharp
###############################################################################
*.cs text eol=lf diff=csharp
###############################################################################
# Set explicit file behavior to:
# treat as text
# normalize to Unix-style line endings and
# use a union merge when resoling conflicts
###############################################################################
*.csproj text eol=lf merge=union
*.dbproj text eol=lf merge=union
*.fsproj text eol=lf merge=union
*.ncrunchproject text eol=lf merge=union
*.vbproj text eol=lf merge=union
###############################################################################
# Set explicit file behavior to:
# treat as text
# normalize to Windows-style line endings and
# use a union merge when resoling conflicts
###############################################################################
*.sln text eol=crlf merge=union
###############################################################################
# Set explicit file behavior to:
# treat as binary
###############################################################################
*.basis binary
*.bmp binary
*.dds binary
@ -90,7 +115,11 @@
*.woff2 binary
*.xls binary
*.xlsx binary
###############################################################################
# Set explicit file behavior to:
# diff as plain text
###############################################################################
*.doc diff=astextplain
*.docx diff=astextplain
*.dot diff=astextplain
@ -98,12 +127,3 @@
*.pptx diff=astextplain
*.rtf diff=astextplain
*.svg diff=astextplain
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.bmp filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.tif filter=lfs diff=lfs merge=lfs -text
*.tiff filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
*.webp filter=lfs diff=lfs merge=lfs -text

9
src/ImageSharp/ColorSpaces/YCbCr.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -17,6 +17,11 @@ namespace SixLabors.ImageSharp.ColorSpaces
private static readonly Vector3 Min = Vector3.Zero;
private static readonly Vector3 Max = new Vector3(255);
/// <summary>
/// The medium luminance achromatic color.
/// </summary>
public static readonly YCbCr Achromatic = new YCbCr(127.5F, 127.5F, 127.5F);
/// <summary>
/// Gets the Y luminance component.
/// <remarks>A value ranging between 0 and 255.</remarks>
@ -100,4 +105,4 @@ namespace SixLabors.ImageSharp.ColorSpaces
&& this.Cr.Equals(other.Cr);
}
}
}
}

83
src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Binarization;
@ -16,15 +16,44 @@ namespace SixLabors.ImageSharp.Processing
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="colorComponent">The color component to be compared to threshold.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, BinaryThresholdColorComponent colorComponent) =>
source.ApplyProcessor(new BinaryThresholdProcessor(threshold, colorComponent));
/// <summary>
/// Applies binarization to the image splitting the pixels at the given threshold with
/// Luminance as color component to be compared to threshold.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold) =>
source.ApplyProcessor(new BinaryThresholdProcessor(threshold));
source.ApplyProcessor(new BinaryThresholdProcessor(threshold, BinaryThresholdColorComponent.Luminance));
/// <summary>
/// Applies binarization to the image splitting the pixels at the given threshold.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="colorComponent">The color component to be compared to threshold.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BinaryThreshold(
this IImageProcessingContext source,
float threshold,
BinaryThresholdColorComponent colorComponent,
Rectangle rectangle) =>
source.ApplyProcessor(new BinaryThresholdProcessor(threshold, colorComponent), rectangle);
/// <summary>
/// Applies binarization to the image splitting the pixels at the given threshold with
/// Luminance as color component to be compared to threshold.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
@ -33,7 +62,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
float threshold,
Rectangle rectangle) =>
source.ApplyProcessor(new BinaryThresholdProcessor(threshold), rectangle);
source.ApplyProcessor(new BinaryThresholdProcessor(threshold, BinaryThresholdColorComponent.Luminance), rectangle);
/// <summary>
/// Applies binarization to the image splitting the pixels at the given threshold.
@ -42,21 +71,61 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <param name="colorComponent">The color component to be compared to threshold.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BinaryThreshold(
this IImageProcessingContext source,
float threshold,
Color upperColor,
Color lowerColor,
BinaryThresholdColorComponent colorComponent) =>
source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, colorComponent));
/// <summary>
/// Applies binarization to the image splitting the pixels at the given threshold with
/// Luminance as color component to be compared to threshold.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BinaryThreshold(
this IImageProcessingContext source,
float threshold,
Color upperColor,
Color lowerColor) =>
source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor));
source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, BinaryThresholdColorComponent.Luminance));
/// <summary>
/// <summary>
/// Applies binarization to the image splitting the pixels at the given threshold.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <param name="colorComponent">The color component to be compared to threshold.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BinaryThreshold(
this IImageProcessingContext source,
float threshold,
Color upperColor,
Color lowerColor,
BinaryThresholdColorComponent colorComponent,
Rectangle rectangle) =>
source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, colorComponent), rectangle);
/// <summary>
/// Applies binarization to the image splitting the pixels at the given threshold with
/// Luminance as color component to be compared to threshold.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
@ -67,6 +136,6 @@ namespace SixLabors.ImageSharp.Processing
Color upperColor,
Color lowerColor,
Rectangle rectangle) =>
source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor), rectangle);
source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, BinaryThresholdColorComponent.Luminance), rectangle);
}
}
}

57
src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs

@ -5,6 +5,27 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization
{
/// <summary>
/// The color component to be compared to threshold.
/// </summary>
public enum BinaryThresholdColorComponent : int
{
/// <summary>
/// Luminance color component according to ITU-R Recommendation BT.709.
/// </summary>
Luminance = 0,
/// <summary>
/// HSL saturation color component.
/// </summary>
Saturation = 1,
/// <summary>
/// Maximum of YCbCr chroma value, i.e. Cb and Cr distance from achromatic value.
/// </summary>
MaxChroma = 2,
}
/// <summary>
/// Performs simple binary threshold filtering against an image.
/// </summary>
@ -14,8 +35,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// Initializes a new instance of the <see cref="BinaryThresholdProcessor"/> class.
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="colorComponent">The color component to be compared to threshold.</param>
public BinaryThresholdProcessor(float threshold, BinaryThresholdColorComponent colorComponent)
: this(threshold, Color.White, Color.Black, colorComponent)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryThresholdProcessor"/> class with
/// Luminance as color component to be compared to threshold.
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
public BinaryThresholdProcessor(float threshold)
: this(threshold, Color.White, Color.Black)
: this(threshold, Color.White, Color.Black, BinaryThresholdColorComponent.Luminance)
{
}
@ -25,12 +57,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold.</param>
public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor)
/// <param name="colorComponent">The color component to be compared to threshold.</param>
public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor, BinaryThresholdColorComponent colorComponent)
{
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
this.Threshold = threshold;
this.UpperColor = upperColor;
this.LowerColor = lowerColor;
this.ColorComponent = colorComponent;
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryThresholdProcessor"/> class with
/// Luminance as color component to be compared to threshold.
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold.</param>
public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor)
: this(threshold, upperColor, lowerColor, BinaryThresholdColorComponent.Luminance)
{
}
/// <summary>
@ -48,7 +94,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// </summary>
public Color LowerColor { get; }
/// <inheritdoc />
/// <summary>
/// Gets a value indicating whether to use saturation value instead of luminance.
/// </summary>
public BinaryThresholdColorComponent ColorComponent { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new BinaryThresholdProcessor<TPixel>(configuration, this, source, sourceRectangle);

62
src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs

@ -5,6 +5,8 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization
@ -44,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
bool isAlphaOnly = typeof(TPixel) == typeof(A8);
var operation = new RowOperation(interest, source, upper, lower, threshold, isAlphaOnly);
var operation = new RowOperation(interest, source, upper, lower, threshold, this.definition.ColorComponent, isAlphaOnly);
ParallelRowIterator.IterateRows(
configuration,
interest,
@ -60,9 +62,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
private readonly TPixel upper;
private readonly TPixel lower;
private readonly byte threshold;
private readonly BinaryThresholdColorComponent colorComponent;
private readonly int minX;
private readonly int maxX;
private readonly bool isAlphaOnly;
private readonly ColorSpaceConverter colorSpaceConverter;
[MethodImpl(InliningOptions.ShortMethod)]
public RowOperation(
@ -71,15 +75,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
TPixel upper,
TPixel lower,
byte threshold,
BinaryThresholdColorComponent colorComponent,
bool isAlphaOnly)
{
this.source = source;
this.upper = upper;
this.lower = lower;
this.threshold = threshold;
this.colorComponent = colorComponent;
this.minX = bounds.X;
this.maxX = bounds.Right;
this.isAlphaOnly = isAlphaOnly;
this.colorSpaceConverter = new ColorSpaceConverter();
}
/// <inheritdoc/>
@ -90,14 +97,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
Span<TPixel> row = this.source.GetPixelRowSpan(y);
ref TPixel rowRef = ref MemoryMarshal.GetReference(row);
for (int x = this.minX; x < this.maxX; x++)
if (this.colorComponent == BinaryThresholdColorComponent.Luminance)
{
ref TPixel color = ref Unsafe.Add(ref rowRef, x);
color.ToRgba32(ref rgba);
for (int x = this.minX; x < this.maxX; x++)
{
ref TPixel color = ref Unsafe.Add(ref rowRef, x);
color.ToRgba32(ref rgba);
// Convert to grayscale using ITU-R Recommendation BT.709 if required
byte luminance = this.isAlphaOnly ? rgba.A : ColorNumerics.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
color = luminance >= this.threshold ? this.upper : this.lower;
// Convert to grayscale using ITU-R Recommendation BT.709 if required
byte luminance = this.isAlphaOnly ? rgba.A : ColorNumerics.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
color = luminance >= this.threshold ? this.upper : this.lower;
}
}
else if (this.colorComponent == BinaryThresholdColorComponent.Saturation)
{
float fThreshold = this.threshold / 255F;
for (int x = this.minX; x < this.maxX; x++)
{
ref TPixel color = ref Unsafe.Add(ref rowRef, x);
color.ToRgba32(ref rgba);
// Extract saturation and compare to threshold.
float sat = this.colorSpaceConverter.ToHsl(rgba).S;
color = (sat >= fThreshold) ? this.upper : this.lower;
}
}
else if (this.colorComponent == BinaryThresholdColorComponent.MaxChroma)
{
float fThreshold = this.threshold / 2F;
for (int x = this.minX; x < this.maxX; x++)
{
ref TPixel color = ref Unsafe.Add(ref rowRef, x);
color.ToRgba32(ref rgba);
// Calculate YCbCr value and compare to threshold.
var yCbCr = this.colorSpaceConverter.ToYCbCr(rgba);
if (MathF.Max(MathF.Abs(yCbCr.Cb - YCbCr.Achromatic.Cb), MathF.Abs(yCbCr.Cr - YCbCr.Achromatic.Cr)) >= fThreshold)
{
color = this.upper;
}
else
{
color = this.lower;
}
}
}
else
{
throw new NotImplementedException("Unknown BinaryThresholdColorComponent value " + this.colorComponent);
}
}
}

96
tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing;
@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
this.operations.BinaryThreshold(.23f);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>();
Assert.Equal(.23f, p.Threshold);
Assert.Equal(BinaryThresholdColorComponent.Luminance, p.ColorComponent);
Assert.Equal(Color.White, p.UpperColor);
Assert.Equal(Color.Black, p.LowerColor);
}
@ -26,6 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
this.operations.BinaryThreshold(.93f, this.rect);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>(this.rect);
Assert.Equal(.93f, p.Threshold);
Assert.Equal(BinaryThresholdColorComponent.Luminance, p.ColorComponent);
Assert.Equal(Color.White, p.UpperColor);
Assert.Equal(Color.Black, p.LowerColor);
}
@ -36,6 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>();
Assert.Equal(.23f, p.Threshold);
Assert.Equal(BinaryThresholdColorComponent.Luminance, p.ColorComponent);
Assert.Equal(Color.HotPink, p.UpperColor);
Assert.Equal(Color.Yellow, p.LowerColor);
}
@ -45,9 +48,98 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
{
this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, this.rect);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>(this.rect);
Assert.Equal(BinaryThresholdColorComponent.Luminance, p.ColorComponent);
Assert.Equal(.93f, p.Threshold);
Assert.Equal(Color.HotPink, p.UpperColor);
Assert.Equal(Color.Yellow, p.LowerColor);
}
[Fact]
public void BinarySaturationThreshold_CorrectProcessor()
{
this.operations.BinaryThreshold(.23f, BinaryThresholdColorComponent.Saturation);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>();
Assert.Equal(.23f, p.Threshold);
Assert.Equal(BinaryThresholdColorComponent.Saturation, p.ColorComponent);
Assert.Equal(Color.White, p.UpperColor);
Assert.Equal(Color.Black, p.LowerColor);
}
[Fact]
public void BinarySaturationThreshold_rect_CorrectProcessor()
{
this.operations.BinaryThreshold(.93f, BinaryThresholdColorComponent.Saturation, this.rect);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>(this.rect);
Assert.Equal(.93f, p.Threshold);
Assert.Equal(BinaryThresholdColorComponent.Saturation, p.ColorComponent);
Assert.Equal(Color.White, p.UpperColor);
Assert.Equal(Color.Black, p.LowerColor);
}
[Fact]
public void BinarySaturationThreshold_CorrectProcessorWithUpperLower()
{
this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow, BinaryThresholdColorComponent.Saturation);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>();
Assert.Equal(.23f, p.Threshold);
Assert.Equal(BinaryThresholdColorComponent.Saturation, p.ColorComponent);
Assert.Equal(Color.HotPink, p.UpperColor);
Assert.Equal(Color.Yellow, p.LowerColor);
}
[Fact]
public void BinarySaturationThreshold_rect_CorrectProcessorWithUpperLower()
{
this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, BinaryThresholdColorComponent.Saturation, this.rect);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>(this.rect);
Assert.Equal(.93f, p.Threshold);
Assert.Equal(BinaryThresholdColorComponent.Saturation, p.ColorComponent);
Assert.Equal(Color.HotPink, p.UpperColor);
Assert.Equal(Color.Yellow, p.LowerColor);
}
[Fact]
public void BinaryMaxChromaThreshold_CorrectProcessor()
{
this.operations.BinaryThreshold(.23f, BinaryThresholdColorComponent.MaxChroma);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>();
Assert.Equal(.23f, p.Threshold);
Assert.Equal(BinaryThresholdColorComponent.MaxChroma, p.ColorComponent);
Assert.Equal(Color.White, p.UpperColor);
Assert.Equal(Color.Black, p.LowerColor);
}
[Fact]
public void BinaryMaxChromaThreshold_rect_CorrectProcessor()
{
this.operations.BinaryThreshold(.93f, BinaryThresholdColorComponent.MaxChroma, this.rect);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>(this.rect);
Assert.Equal(.93f, p.Threshold);
Assert.Equal(BinaryThresholdColorComponent.MaxChroma, p.ColorComponent);
Assert.Equal(Color.White, p.UpperColor);
Assert.Equal(Color.Black, p.LowerColor);
}
[Fact]
public void BinaryMaxChromaThreshold_CorrectProcessorWithUpperLower()
{
this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow, BinaryThresholdColorComponent.MaxChroma);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>();
Assert.Equal(.23f, p.Threshold);
Assert.Equal(BinaryThresholdColorComponent.MaxChroma, p.ColorComponent);
Assert.Equal(Color.HotPink, p.UpperColor);
Assert.Equal(Color.Yellow, p.LowerColor);
}
[Fact]
public void BinaryMaxChromaThreshold_rect_CorrectProcessorWithUpperLower()
{
this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, BinaryThresholdColorComponent.MaxChroma, this.rect);
BinaryThresholdProcessor p = this.Verify<BinaryThresholdProcessor>(this.rect);
Assert.Equal(.93f, p.Threshold);
Assert.Equal(BinaryThresholdColorComponent.MaxChroma, p.ColorComponent);
Assert.Equal(Color.HotPink, p.UpperColor);
Assert.Equal(Color.Yellow, p.LowerColor);
}
}
}
}

73
tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs

@ -2,13 +2,13 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Binarization;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
using SixLabors.ImageSharp.Processing;
public class BinaryThresholdTest
{
public static readonly TheoryData<float> BinaryThresholdValues
@ -19,9 +19,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
};
public static readonly string[] CommonTestImages =
{
TestImages.Png.CalliphoraPartial, TestImages.Png.Bike
};
{
TestImages.Png.Rgb48Bpp,
TestImages.Png.ColorsSaturationLightness,
};
public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24;
@ -43,9 +44,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> source = provider.GetImage())
using (var image = source.Clone())
using (Image<TPixel> image = source.Clone())
{
var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2);
var bounds = new Rectangle(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8);
image.Mutate(x => x.BinaryThreshold(value, bounds));
image.DebugSave(provider, value);
@ -53,5 +54,63 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds);
}
}
[Theory]
[WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)]
public void ImageShouldApplyBinarySaturationThresholdFilter<TPixel>(TestImageProvider<TPixel> provider, float value)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdColorComponent.Saturation));
image.DebugSave(provider, value);
image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", System.Globalization.NumberFormatInfo.InvariantInfo));
}
}
[Theory]
[WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)]
public void ImageShouldApplyBinarySaturationThresholdInBox<TPixel>(TestImageProvider<TPixel> provider, float value)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> source = provider.GetImage())
using (Image<TPixel> image = source.Clone())
{
var bounds = new Rectangle(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8);
image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdColorComponent.Saturation, bounds));
image.DebugSave(provider, value);
image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", System.Globalization.NumberFormatInfo.InvariantInfo));
}
}
[Theory]
[WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)]
public void ImageShouldApplyBinaryMaxChromaThresholdFilter<TPixel>(TestImageProvider<TPixel> provider, float value)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdColorComponent.MaxChroma));
image.DebugSave(provider, value);
image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", System.Globalization.NumberFormatInfo.InvariantInfo));
}
}
[Theory]
[WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)]
public void ImageShouldApplyBinaryMaxChromaThresholdInBox<TPixel>(TestImageProvider<TPixel> provider, float value)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> source = provider.GetImage())
using (Image<TPixel> image = source.Clone())
{
var bounds = new Rectangle(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8);
image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdColorComponent.MaxChroma, bounds));
image.DebugSave(provider, value);
image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", System.Globalization.NumberFormatInfo.InvariantInfo));
}
}
}
}

1
tests/ImageSharp.Tests/TestImages.cs

@ -40,6 +40,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Rgb48BppInterlaced = "Png/rgb-48bpp-interlaced.png";
public const string Rgb48BppTrans = "Png/rgb-16-tRNS.png";
public const string Rgba64Bpp = "Png/rgb-16-alpha.png";
public const string ColorsSaturationLightness = "Png/colors-saturation-lightness.png";
public const string CalliphoraPartial = "Png/CalliphoraPartial.png";
public const string CalliphoraPartialGrayscale = "Png/CalliphoraPartialGrayscale.png";
public const string Bike = "Png/Bike.png";

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.25.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

BIN
tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.75.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

BIN
tests/Images/Input/Png/colors-saturation-lightness.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Loading…
Cancel
Save