Browse Source

JpegColorConverter.FromYCbCrSimd256 conversion core method seems to work

af/merge-core
Anton Firszov 9 years ago
parent
commit
35d9f04c1b
  1. 11
      src/ImageSharp/Common/Extensions/Vector4Extensions.cs
  2. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs
  3. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs
  4. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs
  5. 161
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
  6. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs
  7. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs
  8. 114
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

11
src/ImageSharp/Common/Extensions/Vector4Extensions.cs

@ -79,5 +79,16 @@ namespace SixLabors.ImageSharp
return MathF.Pow((signal + 0.055F) / 1.055F, 2.4F);
}
/// <summary>
/// Transform all scalars in 'v' in a way that converting them to <see cref="int"/> would have rounding semantics.
/// </summary>
[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);
}
}
}

2
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)

2
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)

2
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)

161
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<Vector4> result)
{
ConvertCore(values, result);
}
internal static void ConvertCore(ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> 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<Vector4> 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<Vector4> result)
{
if (!IsAvailable)
{
throw new InvalidOperationException(
"JpegColorConverter.FromYCbCrSimd256 can be used only on architecture having 256 byte floating point SIMD registers!");
}
ref Vector<float> yBase =
ref Unsafe.As<float, Vector<float>>(ref values.Component0.DangerousGetPinnableReference());
ref Vector<float> cbBase =
ref Unsafe.As<float, Vector<float>>(ref values.Component1.DangerousGetPinnableReference());
ref Vector<float> crBase =
ref Unsafe.As<float, Vector<float>>(ref values.Component2.DangerousGetPinnableReference());
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref result.DangerousGetPinnableReference());
var chromaOffset = new Vector<float>(-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<float> y = Unsafe.Add(ref yBase, i);
Vector<float> cb = Unsafe.Add(ref cbBase, i) + chromaOffset;
Vector<float> 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<float> r = y + (cr * new Vector<float>(1.402F));
Vector<float> g = y - (cb * new Vector<float>(0.344136F)) - (cr * new Vector<float>(0.714136F));
Vector<float> b = y + (cb * new Vector<float>(1.772F));
// Vector<float> has no .Clamp(), need to switch to Vector4 for the next operation:
// TODO: Is it worth to use Vector<float> at all?
Vector4Pair rr = Unsafe.As<Vector<float>, Vector4Pair>(ref r);
Vector4Pair gg = Unsafe.As<Vector<float>, Vector4Pair>(ref g);
Vector4Pair bb = Unsafe.As<Vector<float>, 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<float>.Count == 8;
}
}
}

2
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)

2
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// <summary>
/// The avalilable converters
/// </summary>
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() };
/// <summary>
/// Initializes a new instance of the <see cref="JpegColorConverter"/> class.

114
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<int, int, int> CommonConversionData =
new TheoryData<int, int, int>
@ -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<Vector4> 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<JpegColorConverter.ComponentValues, Span<Vector4>, int> doValidate)
Action<JpegColorConverter.ComponentValues, Span<Vector4>, 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<JpegColorConverter.ComponentValues, Span<Vector4>> doConvert,
int componentCount,
int inputBufferLength,
int resultBufferLength,
int seed,
Action<JpegColorConverter.ComponentValues, Span<Vector4>, 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);
}
}
}

Loading…
Cancel
Save