Browse Source

Begin re-implementing converters

pull/2917/head
James Jackson-South 1 year ago
parent
commit
959e6004d7
  1. 14
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  2. 61
      src/ImageSharp/Common/Helpers/Vector128Utilities.cs
  3. 51
      src/ImageSharp/Common/Helpers/Vector256Utilities.cs
  4. 37
      src/ImageSharp/Common/Helpers/Vector512Utilities.cs
  5. 4
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs
  6. 4
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs
  7. 4
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs
  8. 4
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs
  9. 116
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs
  10. 116
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs
  11. 123
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs
  12. 4
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs
  13. 15
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
  14. 30
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs
  15. 34
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs
  16. 34
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs
  17. 111
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs
  18. 28
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs
  19. 13
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

14
src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

@ -616,7 +616,12 @@ internal static partial class SimdUtils
return Fma.MultiplyAdd(vm1, vm0, va);
}
return Avx.Add(Avx.Multiply(vm0, vm1), va);
if (Avx.IsSupported)
{
return Avx.Add(Avx.Multiply(vm0, vm1), va);
}
return va + (vm0 * vm1);
}
/// <summary>
@ -644,7 +649,12 @@ internal static partial class SimdUtils
return AdvSimd.Add(AdvSimd.Multiply(vm0, vm1), va);
}
return Sse.Add(Sse.Multiply(vm0, vm1), va);
if (Sse.IsSupported)
{
return Sse.Add(Sse.Multiply(vm0, vm1), va);
}
return va + (vm0 * vm1);
}
/// <summary>

61
src/ImageSharp/Common/Helpers/Vector128Utilities.cs

@ -193,13 +193,70 @@ internal static class Vector128Utilities
return AdvSimd.ConvertToInt32RoundToEven(vector);
}
Vector128<float> sign = vector & Vector128.Create(-0.0f);
Vector128<float> val_2p23_f32 = sign | Vector128.Create(8388608.0f);
Vector128<float> sign = vector & Vector128.Create(-0F);
Vector128<float> val_2p23_f32 = sign | Vector128.Create(8388608F);
val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32;
return Vector128.ConvertToInt32(val_2p23_f32 | sign);
}
/// <summary>
/// Rounds all values in <paramref name="vector"/> to the nearest integer
/// following <see cref="MidpointRounding.ToEven"/> semantics.
/// </summary>
/// <param name="vector">The vector</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<float> RoundToNearestInteger(Vector128<float> vector)
{
if (Sse41.IsSupported)
{
return Sse41.RoundToNearestInteger(vector);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.RoundToNearest(vector);
}
Vector128<float> sign = vector & Vector128.Create(-0F);
Vector128<float> val_2p23_f32 = sign | Vector128.Create(8388608F);
val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32;
return val_2p23_f32 | sign;
}
/// <summary>
/// Performs a multiplication and an addition of the <see cref="Vector128{Single}"/>.
/// </summary>
/// <remarks>ret = (vm0 * vm1) + va</remarks>
/// <param name="va">The vector to add to the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<float> MultiplyAdd(
Vector128<float> va,
Vector128<float> vm0,
Vector128<float> vm1)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(vm1, vm0, va);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.Add(AdvSimd.Multiply(vm0, vm1), va);
}
if (Sse.IsSupported)
{
return Sse.Add(Sse.Multiply(vm0, vm1), va);
}
return va + (vm0 * vm1);
}
/// <summary>
/// Packs signed 16-bit integers to unsigned 8-bit integers and saturates.
/// </summary>

51
src/ImageSharp/Common/Helpers/Vector256Utilities.cs

@ -103,13 +103,60 @@ internal static class Vector256Utilities
return Vector256.Create(lower, upper);
}
Vector256<float> sign = vector & Vector256.Create(-0.0f);
Vector256<float> val_2p23_f32 = sign | Vector256.Create(8388608.0f);
Vector256<float> sign = vector & Vector256.Create(-0F);
Vector256<float> val_2p23_f32 = sign | Vector256.Create(8388608F);
val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32;
return Vector256.ConvertToInt32(val_2p23_f32 | sign);
}
/// <summary>
/// Rounds all values in <paramref name="vector"/> to the nearest integer
/// following <see cref="MidpointRounding.ToEven"/> semantics.
/// </summary>
/// <param name="vector">The vector</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> RoundToNearestInteger(Vector256<float> vector)
{
if (Avx.IsSupported)
{
return Avx.RoundToNearestInteger(vector);
}
Vector256<float> sign = vector & Vector256.Create(-0F);
Vector256<float> val_2p23_f32 = sign | Vector256.Create(8388608F);
val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32;
return val_2p23_f32 | sign;
}
/// <summary>
/// Performs a multiplication and an addition of the <see cref="Vector256{Single}"/>.
/// </summary>
/// <remarks>ret = (vm0 * vm1) + va</remarks>
/// <param name="va">The vector to add to the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> MultiplyAdd(
Vector256<float> va,
Vector256<float> vm0,
Vector256<float> vm1)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(vm1, vm0, va);
}
if (Avx.IsSupported)
{
return Avx.Add(Avx.Multiply(vm0, vm1), va);
}
return va + (vm0 * vm1);
}
[DoesNotReturn]
private static void ThrowUnreachableException() => throw new UnreachableException();
}

37
src/ImageSharp/Common/Helpers/Vector512Utilities.cs

@ -110,6 +110,43 @@ internal static class Vector512Utilities
return Vector512.ConvertToInt32(val_2p23_f32 | sign);
}
/// <summary>
/// Rounds all values in <paramref name="vector"/> to the nearest integer
/// following <see cref="MidpointRounding.ToEven"/> semantics.
/// </summary>
/// <param name="vector">The vector</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> RoundToNearestInteger(Vector512<float> vector)
{
if (Avx512F.IsSupported)
{
// imm8 = 0b1000:
// imm8[7:4] = 0b0000 -> preserve 0 fractional bits (round to whole numbers)
// imm8[3:0] = 0b1000 -> _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC (round to nearest even, suppress exceptions)
return Avx512F.RoundScale(vector, 0b0000_1000);
}
Vector512<float> sign = vector & Vector512.Create(-0F);
Vector512<float> val_2p23_f32 = sign | Vector512.Create(8388608F);
val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32;
return val_2p23_f32 | sign;
}
/// <summary>
/// Performs a multiplication and an addition of the <see cref="Vector512{Single}"/>.
/// </summary>
/// <remarks>ret = (vm0 * vm1) + va</remarks>
/// <param name="va">The vector to add to the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> MultiplyAdd(
Vector512<float> va,
Vector512<float> vm0,
Vector512<float> vm1) => va + (vm0 * vm1);
[DoesNotReturn]
private static void ThrowUnreachableException() => throw new UnreachableException();
}

4
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs

@ -17,7 +17,7 @@ internal abstract partial class JpegColorConverterBase
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceVectorized(in ComponentValues values)
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{
ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
@ -46,7 +46,7 @@ internal abstract partial class JpegColorConverterBase
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values)
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> CmykScalar.ConvertToRgbInplace(values, this.MaximumValue);
/// <inheritdoc/>

4
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs

@ -17,7 +17,7 @@ internal abstract partial class JpegColorConverterBase
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceVectorized(in ComponentValues values)
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{
ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
@ -33,7 +33,7 @@ internal abstract partial class JpegColorConverterBase
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values)
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> GrayscaleScalar.ConvertToRgbInplace(values.Component0, this.MaximumValue);
/// <inheritdoc/>

4
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs

@ -17,7 +17,7 @@ internal abstract partial class JpegColorConverterBase
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceVectorized(in ComponentValues values)
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{
ref Vector<float> rBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
@ -41,7 +41,7 @@ internal abstract partial class JpegColorConverterBase
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values)
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> RgbScalar.ConvertToRgbInplace(values, this.MaximumValue);
/// <inheritdoc/>

4
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs

@ -18,7 +18,7 @@ internal abstract partial class JpegColorConverterBase
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceVectorized(in ComponentValues values)
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{
ref Vector<float> c0Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
@ -69,7 +69,7 @@ internal abstract partial class JpegColorConverterBase
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values)
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> YCbCrScalar.ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>

116
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs

@ -0,0 +1,116 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class YCbCrVector128 : JpegColorConverterVector128
{
public YCbCrVector128(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
{
ref Vector128<float> c0Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> c1Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> c2Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
Vector128<float> chromaOffset = Vector128.Create(-this.HalfValue);
Vector128<float> scale = Vector128.Create(1 / this.MaximumValue);
Vector128<float> rCrMult = Vector128.Create(YCbCrScalar.RCrMult);
Vector128<float> gCbMult = Vector128.Create(-YCbCrScalar.GCbMult);
Vector128<float> gCrMult = Vector128.Create(-YCbCrScalar.GCrMult);
Vector128<float> bCbMult = Vector128.Create(YCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
ref Vector128<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector128<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector128<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector128<float> y = c0;
Vector128<float> cb = c1 + chromaOffset;
Vector128<float> cr = c2 + chromaOffset;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector128<float> r = Vector128Utilities.MultiplyAdd(y, cr, rCrMult);
Vector128<float> g = Vector128Utilities.MultiplyAdd(Vector128Utilities.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector128<float> b = Vector128Utilities.MultiplyAdd(y, cb, bCbMult);
r = Vector128Utilities.RoundToNearestInteger(r) * scale;
g = Vector128Utilities.RoundToNearestInteger(g) * scale;
b = Vector128Utilities.RoundToNearestInteger(b) * scale;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector128<float> destY =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> destCb =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> destCr =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> srcR =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector128<float> srcG =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector128<float> srcB =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane));
Vector128<float> chromaOffset = Vector128.Create(this.HalfValue);
Vector128<float> f0299 = Vector128.Create(0.299f);
Vector128<float> f0587 = Vector128.Create(0.587f);
Vector128<float> f0114 = Vector128.Create(0.114f);
Vector128<float> fn0168736 = Vector128.Create(-0.168736f);
Vector128<float> fn0331264 = Vector128.Create(-0.331264f);
Vector128<float> fn0418688 = Vector128.Create(-0.418688f);
Vector128<float> fn0081312F = Vector128.Create(-0.081312F);
Vector128<float> f05 = Vector128.Create(0.5f);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector128<float> r = Unsafe.Add(ref srcR, i);
Vector128<float> g = Unsafe.Add(ref srcG, i);
Vector128<float> b = Unsafe.Add(ref srcB, i);
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Vector128<float> y = Vector128Utilities.MultiplyAdd(Vector128Utilities.MultiplyAdd(f0114 * b, f0587, g), f0299, r);
Vector128<float> cb = chromaOffset + Vector128Utilities.MultiplyAdd(Vector128Utilities.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r);
Vector128<float> cr = chromaOffset + Vector128Utilities.MultiplyAdd(Vector128Utilities.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r);
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
}
}

116
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs

@ -0,0 +1,116 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class YCbCrVector256 : JpegColorConverterVector256
{
public YCbCrVector256(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
Vector256<float> chromaOffset = Vector256.Create(-this.HalfValue);
Vector256<float> scale = Vector256.Create(1 / this.MaximumValue);
Vector256<float> rCrMult = Vector256.Create(YCbCrScalar.RCrMult);
Vector256<float> gCbMult = Vector256.Create(-YCbCrScalar.GCbMult);
Vector256<float> gCrMult = Vector256.Create(-YCbCrScalar.GCrMult);
Vector256<float> bCbMult = Vector256.Create(YCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector256<float> y = c0;
Vector256<float> cb = c1 + chromaOffset;
Vector256<float> cr = c2 + chromaOffset;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector256<float> r = Vector256Utilities.MultiplyAdd(y, cr, rCrMult);
Vector256<float> g = Vector256Utilities.MultiplyAdd(Vector256Utilities.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector256<float> b = Vector256Utilities.MultiplyAdd(y, cb, bCbMult);
r = Vector256Utilities.RoundToNearestInteger(r) * scale;
g = Vector256Utilities.RoundToNearestInteger(g) * scale;
b = Vector256Utilities.RoundToNearestInteger(b) * scale;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector256<float> destY =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> destCb =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> destCr =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> srcR =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector256<float> srcG =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector256<float> srcB =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane));
Vector256<float> chromaOffset = Vector256.Create(this.HalfValue);
Vector256<float> f0299 = Vector256.Create(0.299f);
Vector256<float> f0587 = Vector256.Create(0.587f);
Vector256<float> f0114 = Vector256.Create(0.114f);
Vector256<float> fn0168736 = Vector256.Create(-0.168736f);
Vector256<float> fn0331264 = Vector256.Create(-0.331264f);
Vector256<float> fn0418688 = Vector256.Create(-0.418688f);
Vector256<float> fn0081312F = Vector256.Create(-0.081312F);
Vector256<float> f05 = Vector256.Create(0.5f);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector256<float> r = Unsafe.Add(ref srcR, i);
Vector256<float> g = Unsafe.Add(ref srcG, i);
Vector256<float> b = Unsafe.Add(ref srcB, i);
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Vector256<float> y = Vector256Utilities.MultiplyAdd(Vector256Utilities.MultiplyAdd(f0114 * b, f0587, g), f0299, r);
Vector256<float> cb = chromaOffset + Vector256Utilities.MultiplyAdd(Vector256Utilities.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r);
Vector256<float> cr = chromaOffset + Vector256Utilities.MultiplyAdd(Vector256Utilities.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r);
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
}
}

123
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs

@ -0,0 +1,123 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class YCbCrVector512 : JpegColorConverterVector512
{
public YCbCrVector512(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{
ref Vector512<float> c0Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector512<float> c1Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector512<float> c2Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2));
Vector512<float> chromaOffset = Vector512.Create(-this.HalfValue);
Vector512<float> scale = Vector512.Create(1 / this.MaximumValue);
Vector512<float> rCrMult = Vector512.Create(YCbCrScalar.RCrMult);
Vector512<float> gCbMult = Vector512.Create(-YCbCrScalar.GCbMult);
Vector512<float> gCrMult = Vector512.Create(-YCbCrScalar.GCrMult);
Vector512<float> bCbMult = Vector512.Create(YCbCrScalar.BCbMult);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
ref Vector512<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector512<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector512<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector512<float> y = c0;
Vector512<float> cb = c1 + chromaOffset;
Vector512<float> cr = c2 + chromaOffset;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector512<float> r = Vector512Utilities.MultiplyAdd(y, cr, rCrMult);
Vector512<float> g = Vector512Utilities.MultiplyAdd(Vector512Utilities.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector512<float> b = Vector512Utilities.MultiplyAdd(y, cb, bCbMult);
r = Vector512Utilities.RoundToNearestInteger(r) * scale;
g = Vector512Utilities.RoundToNearestInteger(g) * scale;
b = Vector512Utilities.RoundToNearestInteger(b) * scale;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector512<float> destY =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector512<float> destCb =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector512<float> destCr =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector512<float> srcR =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector512<float> srcG =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector512<float> srcB =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(bLane));
Vector512<float> chromaOffset = Vector512.Create(this.HalfValue);
Vector512<float> f0299 = Vector512.Create(0.299f);
Vector512<float> f0587 = Vector512.Create(0.587f);
Vector512<float> f0114 = Vector512.Create(0.114f);
Vector512<float> fn0168736 = Vector512.Create(-0.168736f);
Vector512<float> fn0331264 = Vector512.Create(-0.331264f);
Vector512<float> fn0418688 = Vector512.Create(-0.418688f);
Vector512<float> fn0081312F = Vector512.Create(-0.081312F);
Vector512<float> f05 = Vector512.Create(0.5f);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector512<float> r = Unsafe.Add(ref srcR, i);
Vector512<float> g = Unsafe.Add(ref srcG, i);
Vector512<float> b = Unsafe.Add(ref srcB, i);
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Vector512<float> y = Vector512Utilities.MultiplyAdd(Vector512Utilities.MultiplyAdd(f0114 * b, f0587, g), f0299, r);
Vector512<float> cb = chromaOffset + Vector512Utilities.MultiplyAdd(Vector512Utilities.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r);
Vector512<float> cr = chromaOffset + Vector512Utilities.MultiplyAdd(Vector512Utilities.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r);
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> YCbCrScalar.ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> YCbCrScalar.ConvertFromRgb(values, this.HalfValue, rLane, gLane, bLane);
}
}

4
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs

@ -17,7 +17,7 @@ internal abstract partial class JpegColorConverterBase
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceVectorized(in ComponentValues values)
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{
ref Vector<float> c0Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
@ -70,7 +70,7 @@ internal abstract partial class JpegColorConverterBase
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values)
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>

15
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs

@ -116,6 +116,21 @@ internal abstract partial class JpegColorConverterBase
/// <param name="precision">The precision in bits.</param>
private static JpegColorConverterBase GetYCbCrConverter(int precision)
{
if (JpegColorConverterVector512.IsSupported)
{
return new YCbCrVector512(precision);
}
if (JpegColorConverterVector256.IsSupported)
{
return new YCbCrVector256(precision);
}
if (JpegColorConverterVector128.IsSupported)
{
return new YCbCrVector128(precision);
}
if (JpegColorConverterVector.IsSupported)
{
return new YCbCrVector(precision);

30
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs

@ -46,7 +46,7 @@ internal abstract partial class JpegColorConverterBase
int simdCount = length - remainder;
if (simdCount > 0)
{
this.ConvertToRgbInplaceVectorized(values.Slice(0, simdCount));
this.ConvertToRgbInPlaceVectorized(values.Slice(0, simdCount));
}
// Jpeg images width is always divisible by 8 without a remainder
@ -56,12 +56,12 @@ internal abstract partial class JpegColorConverterBase
// remainder pixels
if (remainder > 0)
{
this.ConvertToRgbInplaceScalarRemainder(values.Slice(simdCount, remainder));
this.ConvertToRgbInPlaceScalarRemainder(values.Slice(simdCount, remainder));
}
}
/// <inheritdoc/>
public sealed override void ConvertFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
public sealed override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware.");
@ -73,9 +73,9 @@ internal abstract partial class JpegColorConverterBase
{
this.ConvertFromRgbVectorized(
values.Slice(0, simdCount),
r.Slice(0, simdCount),
g.Slice(0, simdCount),
b.Slice(0, simdCount));
rLane[..simdCount],
gLane[..simdCount],
bLane[..simdCount]);
}
// Jpeg images width is always divisible by 8 without a remainder
@ -87,25 +87,25 @@ internal abstract partial class JpegColorConverterBase
{
this.ConvertFromRgbScalarRemainder(
values.Slice(simdCount, remainder),
r.Slice(simdCount, remainder),
g.Slice(simdCount, remainder),
b.Slice(simdCount, remainder));
rLane.Slice(simdCount, remainder),
gLane.Slice(simdCount, remainder),
bLane.Slice(simdCount, remainder));
}
}
/// <summary>
/// Converts planar jpeg component values in <paramref name="values"/>
/// to RGB color space inplace using <see cref="Vector"/> API.
/// to RGB color space in place using <see cref="Vector"/> API.
/// </summary>
/// <param name="values">The input/ouptut as a stack-only <see cref="ComponentValues"/> struct</param>
protected abstract void ConvertToRgbInplaceVectorized(in ComponentValues values);
/// <param name="values">The input/output as a stack-only <see cref="ComponentValues"/> struct</param>
protected abstract void ConvertToRgbInPlaceVectorized(in ComponentValues values);
/// <summary>
/// Converts remainder of the planar jpeg component values after
/// conversion in <see cref="ConvertToRgbInplaceVectorized(in ComponentValues)"/>.
/// conversion in <see cref="ConvertToRgbInPlaceVectorized(in ComponentValues)"/>.
/// </summary>
/// <param name="values">The input/ouptut as a stack-only <see cref="ComponentValues"/> struct</param>
protected abstract void ConvertToRgbInplaceScalarRemainder(in ComponentValues values);
/// <param name="values">The input/output as a stack-only <see cref="ComponentValues"/> struct</param>
protected abstract void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values);
/// <summary>
/// Converts RGB lanes to jpeg component values using <see cref="Vector"/> API.

34
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs

@ -0,0 +1,34 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
/// based on <see cref="Vector128{T}"/> instructions.
/// </summary>
/// <remarks>
/// Converters of this family would expect input buffers lengths to be
/// divisible by 8 without a remainder.
/// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks.
/// DO NOT pass test data of invalid size to these converters as they
/// potentially won't do a bound check and return a false positive result.
/// </remarks>
internal abstract class JpegColorConverterVector128 : JpegColorConverterBase
{
protected JpegColorConverterVector128(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
public static bool IsSupported => Vector128.IsHardwareAccelerated && Vector128<float>.IsSupported;
public sealed override bool IsAvailable => IsSupported;
public sealed override int ElementsPerBatch => Vector128<float>.Count;
}
}

34
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs

@ -0,0 +1,34 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
/// based on <see cref="Vector256{T}"/> instructions.
/// </summary>
/// <remarks>
/// Converters of this family would expect input buffers lengths to be
/// divisible by 8 without a remainder.
/// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks.
/// DO NOT pass test data of invalid size to these converters as they
/// potentially won't do a bound check and return a false positive result.
/// </remarks>
internal abstract class JpegColorConverterVector256 : JpegColorConverterBase
{
protected JpegColorConverterVector256(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
public static bool IsSupported => Vector256.IsHardwareAccelerated && Vector256<float>.IsSupported;
public sealed override bool IsAvailable => IsSupported;
public sealed override int ElementsPerBatch => Vector256<float>.Count;
}
}

111
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs

@ -0,0 +1,111 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
/// based on <see cref="Vector512{T}"/> instructions.
/// </summary>
internal abstract class JpegColorConverterVector512 : JpegColorConverterBase
{
protected JpegColorConverterVector512(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
public static bool IsSupported => Vector512.IsHardwareAccelerated && Vector512<float>.IsSupported;
/// <inheritdoc/>
public override bool IsAvailable => IsSupported;
/// <inheritdoc/>
public override int ElementsPerBatch => Vector512<float>.Count;
/// <inheritdoc/>
public sealed override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware.");
int length = values.Component0.Length;
int remainder = (int)((uint)length % (uint)Vector512<float>.Count);
int simdCount = length - remainder;
if (simdCount > 0)
{
this.ConvertFromRgbVectorized(
values.Slice(0, simdCount),
rLane[..simdCount],
gLane[..simdCount],
bLane[..simdCount]);
}
if (remainder > 0)
{
this.ConvertFromRgbScalarRemainder(
values.Slice(simdCount, remainder),
rLane.Slice(simdCount, remainder),
gLane.Slice(simdCount, remainder),
bLane.Slice(simdCount, remainder));
}
}
/// <inheritdoc/>
public sealed override void ConvertToRgbInPlace(in ComponentValues values)
{
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware.");
int length = values.Component0.Length;
int remainder = (int)((uint)length % (uint)Vector512<float>.Count);
int simdCount = length - remainder;
if (simdCount > 0)
{
this.ConvertToRgbInPlaceVectorized(values.Slice(0, simdCount));
}
if (remainder > 0)
{
this.ConvertToRgbInPlaceScalarRemainder(values.Slice(simdCount, remainder));
}
}
/// <summary>
/// Converts planar jpeg component values in <paramref name="values"/>
/// to RGB color space in place using <see cref="Vector"/> API.
/// </summary>
/// <param name="values">The input/output as a stack-only <see cref="ComponentValues"/> struct</param>
protected abstract void ConvertToRgbInPlaceVectorized(in ComponentValues values);
/// <summary>
/// Converts remainder of the planar jpeg component values after
/// conversion in <see cref="ConvertToRgbInPlaceVectorized(in ComponentValues)"/>.
/// </summary>
/// <param name="values">The input/output as a stack-only <see cref="ComponentValues"/> struct</param>
protected abstract void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values);
/// <summary>
/// Converts RGB lanes to jpeg component values using <see cref="Vector"/> API.
/// </summary>
/// <param name="values">Jpeg component values.</param>
/// <param name="rLane">Red colors lane.</param>
/// <param name="gLane">Green colors lane.</param>
/// <param name="bLane">Blue colors lane.</param>
protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane);
/// <summary>
/// Converts remainder of RGB lanes to jpeg component values after
/// conversion in <see cref="ConvertFromRgbVectorized(in ComponentValues, Span{float}, Span{float}, Span{float})"/>.
/// </summary>
/// <param name="values">Jpeg component values.</param>
/// <param name="rLane">Red colors lane.</param>
/// <param name="gLane">Green colors lane.</param>
/// <param name="bLane">Blue colors lane.</param>
protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane);
}
}

28
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs

@ -17,7 +17,7 @@ public class YCbCrColorConversion : ColorConversionBenchmark
[Benchmark]
public void Scalar()
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.YCbCrScalar(8).ConvertToRgbInPlace(values);
}
@ -25,8 +25,32 @@ public class YCbCrColorConversion : ColorConversionBenchmark
[Benchmark]
public void SimdVector8()
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.YCbCrVector(8).ConvertToRgbInPlace(values);
}
[Benchmark]
public void SimdVector128()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.YCbCrVector128(8).ConvertToRgbInPlace(values);
}
[Benchmark]
public void SimdVector256()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.YCbCrVector256(8).ConvertToRgbInPlace(values);
}
[Benchmark]
public void SimdVector512()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.YCbCrVector512(8).ConvertToRgbInPlace(values);
}
}

13
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.ColorProfiles;
@ -180,9 +181,17 @@ public class JpegColorConverterTests
{
// arrange
Type expectedType = typeof(JpegColorConverterBase.YCbCrScalar);
if (Avx.IsSupported)
if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.YCbCrVector);
expectedType = typeof(JpegColorConverterBase.YCbCrVector512);
}
else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.YCbCrVector256);
}
else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.YCbCrVector128);
}
else if (Sse2.IsSupported)
{

Loading…
Cancel
Save