.../Binarization/BinaryThresholdExtensions.cs | 35 +++++++++++++++------- .../Binarization/BinaryThresholdProcessor.cs | 20 ++++++++++--- .../BinaryThresholdProcessor{TPixel}.cs | 22 +++++++++++--- tests/Images/External | 0 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs b/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs index d21429589..02d5dab41 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,9 +16,12 @@ public static class BinaryThresholdExtensions /// /// The image this method extends. /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// + /// Use saturation value instead of luminance. + /// /// The to allow chaining of operations. - public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold) => - source.ApplyProcessor(new BinaryThresholdProcessor(threshold)); + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, bool useSaturationNotLuminance = false) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, useSaturationNotLuminance)); /// /// Applies binarization to the image splitting the pixels at the given threshold. @@ -28,12 +31,16 @@ public static class BinaryThresholdExtensions /// /// The structure that specifies the portion of the image object to alter. /// + /// + /// Use saturation value instead of luminance. + /// /// The to allow chaining of operations. public static IImageProcessingContext BinaryThreshold( this IImageProcessingContext source, float threshold, - Rectangle rectangle) => - source.ApplyProcessor(new BinaryThresholdProcessor(threshold), rectangle); + Rectangle rectangle, + bool useSaturationNotLuminance = false) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, useSaturationNotLuminance), rectangle); /// /// Applies binarization to the image splitting the pixels at the given threshold. @@ -42,13 +49,17 @@ public static class BinaryThresholdExtensions /// 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 + /// + /// Use saturation value instead of luminance. + /// /// 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)); + Color lowerColor, + bool useSaturationNotLuminance = false) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, useSaturationNotLuminance)); /// /// Applies binarization to the image splitting the pixels at the given threshold. @@ -60,13 +71,17 @@ public static class BinaryThresholdExtensions /// /// The structure that specifies the portion of the image object to alter. /// + /// + /// Use saturation value instead of luminance. + /// /// The to allow chaining of operations. public static IImageProcessingContext BinaryThreshold( this IImageProcessingContext source, float threshold, Color upperColor, Color lowerColor, - Rectangle rectangle) => - source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor), rectangle); + Rectangle rectangle, + bool useSaturationNotLuminance = false) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, useSaturationNotLuminance), 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..cc61efbf1 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -14,8 +14,11 @@ public class BinaryThresholdProcessor : IImageProcessor /// Initializes a new instance of the class. /// /// The threshold to split the image. Must be between 0 and 1. - public BinaryThresholdProcessor(float threshold) - : this(threshold, Color.White, Color.Black) + /// + /// Use saturation value instead of luminance. + /// + public BinaryThresholdProcessor(float threshold,bool useSaturationNotLuminance = false) + : this(threshold, Color.White, Color.Black,useSaturationNotLuminance) { } @@ -25,12 +28,16 @@ public BinaryThresholdProcessor(float 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) + /// + /// Use saturation value instead of luminance. + /// + public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor,bool useSaturationNotLuminance = false) { Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); this.Threshold = threshold; this.UpperColor = upperColor; this.LowerColor = lowerColor; + this.UseSaturationNotLuminance = useSaturationNotLuminance; } /// @@ -48,7 +55,12 @@ public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerCo /// public Color LowerColor { get; } - /// + /// + /// Gets a value indicating whether to use saturation value instead of luminance. + /// + public bool UseSaturationNotLuminance { 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..26c051b64 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -44,7 +44,7 @@ protected override void OnFrameApply(ImageFrame source) 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.UseSaturationNotLuminance, isAlphaOnly); ParallelRowIterator.IterateRows( configuration, interest, @@ -60,9 +60,11 @@ protected override void OnFrameApply(ImageFrame source) private readonly TPixel upper; private readonly TPixel lower; private readonly byte threshold; + private readonly bool useSaturationNotLuminance; private readonly int minX; private readonly int maxX; private readonly bool isAlphaOnly; + private readonly ColorSpaces.Conversion.ColorSpaceConverter colorSpaceConverter; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( @@ -71,15 +73,18 @@ protected override void OnFrameApply(ImageFrame source) TPixel upper, TPixel lower, byte threshold, + bool useSaturationNotLuminance, bool isAlphaOnly) { this.source = source; this.upper = upper; this.lower = lower; this.threshold = threshold; + this.useSaturationNotLuminance = useSaturationNotLuminance; this.minX = bounds.X; this.maxX = bounds.Right; this.isAlphaOnly = isAlphaOnly; + this.colorSpaceConverter = new ColorSpaces.Conversion.ColorSpaceConverter(); } /// @@ -95,9 +100,18 @@ public void Invoke(int y) 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; + if (this.useSaturationNotLuminance) + { + float sat = this.colorSpaceConverter.ToHsl(rgba).S; + byte val = (byte)(sat * 255); + color = val >= this.threshold ? this.upper : this.lower; + } + else + { + // 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; + } } } } diff --git a/tests/Images/External b/tests/Images/External --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 1df65162448996332449387a46da942e181043c8 +Subproject commit 1df65162448996332449387a46da942e181043c8-dirty