Browse Source

Optimization pass.

pull/1555/head
James Jackson-South 6 years ago
parent
commit
ce2c1d28b3
  1. 5
      src/ImageSharp/ColorSpaces/YCbCr.cs
  2. 153
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs

5
src/ImageSharp/ColorSpaces/YCbCr.cs

@ -17,11 +17,6 @@ namespace SixLabors.ImageSharp.ColorSpaces
private static readonly Vector3 Min = Vector3.Zero; private static readonly Vector3 Min = Vector3.Zero;
private static readonly Vector3 Max = new Vector3(255); 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> /// <summary>
/// Gets the Y luminance component. /// Gets the Y luminance component.
/// <remarks>A value ranging between 0 and 255.</remarks> /// <remarks>A value ranging between 0 and 255.</remarks>

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

@ -3,10 +3,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization namespace SixLabors.ImageSharp.Processing.Processors.Binarization
@ -29,9 +26,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param> /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public BinaryThresholdProcessor(Configuration configuration, BinaryThresholdProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public BinaryThresholdProcessor(Configuration configuration, BinaryThresholdProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ => this.definition = definition;
this.definition = definition;
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
@ -44,10 +39,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); 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<RowOperation, Rgb24>(
ParallelRowIterator.IterateRows(
configuration, configuration,
interest, interest,
in operation); in operation);
@ -56,98 +57,132 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// <summary> /// <summary>
/// A <see langword="struct"/> implementing the clone logic for <see cref="BinaryThresholdProcessor{TPixel}"/>. /// A <see langword="struct"/> implementing the clone logic for <see cref="BinaryThresholdProcessor{TPixel}"/>.
/// </summary> /// </summary>
private readonly struct RowOperation : IRowOperation private readonly struct RowOperation : IRowOperation<Rgb24>
{ {
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly TPixel upper; private readonly TPixel upper;
private readonly TPixel lower; private readonly TPixel lower;
private readonly byte threshold; private readonly byte threshold;
private readonly BinaryThresholdColorComponent colorComponent; private readonly BinaryThresholdColorComponent colorComponent;
private readonly int minX; private readonly int startX;
private readonly int maxX; private readonly Configuration configuration;
private readonly bool isAlphaOnly;
private readonly ColorSpaceConverter colorSpaceConverter;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public RowOperation( public RowOperation(
Rectangle bounds, int startX,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
TPixel upper, TPixel upper,
TPixel lower, TPixel lower,
byte threshold, byte threshold,
BinaryThresholdColorComponent colorComponent, BinaryThresholdColorComponent colorComponent,
bool isAlphaOnly) Configuration configuration)
{ {
this.startX = startX;
this.source = source; this.source = source;
this.upper = upper; this.upper = upper;
this.lower = lower; this.lower = lower;
this.threshold = threshold; this.threshold = threshold;
this.colorComponent = colorComponent; this.colorComponent = colorComponent;
this.minX = bounds.X; this.configuration = configuration;
this.maxX = bounds.Right;
this.isAlphaOnly = isAlphaOnly;
this.colorSpaceConverter = new ColorSpaceConverter();
} }
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y) public void Invoke(int y, Span<Rgb24> span)
{ {
Rgba32 rgba = default; TPixel upper = this.upper;
Span<TPixel> row = this.source.GetPixelRowSpan(y); TPixel lower = this.lower;
ref TPixel rowRef = ref MemoryMarshal.GetReference(row);
if (this.colorComponent == BinaryThresholdColorComponent.Luminance) Span<TPixel> rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length);
PixelOperations<TPixel>.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); byte threshold = this.threshold;
color.ToRgba32(ref rgba); 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 break;
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++) case BinaryThresholdColorComponent.Saturation:
{ {
ref TPixel color = ref Unsafe.Add(ref rowRef, x); float threshold = this.threshold / 255F;
color.ToRgba32(ref rgba); 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. break;
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. case BinaryThresholdColorComponent.MaxChroma:
var yCbCr = this.colorSpaceConverter.ToYCbCr(rgba); {
if (MathF.Max(MathF.Abs(yCbCr.Cb - YCbCr.Achromatic.Cb), MathF.Abs(yCbCr.Cr - YCbCr.Achromatic.Cr)) >= fThreshold) float threshold = this.threshold / 2F;
{ for (int x = 0; x < rowSpan.Length; x++)
color = this.upper;
}
else
{ {
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 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));
}
} }
} }
} }

Loading…
Cancel
Save