diff --git a/.gitattributes b/.gitattributes index 7c648c077..01a3825f8 100644 --- a/.gitattributes +++ b/.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 diff --git a/src/ImageSharp/ColorSpaces/YCbCr.cs b/src/ImageSharp/ColorSpaces/YCbCr.cs index eaaf7f58f..49c5e57ed 100644 --- a/src/ImageSharp/ColorSpaces/YCbCr.cs +++ b/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); + /// + /// The medium luminance achromatic color. + /// + public static readonly YCbCr Achromatic = new YCbCr(127.5F, 127.5F, 127.5F); + /// /// Gets the Y luminance component. /// A value ranging between 0 and 255. @@ -100,4 +105,4 @@ namespace SixLabors.ImageSharp.ColorSpaces && this.Cr.Equals(other.Cr); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs b/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs index d21429589..375c787db 100644 --- a/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs +++ b/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 /// /// The image this method extends. /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color component to be compared to threshold. + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, BinaryThresholdColorComponent colorComponent) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, colorComponent)); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold with + /// Luminance as color component to be compared to threshold. + /// + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. /// The to allow chaining of operations. public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold) => - source.ApplyProcessor(new BinaryThresholdProcessor(threshold)); + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, BinaryThresholdColorComponent.Luminance)); /// /// Applies binarization to the image splitting the pixels at the given threshold. /// /// The image this method extends. /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color component to be compared to threshold. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + BinaryThresholdColorComponent colorComponent, + Rectangle rectangle) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, colorComponent), rectangle); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold with + /// Luminance as color component to be compared to threshold. + /// + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. /// /// The structure that specifies the portion of the image object to alter. /// @@ -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); /// /// Applies binarization to the image splitting the pixels at the given threshold. @@ -42,21 +71,61 @@ namespace SixLabors.ImageSharp.Processing /// The threshold to apply binarization of the image. Must be between 0 and 1. /// The color to use for pixels that are above the threshold. /// The color to use for pixels that are below the threshold + /// The color component to be compared to threshold. + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Color upperColor, + Color lowerColor, + BinaryThresholdColorComponent colorComponent) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, colorComponent)); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold with + /// Luminance as color component to be compared to threshold. + /// + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold /// The to allow chaining of operations. 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)); - /// + /// /// Applies binarization to the image splitting the pixels at the given threshold. /// /// The image this method extends. /// The threshold to apply binarization of the image. Must be between 0 and 1. /// The color to use for pixels that are above the threshold. /// The color to use for pixels that are below the threshold + /// The color component to be compared to threshold. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold with + /// Luminance as color component to be compared to threshold. + /// + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold /// /// The structure that specifies the portion of the image object to alter. /// @@ -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); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index 460a82f0a..992852499 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -5,6 +5,27 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Binarization { + /// + /// The color component to be compared to threshold. + /// + public enum BinaryThresholdColorComponent : int + { + /// + /// Luminance color component according to ITU-R Recommendation BT.709. + /// + Luminance = 0, + + /// + /// HSL saturation color component. + /// + Saturation = 1, + + /// + /// Maximum of YCbCr chroma value, i.e. Cb and Cr distance from achromatic value. + /// + MaxChroma = 2, + } + /// /// Performs simple binary threshold filtering against an image. /// @@ -14,8 +35,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// Initializes a new instance of the class. /// /// The threshold to split the image. Must be between 0 and 1. + /// The color component to be compared to threshold. + public BinaryThresholdProcessor(float threshold, BinaryThresholdColorComponent colorComponent) + : this(threshold, Color.White, Color.Black, colorComponent) + { + } + + /// + /// Initializes a new instance of the class with + /// Luminance as color component to be compared to threshold. + /// + /// The threshold to split the image. Must be between 0 and 1. 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 /// The threshold to split the image. Must be between 0 and 1. /// The color to use for pixels that are above the threshold. /// The color to use for pixels that are below the threshold. - public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor) + /// The color component to be compared to threshold. + 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; + } + + /// + /// Initializes a new instance of the class with + /// Luminance as color component to be compared to threshold. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold. + public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor) + : this(threshold, upperColor, lowerColor, BinaryThresholdColorComponent.Luminance) + { } /// @@ -48,7 +94,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// public Color LowerColor { get; } - /// + /// + /// Gets a value indicating whether to use saturation value instead of luminance. + /// + public BinaryThresholdColorComponent ColorComponent { get; } + + /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) where TPixel : unmanaged, IPixel => new BinaryThresholdProcessor(configuration, this, source, sourceRectangle); diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index df95b6f1b..b33098e57 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/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(); } /// @@ -90,14 +97,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization Span 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); } } } diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs index 5bdfda02e..a02ca36ee 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs +++ b/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(); 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(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(); 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(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(); + 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(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(); + 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(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(); + 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(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(); + 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(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); + } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs index c5b7808cc..2a0696356 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs +++ b/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 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 { using (Image source = provider.GetImage()) - using (var image = source.Clone()) + using (Image 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(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + using (Image 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(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + using (Image source = provider.GetImage()) + using (Image 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(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + using (Image 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(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + using (Image source = provider.GetImage()) + using (Image 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)); + } + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index ac9d6422a..c15327e0d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/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"; diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png new file mode 100644 index 000000000..0cf2d9b99 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png new file mode 100644 index 000000000..029ec4c31 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.25.png new file mode 100644 index 000000000..48e24d0c5 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.75.png new file mode 100644 index 000000000..00f971df4 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.75.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png new file mode 100644 index 000000000..8be07a4bb Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png new file mode 100644 index 000000000..88029f1e1 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.25.png new file mode 100644 index 000000000..05ead8ee1 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.75.png new file mode 100644 index 000000000..b9d36892e Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.75.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png new file mode 100644 index 000000000..241a312ba Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png new file mode 100644 index 000000000..9ab0bd25c Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.25.png new file mode 100644 index 000000000..c162e16ed Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.75.png new file mode 100644 index 000000000..c09359c88 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.75.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png new file mode 100644 index 000000000..e5b136185 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png new file mode 100644 index 000000000..036dda663 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.25.png new file mode 100644 index 000000000..fb58f606d Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.75.png new file mode 100644 index 000000000..a06aa0d6e Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.75.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png new file mode 100644 index 000000000..726786111 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png new file mode 100644 index 000000000..a5286f956 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.25.png new file mode 100644 index 000000000..ee5f95451 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.75.png new file mode 100644 index 000000000..0d528245e Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.75.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png new file mode 100644 index 000000000..1dbf895df Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png new file mode 100644 index 000000000..7e4300a2e Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.25.png new file mode 100644 index 000000000..6a4568ea7 Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.25.png differ diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.75.png new file mode 100644 index 000000000..129ee82cc Binary files /dev/null and b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.75.png differ diff --git a/tests/Images/Input/Png/colors-saturation-lightness.png b/tests/Images/Input/Png/colors-saturation-lightness.png new file mode 100644 index 000000000..59069002d Binary files /dev/null and b/tests/Images/Input/Png/colors-saturation-lightness.png differ