Browse Source

Merge pull request #1411 from tkp1n/tkp1n/avx2-color-converter

Vectorize (AVX2) JPEG Color Converter
js/color-alpha-handling
James Jackson-South 5 years ago
committed by GitHub
parent
commit
d2de2a1404
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      src/ImageSharp/Common/Helpers/SimdUtils.cs
  2. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs
  3. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs
  4. 81
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs
  5. 16
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs
  6. 71
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs
  7. 63
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs
  8. 13
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs
  9. 72
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs
  10. 14
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs
  11. 67
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs
  12. 101
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs
  13. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
  14. 182
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
  15. 53
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs
  16. 87
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs
  17. 110
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs
  18. 25
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs
  19. 91
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs
  20. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs
  21. 46
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs
  22. 160
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  23. 41
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs
  24. 64
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs
  25. 33
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs
  26. 41
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs
  27. 59
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs
  28. 41
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs
  29. 490
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

19
src/ImageSharp/Common/Helpers/SimdUtils.cs

@ -25,6 +25,25 @@ namespace SixLabors.ImageSharp
public static bool HasVector8 { get; } =
Vector.IsHardwareAccelerated && Vector<float>.Count == 8 && Vector<int>.Count == 8;
/// <summary>
/// Gets a value indicating whether <see cref="Vector{T}"/> code is being JIT-ed to SSE instructions
/// where float and integer registers are of size 128 byte.
/// </summary>
public static bool HasVector4 { get; } =
Vector.IsHardwareAccelerated && Vector<float>.Count == 4;
public static bool HasAvx2
{
get
{
#if SUPPORTS_RUNTIME_INTRINSICS
return Avx2.IsSupported;
#else
return false;
#endif
}
}
/// <summary>
/// Transform all scalars in 'v' in a way that converting them to <see cref="int"/> would have rounding semantics.
/// </summary>

18
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs

@ -0,0 +1,18 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal abstract class Avx2JpegColorConverter : VectorizedJpegColorConverter
{
protected Avx2JpegColorConverter(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision, 8)
{
}
protected sealed override bool IsAvailable => SimdUtils.HasAvx2;
}
}
}

18
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs

@ -0,0 +1,18 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal abstract class BasicJpegColorConverter : JpegColorConverter
{
protected BasicJpegColorConverter(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
protected override bool IsAvailable => true;
}
}
}

81
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs

@ -0,0 +1,81 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
#endif
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromCmykAvx2 : Avx2JpegColorConverter
{
public FromCmykAvx2(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result)
{
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> cBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> mBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> yBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> kBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
var one = Vector256.Create(1F);
// Used for packing
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
int n = result.Length / 8;
for (int i = 0; i < n; i++)
{
Vector256<float> k = Avx2.PermuteVar8x32(Unsafe.Add(ref kBase, i), vcontrol);
Vector256<float> c = Avx2.PermuteVar8x32(Unsafe.Add(ref cBase, i), vcontrol);
Vector256<float> m = Avx2.PermuteVar8x32(Unsafe.Add(ref mBase, i), vcontrol);
Vector256<float> y = Avx2.PermuteVar8x32(Unsafe.Add(ref yBase, i), vcontrol);
k = Avx.Multiply(k, scale);
c = Avx.Multiply(Avx.Multiply(c, k), scale);
m = Avx.Multiply(Avx.Multiply(m, k), scale);
y = Avx.Multiply(Avx.Multiply(y, k), scale);
Vector256<float> cmLo = Avx.UnpackLow(c, m);
Vector256<float> yoLo = Avx.UnpackLow(y, one);
Vector256<float> cmHi = Avx.UnpackHigh(c, m);
Vector256<float> yoHi = Avx.UnpackHigh(y, one);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.Shuffle(cmLo, yoLo, 0b01_00_01_00);
Unsafe.Add(ref destination, 1) = Avx.Shuffle(cmLo, yoLo, 0b11_10_11_10);
Unsafe.Add(ref destination, 2) = Avx.Shuffle(cmHi, yoHi, 0b01_00_01_00);
Unsafe.Add(ref destination, 3) = Avx.Shuffle(cmHi, yoHi, 0b11_10_11_10);
}
#endif
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromCmykBasic.ConvertCore(values, result, this.MaximumValue);
}
}
}

16
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs

@ -8,16 +8,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromCmyk : JpegColorConverter
internal sealed class FromCmykBasic : BasicJpegColorConverter
{
public FromCmyk(int precision)
public FromCmykBasic(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ConvertCore(values, result, this.MaximumValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue)
{
ReadOnlySpan<float> cVals = values.Component0;
ReadOnlySpan<float> mVals = values.Component1;
ReadOnlySpan<float> yVals = values.Component2;
@ -25,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
var v = new Vector4(0, 0, 0, 1F);
var maximum = 1 / this.MaximumValue;
var maximum = 1 / maxValue;
var scale = new Vector4(maximum, maximum, maximum, 1F);
for (int i = 0; i < result.Length; i++)
@ -33,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
float c = cVals[i];
float m = mVals[i];
float y = yVals[i];
float k = kVals[i] / this.MaximumValue;
float k = kVals[i] / maxValue;
v.X = c * k;
v.Y = m * k;
@ -47,4 +51,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
}
}

71
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs

@ -0,0 +1,71 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromCmykVector8 : Vector8JpegColorConverter
{
public FromCmykVector8(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result)
{
ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> mBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> yBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
Vector4Pair cc = default;
Vector4Pair mm = default;
Vector4Pair yy = default;
ref Vector<float> ccRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref cc);
ref Vector<float> mmRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref mm);
ref Vector<float> yyRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref yy);
var scale = new Vector<float>(1 / this.MaximumValue);
// Walking 8 elements at one step:
int n = result.Length / 8;
for (int i = 0; i < n; i++)
{
Vector<float> c = Unsafe.Add(ref cBase, i);
Vector<float> m = Unsafe.Add(ref mBase, i);
Vector<float> y = Unsafe.Add(ref yBase, i);
Vector<float> k = Unsafe.Add(ref kBase, i) * scale;
c = (c * k) * scale;
m = (m * k) * scale;
y = (y * k) * scale;
ccRefAsVector = c;
mmRefAsVector = m;
yyRefAsVector = y;
// Collect (c0,c1...c8) (m0,m1...m8) (y0,y1...y8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
destination.Pack(ref cc, ref mm, ref yy);
}
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromCmykBasic.ConvertCore(values, result, this.MaximumValue);
}
}
}

63
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs

@ -0,0 +1,63 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
#endif
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromGrayscaleAvx2 : Avx2JpegColorConverter
{
public FromGrayscaleAvx2(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result)
{
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> gBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
var one = Vector256.Create(1F);
// Used for packing
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
int n = result.Length / 8;
for (int i = 0; i < n; i++)
{
Vector256<float> g = Avx.Multiply(Unsafe.Add(ref gBase, i), scale);
g = Avx2.PermuteVar8x32(g, vcontrol);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.Blend(Avx.Permute(g, 0b00_00_00_00), one, 0b1000_1000);
Unsafe.Add(ref destination, 1) = Avx.Blend(Avx.Shuffle(g, g, 0b01_01_01_01), one, 0b1000_1000);
Unsafe.Add(ref destination, 2) = Avx.Blend(Avx.Shuffle(g, g, 0b10_10_10_10), one, 0b1000_1000);
Unsafe.Add(ref destination, 3) = Avx.Blend(Avx.Shuffle(g, g, 0b11_11_11_11), one, 0b1000_1000);
}
#endif
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromGrayscaleBasic.ConvertCore(values, result, this.MaximumValue);
}
}
}

13
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs

@ -10,16 +10,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromGrayscale : JpegColorConverter
internal sealed class FromGrayscaleBasic : BasicJpegColorConverter
{
public FromGrayscale(int precision)
public FromGrayscaleBasic(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
var maximum = 1 / this.MaximumValue;
ConvertCore(values, result, this.MaximumValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue)
{
var maximum = 1 / maxValue;
var scale = new Vector4(maximum, maximum, maximum, 1F);
ref float sBase = ref MemoryMarshal.GetReference(values.Component0);
@ -35,4 +40,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
}
}

72
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs

@ -0,0 +1,72 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
#endif
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromRgbAvx2 : Avx2JpegColorConverter
{
public FromRgbAvx2(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result)
{
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> rBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> gBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> bBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
var one = Vector256.Create(1F);
// Used for packing
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
int n = result.Length / 8;
for (int i = 0; i < n; i++)
{
Vector256<float> r = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref rBase, i), vcontrol), scale);
Vector256<float> g = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref gBase, i), vcontrol), scale);
Vector256<float> b = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref bBase, i), vcontrol), scale);
Vector256<float> rgLo = Avx.UnpackLow(r, g);
Vector256<float> boLo = Avx.UnpackLow(b, one);
Vector256<float> rgHi = Avx.UnpackHigh(r, g);
Vector256<float> boHi = Avx.UnpackHigh(b, one);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.Shuffle(rgLo, boLo, 0b01_00_01_00);
Unsafe.Add(ref destination, 1) = Avx.Shuffle(rgLo, boLo, 0b11_10_11_10);
Unsafe.Add(ref destination, 2) = Avx.Shuffle(rgHi, boHi, 0b01_00_01_00);
Unsafe.Add(ref destination, 3) = Avx.Shuffle(rgHi, boHi, 0b11_10_11_10);
}
#endif
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromRgbBasic.ConvertCore(values, result, this.MaximumValue);
}
}
}

14
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs

@ -8,23 +8,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromRgb : JpegColorConverter
internal sealed class FromRgbBasic : BasicJpegColorConverter
{
public FromRgb(int precision)
public FromRgbBasic(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ConvertCore(values, result, this.MaximumValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue)
{
ReadOnlySpan<float> rVals = values.Component0;
ReadOnlySpan<float> gVals = values.Component1;
ReadOnlySpan<float> bVals = values.Component2;
var v = new Vector4(0, 0, 0, 1);
var maximum = 1 / this.MaximumValue;
var maximum = 1 / maxValue;
var scale = new Vector4(maximum, maximum, maximum, 1F);
for (int i = 0; i < result.Length; i++)
@ -44,4 +48,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
}
}

67
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs

@ -0,0 +1,67 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromRgbVector8 : Vector8JpegColorConverter
{
public FromRgbVector8(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result)
{
ref Vector<float> rBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> gBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> bBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
Vector4Pair rr = default;
Vector4Pair gg = default;
Vector4Pair bb = default;
ref Vector<float> rrRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref rr);
ref Vector<float> ggRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref gg);
ref Vector<float> bbRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref bb);
var scale = new Vector<float>(1 / this.MaximumValue);
// Walking 8 elements at one step:
int n = result.Length / 8;
for (int i = 0; i < n; i++)
{
Vector<float> r = Unsafe.Add(ref rBase, i);
Vector<float> g = Unsafe.Add(ref gBase, i);
Vector<float> b = Unsafe.Add(ref bBase, i);
r *= scale;
g *= scale;
b *= scale;
rrRefAsVector = r;
ggRefAsVector = g;
bbRefAsVector = b;
// 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.Pack(ref rr, ref gg, ref bb);
}
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromRgbBasic.ConvertCore(values, result, this.MaximumValue);
}
}
}

101
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs

@ -0,0 +1,101 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
#endif
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromYCbCrAvx2 : Avx2JpegColorConverter
{
public FromYCbCrAvx2(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result)
{
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> yBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> cbBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> crBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / this.MaximumValue);
var rCrMult = Vector256.Create(1.402F);
var gCbMult = Vector256.Create(-0.344136F);
var gCrMult = Vector256.Create(-0.714136F);
var bCbMult = Vector256.Create(1.772F);
// Used for packing.
var va = Vector256.Create(1F);
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
// Walking 8 elements at one step:
int n = result.Length / 8;
for (int i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
Vector256<float> y = Unsafe.Add(ref yBase, i);
Vector256<float> cb = Avx.Add(Unsafe.Add(ref cbBase, i), chromaOffset);
Vector256<float> cr = Avx.Add(Unsafe.Add(ref crBase, i), chromaOffset);
y = Avx2.PermuteVar8x32(y, vcontrol);
cb = Avx2.PermuteVar8x32(cb, vcontrol);
cr = Avx2.PermuteVar8x32(cr, vcontrol);
// 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:
Vector256<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
Vector256<float> g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
// TODO: We should be saving to RGBA not Vector4
r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale);
g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale);
b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale);
Vector256<float> vte = Avx.UnpackLow(r, b);
Vector256<float> vto = Avx.UnpackLow(g, va);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 1) = Avx.UnpackHigh(vte, vto);
vte = Avx.UnpackHigh(r, b);
vto = Avx.UnpackHigh(g, va);
Unsafe.Add(ref destination, 2) = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 3) = Avx.UnpackHigh(vte, vto);
}
#endif
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromYCbCrBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}
}
}

4
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs

@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromYCbCrBasic : JpegColorConverter
internal sealed class FromYCbCrBasic : BasicJpegColorConverter
{
public FromYCbCrBasic(int precision)
: base(JpegColorSpace.YCbCr, precision)
@ -48,4 +48,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
}
}

182
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs

@ -1,182 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
#endif
using SixLabors.ImageSharp.Tuples;
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromYCbCrSimdVector8 : JpegColorConverter
{
public FromYCbCrSimdVector8(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.HasVector8;
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
int remainder = result.Length % 8;
int simdCount = result.Length - remainder;
if (simdCount > 0)
{
ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue);
}
FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder), this.MaximumValue, this.HalfValue);
}
/// <summary>
/// SIMD convert using buffers of sizes divisible by 8.
/// </summary>
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue)
{
// This implementation is actually AVX specific.
// An AVX register is capable of storing 8 float-s.
if (!IsAvailable)
{
throw new InvalidOperationException(
"JpegColorConverter.FromYCbCrSimd256 can be used only on architecture having 256 byte floating point SIMD registers!");
}
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> yBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> cbBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> crBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion
var chromaOffset = Vector256.Create(-halfValue);
var scale = Vector256.Create(1 / maxValue);
var rCrMult = Vector256.Create(1.402F);
var gCbMult = Vector256.Create(-0.344136F);
var gCrMult = Vector256.Create(-0.714136F);
var bCbMult = Vector256.Create(1.772F);
// Used for packing.
var va = Vector256.Create(1F);
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
// Walking 8 elements at one step:
int n = result.Length / 8;
for (int i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
Vector256<float> y = Unsafe.Add(ref yBase, i);
Vector256<float> cb = Avx.Add(Unsafe.Add(ref cbBase, i), chromaOffset);
Vector256<float> cr = Avx.Add(Unsafe.Add(ref crBase, i), chromaOffset);
y = Avx2.PermuteVar8x32(y, vcontrol);
cb = Avx2.PermuteVar8x32(cb, vcontrol);
cr = Avx2.PermuteVar8x32(cr, vcontrol);
// 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:
Vector256<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
Vector256<float> g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
// TODO: We should be savving to RGBA not Vector4
r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale);
g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale);
b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale);
Vector256<float> vte = Avx.UnpackLow(r, b);
Vector256<float> vto = Avx.UnpackLow(g, va);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 1) = Avx.UnpackHigh(vte, vto);
vte = Avx.UnpackHigh(r, b);
vto = Avx.UnpackHigh(g, va);
Unsafe.Add(ref destination, 2) = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 3) = Avx.UnpackHigh(vte, vto);
}
#else
ref Vector<float> yBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> cbBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> crBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector<float>(-halfValue);
// Walking 8 elements at one step:
int n = result.Length / 8;
Vector4Pair rr = default;
Vector4Pair gg = default;
Vector4Pair bb = default;
ref Vector<float> rrRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref rr);
ref Vector<float> ggRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref gg);
ref Vector<float> bbRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref bb);
var scale = new Vector<float>(1 / maxValue);
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));
r = r.FastRound();
g = g.FastRound();
b = b.FastRound();
r *= scale;
g *= scale;
b *= scale;
rrRefAsVector = r;
ggRefAsVector = g;
bbRefAsVector = b;
// 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.Pack(ref rr, ref gg, ref bb);
}
#endif
}
}
}
}

53
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs

@ -5,37 +5,24 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromYCbCrSimd : JpegColorConverter
internal sealed class FromYCbCrVector4 : VectorizedJpegColorConverter
{
public FromYCbCrSimd(int precision)
: base(JpegColorSpace.YCbCr, precision)
public FromYCbCrVector4(int precision)
: base(JpegColorSpace.YCbCr, precision, 8)
{
}
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
int remainder = result.Length % 8;
int simdCount = result.Length - remainder;
if (simdCount > 0)
{
ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue);
}
protected override bool IsAvailable => SimdUtils.HasVector4;
FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder), this.MaximumValue, this.HalfValue);
}
/// <summary>
/// SIMD convert using buffers of sizes divisible by 8.
/// </summary>
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue)
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result)
{
// TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector<T> is terrible?)
DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisible by 8!");
ref Vector4Pair yBase =
@ -48,7 +35,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector4(-halfValue);
var chromaOffset = new Vector4(-this.HalfValue);
var maxValue = this.MaximumValue;
// Walking 8 elements at one step:
int n = result.Length / 8;
@ -87,31 +75,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
tmp.MultiplyInplace(1.772F);
b.AddInplace(ref tmp);
if (Vector<float>.Count == 4)
{
// TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector<T> is terrible?)
r.RoundAndDownscalePreVector8(maxValue);
g.RoundAndDownscalePreVector8(maxValue);
b.RoundAndDownscalePreVector8(maxValue);
}
else if (SimdUtils.HasVector8)
{
r.RoundAndDownscaleVector8(maxValue);
g.RoundAndDownscaleVector8(maxValue);
b.RoundAndDownscaleVector8(maxValue);
}
else
{
// TODO: Run fallback scalar code here
// However, no issues expected before someone implements this: https://github.com/dotnet/coreclr/issues/12007
JpegThrowHelper.ThrowNotImplementedException("Your CPU architecture is too modern!");
}
r.RoundAndDownscalePreVector8(maxValue);
g.RoundAndDownscalePreVector8(maxValue);
b.RoundAndDownscalePreVector8(maxValue);
// 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.Pack(ref r, ref g, ref b);
}
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromYCbCrBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}
}
}

87
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs

@ -0,0 +1,87 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Tuples;
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromYCbCrVector8 : Vector8JpegColorConverter
{
public FromYCbCrVector8(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result)
{
ref Vector<float> yBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> cbBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> crBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector<float>(-this.HalfValue);
// Walking 8 elements at one step:
int n = result.Length / 8;
Vector4Pair rr = default;
Vector4Pair gg = default;
Vector4Pair bb = default;
ref Vector<float> rrRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref rr);
ref Vector<float> ggRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref gg);
ref Vector<float> bbRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref bb);
var scale = new Vector<float>(1 / this.MaximumValue);
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));
r = r.FastRound();
g = g.FastRound();
b = b.FastRound();
r *= scale;
g *= scale;
b *= scale;
rrRefAsVector = r;
ggRefAsVector = g;
bbRefAsVector = b;
// 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.Pack(ref rr, ref gg, ref bb);
}
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromYCbCrBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}
}
}

110
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs

@ -0,0 +1,110 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
#endif
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromYccKAvx2 : Avx2JpegColorConverter
{
public FromYccKAvx2(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result)
{
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> yBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> cbBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> crBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> kBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / this.MaximumValue);
var max = Vector256.Create(this.MaximumValue);
var rCrMult = Vector256.Create(1.402F);
var gCbMult = Vector256.Create(-0.344136F);
var gCrMult = Vector256.Create(-0.714136F);
var bCbMult = Vector256.Create(1.772F);
// Used for packing.
var va = Vector256.Create(1F);
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
// Walking 8 elements at one step:
int n = result.Length / 8;
for (int i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
// k = kVals[i] / 256F;
Vector256<float> y = Unsafe.Add(ref yBase, i);
Vector256<float> cb = Avx.Add(Unsafe.Add(ref cbBase, i), chromaOffset);
Vector256<float> cr = Avx.Add(Unsafe.Add(ref crBase, i), chromaOffset);
Vector256<float> k = Avx.Divide(Unsafe.Add(ref kBase, i), max);
y = Avx2.PermuteVar8x32(y, vcontrol);
cb = Avx2.PermuteVar8x32(cb, vcontrol);
cr = Avx2.PermuteVar8x32(cr, vcontrol);
k = Avx2.PermuteVar8x32(k, vcontrol);
// 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:
Vector256<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
Vector256<float> g =
HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
r = Avx.Subtract(max, Avx.RoundToNearestInteger(r));
g = Avx.Subtract(max, Avx.RoundToNearestInteger(g));
b = Avx.Subtract(max, Avx.RoundToNearestInteger(b));
r = Avx.Multiply(Avx.Multiply(r, k), scale);
g = Avx.Multiply(Avx.Multiply(g, k), scale);
b = Avx.Multiply(Avx.Multiply(b, k), scale);
Vector256<float> vte = Avx.UnpackLow(r, b);
Vector256<float> vto = Avx.UnpackLow(g, va);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 1) = Avx.UnpackHigh(vte, vto);
vte = Avx.UnpackHigh(r, b);
vto = Avx.UnpackHigh(g, va);
Unsafe.Add(ref destination, 2) = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 3) = Avx.UnpackHigh(vte, vto);
}
#endif
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromYccKBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}
}
}

25
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs

@ -8,14 +8,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromYccK : JpegColorConverter
internal sealed class FromYccKBasic : BasicJpegColorConverter
{
public FromYccK(int precision)
public FromYccKBasic(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> yVals = values.Component0;
@ -25,19 +30,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
var v = new Vector4(0, 0, 0, 1F);
var maximum = 1 / this.MaximumValue;
var maximum = 1 / maxValue;
var scale = new Vector4(maximum, maximum, maximum, 1F);
for (int i = 0; i < result.Length; i++)
{
float y = yVals[i];
float cb = cbVals[i] - this.HalfValue;
float cr = crVals[i] - this.HalfValue;
float k = kVals[i] / this.MaximumValue;
float cb = cbVals[i] - halfValue;
float cr = crVals[i] - halfValue;
float k = kVals[i] / maxValue;
v.X = (this.MaximumValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k;
v.Y = (this.MaximumValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k;
v.Z = (this.MaximumValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k;
v.X = (maxValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k;
v.Y = (maxValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k;
v.Z = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k;
v.W = 1F;
v *= scale;
@ -47,4 +52,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
}
}

91
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs

@ -0,0 +1,91 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromYccKVector8 : Vector8JpegColorConverter
{
public FromYccKVector8(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result)
{
ref Vector<float> yBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> cbBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> crBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector<float>(-this.HalfValue);
// Walking 8 elements at one step:
int n = result.Length / 8;
Vector4Pair rr = default;
Vector4Pair gg = default;
Vector4Pair bb = default;
ref Vector<float> rrRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref rr);
ref Vector<float> ggRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref gg);
ref Vector<float> bbRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref bb);
var scale = new Vector<float>(1 / this.MaximumValue);
var max = new Vector<float>(this.MaximumValue);
for (int i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
// k = kVals[i] / 256F;
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;
Vector<float> k = Unsafe.Add(ref kBase, i) / max;
// 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));
r = (max - r.FastRound()) * k;
g = (max - g.FastRound()) * k;
b = (max - b.FastRound()) * k;
r *= scale;
g *= scale;
b *= scale;
rrRefAsVector = r;
ggRefAsVector = g;
bbRefAsVector = b;
// 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.Pack(ref rr, ref gg, ref bb);
}
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromYccKBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}
}
}

18
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs

@ -0,0 +1,18 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal abstract class Vector8JpegColorConverter : VectorizedJpegColorConverter
{
protected Vector8JpegColorConverter(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision, 8)
{
}
protected sealed override bool IsAvailable => SimdUtils.HasVector8;
}
}
}

46
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs

@ -0,0 +1,46 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal abstract class VectorizedJpegColorConverter : JpegColorConverter
{
private readonly int vectorSize;
protected VectorizedJpegColorConverter(JpegColorSpace colorSpace, int precision, int vectorSize)
: base(colorSpace, precision)
{
this.vectorSize = vectorSize;
}
public sealed override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
int remainder = result.Length % this.vectorSize;
int simdCount = result.Length - remainder;
if (simdCount > 0)
{
// This implementation is actually AVX specific.
// An AVX register is capable of storing 8 float-s.
if (!this.IsAvailable)
{
throw new InvalidOperationException(
"This converter can be used only on architecture having 256 byte floating point SIMD registers!");
}
this.ConvertCoreVectorized(values.Slice(0, simdCount), result.Slice(0, simdCount));
}
this.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder));
}
protected abstract void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result);
protected abstract void ConvertCore(in ComponentValues values, Span<Vector4> result);
}
}
}

160
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Tuples;
@ -17,22 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <summary>
/// The available converters
/// </summary>
private static readonly JpegColorConverter[] Converters =
{
// 8-bit converters
GetYCbCrConverter(8),
new FromYccK(8),
new FromCmyk(8),
new FromGrayscale(8),
new FromRgb(8),
// 12-bit converters
GetYCbCrConverter(12),
new FromYccK(12),
new FromCmyk(12),
new FromGrayscale(12),
new FromRgb(12),
};
private static readonly JpegColorConverter[] Converters = CreateConverters();
/// <summary>
/// Initializes a new instance of the <see cref="JpegColorConverter"/> class.
@ -45,6 +31,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
this.HalfValue = MathF.Ceiling(this.MaximumValue / 2);
}
/// <summary>
/// Gets a value indicating whether this <see cref="JpegColorConverter"/> is available
/// on the current runtime and CPU architecture.
/// </summary>
protected abstract bool IsAvailable { get; }
/// <summary>
/// Gets the <see cref="JpegColorSpace"/> of this converter.
/// </summary>
@ -70,8 +62,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// </summary>
public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, int precision)
{
JpegColorConverter converter = Array.Find(Converters, c => c.ColorSpace == colorSpace
&& c.Precision == precision);
JpegColorConverter converter = Array.Find(
Converters,
c => c.ColorSpace == colorSpace
&& c.Precision == precision);
if (converter is null)
{
@ -89,10 +83,88 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
public abstract void ConvertToRgba(in ComponentValues values, Span<Vector4> result);
/// <summary>
/// Returns the <see cref="JpegColorConverter"/> for the YCbCr colorspace that matches the current CPU architecture.
/// Returns the <see cref="JpegColorConverter"/>s for all supported colorspaces and precisions.
/// </summary>
private static JpegColorConverter[] CreateConverters()
{
var converters = new List<JpegColorConverter>();
// 8-bit converters
converters.AddRange(GetYCbCrConverters(8));
converters.AddRange(GetYccKConverters(8));
converters.AddRange(GetCmykConverters(8));
converters.AddRange(GetGrayScaleConverters(8));
converters.AddRange(GetRgbConverters(8));
// 12-bit converters
converters.AddRange(GetYCbCrConverters(12));
converters.AddRange(GetYccKConverters(12));
converters.AddRange(GetCmykConverters(12));
converters.AddRange(GetGrayScaleConverters(12));
converters.AddRange(GetRgbConverters(12));
return converters.Where(x => x.IsAvailable).ToArray();
}
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for the YCbCr colorspace.
/// </summary>
private static IEnumerable<JpegColorConverter> GetYCbCrConverters(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromYCbCrAvx2(precision);
#endif
yield return new FromYCbCrVector8(precision);
yield return new FromYCbCrVector4(precision);
yield return new FromYCbCrBasic(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for the YccK colorspace.
/// </summary>
private static IEnumerable<JpegColorConverter> GetYccKConverters(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromYccKAvx2(precision);
#endif
yield return new FromYccKVector8(precision);
yield return new FromYccKBasic(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for the CMYK colorspace.
/// </summary>
private static JpegColorConverter GetYCbCrConverter(int precision) =>
FromYCbCrSimdVector8.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdVector8(precision) : new FromYCbCrSimd(precision);
private static IEnumerable<JpegColorConverter> GetCmykConverters(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromCmykAvx2(precision);
#endif
yield return new FromCmykVector8(precision);
yield return new FromCmykBasic(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for the gray scale colorspace.
/// </summary>
private static IEnumerable<JpegColorConverter> GetGrayScaleConverters(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromGrayscaleAvx2(precision);
#endif
yield return new FromGrayscaleBasic(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for the RGB colorspace.
/// </summary>
private static IEnumerable<JpegColorConverter> GetRgbConverters(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromRgbAvx2(precision);
#endif
yield return new FromRgbVector8(precision);
yield return new FromRgbBasic(precision);
}
/// <summary>
/// A stack-only struct to reference the input buffers using <see cref="ReadOnlySpan{T}"/>-s.
@ -229,6 +301,52 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
this.V7.Z = b.B.W;
this.V7.W = 1f;
}
/// <summary>
/// Pack (g0,g1...g7) vector values as (g0,g0,g0,1), (g1,g1,g1,1) ...
/// </summary>
public void Pack(ref Vector4Pair g)
{
this.V0.X = g.A.X;
this.V0.Y = g.A.X;
this.V0.Z = g.A.X;
this.V0.W = 1f;
this.V1.X = g.A.Y;
this.V1.Y = g.A.Y;
this.V1.Z = g.A.Y;
this.V1.W = 1f;
this.V2.X = g.A.Z;
this.V2.Y = g.A.Z;
this.V2.Z = g.A.Z;
this.V2.W = 1f;
this.V3.X = g.A.W;
this.V3.Y = g.A.W;
this.V3.Z = g.A.W;
this.V3.W = 1f;
this.V4.X = g.B.X;
this.V4.Y = g.B.X;
this.V4.Z = g.B.X;
this.V4.W = 1f;
this.V5.X = g.B.Y;
this.V5.Y = g.B.Y;
this.V5.Z = g.B.Y;
this.V5.W = 1f;
this.V6.X = g.B.Z;
this.V6.Y = g.B.Z;
this.V6.Z = g.B.Z;
this.V6.W = 1f;
this.V7.X = g.B.W;
this.V7.Y = g.B.W;
this.V7.Z = g.B.W;
this.V7.W = 1f;
}
}
}
}

41
tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
public class CmykColorConversion : ColorConversionBenchmark
{
public CmykColorConversion()
: base(4)
{
}
[Benchmark(Baseline = true)]
public void Scalar()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
new JpegColorConverter.FromCmykBasic(8).ConvertToRgba(values, this.output);
}
[Benchmark]
public void SimdVector8()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
new JpegColorConverter.FromCmykVector8(8).ConvertToRgba(values, this.output);
}
[Benchmark]
public void SimdVectorAvx2()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
new JpegColorConverter.FromCmykAvx2(8).ConvertToRgba(values, this.output);
}
}
}

64
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs

@ -0,0 +1,64 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
public abstract class ColorConversionBenchmark
{
private readonly int componentCount;
protected Buffer2D<float>[] input;
protected Vector4[] output;
protected ColorConversionBenchmark(int componentCount)
{
this.componentCount = componentCount;
}
public const int Count = 128;
[GlobalSetup]
public void Setup()
{
this.input = CreateRandomValues(this.componentCount, Count);
this.output = new Vector4[Count];
}
[GlobalCleanup]
public void Cleanup()
{
foreach (Buffer2D<float> buffer in this.input)
{
buffer.Dispose();
}
}
private static Buffer2D<float>[] CreateRandomValues(
int componentCount,
int inputBufferLength,
float minVal = 0f,
float maxVal = 255f)
{
var rnd = new Random(42);
var buffers = new Buffer2D<float>[componentCount];
for (int i = 0; i < componentCount; i++)
{
var values = new float[inputBufferLength];
for (int j = 0; j < inputBufferLength; j++)
{
values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal;
}
// no need to dispose when buffer is not array owner
buffers[i] = Configuration.Default.MemoryAllocator.Allocate2D<float>(values.Length, 1);
}
return buffers;
}
}
}

33
tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs

@ -0,0 +1,33 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
public class GrayscaleColorConversion : ColorConversionBenchmark
{
public GrayscaleColorConversion()
: base(1)
{
}
[Benchmark(Baseline = true)]
public void Scalar()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
new JpegColorConverter.FromGrayscaleBasic(8).ConvertToRgba(values, this.output);
}
[Benchmark]
public void SimdVectorAvx2()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
new JpegColorConverter.FromGrayscaleAvx2(8).ConvertToRgba(values, this.output);
}
}
}

41
tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
public class RgbColorConversion : ColorConversionBenchmark
{
public RgbColorConversion()
: base(3)
{
}
[Benchmark(Baseline = true)]
public void Scalar()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
new JpegColorConverter.FromRgbBasic(8).ConvertToRgba(values, this.output);
}
[Benchmark]
public void SimdVector8()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
new JpegColorConverter.FromRgbVector8(8).ConvertToRgba(values, this.output);
}
[Benchmark]
public void SimdVectorAvx2()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
new JpegColorConverter.FromRgbAvx2(8).ConvertToRgba(values, this.output);
}
}
}

59
tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs

@ -1,39 +1,18 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
public class YCbCrColorConversion
public class YCbCrColorConversion : ColorConversionBenchmark
{
private Buffer2D<float>[] input;
private Vector4[] output;
public const int Count = 128;
[GlobalSetup]
public void Setup()
public YCbCrColorConversion()
: base(3)
{
this.input = CreateRandomValues(3, Count);
this.output = new Vector4[Count];
}
[GlobalCleanup]
public void Cleanup()
{
foreach (Buffer2D<float> buffer in this.input)
{
buffer.Dispose();
}
}
[Benchmark]
@ -41,15 +20,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
JpegColorConverter.FromYCbCrBasic.ConvertCore(values, this.output, 255F, 128F);
new JpegColorConverter.FromYCbCrBasic(8).ConvertToRgba(values, this.output);
}
[Benchmark(Baseline = true)]
public void SimdVector4()
public void SimdVector()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
JpegColorConverter.FromYCbCrSimd.ConvertCore(values, this.output, 255F, 128F);
new JpegColorConverter.FromYCbCrVector4(8).ConvertToRgba(values, this.output);
}
[Benchmark]
@ -57,31 +36,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
JpegColorConverter.FromYCbCrSimdVector8.ConvertCore(values, this.output, 255F, 128F);
new JpegColorConverter.FromYCbCrVector8(8).ConvertToRgba(values, this.output);
}
private static Buffer2D<float>[] CreateRandomValues(
int componentCount,
int inputBufferLength,
float minVal = 0f,
float maxVal = 255f)
[Benchmark]
public void SimdVectorAvx2()
{
var rnd = new Random(42);
var buffers = new Buffer2D<float>[componentCount];
for (int i = 0; i < componentCount; i++)
{
var values = new float[inputBufferLength];
for (int j = 0; j < inputBufferLength; j++)
{
values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal;
}
// no need to dispose when buffer is not array owner
buffers[i] = Configuration.Default.MemoryAllocator.Allocate2D<float>(values.Length, 1);
}
var values = new JpegColorConverter.ComponentValues(this.input, 0);
return buffers;
new JpegColorConverter.FromYCbCrAvx2(8).ConvertToRgba(values, this.output);
}
}
}

41
tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
public class YccKColorConverter : ColorConversionBenchmark
{
public YccKColorConverter()
: base(4)
{
}
[Benchmark(Baseline = true)]
public void Scalar()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
new JpegColorConverter.FromYccKBasic(8).ConvertToRgba(values, this.output);
}
[Benchmark]
public void SimdVector8()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
new JpegColorConverter.FromYccKVector8(8).ConvertToRgba(values, this.output);
}
[Benchmark]
public void SimdVectorAvx2()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
new JpegColorConverter.FromYccKAvx2(8).ConvertToRgba(values, this.output);
}
}
}

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

@ -22,6 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(Precision);
// int inputBufferLength, int resultBufferLength, int seed
public static readonly TheoryData<int, int, int> CommonConversionData =
new TheoryData<int, int, int>
{
@ -41,9 +42,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(CommonConversionData))]
public void ConvertFromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed)
public void FromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed)
{
ValidateRgbToYCbCrConversion(
ValidateConversion(
new JpegColorConverter.FromYCbCrBasic(8),
3,
inputBufferLength,
@ -51,44 +52,36 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
seed);
}
private static void ValidateYCbCr(in JpegColorConverter.ComponentValues values, 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);
Vector4 rgba = result[i];
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
var expected = ColorSpaceConverter.ToRgb(ycbcr);
Assert.Equal(expected, actual, ColorSpaceComparer);
Assert.Equal(1, rgba.W);
}
[Theory]
[InlineData(64, 1)]
[InlineData(16, 2)]
[InlineData(8, 3)]
public void FromYCbCrSimd_ConvertCore(int size, int seed)
[MemberData(nameof(CommonConversionData))]
public void FromYCbCrVector4(int inputBufferLength, int resultBufferLength, int seed)
{
JpegColorConverter.ComponentValues values = CreateRandomValues(3, size, seed);
var result = new Vector4[size];
JpegColorConverter.FromYCbCrSimd.ConvertCore(values, result, 255, 128);
for (int i = 0; i < size; i++)
if (!SimdUtils.HasVector4)
{
ValidateYCbCr(values, result, i);
this.Output.WriteLine("No SSE present, skipping test!");
return;
}
ValidateConversion(
new JpegColorConverter.FromYCbCrVector4(8),
3,
inputBufferLength,
resultBufferLength,
seed);
}
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromYCbCrSimd(int inputBufferLength, int resultBufferLength, int seed)
public void FromYCbCrVector8(int inputBufferLength, int resultBufferLength, int seed)
{
ValidateRgbToYCbCrConversion(
new JpegColorConverter.FromYCbCrSimd(8),
if (!SimdUtils.HasVector8)
{
this.Output.WriteLine("No AVX2 present, skipping test!");
return;
}
ValidateConversion(
new JpegColorConverter.FromYCbCrVector8(8),
3,
inputBufferLength,
resultBufferLength,
@ -97,17 +90,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromYCbCrSimdAvx2(int inputBufferLength, int resultBufferLength, int seed)
public void FromYCbCrAvx2(int inputBufferLength, int resultBufferLength, int seed)
{
if (!SimdUtils.HasVector8)
if (!SimdUtils.HasAvx2)
{
this.Output.WriteLine("No AVX2 present, skipping test!");
return;
}
// JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s);
ValidateRgbToYCbCrConversion(
new JpegColorConverter.FromYCbCrSimdVector8(8),
ValidateConversion(
new JpegColorConverter.FromYCbCrAvx2(8),
3,
inputBufferLength,
resultBufferLength,
@ -116,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(CommonConversionData))]
public void ConvertFromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed)
public void FromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed)
{
ValidateConversion(
JpegColorSpace.YCbCr,
@ -126,149 +118,251 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
seed);
}
// Benchmark, for local execution only
// [Theory]
// [InlineData(false)]
// [InlineData(true)]
public void BenchmarkYCbCr(bool simd)
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromCmykBasic(int inputBufferLength, int resultBufferLength, int seed)
{
int count = 2053;
int times = 50000;
JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1);
var result = new Vector4[count];
ValidateConversion(
new JpegColorConverter.FromCmykBasic(8),
4,
inputBufferLength,
resultBufferLength,
seed);
}
JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd(8) : new JpegColorConverter.FromYCbCrBasic(8);
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromCmykVector8(int inputBufferLength, int resultBufferLength, int seed)
{
if (!SimdUtils.HasVector8)
{
this.Output.WriteLine("No AVX2 present, skipping test!");
return;
}
// Warm up:
converter.ConvertToRgba(values, result);
ValidateConversion(
new JpegColorConverter.FromCmykVector8(8),
4,
inputBufferLength,
resultBufferLength,
seed);
}
using (new MeasureGuard(this.Output, $"{converter.GetType().Name} x {times}"))
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromCmykAvx2(int inputBufferLength, int resultBufferLength, int seed)
{
if (!SimdUtils.HasAvx2)
{
for (int i = 0; i < times; i++)
{
converter.ConvertToRgba(values, result);
}
this.Output.WriteLine("No AVX2 present, skipping test!");
return;
}
ValidateConversion(
new JpegColorConverter.FromCmykAvx2(8),
4,
inputBufferLength,
resultBufferLength,
seed);
}
[Theory]
[MemberData(nameof(CommonConversionData))]
public void ConvertFromCmyk(int inputBufferLength, int resultBufferLength, int seed)
public void FromCmyk_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed)
{
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk, 8);
JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed);
var result = new Vector4[resultBufferLength];
ValidateConversion(
JpegColorSpace.Cmyk,
4,
inputBufferLength,
resultBufferLength,
seed);
}
converter.ConvertToRgba(values, result);
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromGrayscaleBasic(int inputBufferLength, int resultBufferLength, int seed)
{
ValidateConversion(
new JpegColorConverter.FromGrayscaleBasic(8),
1,
inputBufferLength,
resultBufferLength,
seed);
}
for (int i = 0; i < resultBufferLength; i++)
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromGrayscaleAvx2(int inputBufferLength, int resultBufferLength, int seed)
{
if (!SimdUtils.HasAvx2)
{
float c = values.Component0[i];
float m = values.Component1[i];
float y = values.Component2[i];
float k = values.Component3[i] / 255F;
this.Output.WriteLine("No AVX2 present, skipping test!");
return;
}
v.X = c * k;
v.Y = m * k;
v.Z = y * k;
v.W = 1F;
ValidateConversion(
new JpegColorConverter.FromGrayscaleAvx2(8),
1,
inputBufferLength,
resultBufferLength,
seed);
}
v *= scale;
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromGraysacle_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed)
{
ValidateConversion(
JpegColorSpace.Grayscale,
1,
inputBufferLength,
resultBufferLength,
seed);
}
Vector4 rgba = result[i];
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
var expected = new Rgb(v.X, v.Y, v.Z);
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromRgbBasic(int inputBufferLength, int resultBufferLength, int seed)
{
ValidateConversion(
new JpegColorConverter.FromRgbBasic(8),
3,
inputBufferLength,
resultBufferLength,
seed);
}
Assert.Equal(expected, actual);
Assert.Equal(1, rgba.W);
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromRgbVector8(int inputBufferLength, int resultBufferLength, int seed)
{
if (!SimdUtils.HasVector8)
{
this.Output.WriteLine("No AVX2 present, skipping test!");
return;
}
ValidateConversion(
new JpegColorConverter.FromRgbVector8(8),
3,
inputBufferLength,
resultBufferLength,
seed);
}
[Theory]
[MemberData(nameof(CommonConversionData))]
public void ConvertFromGrayScale(int inputBufferLength, int resultBufferLength, int seed)
public void FromRgbAvx2(int inputBufferLength, int resultBufferLength, int seed)
{
var converter = JpegColorConverter.GetConverter(JpegColorSpace.Grayscale, 8);
JpegColorConverter.ComponentValues values = CreateRandomValues(1, inputBufferLength, seed);
var result = new Vector4[resultBufferLength];
if (!SimdUtils.HasAvx2)
{
this.Output.WriteLine("No AVX2 present, skipping test!");
return;
}
converter.ConvertToRgba(values, result);
ValidateConversion(
new JpegColorConverter.FromRgbAvx2(8),
3,
inputBufferLength,
resultBufferLength,
seed);
}
for (int i = 0; i < resultBufferLength; i++)
{
float y = values.Component0[i];
Vector4 rgba = result[i];
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
var expected = new Rgb(y / 255F, y / 255F, y / 255F);
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromRgb_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed)
{
ValidateConversion(
JpegColorSpace.RGB,
3,
inputBufferLength,
resultBufferLength,
seed);
}
Assert.Equal(expected, actual, ColorSpaceComparer);
Assert.Equal(1, rgba.W);
}
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromYccKBasic(int inputBufferLength, int resultBufferLength, int seed)
{
ValidateConversion(
new JpegColorConverter.FromYccKBasic(8),
4,
inputBufferLength,
resultBufferLength,
seed);
}
[Theory]
[MemberData(nameof(CommonConversionData))]
public void ConvertFromRgb(int inputBufferLength, int resultBufferLength, int seed)
public void FromYccKVector8(int inputBufferLength, int resultBufferLength, int seed)
{
var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB, 8);
JpegColorConverter.ComponentValues values = CreateRandomValues(3, inputBufferLength, seed);
var result = new Vector4[resultBufferLength];
if (!SimdUtils.HasVector8)
{
this.Output.WriteLine("No AVX2 present, skipping test!");
return;
}
converter.ConvertToRgba(values, result);
ValidateConversion(
new JpegColorConverter.FromYccKVector8(8),
4,
inputBufferLength,
resultBufferLength,
seed);
}
for (int i = 0; i < resultBufferLength; i++)
[Theory]
[MemberData(nameof(CommonConversionData))]
public void FromYccKAvx2(int inputBufferLength, int resultBufferLength, int seed)
{
if (!SimdUtils.HasAvx2)
{
float r = values.Component0[i];
float g = values.Component1[i];
float b = values.Component2[i];
Vector4 rgba = result[i];
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
var expected = new Rgb(r / 255F, g / 255F, b / 255F);
Assert.Equal(expected, actual, ColorSpaceComparer);
Assert.Equal(1, rgba.W);
this.Output.WriteLine("No AVX2 present, skipping test!");
return;
}
ValidateConversion(
new JpegColorConverter.FromYccKAvx2(8),
4,
inputBufferLength,
resultBufferLength,
seed);
}
[Theory]
[MemberData(nameof(CommonConversionData))]
public void ConvertFromYcck(int inputBufferLength, int resultBufferLength, int seed)
public void FromYcck_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed)
{
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
ValidateConversion(
JpegColorSpace.Ycck,
4,
inputBufferLength,
resultBufferLength,
seed);
}
var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck, 8);
JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed);
var result = new Vector4[resultBufferLength];
// Benchmark, for local execution only
// [Theory]
// [InlineData(false)]
// [InlineData(true)]
public void BenchmarkYCbCr(bool simd)
{
int count = 2053;
int times = 50000;
JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1);
var result = new Vector4[count];
JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrVector4(8) : new JpegColorConverter.FromYCbCrBasic(8);
// Warm up:
converter.ConvertToRgba(values, result);
for (int i = 0; i < resultBufferLength; i++)
using (new MeasureGuard(this.Output, $"{converter.GetType().Name} x {times}"))
{
float y = values.Component0[i];
float cb = values.Component1[i] - 128F;
float cr = values.Component2[i] - 128F;
float k = values.Component3[i] / 255F;
v.X = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k;
v.Y = (255F - (float)Math.Round(
y - (0.344136F * cb) - (0.714136F * cr),
MidpointRounding.AwayFromZero)) * k;
v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k;
v.W = 1F;
v *= scale;
Vector4 rgba = result[i];
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
var expected = new Rgb(v.X, v.Y, v.Z);
Assert.Equal(expected, actual, ColorSpaceComparer);
Assert.Equal(1, rgba.W);
for (int i = 0; i < times; i++)
{
converter.ConvertToRgba(values, result);
}
}
}
@ -283,7 +377,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var buffers = new Buffer2D<float>[componentCount];
for (int i = 0; i < componentCount; i++)
{
float[] values = new float[inputBufferLength];
var values = new float[inputBufferLength];
for (int j = 0; j < inputBufferLength; j++)
{
@ -306,7 +400,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
int resultBufferLength,
int seed)
{
ValidateRgbToYCbCrConversion(
ValidateConversion(
JpegColorConverter.GetConverter(colorSpace, 8),
componentCount,
inputBufferLength,
@ -314,7 +408,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
seed);
}
private static void ValidateRgbToYCbCrConversion(
private static void ValidateConversion(
JpegColorConverter converter,
int componentCount,
int inputBufferLength,
@ -328,8 +422,128 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
for (int i = 0; i < resultBufferLength; i++)
{
ValidateYCbCr(values, result, i);
Validate(converter.ColorSpace, values, result, i);
}
}
private static void Validate(
JpegColorSpace colorSpace,
in JpegColorConverter.ComponentValues values,
Vector4[] result,
int i)
{
switch (colorSpace)
{
case JpegColorSpace.Grayscale:
ValidateGrayScale(values, result, i);
break;
case JpegColorSpace.Ycck:
ValidateCyyK(values, result, i);
break;
case JpegColorSpace.Cmyk:
ValidateCmyk(values, result, i);
break;
case JpegColorSpace.RGB:
ValidateRgb(values, result, i);
break;
case JpegColorSpace.YCbCr:
ValidateYCbCr(values, result, i);
break;
default:
Assert.True(false, $"Colorspace {colorSpace} not supported!");
break;
}
}
private static void ValidateYCbCr(in JpegColorConverter.ComponentValues values, 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);
Vector4 rgba = result[i];
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
var expected = ColorSpaceConverter.ToRgb(ycbcr);
Assert.Equal(expected, actual, ColorSpaceComparer);
Assert.Equal(1, rgba.W);
}
private static void ValidateCyyK(in JpegColorConverter.ComponentValues values, Vector4[] result, int i)
{
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
float y = values.Component0[i];
float cb = values.Component1[i] - 128F;
float cr = values.Component2[i] - 128F;
float k = values.Component3[i] / 255F;
v.X = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k;
v.Y = (255F - (float)Math.Round(
y - (0.344136F * cb) - (0.714136F * cr),
MidpointRounding.AwayFromZero)) * k;
v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k;
v.W = 1F;
v *= scale;
Vector4 rgba = result[i];
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
var expected = new Rgb(v.X, v.Y, v.Z);
Assert.Equal(expected, actual, ColorSpaceComparer);
Assert.Equal(1, rgba.W);
}
private static void ValidateRgb(in JpegColorConverter.ComponentValues values, Vector4[] result, int i)
{
float r = values.Component0[i];
float g = values.Component1[i];
float b = values.Component2[i];
Vector4 rgba = result[i];
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
var expected = new Rgb(r / 255F, g / 255F, b / 255F);
Assert.Equal(expected, actual, ColorSpaceComparer);
Assert.Equal(1, rgba.W);
}
private static void ValidateGrayScale(in JpegColorConverter.ComponentValues values, Vector4[] result, int i)
{
float y = values.Component0[i];
Vector4 rgba = result[i];
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
var expected = new Rgb(y / 255F, y / 255F, y / 255F);
Assert.Equal(expected, actual, ColorSpaceComparer);
Assert.Equal(1, rgba.W);
}
private static void ValidateCmyk(in JpegColorConverter.ComponentValues values, Vector4[] result, int i)
{
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
float c = values.Component0[i];
float m = values.Component1[i];
float y = values.Component2[i];
float k = values.Component3[i] / 255F;
v.X = c * k;
v.Y = m * k;
v.Z = y * k;
v.W = 1F;
v *= scale;
Vector4 rgba = result[i];
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
var expected = new Rgb(v.X, v.Y, v.Z);
Assert.Equal(expected, actual, ColorSpaceComparer);
Assert.Equal(1, rgba.W);
}
}
}

Loading…
Cancel
Save