diff --git a/src/ImageSharp/ColorSpaces/YCbCr.cs b/src/ImageSharp/ColorSpaces/YCbCr.cs
index 49c5e57ede..b39fe30252 100644
--- a/src/ImageSharp/ColorSpaces/YCbCr.cs
+++ b/src/ImageSharp/ColorSpaces/YCbCr.cs
@@ -17,11 +17,6 @@ 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.
diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
index b33098e574..aa03cc27d2 100644
--- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
@@ -3,10 +3,7 @@
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
@@ -29,9 +26,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// The source area to process for the current processor instance.
public BinaryThresholdProcessor(Configuration configuration, BinaryThresholdProcessor definition, Image source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
- {
- this.definition = definition;
- }
+ => this.definition = definition;
///
protected override void OnFrameApply(ImageFrame source)
@@ -44,10 +39,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
Configuration configuration = this.Configuration;
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
- bool isAlphaOnly = typeof(TPixel) == typeof(A8);
+ var operation = new RowOperation(
+ interest.X,
+ source,
+ upper,
+ lower,
+ threshold,
+ this.definition.ColorComponent,
+ configuration);
- var operation = new RowOperation(interest, source, upper, lower, threshold, this.definition.ColorComponent, isAlphaOnly);
- ParallelRowIterator.IterateRows(
+ ParallelRowIterator.IterateRows(
configuration,
interest,
in operation);
@@ -56,98 +57,132 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
///
/// A implementing the clone logic for .
///
- private readonly struct RowOperation : IRowOperation
+ private readonly struct RowOperation : IRowOperation
{
private readonly ImageFrame source;
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;
+ private readonly int startX;
+ private readonly Configuration configuration;
[MethodImpl(InliningOptions.ShortMethod)]
public RowOperation(
- Rectangle bounds,
+ int startX,
ImageFrame source,
TPixel upper,
TPixel lower,
byte threshold,
BinaryThresholdColorComponent colorComponent,
- bool isAlphaOnly)
+ Configuration configuration)
{
+ this.startX = startX;
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();
+ this.configuration = configuration;
}
///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void Invoke(int y)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Invoke(int y, Span span)
{
- Rgba32 rgba = default;
- Span row = this.source.GetPixelRowSpan(y);
- ref TPixel rowRef = ref MemoryMarshal.GetReference(row);
+ TPixel upper = this.upper;
+ TPixel lower = this.lower;
- if (this.colorComponent == BinaryThresholdColorComponent.Luminance)
+ Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length);
+ PixelOperations.Instance.ToRgb24(this.configuration, rowSpan, span);
+
+ switch (this.colorComponent)
{
- for (int x = this.minX; x < this.maxX; x++)
+ case BinaryThresholdColorComponent.Luminance:
{
- ref TPixel color = ref Unsafe.Add(ref rowRef, x);
- color.ToRgba32(ref rgba);
+ byte threshold = this.threshold;
+ for (int x = 0; x < rowSpan.Length; x++)
+ {
+ Rgb24 rgb = span[x];
+ byte luminance = ColorNumerics.Get8BitBT709Luminance(rgb.R, rgb.G, rgb.B);
+ ref TPixel color = ref rowSpan[x];
+ color = luminance >= threshold ? upper : 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;
+ break;
}
- }
- else if (this.colorComponent == BinaryThresholdColorComponent.Saturation)
- {
- float fThreshold = this.threshold / 255F;
- for (int x = this.minX; x < this.maxX; x++)
+ case BinaryThresholdColorComponent.Saturation:
{
- ref TPixel color = ref Unsafe.Add(ref rowRef, x);
- color.ToRgba32(ref rgba);
+ float threshold = this.threshold / 255F;
+ for (int x = 0; x < rowSpan.Length; x++)
+ {
+ float saturation = GetSaturation(span[x]);
+ ref TPixel color = ref rowSpan[x];
+ color = saturation >= threshold ? upper : lower;
+ }
- // Extract saturation and compare to threshold.
- float sat = this.colorSpaceConverter.ToHsl(rgba).S;
- color = (sat >= fThreshold) ? this.upper : this.lower;
+ break;
}
- }
- 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
+ case BinaryThresholdColorComponent.MaxChroma:
+ {
+ float threshold = this.threshold / 2F;
+ for (int x = 0; x < rowSpan.Length; x++)
{
- color = this.lower;
+ float chroma = GetMaxChroma(span[x]);
+ ref TPixel color = ref rowSpan[x];
+ color = chroma >= threshold ? upper : lower;
}
+
+ break;
}
}
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float GetSaturation(Rgb24 rgb)
+ {
+ // Slimmed down RGB => HSL formula. See HslAndRgbConverter.
+ float r = rgb.R / 255F;
+ float g = rgb.G / 255F;
+ float b = rgb.B / 255F;
+
+ float max = MathF.Max(r, MathF.Max(g, b));
+ float min = MathF.Min(r, MathF.Min(g, b));
+ float chroma = max - min;
+
+ if (MathF.Abs(chroma) < Constants.Epsilon)
+ {
+ return 0F;
+ }
+
+ float l = (max + min) / 2F;
+
+ if (l <= .5F)
+ {
+ return chroma / (max + min);
+ }
else
{
- throw new NotImplementedException("Unknown BinaryThresholdColorComponent value " + this.colorComponent);
+ return chroma / (2F - max - min);
}
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float GetMaxChroma(Rgb24 rgb)
+ {
+ // Slimmed down RGB => YCbCr formula. See YCbCrAndRgbConverter.
+ float r = rgb.R;
+ float g = rgb.G;
+ float b = rgb.B;
+ const float achromatic = 127.5F;
+
+ float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
+ float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
+
+ return MathF.Max(MathF.Abs(cb - achromatic), MathF.Abs(cr - achromatic));
+ }
}
}
}