diff --git a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs
index 5fbc3960a..1809dd329 100644
--- a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs
+++ b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs
@@ -79,5 +79,16 @@ namespace SixLabors.ImageSharp
return MathF.Pow((signal + 0.055F) / 1.055F, 2.4F);
}
+
+ ///
+ /// Transform all scalars in 'v' in a way that converting them to would have rounding semantics.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static Vector4 PseudoRound(this Vector4 v)
+ {
+ var sign = Vector4.Clamp(v, new Vector4(-1), new Vector4(1));
+
+ return v + (sign * 0.5f);
+ }
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs
index 524cc76df..908ffb85e 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs
@@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
- private class FromCmyk : JpegColorConverter
+ internal class FromCmyk : JpegColorConverter
{
public FromCmyk()
: base(JpegColorSpace.Cmyk)
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs
index 9ff263dcf..2b19eebac 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs
@@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
- private class FromGrayScale : JpegColorConverter
+ internal class FromGrayScale : JpegColorConverter
{
public FromGrayScale()
: base(JpegColorSpace.GrayScale)
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs
index f4a702783..c4a30ea3e 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs
@@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
- private class FromRgb : JpegColorConverter
+ internal class FromRgb : JpegColorConverter
{
public FromRgb()
: base(JpegColorSpace.RGB)
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
index 24e8d753b..a516ceb5a 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
@@ -1,18 +1,26 @@
using System;
using System.Numerics;
+using System.Runtime.CompilerServices;
+
+// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
- private class FromYCbCr : JpegColorConverter
+ internal class FromYCbCrBasic : JpegColorConverter
{
- public FromYCbCr()
+ public FromYCbCrBasic()
: base(JpegColorSpace.YCbCr)
{
}
public override void ConvertToRGBA(ComponentValues values, Span result)
+ {
+ ConvertCore(values, result);
+ }
+
+ internal static void ConvertCore(ComponentValues values, Span result)
{
// TODO: We can optimize a lot here with Vector and SRCS.Unsafe()!
ReadOnlySpan yVals = values.Component0;
@@ -39,5 +47,154 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
}
}
}
+
+ internal class FromYCbCrSimd256 : JpegColorConverter
+ {
+ public FromYCbCrSimd256()
+ : base(JpegColorSpace.YCbCr)
+ {
+ }
+
+ public override void ConvertToRGBA(ComponentValues values, Span result)
+ {
+ }
+
+ private struct Vector4Pair
+ {
+ public Vector4 A;
+
+ public Vector4 B;
+
+ private static readonly Vector4 Scale = new Vector4(1 / 255F);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void RoundAndDownscale()
+ {
+ this.A = this.A.PseudoRound() * Scale;
+ this.B = this.B.PseudoRound() * Scale;
+ }
+ }
+
+ private struct Vector4Octet
+ {
+#pragma warning disable SA1132 // Do not combine fields
+ public Vector4 V0, V1, V2, V3, V4, V5, V6, V7;
+#pragma warning restore SA1132 // Do not combine fields
+
+ public static Vector4Octet CreateCollector()
+ {
+ var result = default(Vector4Octet);
+ result.V0.W = 1f;
+ result.V1.W = 1f;
+ result.V2.W = 1f;
+ result.V3.W = 1f;
+ result.V4.W = 1f;
+ result.V5.W = 1f;
+ result.V6.W = 1f;
+ result.V7.W = 1f;
+ return result;
+ }
+
+ public void Collect(ref Vector4Pair rr, ref Vector4Pair gg, ref Vector4Pair bb)
+ {
+ this.V0.X = rr.A.X;
+ this.V0.Y = gg.A.X;
+ this.V0.Z = bb.A.X;
+ this.V0.W = 1f;
+
+ this.V1.X = rr.A.Y;
+ this.V1.Y = gg.A.Y;
+ this.V1.Z = bb.A.Y;
+ this.V1.W = 1f;
+
+ this.V2.X = rr.A.Z;
+ this.V2.Y = gg.A.Z;
+ this.V2.Z = bb.A.Z;
+ this.V2.W = 1f;
+
+ this.V3.X = rr.A.W;
+ this.V3.Y = gg.A.W;
+ this.V3.Z = bb.A.W;
+ this.V3.W = 1f;
+
+ this.V4.X = rr.B.X;
+ this.V4.Y = gg.B.X;
+ this.V4.Z = bb.B.X;
+ this.V4.W = 1f;
+
+ this.V5.X = rr.B.Y;
+ this.V5.Y = gg.B.Y;
+ this.V5.Z = bb.B.Y;
+ this.V5.W = 1f;
+
+ this.V6.X = rr.B.Z;
+ this.V6.Y = gg.B.Z;
+ this.V6.Z = bb.B.Z;
+ this.V6.W = 1f;
+
+ this.V7.X = rr.B.W;
+ this.V7.Y = gg.B.W;
+ this.V7.Z = bb.B.W;
+ this.V7.W = 1f;
+ }
+ }
+
+ internal static void ConvertAligned(ComponentValues values, Span result)
+ {
+ if (!IsAvailable)
+ {
+ throw new InvalidOperationException(
+ "JpegColorConverter.FromYCbCrSimd256 can be used only on architecture having 256 byte floating point SIMD registers!");
+ }
+
+ ref Vector yBase =
+ ref Unsafe.As>(ref values.Component0.DangerousGetPinnableReference());
+ ref Vector cbBase =
+ ref Unsafe.As>(ref values.Component1.DangerousGetPinnableReference());
+ ref Vector crBase =
+ ref Unsafe.As>(ref values.Component2.DangerousGetPinnableReference());
+
+ ref Vector4Octet resultBase =
+ ref Unsafe.As(ref result.DangerousGetPinnableReference());
+
+ var chromaOffset = new Vector(-128f);
+
+ int n = result.Length / 8;
+
+ for (int i = 0; i < n; i++)
+ {
+ // y = yVals[i];
+ // cb = cbVals[i] - 128F;
+ // cr = crVals[i] - 128F;
+ Vector y = Unsafe.Add(ref yBase, i);
+ Vector cb = Unsafe.Add(ref cbBase, i) + chromaOffset;
+ Vector cr = Unsafe.Add(ref crBase, i) + chromaOffset;
+
+ // r = y + (1.402F * cr);
+ // g = y - (0.344136F * cb) - (0.714136F * cr);
+ // b = y + (1.772F * cb);
+ // Adding & multiplying 8 elements at one time:
+ Vector r = y + (cr * new Vector(1.402F));
+ Vector g = y - (cb * new Vector(0.344136F)) - (cr * new Vector(0.714136F));
+ Vector b = y + (cb * new Vector(1.772F));
+
+ // Vector has no .Clamp(), need to switch to Vector4 for the next operation:
+ // TODO: Is it worth to use Vector at all?
+ Vector4Pair rr = Unsafe.As, Vector4Pair>(ref r);
+ Vector4Pair gg = Unsafe.As, Vector4Pair>(ref g);
+ Vector4Pair bb = Unsafe.As, Vector4Pair>(ref b);
+
+ rr.RoundAndDownscale();
+ gg.RoundAndDownscale();
+ bb.RoundAndDownscale();
+
+ // Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
+ ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
+ destination.Collect(ref rr, ref gg, ref bb);
+ }
+ }
+
+ public static bool IsAvailable => Vector.Count == 8;
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs
index 3449cc6b1..7e6edbdce 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs
@@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
- private class FromYccK : JpegColorConverter
+ internal class FromYccK : JpegColorConverter
{
public FromYccK()
: base(JpegColorSpace.Ycck)
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs
index 567713422..0d0b6d3e9 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs
@@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
///
/// The avalilable converters
///
- private static readonly JpegColorConverter[] Converters = { new FromYCbCr(), new FromYccK(), new FromCmyk(), new FromGrayScale(), new FromRgb() };
+ private static readonly JpegColorConverter[] Converters = { new FromYCbCrBasic(), new FromYccK(), new FromCmyk(), new FromGrayScale(), new FromRgb() };
///
/// Initializes a new instance of the class.
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
index d24890e8f..885a8f809 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
@@ -1,19 +1,20 @@
-namespace SixLabors.ImageSharp.Tests.Formats.Jpg
-{
- using System;
- using System.Numerics;
+using System;
+using System.Numerics;
- using SixLabors.ImageSharp.ColorSpaces;
- using SixLabors.ImageSharp.ColorSpaces.Conversion;
- using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
- using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ColorSpaces;
+using SixLabors.ImageSharp.ColorSpaces.Conversion;
+using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
+using SixLabors.ImageSharp.Memory;
- using Xunit;
- using Xunit.Abstractions;
+using Xunit;
+using Xunit.Abstractions;
+// ReSharper disable InconsistentNaming
+namespace SixLabors.ImageSharp.Tests.Formats.Jpg
+{
public class JpegColorConverterTests
{
- private const float Precision = 0.01f;
+ private const float Precision = 1/255f;
public static readonly TheoryData CommonConversionData =
new TheoryData
@@ -32,30 +33,62 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private ITestOutputHelper Output { get; }
+ private static int R(float f) => (int)MathF.Round(f, MidpointRounding.AwayFromZero);
+
+ // TODO: Move this to a proper test class!
+ [Theory]
+ [InlineData(0.32, 54.5, -3.5, -4.1)]
+ [InlineData(5.3, 536.4, 4.5, 8.1)]
+ public void Vector4_PseudoRound(float x, float y, float z, float w)
+ {
+ var v = new Vector4(x, y, z, w);
+
+ Vector4 actual = v.PseudoRound();
+
+ Assert.Equal(
+ R(v.X),
+ (int)actual.X
+ );
+ Assert.Equal(
+ R(v.Y),
+ (int)actual.Y
+ );
+ Assert.Equal(
+ R(v.Z),
+ (int)actual.Z
+ );
+ Assert.Equal(
+ R(v.W),
+ (int)actual.W
+ );
+ }
+
[Theory]
[MemberData(nameof(CommonConversionData))]
public void ConvertFromYCbCr(int inputBufferLength, int resultBufferLength, int seed)
{
- ValidateConversion(
- JpegColorSpace.YCbCr,
- 3,
- inputBufferLength,
- resultBufferLength,
- seed,
- (values, result, i) =>
- {
- float y = values.Component0[i];
- float cb = values.Component1[i];
- float cr = values.Component2[i];
- var ycbcr = new YCbCr(y, cb, cr);
+ ValidateConversion(JpegColorSpace.YCbCr, 3, inputBufferLength, resultBufferLength, seed, ValidateYCbCr);
+ }
- Vector4 rgba = result[i];
- var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
- var expected = ColorSpaceConverter.ToRgb(ycbcr);
+ private static void ValidateYCbCr(JpegColorConverter.ComponentValues values, Span result, int i)
+ {
+ float y = values.Component0[i];
+ float cb = values.Component1[i];
+ float cr = values.Component2[i];
+ var ycbcr = new YCbCr(y, cb, cr);
- Assert.True(actual.AlmostEquals(expected, Precision));
- Assert.Equal(1, rgba.W);
- });
+ Vector4 rgba = result[i];
+ var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
+ var expected = ColorSpaceConverter.ToRgb(ycbcr);
+
+ Assert.True(actual.AlmostEquals(expected, Precision));
+ Assert.Equal(1, rgba.W);
+ }
+
+ [Fact]
+ public void ConvertFromYCbCr_SimdWithAlignedValues()
+ {
+ ValidateConversion(JpegColorConverter.FromYCbCrSimd256.ConvertAligned, 3, 64, 64, 1, ValidateYCbCr);
}
[Theory]
@@ -207,18 +240,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
int inputBufferLength,
int resultBufferLength,
int seed,
- Action, int> doValidate)
+ Action, int> validatePixelValue)
{
- var converter = JpegColorConverter.GetConverter(colorSpace);
+ ValidateConversion(
+ (v, r) => JpegColorConverter.GetConverter(colorSpace).ConvertToRGBA(v, r),
+ componentCount,
+ inputBufferLength,
+ resultBufferLength,
+ seed,
+ validatePixelValue);
+ }
+ private static void ValidateConversion(
+ Action> doConvert,
+ int componentCount,
+ int inputBufferLength,
+ int resultBufferLength,
+ int seed,
+ Action, int> validatePixelValue)
+ {
JpegColorConverter.ComponentValues values = CreateRandomValues(componentCount, inputBufferLength, seed);
Vector4[] result = new Vector4[resultBufferLength];
- converter.ConvertToRGBA(values, result);
+ doConvert(values, result);
for (int i = 0; i < resultBufferLength; i++)
{
- doValidate(values, result, i);
+ validatePixelValue(values, result, i);
}
}
}