Browse Source

Remove replaced SIMD implementations

pull/3161/head
James Jackson-South 3 weeks ago
parent
commit
ca126a5f4a
  1. 30
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs
  2. 116
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs
  3. 100
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs
  4. 100
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs
  5. 108
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs
  6. 29
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs
  7. 97
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs
  8. 81
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs
  9. 81
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs
  10. 89
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs
  11. 27
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs
  12. 84
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs
  13. 56
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs
  14. 56
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs
  15. 64
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs
  16. 29
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs
  17. 118
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs
  18. 99
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs
  19. 99
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs
  20. 108
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs
  21. 56
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs
  22. 153
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs
  23. 131
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs
  24. 131
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs
  25. 142
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs
  26. 81
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs
  27. 121
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs
  28. 121
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs
  29. 121
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs
  30. 128
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs
  31. 56
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs
  32. 125
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs
  33. 135
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs
  34. 135
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs
  35. 143
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs
  36. 23
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs
  37. 129
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs
  38. 34
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs
  39. 34
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs
  40. 111
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs
  41. 37
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs
  42. 37
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs
  43. 239
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs
  44. 325
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs
  45. 37
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs
  46. 37
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs
  47. 127
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs
  48. 37
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs
  49. 94
      tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs
  50. 54
      tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs
  51. 470
      tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs
  52. 65
      tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs
  53. 61
      tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs
  54. 175
      tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs
  55. 813
      tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs
  56. 160
      tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs
  57. 14
      tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs
  58. 1149
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

30
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs

@ -1,8 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -240,6 +245,29 @@ internal abstract partial class JpegColorConverterBase
IccProfile profile,
in ComponentValues values,
float maximumValue)
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue);
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
// JPEG CMYK stores inverted components, while the ICC converter consumes normalized conventional CMYK.
PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue);
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

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

@ -1,116 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class CmykScalar : JpegColorConverterScalar
{
public CmykScalar(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values) =>
ConvertToRgbInPlace(values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
float scale = 1 / (maxValue * maxValue);
for (int i = 0; i < c0.Length; i++)
{
float c = c0[i];
float m = c1[i];
float y = c2[i];
float k = c3[i];
k *= scale;
c0[i] = c * k;
c1[i] = m * k;
c2[i] = y * k;
}
}
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
Span<float> c = values.Component0;
Span<float> m = values.Component1;
Span<float> y = values.Component2;
Span<float> k = values.Component3;
for (int i = 0; i < c.Length; i++)
{
float ctmp = 255f - rLane[i];
float mtmp = 255f - gLane[i];
float ytmp = 255f - bLane[i];
float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp);
if (ktmp >= 255f)
{
ctmp = 0f;
mtmp = 0f;
ytmp = 0f;
}
else
{
ctmp = (ctmp - ktmp) / (255f - ktmp);
mtmp = (mtmp - ktmp) / (255f - ktmp);
ytmp = (ytmp - ktmp) / (255f - ktmp);
}
c[i] = maxValue - (ctmp * maxValue);
m[i] = maxValue - (mtmp * maxValue);
y[i] = maxValue - (ytmp * maxValue);
k[i] = maxValue - ktmp;
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue);
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

100
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs

@ -1,100 +0,0 @@
// 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.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class CmykVector128 : JpegColorConverterVector128
{
public CmykVector128(int precision)
: base(JpegColorSpace.Cmyk, 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));
ref Vector128<float> c3Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
Vector128<float> scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue));
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector128<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector128<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector128<float> y = ref Unsafe.Add(ref c2Base, i);
Vector128<float> k = Unsafe.Add(ref c3Base, i);
k *= scale;
c *= k;
m *= k;
y *= k;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector128<float> destC =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> destM =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> destY =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> destK =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
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> scale = Vector128.Create(maxValue);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector128<float> ctmp = scale - Unsafe.Add(ref srcR, i);
Vector128<float> mtmp = scale - Unsafe.Add(ref srcG, i);
Vector128<float> ytmp = scale - Unsafe.Add(ref srcB, i);
Vector128<float> ktmp = Vector128.Min(ctmp, Vector128.Min(mtmp, ytmp));
Vector128<float> kMask = ~Vector128.Equals(ktmp, scale);
Vector128<float> divisor = scale - ktmp;
ctmp = ((ctmp - ktmp) / divisor) & kMask;
mtmp = ((mtmp - ktmp) / divisor) & kMask;
ytmp = ((ytmp - ktmp) / divisor) & kMask;
Unsafe.Add(ref destC, i) = scale - (ctmp * scale);
Unsafe.Add(ref destM, i) = scale - (mtmp * scale);
Unsafe.Add(ref destY, i) = scale - (ytmp * scale);
Unsafe.Add(ref destK, i) = scale - ktmp;
}
}
}
}

100
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs

@ -1,100 +0,0 @@
// 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.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class CmykVector256 : JpegColorConverterVector256
{
public CmykVector256(int precision)
: base(JpegColorSpace.Cmyk, 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));
ref Vector256<float> c3Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
Vector256<float> scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i);
Vector256<float> k = Unsafe.Add(ref c3Base, i);
k *= scale;
c *= k;
m *= k;
y *= k;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector256<float> destC =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> destM =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> destY =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> destK =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
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> scale = Vector256.Create(maxValue);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector256<float> ctmp = scale - Unsafe.Add(ref srcR, i);
Vector256<float> mtmp = scale - Unsafe.Add(ref srcG, i);
Vector256<float> ytmp = scale - Unsafe.Add(ref srcB, i);
Vector256<float> ktmp = Vector256.Min(ctmp, Vector256.Min(mtmp, ytmp));
Vector256<float> kMask = ~Vector256.Equals(ktmp, scale);
Vector256<float> divisor = scale - ktmp;
ctmp = ((ctmp - ktmp) / divisor) & kMask;
mtmp = ((mtmp - ktmp) / divisor) & kMask;
ytmp = ((ytmp - ktmp) / divisor) & kMask;
Unsafe.Add(ref destC, i) = scale - (ctmp * scale);
Unsafe.Add(ref destM, i) = scale - (mtmp * scale);
Unsafe.Add(ref destY, i) = scale - (ytmp * scale);
Unsafe.Add(ref destK, i) = scale - ktmp;
}
}
}
}

108
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs

@ -1,108 +0,0 @@
// 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.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class CmykVector512 : JpegColorConverterVector512
{
public CmykVector512(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <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));
ref Vector512<float> c3Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
Vector512<float> scale = Vector512.Create(1 / (this.MaximumValue * this.MaximumValue));
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector512<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector512<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector512<float> y = ref Unsafe.Add(ref c2Base, i);
Vector512<float> k = Unsafe.Add(ref c3Base, i);
k *= scale;
c *= k;
m *= k;
y *= k;
}
}
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> CmykScalar.ConvertToRgbInPlace(values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> CmykScalar.ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane);
internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector512<float> destC =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector512<float> destM =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector512<float> destY =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector512<float> destK =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3));
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> scale = Vector512.Create(maxValue);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector512<float> ctmp = scale - Unsafe.Add(ref srcR, i);
Vector512<float> mtmp = scale - Unsafe.Add(ref srcG, i);
Vector512<float> ytmp = scale - Unsafe.Add(ref srcB, i);
Vector512<float> ktmp = Vector512.Min(ctmp, Vector512.Min(mtmp, ytmp));
Vector512<float> kMask = ~Vector512.Equals(ktmp, scale);
Vector512<float> divisor = scale - ktmp;
ctmp = ((ctmp - ktmp) / divisor) & kMask;
mtmp = ((mtmp - ktmp) / divisor) & kMask;
ytmp = ((ytmp - ktmp) / divisor) & kMask;
Unsafe.Add(ref destC, i) = scale - (ctmp * scale);
Unsafe.Add(ref destM, i) = scale - (mtmp * scale);
Unsafe.Add(ref destY, i) = scale - (ytmp * scale);
Unsafe.Add(ref destK, i) = scale - ktmp;
}
}
}
}

29
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs

@ -1,8 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@ -198,6 +203,28 @@ internal abstract partial class JpegColorConverterBase
IccProfile profile,
in ComponentValues values,
float maximumValue)
=> GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue);
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
float scale = 1F / maximumValue;
// ICC luminance values are normalized, so the source plane is scaled in place before conversion.
TensorPrimitives_.Multiply(c0, scale, c0);
Span<Y> source = MemoryMarshal.Cast<float, Y>(c0);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed);
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Y, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

97
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs

@ -1,97 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class GrayScaleScalar : JpegColorConverterScalar
{
public GrayScaleScalar(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgbInPlace(in values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgbScalar(values, rLane, gLane, bLane);
internal static void ConvertToRgbInPlace(in ComponentValues values, float maxValue)
{
ref float c0Base = ref MemoryMarshal.GetReference(values.Component0);
ref float c1Base = ref MemoryMarshal.GetReference(values.Component1);
ref float c2Base = ref MemoryMarshal.GetReference(values.Component2);
float scale = 1F / maxValue;
for (nuint i = 0; i < (nuint)values.Component0.Length; i++)
{
float c = Unsafe.Add(ref c0Base, i) * scale;
Unsafe.Add(ref c0Base, i) = c;
Unsafe.Add(ref c1Base, i) = c;
Unsafe.Add(ref c2Base, i) = c;
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
ref float c0Base = ref MemoryMarshal.GetReference(c0);
ref float c1Base = ref MemoryMarshal.GetReference(c1);
ref float c2Base = ref MemoryMarshal.GetReference(c2);
float scale = 1F / maxValue;
for (nuint i = 0; i < (nuint)values.Component0.Length; i++)
{
ref float c = ref Unsafe.Add(ref c0Base, i);
c *= scale;
}
Span<Y> source = MemoryMarshal.Cast<float, Y>(values.Component0);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed);
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Y, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
internal static void ConvertFromRgbScalar(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
Span<float> c0 = values.Component0;
for (int i = 0; i < c0.Length; i++)
{
// luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b)
c0[i] = (float)((0.299f * rLane[i]) + (0.587f * gLane[i]) + (0.114f * bLane[i]));
}
}
}
}

81
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs

@ -1,81 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class GrayScaleVector128 : JpegColorConverterVector128
{
public GrayScaleVector128(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <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));
// Used for the color conversion
Vector128<float> scale = Vector128.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector128<float> c = Unsafe.Add(ref c0Base, i) * scale;
Unsafe.Add(ref c0Base, i) = c;
Unsafe.Add(ref c1Base, i) = c;
Unsafe.Add(ref c2Base, i) = c;
}
}
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector128<float> destLuminance =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> srcRed =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector128<float> srcGreen =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector128<float> srcBlue =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane));
// Used for the color conversion
Vector128<float> f0299 = Vector128.Create(0.299f);
Vector128<float> f0587 = Vector128.Create(0.587f);
Vector128<float> f0114 = Vector128.Create(0.114f);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector128<float> r = ref Unsafe.Add(ref srcRed, i);
ref Vector128<float> g = ref Unsafe.Add(ref srcGreen, i);
ref Vector128<float> b = ref Unsafe.Add(ref srcBlue, i);
// luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b)
Unsafe.Add(ref destLuminance, i) = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b));
}
}
}
}

81
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs

@ -1,81 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class GrayScaleVector256 : JpegColorConverterVector256
{
public GrayScaleVector256(int precision)
: base(JpegColorSpace.Grayscale, 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));
// Used for the color conversion
Vector256<float> scale = Vector256.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector256<float> c = Unsafe.Add(ref c0Base, i) * scale;
Unsafe.Add(ref c0Base, i) = c;
Unsafe.Add(ref c1Base, i) = c;
Unsafe.Add(ref c2Base, i) = c;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector256<float> destLuminance =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> srcRed =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector256<float> srcGreen =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector256<float> srcBlue =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane));
// Used for the color conversion
Vector256<float> f0299 = Vector256.Create(0.299f);
Vector256<float> f0587 = Vector256.Create(0.587f);
Vector256<float> f0114 = Vector256.Create(0.114f);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector256<float> r = ref Unsafe.Add(ref srcRed, i);
ref Vector256<float> g = ref Unsafe.Add(ref srcGreen, i);
ref Vector256<float> b = ref Unsafe.Add(ref srcBlue, i);
// luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b)
Unsafe.Add(ref destLuminance, i) = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b));
}
}
}
}

89
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs

@ -1,89 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class GrayScaleVector512 : JpegColorConverterVector512
{
public GrayScaleVector512(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <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));
// Used for the color conversion
Vector512<float> scale = Vector512.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector512<float> c = Unsafe.Add(ref c0Base, i) * scale;
Unsafe.Add(ref c0Base, i) = c;
Unsafe.Add(ref c1Base, i) = c;
Unsafe.Add(ref c2Base, i) = c;
}
}
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector512<float> destLuminance =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector512<float> srcRed =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector512<float> srcGreen =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector512<float> srcBlue =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(bLane));
// Used for the color conversion
Vector512<float> f0299 = Vector512.Create(0.299f);
Vector512<float> f0587 = Vector512.Create(0.587f);
Vector512<float> f0114 = Vector512.Create(0.114f);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector512<float> r = ref Unsafe.Add(ref srcRed, i);
ref Vector512<float> g = ref Unsafe.Add(ref srcGreen, i);
ref Vector512<float> b = ref Unsafe.Add(ref srcBlue, i);
// luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b)
Unsafe.Add(ref destLuminance, i) = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b));
}
}
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> GrayScaleScalar.ConvertToRgbInPlace(in values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> GrayScaleScalar.ConvertFromRgbScalar(values, rLane, gLane, bLane);
}
}

27
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs

@ -1,8 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -179,6 +184,26 @@ internal abstract partial class JpegColorConverterBase
IccProfile profile,
in ComponentValues values,
float maximumValue)
=> RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue);
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
// JPEG planes use the integer sample domain, while ICC RGB values are normalized and interleaved.
PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maximumValue);
Span<Rgb> rgb = MemoryMarshal.Cast<float, Rgb>(packed);
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Rgb, Rgb>(rgb, rgb);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..rgb.Length], c0, c1, c2);
}
}
}

84
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs

@ -1,84 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class RgbScalar : JpegColorConverterScalar
{
public RgbScalar(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgbInPlace(values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(values, rLane, gLane, bLane);
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maxValue);
Span<Rgb> source = MemoryMarshal.Cast<float, Rgb>(packed);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed);
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Rgb, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
internal static void ConvertToRgbInPlace(ComponentValues values, float maxValue)
{
ref float c0Base = ref MemoryMarshal.GetReference(values.Component0);
ref float c1Base = ref MemoryMarshal.GetReference(values.Component1);
ref float c2Base = ref MemoryMarshal.GetReference(values.Component2);
float scale = 1F / maxValue;
for (nuint i = 0; i < (nuint)values.Component0.Length; i++)
{
Unsafe.Add(ref c0Base, i) *= scale;
Unsafe.Add(ref c1Base, i) *= scale;
Unsafe.Add(ref c2Base, i) *= scale;
}
}
internal static void ConvertFromRgb(ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
// TODO: This doesn't seem correct. We should be scaling to the maximum value here.
rLane.CopyTo(values.Component0);
gLane.CopyTo(values.Component1);
bLane.CopyTo(values.Component2);
}
}
}

56
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs

@ -1,56 +0,0 @@
// 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.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class RgbVector128 : JpegColorConverterVector128
{
public RgbVector128(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
{
ref Vector128<float> rBase =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> gBase =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> bBase =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
// Used for the color conversion
Vector128<float> scale = Vector128.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector128<float> r = ref Unsafe.Add(ref rBase, i);
ref Vector128<float> g = ref Unsafe.Add(ref gBase, i);
ref Vector128<float> b = ref Unsafe.Add(ref bBase, i);
r *= scale;
g *= scale;
b *= scale;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
rLane.CopyTo(values.Component0);
gLane.CopyTo(values.Component1);
bLane.CopyTo(values.Component2);
}
}
}

56
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs

@ -1,56 +0,0 @@
// 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.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class RgbVector256 : JpegColorConverterVector256
{
public RgbVector256(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
{
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));
// Used for the color conversion
Vector256<float> scale = Vector256.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector256<float> r = ref Unsafe.Add(ref rBase, i);
ref Vector256<float> g = ref Unsafe.Add(ref gBase, i);
ref Vector256<float> b = ref Unsafe.Add(ref bBase, i);
r *= scale;
g *= scale;
b *= scale;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
rLane.CopyTo(values.Component0);
gLane.CopyTo(values.Component1);
bLane.CopyTo(values.Component2);
}
}
}

64
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs

@ -1,64 +0,0 @@
// 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.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class RgbVector512 : JpegColorConverterVector512
{
public RgbVector512(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{
ref Vector512<float> rBase =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector512<float> gBase =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector512<float> bBase =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2));
// Used for the color conversion
Vector512<float> scale = Vector512.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector512<float> r = ref Unsafe.Add(ref rBase, i);
ref Vector512<float> g = ref Unsafe.Add(ref gBase, i);
ref Vector512<float> b = ref Unsafe.Add(ref bBase, i);
r *= scale;
g *= scale;
b *= scale;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
rLane.CopyTo(values.Component0);
gLane.CopyTo(values.Component1);
bLane.CopyTo(values.Component2);
}
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> RgbScalar.ConvertToRgbInPlace(values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> RgbScalar.ConvertFromRgb(values, rLane, gLane, bLane);
}
}

29
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs

@ -1,8 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -154,6 +159,28 @@ internal abstract partial class JpegColorConverterBase
/// <inheritdoc/>
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue)
=> TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue);
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
// TIFF CMYK is already non-inverted, so only normalization and interleaving precede ICC conversion.
PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue);
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

118
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs

@ -1,118 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// Color converter for tiff images, which use the jpeg compression and CMYK colorspace.
/// </summary>
internal sealed class TiffCmykScalar : JpegColorConverterScalar
{
public TiffCmykScalar(int precision)
: base(JpegColorSpace.TiffCmyk, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgbInPlace(in values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
float scale = 1 / maxValue;
for (int i = 0; i < c0.Length; i++)
{
float c = c0[i] * scale;
float m = c1[i] * scale;
float y = c2[i] * scale;
float k = 1 - (c3[i] * scale);
c0[i] = (1 - c) * k;
c1[i] = (1 - m) * k;
c2[i] = (1 - y) * k;
}
}
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
Span<float> c = values.Component0;
Span<float> m = values.Component1;
Span<float> y = values.Component2;
Span<float> k = values.Component3;
for (int i = 0; i < c.Length; i++)
{
float ctmp = 255F - rLane[i];
float mtmp = 255F - gLane[i];
float ytmp = 255F - bLane[i];
float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp);
if (ktmp >= 255F)
{
ctmp = 0F;
mtmp = 0F;
ytmp = 0F;
}
else
{
float divisor = 1 / (255F - ktmp);
ctmp = (ctmp - ktmp) * divisor;
mtmp = (mtmp - ktmp) * divisor;
ytmp = (ytmp - ktmp) * divisor;
}
c[i] = ctmp * maxValue;
m[i] = mtmp * maxValue;
y[i] = ytmp * maxValue;
k[i] = ktmp;
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue);
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

99
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs

@ -1,99 +0,0 @@
// 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.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffCmykVector128 : JpegColorConverterVector128
{
public TiffCmykVector128(int precision)
: base(JpegColorSpace.TiffCmyk, 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));
ref Vector128<float> c3Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector128<float> scale = Vector128.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector128<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector128<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector128<float> y = ref Unsafe.Add(ref c2Base, i);
Vector128<float> k = Unsafe.Add(ref c3Base, i);
k = Vector128<float>.One - (k * scale);
c = (Vector128<float>.One - (c * scale)) * k;
m = (Vector128<float>.One - (m * scale)) * k;
y = (Vector128<float>.One - (y * scale)) * k;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector128<float> destC =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> destM =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> destY =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> destK =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
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> scale = Vector128.Create(maxValue);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector128<float> ctmp = scale - Unsafe.Add(ref srcR, i);
Vector128<float> mtmp = scale - Unsafe.Add(ref srcG, i);
Vector128<float> ytmp = scale - Unsafe.Add(ref srcB, i);
Vector128<float> ktmp = Vector128.Min(ctmp, Vector128.Min(mtmp, ytmp));
Vector128<float> kMask = ~Vector128.Equals(ktmp, scale);
Vector128<float> divisor = Vector128<float>.One / (scale - ktmp);
ctmp = ((ctmp - ktmp) * divisor) & kMask;
mtmp = ((mtmp - ktmp) * divisor) & kMask;
ytmp = ((ytmp - ktmp) * divisor) & kMask;
Unsafe.Add(ref destC, i) = ctmp * scale;
Unsafe.Add(ref destM, i) = mtmp * scale;
Unsafe.Add(ref destY, i) = ytmp * scale;
Unsafe.Add(ref destK, i) = ktmp;
}
}
}
}

99
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs

@ -1,99 +0,0 @@
// 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.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffCmykVector256 : JpegColorConverterVector256
{
public TiffCmykVector256(int precision)
: base(JpegColorSpace.TiffCmyk, 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));
ref Vector256<float> c3Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector256<float> scale = Vector256.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i);
Vector256<float> k = Unsafe.Add(ref c3Base, i);
k = Vector256<float>.One - (k * scale);
c = (Vector256<float>.One - (c * scale)) * k;
m = (Vector256<float>.One - (m * scale)) * k;
y = (Vector256<float>.One - (y * scale)) * k;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector256<float> destC =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> destM =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> destY =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> destK =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
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> scale = Vector256.Create(maxValue);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector256<float> ctmp = scale - Unsafe.Add(ref srcR, i);
Vector256<float> mtmp = scale - Unsafe.Add(ref srcG, i);
Vector256<float> ytmp = scale - Unsafe.Add(ref srcB, i);
Vector256<float> ktmp = Vector256.Min(ctmp, Vector256.Min(mtmp, ytmp));
Vector256<float> kMask = ~Vector256.Equals(ktmp, scale);
Vector256<float> divisor = Vector256<float>.One / (scale - ktmp);
ctmp = ((ctmp - ktmp) * divisor) & kMask;
mtmp = ((mtmp - ktmp) * divisor) & kMask;
ytmp = ((ytmp - ktmp) * divisor) & kMask;
Unsafe.Add(ref destC, i) = ctmp * scale;
Unsafe.Add(ref destM, i) = mtmp * scale;
Unsafe.Add(ref destY, i) = ytmp * scale;
Unsafe.Add(ref destK, i) = ktmp;
}
}
}
}

108
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs

@ -1,108 +0,0 @@
// 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.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffCmykVector512 : JpegColorConverterVector512
{
public TiffCmykVector512(int precision)
: base(JpegColorSpace.TiffCmyk, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <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));
ref Vector512<float> c3Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
Vector512<float> scale = Vector512.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector512<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector512<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector512<float> y = ref Unsafe.Add(ref c2Base, i);
Vector512<float> k = Unsafe.Add(ref c3Base, i);
k = Vector512<float>.One - (k * scale);
c = (Vector512<float>.One - (c * scale)) * k;
m = (Vector512<float>.One - (m * scale)) * k;
y = (Vector512<float>.One - (y * scale)) * k;
}
}
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> TiffCmykScalar.ConvertToRgbInPlace(values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> TiffCmykScalar.ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane);
internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector512<float> destC =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector512<float> destM =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector512<float> destY =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector512<float> destK =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3));
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> scale = Vector512.Create(maxValue);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector512<float> ctmp = scale - Unsafe.Add(ref srcR, i);
Vector512<float> mtmp = scale - Unsafe.Add(ref srcG, i);
Vector512<float> ytmp = scale - Unsafe.Add(ref srcB, i);
Vector512<float> ktmp = Vector512.Min(ctmp, Vector512.Min(mtmp, ytmp));
Vector512<float> kMask = ~Vector512.Equals(ktmp, scale);
Vector512<float> divisor = Vector512<float>.One / (scale - ktmp);
ctmp = ((ctmp - ktmp) * divisor) & kMask;
mtmp = ((mtmp - ktmp) * divisor) & kMask;
ytmp = ((ytmp - ktmp) * divisor) & kMask;
Unsafe.Add(ref destC, i) = ctmp * scale;
Unsafe.Add(ref destM, i) = mtmp * scale;
Unsafe.Add(ref destY, i) = ytmp * scale;
Unsafe.Add(ref destK, i) = ktmp;
}
}
}
}

56
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs

@ -1,8 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@ -34,9 +39,9 @@ internal abstract partial class JpegColorConverterBase
// TIFF YccK is non-inverted: decode normalized YCbCr without integer rounding, then let the
// remaining light after K modulate all three channels.
c0 = (y + (YCbCrScalar.RCrMult * cr)) * k;
c1 = (y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr)) * k;
c2 = (y + (YCbCrScalar.BCbMult * cb)) * k;
c0 = (y + (YCbCrOperator.RCrMult * cr)) * k;
c1 = (y - (YCbCrOperator.GCbMult * cb) - (YCbCrOperator.GCrMult * cr)) * k;
c2 = (y + (YCbCrOperator.BCbMult * cb)) * k;
}
/// <inheritdoc/>
@ -49,9 +54,9 @@ internal abstract partial class JpegColorConverterBase
Vector128<float> k = Vector128<float>.One - (c3 * scale);
// Four lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
c0 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y) * k;
c1 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrScalar.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)) * k;
c2 = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y) * k;
c0 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y) * k;
c1 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y)) * k;
c2 = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y) * k;
}
/// <inheritdoc/>
@ -64,9 +69,9 @@ internal abstract partial class JpegColorConverterBase
Vector256<float> k = Vector256<float>.One - (c3 * scale);
// Eight lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
c0 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y) * k;
c1 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrScalar.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)) * k;
c2 = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y) * k;
c0 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y) * k;
c1 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y)) * k;
c2 = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y) * k;
}
/// <inheritdoc/>
@ -79,9 +84,9 @@ internal abstract partial class JpegColorConverterBase
Vector512<float> k = Vector512<float>.One - (c3 * scale);
// Sixteen lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
c0 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y) * k;
c1 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrScalar.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)) * k;
c2 = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y) * k;
c0 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y) * k;
c1 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y)) * k;
c2 = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y) * k;
}
/// <inheritdoc/>
@ -182,6 +187,31 @@ internal abstract partial class JpegColorConverterBase
/// <inheritdoc/>
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue)
=> TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue);
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
// TIFF YccK is non-inverted, so normalize directly before converting its JPEG-specific model to CMYK.
PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue);
ColorProfileConverter converter = new();
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
converter.Convert<YccK, Cmyk>(MemoryMarshal.Cast<Cmyk, YccK>(source), source);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
converter = new ColorProfileConverter(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

153
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs

@ -1,153 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// Color converter for tiff images, which use the jpeg compression and CMYK colorspace.
/// </summary>
internal sealed class TiffYccKScalar : JpegColorConverterScalar
{
// Derived from ITU-T Rec. T.871
internal const float RCrMult = 1.402f;
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587);
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587);
internal const float BCbMult = 1.772f;
public TiffYccKScalar(int precision)
: base(JpegColorSpace.TiffYccK, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgbInPlace(in values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
float scale = 1F / maxValue;
halfValue *= scale;
for (int i = 0; i < values.Component0.Length; i++)
{
float y = c0[i] * scale;
float cb = (c1[i] * scale) - halfValue;
float cr = (c2[i] * scale) - halfValue;
float scaledK = 1 - (c3[i] * scale);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
c0[i] = (y + (RCrMult * cr)) * scaledK;
c1[i] = (y - (GCbMult * cb) - (GCrMult * cr)) * scaledK;
c2[i] = (y + (BCbMult * cb)) * scaledK;
}
}
public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
Span<float> y = values.Component0;
Span<float> cb = values.Component1;
Span<float> cr = values.Component2;
Span<float> k = values.Component3;
for (int i = 0; i < cr.Length; i++)
{
// Scale down to [0-1]
const float divisor = 1F / 255F;
float r = rLane[i] * divisor;
float g = gLane[i] * divisor;
float b = bLane[i] * divisor;
float ytmp;
float cbtmp;
float crtmp;
float ktmp = 1F - MathF.Max(r, MathF.Max(g, b));
if (ktmp >= 1F)
{
ytmp = 0F;
cbtmp = 0.5F;
crtmp = 0.5F;
ktmp = maxValue;
}
else
{
float kmask = 1F / (1F - ktmp);
r *= kmask;
g *= kmask;
b *= kmask;
// Scale to [0-maxValue]
ytmp = ((0.299f * r) + (0.587f * g) + (0.114f * b)) * maxValue;
cbtmp = halfValue - (((0.168736f * r) - (0.331264f * g) + (0.5f * b)) * maxValue);
crtmp = halfValue + (((0.5f * r) - (0.418688f * g) - (0.081312f * b)) * maxValue);
ktmp *= maxValue;
}
y[i] = ytmp;
cb[i] = cbtmp;
cr[i] = crtmp;
k[i] = ktmp;
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue);
ColorProfileConverter converter = new();
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
// YccK is not a defined ICC color space � it's a JPEG-specific encoding used in Adobe-style CMYK JPEGs.
// ICC profiles expect colorimetric CMYK values, so we must first convert YccK to CMYK using a hardcoded inverse transform.
// This transform assumes Rec.601 YCbCr coefficients and an inverted K channel.
//
// The YccK => Cmyk conversion is independent of any embedded ICC profile.
// Since the same RGB working space is used during conversion to and from XYZ,
// colorimetric accuracy is preserved.
converter.Convert<YccK, Cmyk>(MemoryMarshal.Cast<Cmyk, YccK>(source), source);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
converter = new ColorProfileConverter(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

131
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs

@ -1,131 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffYccKVector128 : JpegColorConverterVector128
{
public TiffYccKVector128(int precision)
: base(JpegColorSpace.TiffYccK, 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));
ref Vector128<float> c3Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector128<float> scale = Vector128.Create(1F / this.MaximumValue);
Vector128<float> chromaOffset = Vector128.Create(this.HalfValue) * scale;
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);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
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);
ref Vector128<float> c3 = ref Unsafe.Add(ref c3Base, i);
Vector128<float> y = c0 * scale;
Vector128<float> cb = (c1 * scale) - chromaOffset;
Vector128<float> cr = (c2 * scale) - chromaOffset;
Vector128<float> scaledK = Vector128<float>.One - (c3 * scale);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK;
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK;
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
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));
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> destK =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector128<float> maxSourceValue = Vector128.Create(1 / 255F);
Vector128<float> maxSampleValue = Vector128.Create(this.MaximumValue);
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) * maxSourceValue;
Vector128<float> g = Unsafe.Add(ref srcG, i) * maxSourceValue;
Vector128<float> b = Unsafe.Add(ref srcB, i) * maxSourceValue;
Vector128<float> ktmp = Vector128<float>.One - Vector128.Max(r, Vector128.Min(g, b));
Vector128<float> kMask = ~Vector128.Equals(ktmp, Vector128<float>.One);
Vector128<float> divisor = Vector128<float>.One / (Vector128<float>.One - ktmp);
r = (r * divisor) & kMask;
g = (g * divisor) & kMask;
b = (b * divisor) & kMask;
// 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 = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b));
Vector128<float> cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b));
Vector128<float> cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b));
Unsafe.Add(ref destY, i) = y * maxSampleValue;
Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue);
Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue);
Unsafe.Add(ref destK, i) = ktmp * maxSampleValue;
}
}
}
}

131
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs

@ -1,131 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffYccKVector256 : JpegColorConverterVector256
{
public TiffYccKVector256(int precision)
: base(JpegColorSpace.TiffYccK, 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));
ref Vector256<float> c3Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector256<float> scale = Vector256.Create(1F / this.MaximumValue);
Vector256<float> chromaOffset = Vector256.Create(this.HalfValue) * scale;
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);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
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);
ref Vector256<float> c3 = ref Unsafe.Add(ref c3Base, i);
Vector256<float> y = c0 * scale;
Vector256<float> cb = (c1 * scale) - chromaOffset;
Vector256<float> cr = (c2 * scale) - chromaOffset;
Vector256<float> scaledK = Vector256<float>.One - (c3 * scale);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK;
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK;
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
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));
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> destK =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector256<float> maxSourceValue = Vector256.Create(255F);
Vector256<float> maxSampleValue = Vector256.Create(this.MaximumValue);
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) / maxSourceValue;
Vector256<float> g = Unsafe.Add(ref srcG, i) / maxSourceValue;
Vector256<float> b = Unsafe.Add(ref srcB, i) / maxSourceValue;
Vector256<float> ktmp = Vector256<float>.One - Vector256.Max(r, Vector256.Min(g, b));
Vector256<float> kMask = ~Vector256.Equals(ktmp, Vector256<float>.One);
Vector256<float> divisor = Vector256<float>.One / (Vector256<float>.One - ktmp);
r = (r * divisor) & kMask;
g = (g * divisor) & kMask;
b = (b * divisor) & kMask;
// 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 = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b));
Vector256<float> cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b));
Vector256<float> cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b));
Unsafe.Add(ref destY, i) = y * maxSampleValue;
Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue);
Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue);
Unsafe.Add(ref destK, i) = ktmp * maxSampleValue;
}
}
}
}

142
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs

@ -1,142 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffYccKVector512 : JpegColorConverterVector512
{
public TiffYccKVector512(int precision)
: base(JpegColorSpace.TiffYccK, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <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));
ref Vector512<float> c3Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector512<float> scale = Vector512.Create(1F / this.MaximumValue);
Vector512<float> chromaOffset = Vector512.Create(this.HalfValue) * scale;
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++)
{
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);
ref Vector512<float> c3 = ref Unsafe.Add(ref c3Base, i);
Vector512<float> y = c0 * scale;
Vector512<float> cb = (c1 * scale) - chromaOffset;
Vector512<float> cr = (c2 * scale) - chromaOffset;
Vector512<float> scaledK = Vector512<float>.One - (c3 * scale);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK;
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK;
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgbVectorized(in values, this.MaximumValue, this.HalfValue, rLane, gLane, bLane);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> TiffYccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> TiffYccKScalar.ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane);
internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, float halfValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
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));
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> destK =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector512<float> maxSourceValue = Vector512.Create(255F);
Vector512<float> maxSampleValue = Vector512.Create(maxValue);
Vector512<float> chromaOffset = Vector512.Create(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) / maxSourceValue;
Vector512<float> g = Unsafe.Add(ref srcG, i) / maxSourceValue;
Vector512<float> b = Unsafe.Add(ref srcB, i) / maxSourceValue;
Vector512<float> ktmp = Vector512<float>.One - Vector512.Max(r, Vector512.Min(g, b));
Vector512<float> kMask = ~Vector512.Equals(ktmp, Vector512<float>.One);
Vector512<float> divisor = Vector512<float>.One / (Vector512<float>.One - ktmp);
r = (r * divisor) & kMask;
g = (g * divisor) & kMask;
b = (b * divisor) & kMask;
// 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 = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b));
Vector512<float> cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b));
Vector512<float> cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b));
Unsafe.Add(ref destY, i) = y * maxSampleValue;
Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue);
Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue);
Unsafe.Add(ref destK, i) = ktmp * maxSampleValue;
}
}
}
}

81
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs

@ -1,8 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@ -15,6 +20,26 @@ internal abstract partial class JpegColorConverterBase
/// </summary>
internal readonly struct YCbCrOperator : IJpegColorConverterOperator
{
/// <summary>
/// The BT.601 red contribution from centered Cr.
/// </summary>
public const float RCrMult = 1.402F;
/// <summary>
/// The BT.601 green contribution from centered Cb.
/// </summary>
public const float GCbMult = (float)(0.114 * 1.772 / 0.587);
/// <summary>
/// The BT.601 green contribution from centered Cr.
/// </summary>
public const float GCrMult = (float)(0.299 * 1.402 / 0.587);
/// <summary>
/// The BT.601 blue contribution from centered Cb.
/// </summary>
public const float BCbMult = 1.772F;
/// <inheritdoc/>
public static JpegColorSpace ColorSpace => JpegColorSpace.YCbCr;
@ -40,9 +65,9 @@ internal abstract partial class JpegColorConverterBase
// the BT.601 matrix, then integer-domain RGB is rounded away from zero and normalized
// to [nominally] 0..1. Values intentionally remain unclamped because quantizing RGB into the
// destination pixel format owns saturation; retaining overshoot avoids discarding color information.
c0 = MathF.Round(y + (YCbCrScalar.RCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c1 = MathF.Round(y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c2 = MathF.Round(y + (YCbCrScalar.BCbMult * cb), MidpointRounding.AwayFromZero) * scale;
c0 = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c1 = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c2 = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale;
}
/// <inheritdoc/>
@ -63,12 +88,12 @@ internal abstract partial class JpegColorConverterBase
// Lanes are four independent Y/Cb/Cr samples. MultiplyAddEstimate maps to FMA where available:
// R uses Cr, B uses Cb, and G subtracts both chroma contributions. Rounding occurs in the sample
// domain before the common normalization scale so all precisions use integer JPEG sample semantics.
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y);
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(RCrMult), y);
Vector128<float> g = Vector128_.MultiplyAddEstimate(
cr,
Vector128.Create(-YCbCrScalar.GCrMult),
Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y));
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y);
Vector128.Create(-GCrMult),
Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-GCbMult), y));
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(BCbMult), y);
c0 = Vector128_.RoundToNearestInteger(r) * scale;
c1 = Vector128_.RoundToNearestInteger(g) * scale;
@ -93,12 +118,12 @@ internal abstract partial class JpegColorConverterBase
// These eight lanes have the same layout and BT.601 arithmetic as the Vector128 overload.
// Keeping an explicit overload allows the JIT to emit native YMM operations without a width
// switch or decomposing the vector into smaller values.
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y);
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(RCrMult), y);
Vector256<float> g = Vector256_.MultiplyAddEstimate(
cr,
Vector256.Create(-YCbCrScalar.GCrMult),
Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y));
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y);
Vector256.Create(-GCrMult),
Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-GCbMult), y));
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(BCbMult), y);
c0 = Vector256_.RoundToNearestInteger(r) * scale;
c1 = Vector256_.RoundToNearestInteger(g) * scale;
@ -123,12 +148,12 @@ internal abstract partial class JpegColorConverterBase
// Sixteen independent samples occupy the ZMM lanes. The explicit constants are broadcasts;
// assembly inspection verifies the JIT hoists them from the loop and retains fused operations.
// The formula and rounding order remain identical to the narrower overloads.
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y);
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(RCrMult), y);
Vector512<float> g = Vector512_.MultiplyAddEstimate(
cr,
Vector512.Create(-YCbCrScalar.GCrMult),
Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y));
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y);
Vector512.Create(-GCrMult),
Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-GCbMult), y));
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(BCbMult), y);
c0 = Vector512_.RoundToNearestInteger(r) * scale;
c1 = Vector512_.RoundToNearestInteger(g) * scale;
@ -258,6 +283,30 @@ internal abstract partial class JpegColorConverterBase
IccProfile profile,
in ComponentValues values,
float maximumValue)
=> YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue);
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
// ICC profiles rarely expose YCbCr transforms, so BT.601 first produces RGB in the profile's source space.
PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maximumValue);
ColorProfileConverter converter = new();
Span<YCbCr> source = MemoryMarshal.Cast<float, YCbCr>(packed);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed);
converter.Convert<YCbCr, Rgb>(source, destination);
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
converter = new ColorProfileConverter(options);
converter.Convert<Rgb, Rgb>(destination, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

121
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs

@ -1,121 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class YCbCrScalar : JpegColorConverterScalar
{
// derived from ITU-T Rec. T.871
internal const float RCrMult = 1.402f;
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587);
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587);
internal const float BCbMult = 1.772f;
public YCbCrScalar(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(values, this.HalfValue, rLane, gLane, bLane);
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
float scale = 1 / maxValue;
for (int i = 0; i < c0.Length; i++)
{
float y = c0[i];
float cb = c1[i] - halfValue;
float cr = c2[i] - halfValue;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale;
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
// Although YCbCr is a defined ICC color space, in practice ICC profiles
// do not implement transforms from it.
// Therefore, we first convert JPEG YCbCr to RGB manually, then perform
// color-managed conversion to the target profile.
//
// The YCbCr => RGB conversion is based on BT.601 and is independent of any embedded ICC profile.
// Since the same RGB working space is used during conversion to and from XYZ,
// colorimetric accuracy is preserved.
ColorProfileConverter converter = new();
PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maxValue);
Span<YCbCr> source = MemoryMarshal.Cast<float, YCbCr>(packed);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed);
converter.Convert<YCbCr, Rgb>(source, destination);
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
converter = new ColorProfileConverter(options);
converter.Convert<Rgb, Rgb>(destination, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
public static void ConvertFromRgb(in ComponentValues values, float halfValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
Span<float> y = values.Component0;
Span<float> cb = values.Component1;
Span<float> cr = values.Component2;
for (int i = 0; i < y.Length; i++)
{
float r = rLane[i];
float g = gLane[i];
float b = bLane[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)
y[i] = (0.299f * r) + (0.587f * g) + (0.114f * b);
cb[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b);
cr[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b);
}
}
}
}

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

@ -1,121 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
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 = Vector128_.MultiplyAddEstimate(cr, rCrMult, y);
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y));
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y);
r = Vector128_.RoundToNearestInteger(r) * scale;
g = Vector128_.RoundToNearestInteger(g) * scale;
b = Vector128_.RoundToNearestInteger(b) * scale;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <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 = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b));
Vector128<float> cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b));
Vector128<float> cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b));
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
}
}

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

@ -1,121 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
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 = Vector256_.MultiplyAddEstimate(cr, rCrMult, y);
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y));
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y);
r = Vector256_.RoundToNearestInteger(r) * scale;
g = Vector256_.RoundToNearestInteger(g) * scale;
b = Vector256_.RoundToNearestInteger(b) * scale;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <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 = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b));
Vector256<float> cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b));
Vector256<float> cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b));
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
}
}

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

@ -1,128 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
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/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <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 = Vector512_.MultiplyAddEstimate(cr, rCrMult, y);
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y));
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y);
r = Vector512_.RoundToNearestInteger(r) * scale;
g = Vector512_.RoundToNearestInteger(g) * scale;
b = Vector512_.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 = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b));
Vector512<float> cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b));
Vector512<float> cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b));
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);
}
}

56
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs

@ -1,8 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@ -32,9 +37,9 @@ internal abstract partial class JpegColorConverterBase
// YccK first reconstructs inverted RGB in the integer sample domain. Rounding must occur before
// subtracting from max and applying K because changing that order changes encoded JPEG semantics.
c0 = (maximumValue - MathF.Round(y + (YCbCrScalar.RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK;
c1 = (maximumValue - MathF.Round(y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK;
c2 = (maximumValue - MathF.Round(y + (YCbCrScalar.BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK;
c0 = (maximumValue - MathF.Round(y + (YCbCrOperator.RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK;
c1 = (maximumValue - MathF.Round(y - (YCbCrOperator.GCbMult * cb) - (YCbCrOperator.GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK;
c2 = (maximumValue - MathF.Round(y + (YCbCrOperator.BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK;
}
/// <inheritdoc/>
@ -47,9 +52,9 @@ internal abstract partial class JpegColorConverterBase
Vector128<float> scaledK = c3 * scale * scale;
// Four lanes reconstruct YCbCr concurrently; each rounded result is inverted and modulated by its K lane.
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y);
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrScalar.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y));
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y);
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y);
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y));
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y);
c0 = (maximumValue - Vector128_.RoundToNearestInteger(r)) * scaledK;
c1 = (maximumValue - Vector128_.RoundToNearestInteger(g)) * scaledK;
c2 = (maximumValue - Vector128_.RoundToNearestInteger(b)) * scaledK;
@ -65,9 +70,9 @@ internal abstract partial class JpegColorConverterBase
Vector256<float> scaledK = c3 * scale * scale;
// Eight lanes retain planar alignment from Y/Cb/Cr/K through normalized RGB.
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y);
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrScalar.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y));
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y);
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y);
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y));
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y);
c0 = (maximumValue - Vector256_.RoundToNearestInteger(r)) * scaledK;
c1 = (maximumValue - Vector256_.RoundToNearestInteger(g)) * scaledK;
c2 = (maximumValue - Vector256_.RoundToNearestInteger(b)) * scaledK;
@ -83,9 +88,9 @@ internal abstract partial class JpegColorConverterBase
Vector512<float> scaledK = c3 * scale * scale;
// Sixteen lanes use the same matrix, rounding, inversion, and K modulation order as scalar code.
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y);
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrScalar.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y));
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y);
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y);
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y));
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y);
c0 = (maximumValue - Vector512_.RoundToNearestInteger(r)) * scaledK;
c1 = (maximumValue - Vector512_.RoundToNearestInteger(g)) * scaledK;
c2 = (maximumValue - Vector512_.RoundToNearestInteger(b)) * scaledK;
@ -130,6 +135,31 @@ internal abstract partial class JpegColorConverterBase
/// <inheritdoc/>
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue)
=> YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue);
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
// Adobe-style JPEG YccK is inverted; normalize it before applying the format-defined YccK-to-CMYK transform.
PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue);
ColorProfileConverter converter = new();
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
converter.Convert<YccK, Cmyk>(MemoryMarshal.Cast<Cmyk, YccK>(source), source);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
converter = new ColorProfileConverter(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

125
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs

@ -1,125 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class YccKScalar : JpegColorConverterScalar
{
// Derived from ITU-T Rec. T.871
internal const float RCrMult = 1.402f;
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587);
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587);
internal const float BCbMult = 1.772f;
public YccKScalar(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
float scale = 1 / (maxValue * maxValue);
for (int i = 0; i < values.Component0.Length; i++)
{
float y = c0[i];
float cb = c1[i] - halfValue;
float cr = c2[i] - halfValue;
float scaledK = c3[i] * scale;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
c0[i] = (maxValue - MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK;
c1[i] = (maxValue - MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK;
c2[i] = (maxValue - MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK;
}
}
public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
// rgb -> cmyk
CmykScalar.ConvertFromRgb(in values, maxValue, rLane, gLane, bLane);
// cmyk -> ycck
Span<float> c = values.Component0;
Span<float> m = values.Component1;
Span<float> y = values.Component2;
for (int i = 0; i < y.Length; i++)
{
float r = maxValue - c[i];
float g = maxValue - m[i];
float b = maxValue - y[i];
// k value is passed untouched from rgb -> cmyk conversion
c[i] = (0.299f * r) + (0.587f * g) + (0.114f * b);
m[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b);
y[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b);
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue);
ColorProfileConverter converter = new();
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
// YccK is not a defined ICC color space — it's a JPEG-specific encoding used in Adobe-style CMYK JPEGs.
// ICC profiles expect colorimetric CMYK values, so we must first convert YccK to CMYK using a hardcoded inverse transform.
// This transform assumes Rec.601 YCbCr coefficients and an inverted K channel.
//
// The YccK => Cmyk conversion is independent of any embedded ICC profile.
// Since the same RGB working space is used during conversion to and from XYZ,
// colorimetric accuracy is preserved.
converter.Convert<YccK, Cmyk>(MemoryMarshal.Cast<Cmyk, YccK>(source), source);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
converter = new ColorProfileConverter(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

135
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs

@ -1,135 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class YccKVector128 : JpegColorConverterVector128
{
public YccKVector128(int precision)
: base(JpegColorSpace.Ycck, 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));
ref Vector128<float> kBase =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
Vector128<float> chromaOffset = Vector128.Create(-this.HalfValue);
Vector128<float> scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue));
Vector128<float> max = Vector128.Create(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;
// k = kVals[i] / 256F;
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;
Vector128<float> scaledK = Unsafe.Add(ref kBase, i) * scale;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, rCrMult, y);
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y));
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y);
r = max - Vector128_.RoundToNearestInteger(r);
g = max - Vector128_.RoundToNearestInteger(g);
b = max - Vector128_.RoundToNearestInteger(b);
r *= scaledK;
g *= scaledK;
b *= scaledK;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
// rgb -> cmyk
CmykVector128.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
// cmyk -> ycck
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 destY;
ref Vector128<float> srcG = ref destCb;
ref Vector128<float> srcB = ref destCr;
// Used for the color conversion
Vector128<float> maxSampleValue = Vector128.Create(this.MaximumValue);
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 = maxSampleValue - Unsafe.Add(ref srcR, i);
Vector128<float> g = maxSampleValue - Unsafe.Add(ref srcG, i);
Vector128<float> b = maxSampleValue - 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 = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b));
Vector128<float> cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b));
Vector128<float> cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b));
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
}
}

135
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs

@ -1,135 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class YccKVector256 : JpegColorConverterVector256
{
public YccKVector256(int precision)
: base(JpegColorSpace.Ycck, 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));
ref Vector256<float> kBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
Vector256<float> chromaOffset = Vector256.Create(-this.HalfValue);
Vector256<float> scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
Vector256<float> max = Vector256.Create(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;
// k = kVals[i] / 256F;
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;
Vector256<float> scaledK = Unsafe.Add(ref kBase, i) * scale;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, rCrMult, y);
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y));
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y);
r = max - Vector256_.RoundToNearestInteger(r);
g = max - Vector256_.RoundToNearestInteger(g);
b = max - Vector256_.RoundToNearestInteger(b);
r *= scaledK;
g *= scaledK;
b *= scaledK;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
// rgb -> cmyk
CmykVector256.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
// cmyk -> ycck
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 destY;
ref Vector256<float> srcG = ref destCb;
ref Vector256<float> srcB = ref destCr;
// Used for the color conversion
Vector256<float> maxSampleValue = Vector256.Create(this.MaximumValue);
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 = maxSampleValue - Unsafe.Add(ref srcR, i);
Vector256<float> g = maxSampleValue - Unsafe.Add(ref srcG, i);
Vector256<float> b = maxSampleValue - 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 = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b));
Vector256<float> cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b));
Vector256<float> cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b));
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
}
}

143
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs

@ -1,143 +0,0 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class YccKVector512 : JpegColorConverterVector512
{
public YccKVector512(int precision)
: base(JpegColorSpace.Ycck, 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));
ref Vector512<float> kBase =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
Vector512<float> chromaOffset = Vector512.Create(-this.HalfValue);
Vector512<float> scale = Vector512.Create(1 / (this.MaximumValue * this.MaximumValue));
Vector512<float> max = Vector512.Create(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);
// Walking 8 elements at one step:
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
// k = kVals[i] / 256F;
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;
Vector512<float> scaledK = Unsafe.Add(ref kBase, i) * scale;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, rCrMult, y);
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y));
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y);
r = max - Vector512_.RoundToNearestInteger(r);
g = max - Vector512_.RoundToNearestInteger(g);
b = max - Vector512_.RoundToNearestInteger(b);
r *= scaledK;
g *= scaledK;
b *= scaledK;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> YccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
// rgb -> cmyk
CmykVector512.ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane);
// cmyk -> ycck
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 destY;
ref Vector512<float> srcG = ref destCb;
ref Vector512<float> srcB = ref destCr;
// Used for the color conversion
Vector512<float> maxSampleValue = Vector512.Create(this.MaximumValue);
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 = maxSampleValue - Unsafe.Add(ref srcR, i);
Vector512<float> g = maxSampleValue - Unsafe.Add(ref srcG, i);
Vector512<float> b = maxSampleValue - 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 = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b));
Vector512<float> cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b));
Vector512<float> cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b));
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane);
}
}

23
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
/// based on scalar instructions.
/// </summary>
internal abstract class JpegColorConverterScalar : JpegColorConverterBase
{
protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
public sealed override bool IsAvailable => true;
public sealed override int ElementsPerBatch => 1;
}
}

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

@ -1,129 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
/// based on <see cref="Vector"/> API.
/// </summary>
/// <remarks>
/// Converters of this family can work with data of any size.
/// Even though real life data is guaranteed to be of size
/// divisible by 8 newer SIMD instructions like AVX512 won't work with
/// such data out of the box. These converters have fallback code
/// for 'remainder' data.
/// </remarks>
internal abstract class JpegColorConverterVector : JpegColorConverterBase
{
protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
/// <summary>
/// Gets a value indicating whether this converter is supported on current hardware.
/// </summary>
public static bool IsSupported => Vector.IsHardwareAccelerated && Vector<float>.Count % 4 == 0;
/// <inheritdoc/>
public sealed override bool IsAvailable => IsSupported;
public override int ElementsPerBatch => Vector<float>.Count;
/// <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)Vector<float>.Count);
int simdCount = length - remainder;
if (simdCount > 0)
{
this.ConvertToRgbInPlaceVectorized(values.Slice(0, simdCount));
}
// Jpeg images width is always divisible by 8 without a remainder
// so it's safe to say SSE/AVX1/AVX2 implementations would never have
// 'remainder' pixels
// But some exotic simd implementations e.g. AVX-512 can have
// remainder pixels
if (remainder > 0)
{
this.ConvertToRgbInPlaceScalarRemainder(values.Slice(simdCount, remainder));
}
}
/// <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)Vector<float>.Count);
int simdCount = length - remainder;
if (simdCount > 0)
{
this.ConvertFromRgbVectorized(
values.Slice(0, simdCount),
rLane[..simdCount],
gLane[..simdCount],
bLane[..simdCount]);
}
// Jpeg images width is always divisible by 8 without a remainder
// so it's safe to say SSE/AVX1/AVX2 implementations would never have
// 'remainder' pixels
// But some exotic simd implementations e.g. AVX-512 can have
// remainder pixels
if (remainder > 0)
{
this.ConvertFromRgbScalarRemainder(
values.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 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);
}
}

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

@ -1,34 +0,0 @@
// 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;
public sealed override bool IsAvailable => IsSupported;
public sealed override int ElementsPerBatch => Vector128<float>.Count;
}
}

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

@ -1,34 +0,0 @@
// 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;
public sealed override bool IsAvailable => IsSupported;
public sealed override int ElementsPerBatch => Vector256<float>.Count;
}
}

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

@ -1,111 +0,0 @@
// 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;
/// <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);
}
}

37
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs

@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg;
[Config(typeof(Config.Short))]
public class CmykColorConversion : ColorConversionBenchmark
{
private readonly JpegColorConverterBase converter =
JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8);
/// <summary>
/// Initializes a new instance of the <see cref="CmykColorConversion"/> class.
/// </summary>
public CmykColorConversion()
: base(4)
{
}
[Benchmark(Baseline = true)]
public void Scalar()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.CmykScalar(8).ConvertToRgbInPlace(values);
}
[Benchmark]
public void SimdVector128()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.CmykVector128(8).ConvertToRgbInPlace(values);
}
[Benchmark]
public void SimdVector256()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.CmykVector256(8).ConvertToRgbInPlace(values);
}
/// <summary>
/// Converts one CMYK component row through the adaptive operator traversal.
/// </summary>
[Benchmark]
public void SimdVector512()
public void ConvertToRgb()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.CmykVector512(8).ConvertToRgbInPlace(values);
this.converter.ConvertToRgbInPlace(values);
}
}

37
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs

@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg;
[Config(typeof(Config.Short))]
public class GrayScaleColorConversion : ColorConversionBenchmark
{
private readonly JpegColorConverterBase converter =
JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8);
/// <summary>
/// Initializes a new instance of the <see cref="GrayScaleColorConversion"/> class.
/// </summary>
public GrayScaleColorConversion()
: base(1)
{
}
[Benchmark(Baseline = true)]
public void Scalar()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.GrayScaleScalar(8).ConvertToRgbInPlace(values);
}
[Benchmark]
public void SimdVector128()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.GrayScaleVector128(8).ConvertToRgbInPlace(values);
}
[Benchmark]
public void SimdVector256()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.GrayScaleVector256(8).ConvertToRgbInPlace(values);
}
/// <summary>
/// Converts one grayscale component row through the adaptive operator traversal.
/// </summary>
[Benchmark]
public void SimdVector512()
public void ConvertToRgb()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.GrayScaleVector512(8).ConvertToRgbInPlace(values);
this.converter.ConvertToRgbInPlace(values);
}
}

239
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs

@ -1,239 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg;
/// <summary>
/// Compares each shared operator converter with the Vector512 converter it replaces.
/// </summary>
[Config(typeof(Config.Standard))]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class JpegColorConverterOperatorComparison
{
private JpegColorConverterBase legacy;
private JpegColorConverterBase operatorConverter;
private float[] legacyC0;
private float[] legacyC1;
private float[] legacyC2;
private float[] legacyC3;
private float[] operatorC0;
private float[] operatorC1;
private float[] operatorC2;
private float[] operatorC3;
private float[] r;
private float[] g;
private float[] b;
private int componentCount;
/// <summary>
/// Gets or sets the color model measured by the current benchmark case.
/// </summary>
[Params(
JpegColorModel.Grayscale,
JpegColorModel.Rgb,
JpegColorModel.Cmyk,
JpegColorModel.YCbCr,
JpegColorModel.YccK,
JpegColorModel.TiffCmyk,
JpegColorModel.TiffYccK)]
public JpegColorModel ColorModel { get; set; }
/// <summary>
/// Gets or sets the number of pixels converted by each invocation.
/// </summary>
[Params(128, 1024)]
public int Count { get; set; }
/// <summary>
/// Creates equivalent legacy and operator converters and their independent component buffers.
/// </summary>
[GlobalSetup]
public void Setup()
{
(JpegColorConverterBase Legacy, JpegColorConverterBase Operator, int ComponentCount) converters =
this.ColorModel switch
{
JpegColorModel.Grayscale => (
new JpegColorConverterBase.GrayScaleVector512(8),
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.GrayScaleOperator>(8),
1),
JpegColorModel.Rgb => (
new JpegColorConverterBase.RgbVector512(8),
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.RgbOperator>(8),
3),
JpegColorModel.Cmyk => (
new JpegColorConverterBase.CmykVector512(8),
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.CmykOperator>(8),
4),
JpegColorModel.YCbCr => (
new JpegColorConverterBase.YCbCrVector512(8),
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YCbCrOperator>(8),
3),
JpegColorModel.YccK => (
new JpegColorConverterBase.YccKVector512(8),
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YccKOperator>(8),
4),
JpegColorModel.TiffCmyk => (
new JpegColorConverterBase.TiffCmykVector512(8),
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.TiffCmykOperator>(8),
4),
JpegColorModel.TiffYccK => (
new JpegColorConverterBase.TiffYccKVector512(8),
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.TiffYccKOperator>(8),
4),
_ => throw new InvalidOperationException(),
};
(this.legacy, this.operatorConverter, this.componentCount) = converters;
Random random = new(42);
this.legacyC0 = CreateRandomValues(this.Count, random);
this.legacyC1 = CreateRandomValues(this.Count, random);
this.legacyC2 = CreateRandomValues(this.Count, random);
this.legacyC3 = CreateRandomValues(this.Count, random);
this.operatorC0 = this.legacyC0.ToArray();
this.operatorC1 = this.legacyC1.ToArray();
this.operatorC2 = this.legacyC2.ToArray();
this.operatorC3 = this.legacyC3.ToArray();
this.r = CreateRandomValues(this.Count, random);
this.g = CreateRandomValues(this.Count, random);
this.b = CreateRandomValues(this.Count, random);
}
/// <summary>
/// Converts JPEG components to RGB using the replaced Vector512 implementation.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("ToRgb")]
public void LegacyToRgb()
{
JpegColorConverterBase.ComponentValues values = this.CreateLegacyValues();
this.legacy.ConvertToRgbInPlace(values);
}
/// <summary>
/// Converts JPEG components to RGB using the shared operator traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("ToRgb")]
public void OperatorToRgb()
{
JpegColorConverterBase.ComponentValues values = this.CreateOperatorValues();
this.operatorConverter.ConvertToRgbInPlace(values);
}
/// <summary>
/// Converts RGB to JPEG components using the replaced Vector512 implementation.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("FromRgb")]
public void LegacyFromRgb()
{
JpegColorConverterBase.ComponentValues values = this.CreateLegacyValues();
this.legacy.ConvertFromRgb(values, this.r, this.g, this.b);
}
/// <summary>
/// Converts RGB to JPEG components using the shared operator traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("FromRgb")]
public void OperatorFromRgb()
{
JpegColorConverterBase.ComponentValues values = this.CreateOperatorValues();
this.operatorConverter.ConvertFromRgb(values, this.r, this.g, this.b);
}
/// <summary>
/// Creates a component view over the buffers owned by the legacy converter.
/// </summary>
/// <returns>The component view for the configured color model.</returns>
private JpegColorConverterBase.ComponentValues CreateLegacyValues()
=> new(
this.componentCount,
this.legacyC0,
this.componentCount > 1 ? this.legacyC1 : this.legacyC0,
this.componentCount > 2 ? this.legacyC2 : this.legacyC0,
this.componentCount > 3 ? this.legacyC3 : []);
/// <summary>
/// Creates a component view over the buffers owned by the operator converter.
/// </summary>
/// <returns>The component view for the configured color model.</returns>
private JpegColorConverterBase.ComponentValues CreateOperatorValues()
=> new(
this.componentCount,
this.operatorC0,
this.componentCount > 1 ? this.operatorC1 : this.operatorC0,
this.componentCount > 2 ? this.operatorC2 : this.operatorC0,
this.componentCount > 3 ? this.operatorC3 : []);
/// <summary>
/// Creates deterministic sample-domain values for one component plane.
/// </summary>
/// <param name="length">The number of samples to create.</param>
/// <param name="random">The deterministic random source shared by setup.</param>
/// <returns>The populated component plane.</returns>
private static float[] CreateRandomValues(int length, Random random)
{
float[] values = new float[length];
for (int i = 0; i < values.Length; i++)
{
values[i] = (float)random.NextDouble() * 255F;
}
return values;
}
/// <summary>
/// Identifies the JPEG color model used by a benchmark case.
/// </summary>
public enum JpegColorModel
{
/// <summary>
/// One luminance component.
/// </summary>
Grayscale,
/// <summary>
/// Three direct RGB components.
/// </summary>
Rgb,
/// <summary>
/// Four inverted Adobe CMYK components.
/// </summary>
Cmyk,
/// <summary>
/// Three JPEG YCbCr components.
/// </summary>
YCbCr,
/// <summary>
/// Four inverted Adobe YCCK components.
/// </summary>
YccK,
/// <summary>
/// Four non-inverted TIFF CMYK components.
/// </summary>
TiffCmyk,
/// <summary>
/// Four non-inverted TIFF YCCK components.
/// </summary>
TiffYccK,
}
}

325
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs

@ -1,325 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg;
/// <summary>
/// Exposes every closed JPEG operator traversal beside the Vector512 implementation it replaces.
/// </summary>
/// <remarks>
/// A 63-pixel buffer leaves 256-bit, 128-bit, and scalar remainders after the 512-bit loop, making
/// every operator overload visible in the generated traversal assembly on AVX-512 hardware.
/// </remarks>
[Config(typeof(Config.Analysis))]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class JpegColorConverterTraversalAssembly
{
private const int Count = 63;
private readonly JpegColorConverterBase.GrayScaleVector512 grayscaleLegacy = new(8);
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.GrayScaleOperator> grayscaleOperator = new(8);
private readonly JpegColorConverterBase.RgbVector512 rgbLegacy = new(8);
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.RgbOperator> rgbOperator = new(8);
private readonly JpegColorConverterBase.CmykVector512 cmykLegacy = new(8);
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.CmykOperator> cmykOperator = new(8);
private readonly JpegColorConverterBase.YCbCrVector512 yCbCrLegacy = new(8);
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YCbCrOperator> yCbCrOperator = new(8);
private readonly JpegColorConverterBase.YccKVector512 yccKLegacy = new(8);
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YccKOperator> yccKOperator = new(8);
private readonly JpegColorConverterBase.TiffCmykVector512 tiffCmykLegacy = new(8);
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.TiffCmykOperator> tiffCmykOperator = new(8);
private readonly JpegColorConverterBase.TiffYccKVector512 tiffYccKLegacy = new(8);
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.TiffYccKOperator> tiffYccKOperator = new(8);
private readonly float[] legacyC0 = new float[Count];
private readonly float[] legacyC1 = new float[Count];
private readonly float[] legacyC2 = new float[Count];
private readonly float[] legacyC3 = new float[Count];
private readonly float[] operatorC0 = new float[Count];
private readonly float[] operatorC1 = new float[Count];
private readonly float[] operatorC2 = new float[Count];
private readonly float[] operatorC3 = new float[Count];
private readonly float[] r = new float[Count];
private readonly float[] g = new float[Count];
private readonly float[] b = new float[Count];
/// <summary>
/// Populates the component and RGB planes with deterministic sample-domain values.
/// </summary>
[GlobalSetup]
public void Setup()
{
Random random = new(42);
for (int i = 0; i < Count; i++)
{
// Independent non-constant lanes prevent the JIT from folding arithmetic or mask decisions.
this.legacyC0[i] = this.operatorC0[i] = (float)random.NextDouble() * 255F;
this.legacyC1[i] = this.operatorC1[i] = (float)random.NextDouble() * 255F;
this.legacyC2[i] = this.operatorC2[i] = (float)random.NextDouble() * 255F;
this.legacyC3[i] = this.operatorC3[i] = (float)random.NextDouble() * 255F;
this.r[i] = (float)random.NextDouble() * 255F;
this.g[i] = (float)random.NextDouble() * 255F;
this.b[i] = (float)random.NextDouble() * 255F;
}
}
/// <summary>
/// Runs the replaced grayscale component-to-RGB traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("Grayscale.ToRgb")]
public void GrayscaleLegacyToRgb()
=> this.grayscaleLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(1));
/// <summary>
/// Runs the shared grayscale component-to-RGB traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("Grayscale.ToRgb")]
public void GrayscaleOperatorToRgb()
=> this.grayscaleOperator.ConvertToRgbInPlace(this.CreateOperatorValues(1));
/// <summary>
/// Runs the replaced grayscale RGB-to-component traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("Grayscale.FromRgb")]
public void GrayscaleLegacyFromRgb()
=> this.grayscaleLegacy.ConvertFromRgb(this.CreateLegacyValues(1), this.r, this.g, this.b);
/// <summary>
/// Runs the shared grayscale RGB-to-component traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("Grayscale.FromRgb")]
public void GrayscaleOperatorFromRgb()
=> this.grayscaleOperator.ConvertFromRgb(this.CreateOperatorValues(1), this.r, this.g, this.b);
/// <summary>
/// Runs the replaced RGB component-to-RGB traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("Rgb.ToRgb")]
public void RgbLegacyToRgb()
=> this.rgbLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(3));
/// <summary>
/// Runs the shared RGB component-to-RGB traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("Rgb.ToRgb")]
public void RgbOperatorToRgb()
=> this.rgbOperator.ConvertToRgbInPlace(this.CreateOperatorValues(3));
/// <summary>
/// Runs the replaced RGB RGB-to-component traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("Rgb.FromRgb")]
public void RgbLegacyFromRgb()
=> this.rgbLegacy.ConvertFromRgb(this.CreateLegacyValues(3), this.r, this.g, this.b);
/// <summary>
/// Runs the shared RGB RGB-to-component traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("Rgb.FromRgb")]
public void RgbOperatorFromRgb()
=> this.rgbOperator.ConvertFromRgb(this.CreateOperatorValues(3), this.r, this.g, this.b);
/// <summary>
/// Runs the replaced CMYK component-to-RGB traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("Cmyk.ToRgb")]
public void CmykLegacyToRgb()
=> this.cmykLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4));
/// <summary>
/// Runs the shared CMYK component-to-RGB traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("Cmyk.ToRgb")]
public void CmykOperatorToRgb()
=> this.cmykOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4));
/// <summary>
/// Runs the replaced CMYK RGB-to-component traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("Cmyk.FromRgb")]
public void CmykLegacyFromRgb()
=> this.cmykLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b);
/// <summary>
/// Runs the shared CMYK RGB-to-component traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("Cmyk.FromRgb")]
public void CmykOperatorFromRgb()
=> this.cmykOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b);
/// <summary>
/// Runs the replaced YCbCr component-to-RGB traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("YCbCr.ToRgb")]
public void YCbCrLegacyToRgb()
=> this.yCbCrLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(3));
/// <summary>
/// Runs the shared YCbCr component-to-RGB traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("YCbCr.ToRgb")]
public void YCbCrOperatorToRgb()
=> this.yCbCrOperator.ConvertToRgbInPlace(this.CreateOperatorValues(3));
/// <summary>
/// Runs the replaced YCbCr RGB-to-component traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("YCbCr.FromRgb")]
public void YCbCrLegacyFromRgb()
=> this.yCbCrLegacy.ConvertFromRgb(this.CreateLegacyValues(3), this.r, this.g, this.b);
/// <summary>
/// Runs the shared YCbCr RGB-to-component traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("YCbCr.FromRgb")]
public void YCbCrOperatorFromRgb()
=> this.yCbCrOperator.ConvertFromRgb(this.CreateOperatorValues(3), this.r, this.g, this.b);
/// <summary>
/// Runs the replaced YCCK component-to-RGB traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("YccK.ToRgb")]
public void YccKLegacyToRgb()
=> this.yccKLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4));
/// <summary>
/// Runs the shared YCCK component-to-RGB traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("YccK.ToRgb")]
public void YccKOperatorToRgb()
=> this.yccKOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4));
/// <summary>
/// Runs the replaced YCCK RGB-to-component traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("YccK.FromRgb")]
public void YccKLegacyFromRgb()
=> this.yccKLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b);
/// <summary>
/// Runs the shared YCCK RGB-to-component traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("YccK.FromRgb")]
public void YccKOperatorFromRgb()
=> this.yccKOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b);
/// <summary>
/// Runs the replaced TIFF CMYK component-to-RGB traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("TiffCmyk.ToRgb")]
public void TiffCmykLegacyToRgb()
=> this.tiffCmykLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4));
/// <summary>
/// Runs the shared TIFF CMYK component-to-RGB traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("TiffCmyk.ToRgb")]
public void TiffCmykOperatorToRgb()
=> this.tiffCmykOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4));
/// <summary>
/// Runs the replaced TIFF CMYK RGB-to-component traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("TiffCmyk.FromRgb")]
public void TiffCmykLegacyFromRgb()
=> this.tiffCmykLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b);
/// <summary>
/// Runs the shared TIFF CMYK RGB-to-component traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("TiffCmyk.FromRgb")]
public void TiffCmykOperatorFromRgb()
=> this.tiffCmykOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b);
/// <summary>
/// Runs the replaced TIFF YCCK component-to-RGB traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("TiffYccK.ToRgb")]
public void TiffYccKLegacyToRgb()
=> this.tiffYccKLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4));
/// <summary>
/// Runs the shared TIFF YCCK component-to-RGB traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("TiffYccK.ToRgb")]
public void TiffYccKOperatorToRgb()
=> this.tiffYccKOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4));
/// <summary>
/// Runs the replaced TIFF YCCK RGB-to-component traversal.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("TiffYccK.FromRgb")]
public void TiffYccKLegacyFromRgb()
=> this.tiffYccKLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b);
/// <summary>
/// Runs the shared TIFF YCCK RGB-to-component traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("TiffYccK.FromRgb")]
public void TiffYccKOperatorFromRgb()
=> this.tiffYccKOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b);
/// <summary>
/// Creates a correctly aliased component view over the legacy planes.
/// </summary>
/// <param name="componentCount">The number of component planes owned by the color model.</param>
/// <returns>The legacy component view.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private JpegColorConverterBase.ComponentValues CreateLegacyValues(int componentCount)
=> new(
componentCount,
this.legacyC0,
componentCount > 1 ? this.legacyC1 : this.legacyC0,
componentCount > 2 ? this.legacyC2 : this.legacyC0,
componentCount > 3 ? this.legacyC3 : []);
/// <summary>
/// Creates a correctly aliased component view over the operator planes.
/// </summary>
/// <param name="componentCount">The number of component planes owned by the color model.</param>
/// <returns>The operator component view.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private JpegColorConverterBase.ComponentValues CreateOperatorValues(int componentCount)
=> new(
componentCount,
this.operatorC0,
componentCount > 1 ? this.operatorC1 : this.operatorC0,
componentCount > 2 ? this.operatorC2 : this.operatorC0,
componentCount > 3 ? this.operatorC3 : []);
}

37
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs

@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg;
[Config(typeof(Config.Short))]
public class RgbColorConversion : ColorConversionBenchmark
{
private readonly JpegColorConverterBase converter =
JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8);
/// <summary>
/// Initializes a new instance of the <see cref="RgbColorConversion"/> class.
/// </summary>
public RgbColorConversion()
: base(3)
{
}
[Benchmark(Baseline = true)]
public void Scalar()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.RgbScalar(8).ConvertToRgbInPlace(values);
}
[Benchmark]
public void SimdVector128()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.RgbVector128(8).ConvertToRgbInPlace(values);
}
[Benchmark]
public void SimdVector256()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.RgbVector256(8).ConvertToRgbInPlace(values);
}
/// <summary>
/// Converts one RGB component row through the adaptive operator traversal.
/// </summary>
[Benchmark]
public void SimdVector512()
public void ConvertToRgb()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.RgbVector512(8).ConvertToRgbInPlace(values);
this.converter.ConvertToRgbInPlace(values);
}
}

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

@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg;
[Config(typeof(Config.Short))]
public class YCbCrColorConversion : ColorConversionBenchmark
{
private readonly JpegColorConverterBase converter =
JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8);
/// <summary>
/// Initializes a new instance of the <see cref="YCbCrColorConversion"/> class.
/// </summary>
public YCbCrColorConversion()
: base(3)
{
}
/// <summary>
/// Converts one YCbCr component row through the adaptive operator traversal.
/// </summary>
[Benchmark]
public void Scalar()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.YCbCrScalar(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()
public void ConvertToRgb()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.YCbCrVector512(8).ConvertToRgbInPlace(values);
this.converter.ConvertToRgbInPlace(values);
}
}

127
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs

@ -1,127 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg;
/// <summary>
/// Compares the shared YCbCr operator traversal with the Vector512 implementation it replaces.
/// </summary>
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class YCbCrOperatorComparison
{
private JpegColorConverterBase.YCbCrVector512 legacy;
private JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YCbCrOperator> operatorConverter;
private float[] legacyC0;
private float[] legacyC1;
private float[] legacyC2;
private float[] operatorC0;
private float[] operatorC1;
private float[] operatorC2;
private float[] r;
private float[] g;
private float[] b;
/// <summary>
/// Gets or sets the number of pixels converted by each invocation.
/// </summary>
[Params(8, 128, 1024)]
public int Count { get; set; }
/// <summary>
/// Creates equivalent converter inputs in independent component buffers.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.legacy = new JpegColorConverterBase.YCbCrVector512(8);
this.operatorConverter =
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YCbCrOperator>(8);
Random random = new(42);
this.legacyC0 = CreateRandomValues(this.Count, random);
this.legacyC1 = CreateRandomValues(this.Count, random);
this.legacyC2 = CreateRandomValues(this.Count, random);
this.operatorC0 = this.legacyC0.ToArray();
this.operatorC1 = this.legacyC1.ToArray();
this.operatorC2 = this.legacyC2.ToArray();
this.r = CreateRandomValues(this.Count, random);
this.g = CreateRandomValues(this.Count, random);
this.b = CreateRandomValues(this.Count, random);
}
/// <summary>
/// Converts YCbCr components to RGB using the Vector512 implementation.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("ToRgb")]
public void LegacyToRgb()
{
JpegColorConverterBase.ComponentValues values =
new(3, this.legacyC0, this.legacyC1, this.legacyC2, []);
this.legacy.ConvertToRgbInPlace(values);
}
/// <summary>
/// Converts YCbCr components to RGB using the shared operator traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("ToRgb")]
public void OperatorToRgb()
{
JpegColorConverterBase.ComponentValues values =
new(3, this.operatorC0, this.operatorC1, this.operatorC2, []);
this.operatorConverter.ConvertToRgbInPlace(values);
}
/// <summary>
/// Converts RGB to YCbCr components using the Vector512 implementation.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("FromRgb")]
public void LegacyFromRgb()
{
JpegColorConverterBase.ComponentValues values =
new(3, this.legacyC0, this.legacyC1, this.legacyC2, []);
this.legacy.ConvertFromRgb(values, this.r, this.g, this.b);
}
/// <summary>
/// Converts RGB to YCbCr components using the shared operator traversal.
/// </summary>
[Benchmark]
[BenchmarkCategory("FromRgb")]
public void OperatorFromRgb()
{
JpegColorConverterBase.ComponentValues values =
new(3, this.operatorC0, this.operatorC1, this.operatorC2, []);
this.operatorConverter.ConvertFromRgb(values, this.r, this.g, this.b);
}
/// <summary>
/// Creates deterministic sample-domain values for one component plane.
/// </summary>
/// <param name="length">The number of samples to create.</param>
/// <param name="random">The deterministic random source shared by setup.</param>
/// <returns>The populated component plane.</returns>
private static float[] CreateRandomValues(int length, Random random)
{
float[] values = new float[length];
for (int i = 0; i < values.Length; i++)
{
values[i] = (float)random.NextDouble() * 255F;
}
return values;
}
}

37
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs

@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg;
[Config(typeof(Config.Short))]
public class YccKColorConverter : ColorConversionBenchmark
{
private readonly JpegColorConverterBase converter =
JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8);
/// <summary>
/// Initializes a new instance of the <see cref="YccKColorConverter"/> class.
/// </summary>
public YccKColorConverter()
: base(4)
{
}
[Benchmark(Baseline = true)]
public void Scalar()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.YccKScalar(8).ConvertToRgbInPlace(values);
}
[Benchmark]
public void SimdVector128()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.YccKVector128(8).ConvertToRgbInPlace(values);
}
[Benchmark]
public void SimdVector256()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.YccKVector256(8).ConvertToRgbInPlace(values);
}
/// <summary>
/// Converts one YccK component row through the adaptive operator traversal.
/// </summary>
[Benchmark]
public void SimdVector512()
public void ConvertToRgb()
{
JpegColorConverterBase.ComponentValues values = new(this.Input, 0);
new JpegColorConverterBase.YccKVector512(8).ConvertToRgbInPlace(values);
this.converter.ConvertToRgbInPlace(values);
}
}

94
tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs

@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Formats.Png.Filters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png;
/// <summary>
/// Compares the shared PNG map/reduce traversal with the filter-specific traversals it replaces.
/// Measures the shared PNG filter map/reduce traversal.
/// </summary>
[Config(typeof(Config.Short))]
public class PngFilterEncode
@ -17,8 +17,7 @@ public class PngFilterEncode
private byte[] scanline;
private byte[] previousScanline;
private byte[] currentResult;
private byte[] baselineResult;
private byte[] result;
/// <summary>
/// Gets or sets the filter evaluated by each invocation.
@ -33,15 +32,14 @@ public class PngFilterEncode
public int Count { get; set; }
/// <summary>
/// Creates deterministic non-uniform inputs and independent result buffers.
/// Creates deterministic non-uniform inputs and a result buffer.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.scanline = new byte[this.Count];
this.previousScanline = new byte[this.Count];
this.currentResult = new byte[this.Count + 1];
this.baselineResult = new byte[this.Count + 1];
this.result = new byte[this.Count + 1];
Random random = new(12345678);
random.NextBytes(this.scanline);
@ -49,109 +47,53 @@ public class PngFilterEncode
}
/// <summary>
/// Executes the operator-driven map/reduce traversal.
/// Executes the shared operator-driven map/reduce traversal.
/// </summary>
/// <returns>The filter variance sum.</returns>
[Benchmark]
public int Current()
public int Encode()
=> this.Filter switch
{
PngFilterMethod.Sub => this.EncodeSubCurrent(),
PngFilterMethod.Up => this.EncodeUpCurrent(),
PngFilterMethod.Average => this.EncodeAverageCurrent(),
PngFilterMethod.Paeth => this.EncodePaethCurrent(),
PngFilterMethod.Sub => this.EncodeSub(),
PngFilterMethod.Up => this.EncodeUp(),
PngFilterMethod.Average => this.EncodeAverage(),
PngFilterMethod.Paeth => this.EncodePaeth(),
_ => throw new InvalidOperationException()
};
/// <summary>
/// Executes the filter-specific traversal being replaced.
/// </summary>
/// <returns>The filter variance sum.</returns>
[Benchmark(Baseline = true)]
public int Baseline()
{
int sum;
switch (this.Filter)
{
case PngFilterMethod.Sub:
PngFilterEncodeBaseline.EncodeSub(
this.scanline,
this.baselineResult,
BytesPerPixel,
out sum);
break;
case PngFilterMethod.Up:
PngFilterEncodeBaseline.EncodeUp(
this.scanline,
this.previousScanline,
this.baselineResult,
out sum);
break;
case PngFilterMethod.Average:
PngFilterEncodeBaseline.EncodeAverage(
this.scanline,
this.previousScanline,
this.baselineResult,
BytesPerPixel,
out sum);
break;
case PngFilterMethod.Paeth:
PngFilterEncodeBaseline.EncodePaeth(
this.scanline,
this.previousScanline,
this.baselineResult,
BytesPerPixel,
out sum);
break;
default:
throw new InvalidOperationException();
}
return sum;
}
/// <summary>
/// Executes the current Sub encoder.
/// </summary>
private int EncodeSubCurrent()
private int EncodeSub()
{
SubFilter.Encode(this.scanline, this.currentResult, BytesPerPixel, out int sum);
SubFilter.Encode(this.scanline, this.result, BytesPerPixel, out int sum);
return sum;
}
/// <summary>
/// Executes the current Up encoder.
/// </summary>
private int EncodeUpCurrent()
private int EncodeUp()
{
UpFilter.Encode(this.scanline, this.previousScanline, this.currentResult, out int sum);
UpFilter.Encode(this.scanline, this.previousScanline, this.result, out int sum);
return sum;
}
/// <summary>
/// Executes the current Average encoder.
/// </summary>
private int EncodeAverageCurrent()
private int EncodeAverage()
{
AverageFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out int sum);
AverageFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out int sum);
return sum;
}
/// <summary>
/// Executes the current Paeth encoder.
/// </summary>
private int EncodePaethCurrent()
private int EncodePaeth()
{
PaethFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out int sum);
PaethFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out int sum);
return sum;
}
}

54
tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs

@ -7,7 +7,7 @@ using SixLabors.ImageSharp.Formats.Png.Filters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png;
/// <summary>
/// Exposes every normalized PNG filter and retained baseline for assembly comparison.
/// Exposes every normalized PNG filter for assembly inspection.
/// </summary>
[Config(typeof(Config.Analysis))]
public class PngFilterEncodeAssembly
@ -17,8 +17,7 @@ public class PngFilterEncodeAssembly
private byte[] scanline;
private byte[] previousScanline;
private byte[] currentResult;
private byte[] baselineResult;
private byte[] result;
/// <summary>
/// Creates inputs whose suffix exercises 512-, 256-, and 128-bit register widths.
@ -28,8 +27,7 @@ public class PngFilterEncodeAssembly
{
this.scanline = new byte[Count];
this.previousScanline = new byte[Count];
this.currentResult = new byte[Count + 1];
this.baselineResult = new byte[Count + 1];
this.result = new byte[Count + 1];
Random random = new(12345678);
random.NextBytes(this.scanline);
@ -41,64 +39,26 @@ public class PngFilterEncodeAssembly
/// </summary>
[Benchmark]
public void Sub()
=> SubFilter.Encode(this.scanline, this.currentResult, BytesPerPixel, out _);
=> SubFilter.Encode(this.scanline, this.result, BytesPerPixel, out _);
/// <summary>
/// Executes the normalized Up encoder.
/// </summary>
[Benchmark]
public void Up()
=> UpFilter.Encode(this.scanline, this.previousScanline, this.currentResult, out _);
=> UpFilter.Encode(this.scanline, this.previousScanline, this.result, out _);
/// <summary>
/// Executes the normalized Average encoder.
/// </summary>
[Benchmark]
public void Average()
=> AverageFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out _);
=> AverageFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out _);
/// <summary>
/// Executes the normalized Paeth encoder.
/// </summary>
[Benchmark]
public void Paeth()
=> PaethFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out _);
/// <summary>
/// Executes the retained Sub encoder.
/// </summary>
[Benchmark]
public void BaselineSub()
=> PngFilterEncodeBaseline.EncodeSub(this.scanline, this.baselineResult, BytesPerPixel, out _);
/// <summary>
/// Executes the retained Up encoder.
/// </summary>
[Benchmark]
public void BaselineUp()
=> PngFilterEncodeBaseline.EncodeUp(this.scanline, this.previousScanline, this.baselineResult, out _);
/// <summary>
/// Executes the retained Average encoder.
/// </summary>
[Benchmark]
public void BaselineAverage()
=> PngFilterEncodeBaseline.EncodeAverage(
this.scanline,
this.previousScanline,
this.baselineResult,
BytesPerPixel,
out _);
/// <summary>
/// Executes the retained Paeth encoder.
/// </summary>
[Benchmark]
public void BaselinePaeth()
=> PngFilterEncodeBaseline.EncodePaeth(
this.scanline,
this.previousScanline,
this.baselineResult,
BytesPerPixel,
out _);
=> PaethFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out _);
}

470
tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs

@ -1,470 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png;
/// <summary>
/// Retains the filter-specific PNG encode traversals for direct performance comparison.
/// </summary>
internal static class PngFilterEncodeBaseline
{
/// <summary>
/// Executes the filter-specific Sub traversal.
/// </summary>
public static void EncodeSub(
ReadOnlySpan<byte> scanline,
Span<byte> result,
int bytesPerPixel,
out int sum)
{
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
resultBaseRef = 1;
nuint x = 0;
for (; x < (uint)bytesPerPixel;)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
x++;
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x);
residual = scan;
sum += Numerics.Abs(unchecked((sbyte)residual));
}
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> accumulator = Vector256<int>.Zero;
for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256<byte>.Count; xLeft += (uint)Vector256<byte>.Count)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> left = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector256<byte> residual = Avx2.Subtract(scan, left);
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual;
x += (uint)Vector256<byte>.Count;
accumulator = Avx2.Add(
accumulator,
Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(accumulator);
}
else if (Vector.IsHardwareAccelerated)
{
Vector<uint> accumulator = Vector<uint>.Zero;
for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector<byte>.Count; xLeft += (uint)Vector<byte>.Count)
{
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector<byte> left = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector<byte> residual = scan - left;
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual;
x += (uint)Vector<byte>.Count;
Numerics.Accumulate(
ref accumulator,
Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual))));
}
for (int i = 0; i < Vector<uint>.Count; i++)
{
sum += (int)accumulator[i];
}
}
for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; xLeft++)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, xLeft);
x++;
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x);
residual = (byte)(scan - left);
sum += Numerics.Abs(unchecked((sbyte)residual));
}
}
/// <summary>
/// Executes the filter-specific Up traversal.
/// </summary>
public static void EncodeUp(
ReadOnlySpan<byte> scanline,
ReadOnlySpan<byte> previousScanline,
Span<byte> result,
out int sum)
{
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
resultBaseRef = 2;
nuint x = 0;
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> accumulator = Vector256<int>.Zero;
for (; (int)x <= scanline.Length - Vector256<byte>.Count;)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, x));
Vector256<byte> residual = Avx2.Subtract(scan, above);
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual;
x += (uint)Vector256<byte>.Count;
accumulator = Avx2.Add(
accumulator,
Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(accumulator);
}
else if (Vector.IsHardwareAccelerated)
{
Vector<uint> accumulator = Vector<uint>.Zero;
for (; (int)x <= scanline.Length - Vector<byte>.Count;)
{
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector<byte> above = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref previousBaseRef, x));
Vector<byte> residual = scan - above;
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual;
x += (uint)Vector<byte>.Count;
Numerics.Accumulate(
ref accumulator,
Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual))));
}
for (int i = 0; i < Vector<uint>.Count; i++)
{
sum += (int)accumulator[i];
}
}
for (; x < (uint)scanline.Length;)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref previousBaseRef, x);
x++;
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x);
residual = (byte)(scan - above);
sum += Numerics.Abs(unchecked((sbyte)residual));
}
}
/// <summary>
/// Executes the filter-specific Average traversal.
/// </summary>
public static void EncodeAverage(
ReadOnlySpan<byte> scanline,
ReadOnlySpan<byte> previousScanline,
Span<byte> result,
uint bytesPerPixel,
out int sum)
{
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
resultBaseRef = 3;
nuint x = 0;
for (; x < bytesPerPixel;)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref previousBaseRef, x);
x++;
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x);
residual = (byte)(scan - (above >> 1));
sum += Numerics.Abs(unchecked((sbyte)residual));
}
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> accumulator = Vector256<int>.Zero;
Vector256<byte> allBitsSet = Avx2.CompareEqual(accumulator, accumulator).AsByte();
for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector256<byte>.Count; xLeft += (uint)Vector256<byte>.Count)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> left = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, x));
Vector256<byte> average = Avx2.Xor(
Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)),
allBitsSet);
Vector256<byte> residual = Avx2.Subtract(scan, average);
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual;
x += (uint)Vector256<byte>.Count;
accumulator = Avx2.Add(
accumulator,
Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(accumulator);
}
else if (Sse2.IsSupported)
{
Vector128<byte> zero = Vector128<byte>.Zero;
Vector128<int> accumulator = Vector128<int>.Zero;
Vector128<byte> allBitsSet = Sse2.CompareEqual(accumulator, accumulator).AsByte();
for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector128<byte>.Count; xLeft += (uint)Vector128<byte>.Count)
{
Vector128<byte> scan = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector128<byte> left = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector128<byte> above = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref previousBaseRef, x));
Vector128<byte> average = Sse2.Xor(
Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)),
allBitsSet);
Vector128<byte> residual = Sse2.Subtract(scan, average);
Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual;
x += (uint)Vector128<byte>.Count;
Vector128<byte> absolute;
if (Ssse3.IsSupported)
{
absolute = Ssse3.Abs(residual.AsSByte());
}
else
{
Vector128<sbyte> mask = Sse2.CompareGreaterThan(zero.AsSByte(), residual.AsSByte());
absolute = Sse2.Xor(Sse2.Add(residual.AsSByte(), mask), mask).AsByte();
}
accumulator = Sse2.Add(
accumulator,
Sse2.SumAbsoluteDifferences(absolute, zero).AsInt32());
}
sum += Numerics.EvenReduceSum(accumulator);
}
for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; xLeft++)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, xLeft);
byte above = Unsafe.Add(ref previousBaseRef, x);
x++;
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x);
residual = (byte)(scan - ((left + above) >> 1));
sum += Numerics.Abs(unchecked((sbyte)residual));
}
}
/// <summary>
/// Executes the filter-specific Paeth traversal.
/// </summary>
public static void EncodePaeth(
ReadOnlySpan<byte> scanline,
ReadOnlySpan<byte> previousScanline,
Span<byte> result,
int bytesPerPixel,
out int sum)
{
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
resultBaseRef = 4;
nuint x = 0;
for (; x < (uint)bytesPerPixel;)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref previousBaseRef, x);
x++;
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x);
residual = (byte)(scan - above);
sum += Numerics.Abs(unchecked((sbyte)residual));
}
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> accumulator = Vector256<int>.Zero;
for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256<byte>.Count; xLeft += (uint)Vector256<byte>.Count)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> left = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, x));
Vector256<byte> upperLeft = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, xLeft));
Vector256<byte> residual = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft));
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual;
x += (uint)Vector256<byte>.Count;
accumulator = Avx2.Add(
accumulator,
Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(accumulator);
}
else if (Vector.IsHardwareAccelerated)
{
Vector<uint> accumulator = Vector<uint>.Zero;
for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector<byte>.Count; xLeft += (uint)Vector<byte>.Count)
{
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector<byte> left = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector<byte> above = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref previousBaseRef, x));
Vector<byte> upperLeft = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref previousBaseRef, xLeft));
Vector<byte> residual = scan - PaethPredictor(left, above, upperLeft);
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual;
x += (uint)Vector<byte>.Count;
Numerics.Accumulate(
ref accumulator,
Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual))));
}
for (int i = 0; i < Vector<uint>.Count; i++)
{
sum += (int)accumulator[i];
}
}
for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; xLeft++)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, xLeft);
byte above = Unsafe.Add(ref previousBaseRef, x);
byte upperLeft = Unsafe.Add(ref previousBaseRef, xLeft);
x++;
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x);
residual = (byte)(scan - PaethPredictor(left, above, upperLeft));
sum += Numerics.Abs(unchecked((sbyte)residual));
}
}
/// <summary>
/// Selects the scalar Paeth predictor.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte PaethPredictor(byte left, byte above, byte upperLeft)
{
int p = left + above - upperLeft;
int distanceLeft = Numerics.Abs(p - left);
int distanceAbove = Numerics.Abs(p - above);
int distanceUpperLeft = Numerics.Abs(p - upperLeft);
if (distanceLeft <= distanceAbove && distanceLeft <= distanceUpperLeft)
{
return left;
}
return distanceAbove <= distanceUpperLeft ? above : upperLeft;
}
/// <summary>
/// Selects the AVX2 Paeth predictor.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<byte> PaethPredictor(
Vector256<byte> left,
Vector256<byte> above,
Vector256<byte> upperLeft)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<byte> aboveMinusUpper = Avx2.SubtractSaturate(above, upperLeft);
Vector256<byte> leftMinusUpper = Avx2.SubtractSaturate(left, upperLeft);
Vector256<byte> distanceLeft =
Avx2.Or(Avx2.SubtractSaturate(upperLeft, above), aboveMinusUpper);
Vector256<byte> distanceAbove =
Avx2.Or(Avx2.SubtractSaturate(upperLeft, left), leftMinusUpper);
Vector256<byte> sameDirection = Avx2.CompareEqual(
Avx2.CompareEqual(aboveMinusUpper, zero),
Avx2.CompareEqual(leftMinusUpper, zero));
Vector256<byte> distanceUpper = Avx2.Or(
sameDirection,
Avx2.Or(
Avx2.SubtractSaturate(distanceAbove, distanceLeft),
Avx2.SubtractSaturate(distanceLeft, distanceAbove)));
Vector256<byte> minimumAboveUpper = Avx2.Min(distanceUpper, distanceAbove);
Vector256<byte> aboveOrUpper = Avx2.BlendVariable(
upperLeft,
above,
Avx2.CompareEqual(minimumAboveUpper, distanceAbove));
return Avx2.BlendVariable(
aboveOrUpper,
left,
Avx2.CompareEqual(Avx2.Min(minimumAboveUpper, distanceLeft), distanceLeft));
}
/// <summary>
/// Selects the portable vector Paeth predictor.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector<byte> PaethPredictor(
Vector<byte> left,
Vector<byte> above,
Vector<byte> upperLeft)
{
Vector.Widen(left, out Vector<ushort> leftLow, out Vector<ushort> leftHigh);
Vector.Widen(above, out Vector<ushort> aboveLow, out Vector<ushort> aboveHigh);
Vector.Widen(upperLeft, out Vector<ushort> upperLow, out Vector<ushort> upperHigh);
Vector<short> lower = PaethPredictor(
Vector.AsVectorInt16(leftLow),
Vector.AsVectorInt16(aboveLow),
Vector.AsVectorInt16(upperLow));
Vector<short> upper = PaethPredictor(
Vector.AsVectorInt16(leftHigh),
Vector.AsVectorInt16(aboveHigh),
Vector.AsVectorInt16(upperHigh));
return Vector.AsVectorByte(Vector.Narrow(lower, upper));
}
/// <summary>
/// Selects the portable widened Paeth predictor.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector<short> PaethPredictor(
Vector<short> left,
Vector<short> above,
Vector<short> upperLeft)
{
Vector<short> p = left + above - upperLeft;
Vector<short> distanceLeft = Vector.Abs(p - left);
Vector<short> distanceAbove = Vector.Abs(p - above);
Vector<short> distanceUpper = Vector.Abs(p - upperLeft);
Vector<short> chooseLeft = Vector.BitwiseAnd(
Vector.LessThanOrEqual(distanceLeft, distanceAbove),
Vector.LessThanOrEqual(distanceLeft, distanceUpper));
return Vector.ConditionalSelect(
chooseLeft,
left,
Vector.ConditionalSelect(
Vector.LessThanOrEqual(distanceAbove, distanceUpper),
above,
upperLeft));
}
}

65
tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs

@ -1,65 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath;
public class AddSpan
{
private byte[] scalarValues = null!;
private byte[] tensorValues = null!;
private byte[] addends = null!;
/// <summary>
/// Gets or sets the number of values to add.
/// </summary>
[Params(32, 257, 2048)]
public int Length { get; set; }
/// <summary>
/// Creates equivalent deterministic inputs for both implementations.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.scalarValues = new byte[this.Length];
this.tensorValues = new byte[this.Length];
this.addends = new byte[this.Length];
for (int i = 0; i < this.Length; i++)
{
byte value = (byte)((i * 17) + 31);
this.scalarValues[i] = value;
this.tensorValues[i] = value;
this.addends[i] = (byte)((i * 29) + 7);
}
}
/// <summary>
/// Adds the values with a scalar loop.
/// </summary>
/// <returns>The first result, which keeps the mutated data observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public byte Scalar()
{
for (int i = 0; i < this.scalarValues.Length; i++)
{
this.scalarValues[i] += this.addends[i];
}
return this.scalarValues[0];
}
/// <summary>
/// Adds the values with the tensor compatibility pipeline.
/// </summary>
/// <returns>The first result, which keeps the mutated data observable to the benchmark harness.</returns>
[Benchmark]
public byte TensorPipeline()
{
TensorPrimitives_.Add<byte>(this.tensorValues, this.addends, this.tensorValues);
return this.tensorValues[0];
}
}

61
tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs

@ -1,61 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using BenchmarkDotNet.Attributes;
namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath;
public class NormalizeSpan
{
private float[] scalarValues = null!;
private float[] tensorValues = null!;
/// <summary>
/// Gets or sets the number of values to normalize.
/// </summary>
[Params(7, 32, 257, 2048)]
public int Length { get; set; }
/// <summary>
/// Creates equivalent deterministic inputs for both implementations.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.scalarValues = new float[this.Length];
this.tensorValues = new float[this.Length];
for (int i = 0; i < this.scalarValues.Length; i++)
{
float value = ((i * 17) % 251) + 1;
this.scalarValues[i] = value;
this.tensorValues[i] = value;
}
}
/// <summary>
/// Normalizes the values with a scalar loop.
/// </summary>
/// <returns>The first result, which keeps the mutated data observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public float Scalar()
{
for (int i = 0; i < this.scalarValues.Length; i++)
{
this.scalarValues[i] /= 4096F;
}
return this.scalarValues[0];
}
/// <summary>
/// Normalizes the values with the tensor compatibility pipeline.
/// </summary>
/// <returns>The first result, which keeps the mutated data observable to the benchmark harness.</returns>
[Benchmark]
public float TensorPipeline()
{
Numerics.Normalize(this.tensorValues, 4096F);
return this.tensorValues[0];
}
}

175
tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs

@ -0,0 +1,175 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath;
/// <summary>
/// Exposes every floating-point tensor compatibility operation for assembly inspection.
/// </summary>
[Config(typeof(Config.Analysis))]
public class TensorPrimitivesAssembly
{
private const int Count = 2048;
private readonly float[] x = new float[Count];
private readonly float[] y = new float[Count];
private readonly float[] destination = new float[Count];
/// <summary>
/// Populates the input spans with deterministic non-uniform values.
/// </summary>
[GlobalSetup]
public void Setup()
{
for (int i = 0; i < Count; i++)
{
this.x[i] = ((i * 17) % 251) + 1;
this.y[i] = ((i * 29) % 251) + 1;
}
}
/// <summary>
/// Adds two floating-point spans.
/// </summary>
/// <returns>The first result, which keeps the destination observable.</returns>
[Benchmark]
public float Add()
{
TensorPrimitives_.Add<float>(this.x, this.y, this.destination);
return this.destination[0];
}
/// <summary>
/// Clamps a floating-point span between scalar bounds.
/// </summary>
/// <returns>The first result, which keeps the destination observable.</returns>
[Benchmark]
public float Clamp()
{
TensorPrimitives_.Clamp(this.x, 64F, 128F, this.destination);
return this.destination[0];
}
/// <summary>
/// Divides a floating-point span by a scalar.
/// </summary>
/// <returns>The first result, which keeps the destination observable.</returns>
[Benchmark]
public float Divide()
{
TensorPrimitives_.Divide(this.x, 4096F, this.destination);
return this.destination[0];
}
/// <summary>
/// Computes the element-wise maximum of a floating-point span and a scalar.
/// </summary>
/// <returns>The first result, which keeps the destination observable.</returns>
[Benchmark]
public float Max()
{
TensorPrimitives_.Max(this.x, 64F, this.destination);
return this.destination[0];
}
/// <summary>
/// Multiplies a floating-point span by a scalar.
/// </summary>
/// <returns>The first result, which keeps the destination observable.</returns>
[Benchmark]
public float Multiply()
{
TensorPrimitives_.Multiply(this.x, 0.5F, this.destination);
return this.destination[0];
}
}
/// <summary>
/// Exposes integral addition specializations for assembly inspection.
/// </summary>
/// <typeparam name="T">The integral element type.</typeparam>
[Config(typeof(Config.Analysis))]
[GenericTypeArguments(typeof(byte))]
[GenericTypeArguments(typeof(uint))]
public class TensorPrimitivesIntegralAddAssembly<T>
where T : unmanaged, INumber<T>
{
private const int Count = 2048;
private readonly T[] x = new T[Count];
private readonly T[] y = new T[Count];
private readonly T[] destination = new T[Count];
/// <summary>
/// Populates the input spans with deterministic non-uniform values.
/// </summary>
[GlobalSetup]
public void Setup()
{
for (int i = 0; i < Count; i++)
{
this.x[i] = T.CreateTruncating((i * 17) + 31);
this.y[i] = T.CreateTruncating((i * 29) + 7);
}
}
/// <summary>
/// Adds two integral spans.
/// </summary>
/// <returns>The first result, which keeps the destination observable.</returns>
[Benchmark]
public T Add()
{
TensorPrimitives_.Add<T>(this.x, this.y, this.destination);
return this.destination[0];
}
}
/// <summary>
/// Exposes integral clamp specializations for assembly inspection.
/// </summary>
/// <typeparam name="T">The integral element type.</typeparam>
[Config(typeof(Config.Analysis))]
[GenericTypeArguments(typeof(byte))]
[GenericTypeArguments(typeof(uint))]
[GenericTypeArguments(typeof(int))]
public class TensorPrimitivesIntegralClampAssembly<T>
where T : unmanaged, INumber<T>
{
private const int Count = 2048;
private readonly T[] source = new T[Count];
private readonly T[] destination = new T[Count];
private T min;
private T max;
/// <summary>
/// Populates the input span and scalar bounds with deterministic values.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.min = T.CreateTruncating(64);
this.max = T.CreateTruncating(128);
for (int i = 0; i < Count; i++)
{
this.source[i] = T.CreateTruncating((i * 31) % 257);
}
}
/// <summary>
/// Clamps an integral span between scalar bounds.
/// </summary>
/// <returns>The first result, which keeps the destination observable.</returns>
[Benchmark]
public T Clamp()
{
TensorPrimitives_.Clamp(this.source, this.min, this.max, this.destination);
return this.destination[0];
}
}

813
tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs

@ -1,813 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath;
#pragma warning disable SA1649 // File name should match first type name
public class TensorPrimitivesJpegMultiplyAssemblyComparison
#pragma warning restore SA1649 // File name should match first type name
{
private readonly float multiplier = -1F;
private float[] legacyValues = null!;
private float[] tensorValues = null!;
/// <summary>
/// Creates equivalent stable inputs for both implementations.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.legacyValues = new float[256];
this.tensorValues = new float[256];
for (int i = 0; i < this.legacyValues.Length; i++)
{
float value = ((i * 17) % 251) + 1;
this.legacyValues[i] = value;
this.tensorValues[i] = value;
}
}
/// <summary>
/// Multiplies the row with the retired JPEG AVX pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public float Legacy()
{
LegacyMultiply(this.legacyValues, this.multiplier);
return this.legacyValues[0];
}
/// <summary>
/// Multiplies the row with the tensor compatibility pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public float Tensor()
{
TensorPrimitives_.Multiply(this.tensorValues, this.multiplier, this.tensorValues);
return this.tensorValues[0];
}
/// <summary>
/// Reproduces the retired JPEG multiplication loop for assembly comparison.
/// </summary>
/// <param name="target">The row to multiply.</param>
/// <param name="multiplier">The scalar multiplier.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void LegacyMultiply(Span<float> target, float multiplier)
{
ref Vector256<float> targetVector = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(target));
nuint count = (uint)target.Length / (uint)Vector256<float>.Count;
Vector256<float> multiplierVector = Vector256.Create(multiplier);
for (nuint i = 0; i < count; i++)
{
Unsafe.Add(ref targetVector, i) = Avx.Multiply(Unsafe.Add(ref targetVector, i), multiplierVector);
}
}
}
public class TensorPrimitivesNormalizeAssemblyComparison
{
private readonly float divisor = -1F;
private float[] legacyValues = null!;
private float[] tensorValues = null!;
/// <summary>
/// Creates equivalent stable inputs for both implementations.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.legacyValues = new float[7];
this.tensorValues = new float[7];
for (int i = 0; i < this.legacyValues.Length; i++)
{
float value = ((i * 17) % 251) + 1;
this.legacyValues[i] = value;
this.tensorValues[i] = value;
}
}
/// <summary>
/// Normalizes the values with the retired fixed-width pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public float Legacy()
{
LegacyNormalize(this.legacyValues, this.divisor);
return this.legacyValues[0];
}
/// <summary>
/// Normalizes the values with the tensor compatibility pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public float Tensor()
{
Numerics.Normalize(this.tensorValues, this.divisor);
return this.tensorValues[0];
}
/// <summary>
/// Reproduces the retired normalization loop for assembly comparison.
/// </summary>
/// <param name="span">The values to normalize.</param>
/// <param name="sum">The scalar divisor.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void LegacyNormalize(Span<float> span, float sum)
{
ref float start = ref MemoryMarshal.GetReference(span);
ref float vectorEnd = ref Unsafe.Add(ref start, span.Length & ~7);
Vector256<float> sum256 = Vector256.Create(sum);
while (Unsafe.IsAddressLessThan(ref start, ref vectorEnd))
{
Unsafe.As<float, Vector256<float>>(ref start) /= sum256;
start = ref Unsafe.Add(ref start, (nuint)8);
}
if ((span.Length & 7) >= 4)
{
Unsafe.As<float, Vector128<float>>(ref start) /= sum256.GetLower();
start = ref Unsafe.Add(ref start, (nuint)4);
}
ref float end = ref Unsafe.Add(ref start, span.Length & 3);
while (Unsafe.IsAddressLessThan(ref start, ref end))
{
start /= sum;
start = ref Unsafe.Add(ref start, (nuint)1);
}
}
}
public class TensorPrimitivesUInt32AssemblyComparison
{
private uint[] x = null!;
private uint[] y = null!;
private uint[] legacyDestination = null!;
private uint[] tensorDestination = null!;
/// <summary>
/// Creates deterministic histogram inputs and independent destinations.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.x = new uint[2048];
this.y = new uint[2048];
this.legacyDestination = new uint[2048];
this.tensorDestination = new uint[2048];
for (int i = 0; i < this.x.Length; i++)
{
this.x[i] = (uint)((i * 17) + 31);
this.y[i] = (uint)((i * 29) + 7);
}
}
/// <summary>
/// Adds histogram bins with the retired four-vector AVX2 pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public uint Legacy()
{
LegacyAdd(this.x, this.y, this.legacyDestination);
return this.legacyDestination[0];
}
/// <summary>
/// Adds histogram bins with the tensor compatibility pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public uint Tensor()
{
TensorPrimitives_.Add<uint>(this.x, this.y, this.tensorDestination);
return this.tensorDestination[0];
}
/// <summary>
/// Reproduces the retired WebP histogram addition loop for assembly comparison.
/// </summary>
/// <param name="x">The first histogram.</param>
/// <param name="y">The second histogram.</param>
/// <param name="destination">The destination receiving the sums.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void LegacyAdd(ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> destination)
{
ref uint xRef = ref MemoryMarshal.GetReference(x);
ref uint yRef = ref MemoryMarshal.GetReference(y);
ref uint destinationRef = ref MemoryMarshal.GetReference(destination);
nuint index = 0;
do
{
Vector256<uint> x0 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref xRef, index));
Vector256<uint> x1 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref xRef, index + 8));
Vector256<uint> x2 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref xRef, index + 16));
Vector256<uint> x3 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref xRef, index + 24));
Vector256<uint> y0 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref yRef, index));
Vector256<uint> y1 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref yRef, index + 8));
Vector256<uint> y2 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref yRef, index + 16));
Vector256<uint> y3 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref yRef, index + 24));
Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref destinationRef, index)) = Avx2.Add(x0, y0);
Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref destinationRef, index + 8)) = Avx2.Add(x1, y1);
Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref destinationRef, index + 16)) = Avx2.Add(x2, y2);
Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref destinationRef, index + 24)) = Avx2.Add(x3, y3);
index += 32;
}
while (index <= (uint)x.Length - 32);
for (int i = (int)index; i < x.Length; i++)
{
destination[i] = x[i] + y[i];
}
}
}
public class TensorPrimitivesByteAssemblyComparison
{
private byte[] x = null!;
private byte[] y = null!;
private byte[] legacyDestination = null!;
private byte[] tensorDestination = null!;
/// <summary>
/// Creates deterministic byte inputs and independent destinations.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.x = new byte[2048];
this.y = new byte[2048];
this.legacyDestination = new byte[2048];
this.tensorDestination = new byte[2048];
for (int i = 0; i < this.x.Length; i++)
{
this.x[i] = (byte)((i * 17) + 31);
this.y[i] = (byte)((i * 29) + 7);
}
}
/// <summary>
/// Adds bytes with the retired WebP AVX2 pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public byte Legacy()
{
LegacyAdd(this.x, this.y, this.legacyDestination);
return this.legacyDestination[0];
}
/// <summary>
/// Adds bytes with the tensor compatibility pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public byte Tensor()
{
TensorPrimitives_.Add<byte>(this.x, this.y, this.tensorDestination);
return this.tensorDestination[0];
}
/// <summary>
/// Reproduces the retired WebP byte addition loop for assembly comparison.
/// </summary>
/// <param name="x">The first input.</param>
/// <param name="y">The second input.</param>
/// <param name="destination">The destination receiving modulo-256 sums.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void LegacyAdd(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y, Span<byte> destination)
{
ref byte xRef = ref MemoryMarshal.GetReference(x);
ref byte yRef = ref MemoryMarshal.GetReference(y);
ref byte destinationRef = ref MemoryMarshal.GetReference(destination);
nuint i;
int maxPosition = x.Length & ~31;
for (i = 0; i < (uint)maxPosition; i += 32)
{
Vector256<int> x0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref xRef, i));
Vector256<int> y0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref yRef, i));
Vector256<byte> result = x0.AsByte() + y0.AsByte();
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref destinationRef, i)) = result;
}
for (; i < (uint)x.Length; i++)
{
Unsafe.Add(ref destinationRef, i) = (byte)(Unsafe.Add(ref xRef, i) + Unsafe.Add(ref yRef, i));
}
}
}
public class TensorPrimitivesSingleAddAssemblyComparison
{
private float[] legacyTarget = null!;
private float[] tensorTarget = null!;
private float[] source = null!;
/// <summary>
/// Creates deterministic JPEG row inputs.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.legacyTarget = new float[2048];
this.tensorTarget = new float[2048];
this.source = new float[2048];
for (int i = 0; i < this.source.Length; i++)
{
float value = ((i * 17) % 251) + 1;
this.legacyTarget[i] = value;
this.tensorTarget[i] = value;
this.source[i] = ((i * 29) % 31) - 15;
}
}
/// <summary>
/// Adds JPEG row values with the retired AVX pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public float Legacy()
{
LegacyAdd(this.legacyTarget, this.source);
return this.legacyTarget[0];
}
/// <summary>
/// Adds JPEG row values with the tensor compatibility pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public float Tensor()
{
TensorPrimitives_.Add<float>(this.tensorTarget, this.source, this.tensorTarget);
return this.tensorTarget[0];
}
/// <summary>
/// Reproduces the retired JPEG row addition loop for assembly comparison.
/// </summary>
/// <param name="target">The destination row.</param>
/// <param name="source">The row added to the destination.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void LegacyAdd(Span<float> target, ReadOnlySpan<float> source)
{
ref Vector256<float> targetVector = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(target));
ref Vector256<float> sourceVector = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(source));
nuint count = (uint)source.Length / (uint)Vector256<float>.Count;
for (nuint i = 0; i < count; i++)
{
Unsafe.Add(ref targetVector, i) = Avx.Add(Unsafe.Add(ref targetVector, i), Unsafe.Add(ref sourceVector, i));
}
}
}
[GenericTypeArguments(typeof(byte))]
[GenericTypeArguments(typeof(uint))]
[GenericTypeArguments(typeof(int))]
[GenericTypeArguments(typeof(float))]
[GenericTypeArguments(typeof(double))]
public class TensorPrimitivesClampAssemblyComparison<T>
where T : unmanaged, INumber<T>
{
private T[] legacyValues = null!;
private T[] tensorValues = null!;
private T min;
private T max;
/// <summary>
/// Creates deterministic clamp inputs for the current element type.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.legacyValues = new T[2048];
this.tensorValues = new T[2048];
this.min = T.CreateTruncating(64);
this.max = T.CreateTruncating(128);
for (int i = 0; i < this.legacyValues.Length; i++)
{
T value = T.CreateTruncating((i * 31) % 257);
this.legacyValues[i] = value;
this.tensorValues[i] = value;
}
}
/// <summary>
/// Clamps values with the retired <see cref="Vector{T}"/> pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public T Legacy()
{
LegacyClamp(this.legacyValues, this.min, this.max);
return this.legacyValues[0];
}
/// <summary>
/// Clamps values with the tensor compatibility pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public T Tensor()
{
TensorPrimitives_.Clamp(this.tensorValues, this.min, this.max, this.tensorValues);
return this.tensorValues[0];
}
/// <summary>
/// Reproduces the retired clamp pipeline for assembly comparison.
/// </summary>
/// <param name="span">The values to clamp.</param>
/// <param name="min">The inclusive lower bound.</param>
/// <param name="max">The inclusive upper bound.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void LegacyClamp(Span<T> span, T min, T max)
{
int remainder = Numerics.ModuloP2(span.Length, Vector<T>.Count);
int adjustedCount = span.Length - remainder;
if (adjustedCount > 0)
{
Vector<T> vectorMin = new(min);
Vector<T> vectorMax = new(max);
nint vectorCount = (nint)(uint)adjustedCount / Vector<T>.Count;
nint remainingVectors = Numerics.Modulo4(vectorCount);
nint unrolledVectors = vectorCount - remainingVectors;
ref Vector<T> current0 = ref Unsafe.As<T, Vector<T>>(ref MemoryMarshal.GetReference(span));
ref Vector<T> current1 = ref Unsafe.Add(ref current0, 1);
ref Vector<T> current2 = ref Unsafe.Add(ref current0, 2);
ref Vector<T> current3 = ref Unsafe.Add(ref current0, 3);
ref Vector<T> end = ref Unsafe.Add(ref current0, unrolledVectors);
while (Unsafe.IsAddressLessThan(ref current0, ref end))
{
current0 = Vector.Min(Vector.Max(vectorMin, current0), vectorMax);
current1 = Vector.Min(Vector.Max(vectorMin, current1), vectorMax);
current2 = Vector.Min(Vector.Max(vectorMin, current2), vectorMax);
current3 = Vector.Min(Vector.Max(vectorMin, current3), vectorMax);
current0 = ref Unsafe.Add(ref current0, 4);
current1 = ref Unsafe.Add(ref current1, 4);
current2 = ref Unsafe.Add(ref current2, 4);
current3 = ref Unsafe.Add(ref current3, 4);
}
if (remainingVectors > 0)
{
current0 = ref end;
end = ref Unsafe.Add(ref end, remainingVectors);
while (Unsafe.IsAddressLessThan(ref current0, ref end))
{
current0 = Vector.Min(Vector.Max(vectorMin, current0), vectorMax);
current0 = ref Unsafe.Add(ref current0, 1);
}
}
}
for (int i = adjustedCount; i < span.Length; i++)
{
T value = span[i];
span[i] = value > max ? max : value < min ? min : value;
}
}
}
public class TensorPrimitivesIccMaxAssemblyComparison
{
private Vector4[] legacyValues = null!;
private Vector4[] tensorValues = null!;
/// <summary>
/// Creates deterministic ICC values containing positive and negative channels.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.legacyValues = new Vector4[512];
this.tensorValues = new Vector4[512];
for (int i = 0; i < this.legacyValues.Length; i++)
{
float value = ((i * 17) % 251) - 125;
Vector4 vector = new(value, value + 1, value - 1, value + 2);
this.legacyValues[i] = vector;
this.tensorValues[i] = vector;
}
}
/// <summary>
/// Clips negative channels with the retired ICC pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public float Legacy()
{
for (int i = 0; i < this.legacyValues.Length; i++)
{
this.legacyValues[i] = Vector4.Max(this.legacyValues[i], Vector4.Zero);
}
return this.legacyValues[0].X;
}
/// <summary>
/// Clips negative channels with the tensor compatibility pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public float Tensor()
{
Span<float> values = MemoryMarshal.Cast<Vector4, float>(this.tensorValues.AsSpan());
TensorPrimitives_.Max(values, 0F, values);
return values[0];
}
}
public class TensorPrimitivesIccMultiplyAssemblyComparison
{
private readonly float multiplier = 65280F / 65535F;
private Vector4[] source = null!;
private Vector4[] legacyDestination = null!;
private Vector4[] tensorDestination = null!;
/// <summary>
/// Creates deterministic ICC inputs and independent destinations.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.source = new Vector4[512];
this.legacyDestination = new Vector4[512];
this.tensorDestination = new Vector4[512];
for (int i = 0; i < this.source.Length; i++)
{
float value = ((i * 17) % 251) + 1;
this.source[i] = new Vector4(value, value + 1, value + 2, value + 3);
}
}
/// <summary>
/// Multiplies ICC channels with the retired <see cref="Vector{T}"/> pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public float Legacy()
{
Span<float> source = MemoryMarshal.Cast<Vector4, float>(this.source.AsSpan());
Span<float> destination = MemoryMarshal.Cast<Vector4, float>(this.legacyDestination.AsSpan());
ref Vector<float> sourceVector = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(source));
ref Vector<float> destinationVector = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(destination));
Vector<float> scale = new(this.multiplier);
nuint count = (uint)source.Length / (uint)Vector<float>.Count;
for (nuint i = 0; i < count; i++)
{
Unsafe.Add(ref destinationVector, i) = Unsafe.Add(ref sourceVector, i) * scale;
}
return destination[0];
}
/// <summary>
/// Multiplies ICC channels with the tensor compatibility pipeline.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public float Tensor()
{
Span<float> source = MemoryMarshal.Cast<Vector4, float>(this.source.AsSpan());
Span<float> destination = MemoryMarshal.Cast<Vector4, float>(this.tensorDestination.AsSpan());
TensorPrimitives_.Multiply(source, this.multiplier, destination);
return destination[0];
}
}
#if NET10_0_OR_GREATER
[GenericTypeArguments(typeof(byte))]
[GenericTypeArguments(typeof(uint))]
[GenericTypeArguments(typeof(float))]
public class TensorPrimitivesRuntimeAddAssemblyComparison<T>
where T : unmanaged, INumber<T>
{
private T[] x = null!;
private T[] y = null!;
private T[] compatibilityDestination = null!;
private T[] runtimeDestination = null!;
/// <summary>
/// Creates deterministic inputs and independent destinations.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.x = new T[2048];
this.y = new T[2048];
this.compatibilityDestination = new T[2048];
this.runtimeDestination = new T[2048];
for (int i = 0; i < this.x.Length; i++)
{
this.x[i] = T.CreateTruncating((i * 17) + 31);
this.y[i] = T.CreateTruncating((i * 29) + 7);
}
}
/// <summary>
/// Adds values with the compatibility implementation.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public T Compatibility()
{
TensorPrimitives_.Add<T>(this.x, this.y, this.compatibilityDestination);
return this.compatibilityDestination[0];
}
/// <summary>
/// Adds values with the .NET runtime implementation.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public T Runtime()
{
System.Numerics.Tensors.TensorPrimitives.Add<T>(this.x, this.y, this.runtimeDestination);
return this.runtimeDestination[0];
}
}
[GenericTypeArguments(typeof(byte))]
[GenericTypeArguments(typeof(uint))]
[GenericTypeArguments(typeof(int))]
[GenericTypeArguments(typeof(float))]
[GenericTypeArguments(typeof(double))]
public class TensorPrimitivesRuntimeClampAssemblyComparison<T>
where T : unmanaged, INumber<T>
{
private T[] compatibilityValues = null!;
private T[] runtimeValues = null!;
private T min;
private T max;
/// <summary>
/// Creates deterministic inputs for both implementations.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.compatibilityValues = new T[2048];
this.runtimeValues = new T[2048];
this.min = T.CreateTruncating(64);
this.max = T.CreateTruncating(128);
for (int i = 0; i < this.compatibilityValues.Length; i++)
{
T value = T.CreateTruncating((i * 31) % 257);
this.compatibilityValues[i] = value;
this.runtimeValues[i] = value;
}
}
/// <summary>
/// Clamps values with the compatibility implementation.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark(Baseline = true)]
public T Compatibility()
{
TensorPrimitives_.Clamp(this.compatibilityValues, this.min, this.max, this.compatibilityValues);
return this.compatibilityValues[0];
}
/// <summary>
/// Clamps values with the .NET runtime implementation.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public T Runtime()
{
System.Numerics.Tensors.TensorPrimitives.Clamp(this.runtimeValues, this.min, this.max, this.runtimeValues);
return this.runtimeValues[0];
}
}
public class TensorPrimitivesRuntimeSingleScalarAssemblyComparison
{
private readonly float scalar = -1F;
private float[] compatibilityValues = null!;
private float[] runtimeValues = null!;
/// <summary>
/// Creates equivalent stable inputs for both implementations.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.compatibilityValues = new float[2048];
this.runtimeValues = new float[2048];
for (int i = 0; i < this.compatibilityValues.Length; i++)
{
float value = ((i * 17) % 251) + 1;
this.compatibilityValues[i] = value;
this.runtimeValues[i] = value;
}
}
/// <summary>
/// Divides values with the compatibility implementation.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public float CompatibilityDivide()
{
TensorPrimitives_.Divide(this.compatibilityValues, this.scalar, this.compatibilityValues);
return this.compatibilityValues[0];
}
/// <summary>
/// Divides values with the .NET runtime implementation.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public float RuntimeDivide()
{
System.Numerics.Tensors.TensorPrimitives.Divide(this.runtimeValues, this.scalar, this.runtimeValues);
return this.runtimeValues[0];
}
/// <summary>
/// Computes maximum values with the compatibility implementation.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public float CompatibilityMax()
{
TensorPrimitives_.Max(this.compatibilityValues, 0F, this.compatibilityValues);
return this.compatibilityValues[0];
}
/// <summary>
/// Computes maximum values with the .NET runtime implementation.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public float RuntimeMax()
{
System.Numerics.Tensors.TensorPrimitives.Max(this.runtimeValues, 0F, this.runtimeValues);
return this.runtimeValues[0];
}
/// <summary>
/// Multiplies values with the compatibility implementation.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public float CompatibilityMultiply()
{
TensorPrimitives_.Multiply(this.compatibilityValues, this.scalar, this.compatibilityValues);
return this.compatibilityValues[0];
}
/// <summary>
/// Multiplies values with the .NET runtime implementation.
/// </summary>
/// <returns>The first result, which keeps the writes observable to the benchmark harness.</returns>
[Benchmark]
public float RuntimeMultiply()
{
System.Numerics.Tensors.TensorPrimitives.Multiply(this.runtimeValues, this.scalar, this.runtimeValues);
return this.runtimeValues[0];
}
}
#endif

160
tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs

@ -2,16 +2,13 @@
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion;
/// <summary>
/// Compares operator-driven affine vector transforms with the duplicated traversals they replace.
/// Measures the operator-driven affine vector transforms.
/// </summary>
[Config(typeof(Config.Short))]
public class Vector4AffineTransform
@ -21,7 +18,6 @@ public class Vector4AffineTransform
private static readonly Vector4 Divisor = new(255F, 2F, 65535F, .5F);
private Vector4[] current;
private Vector4[] baseline;
/// <summary>
/// Gets or sets the number of vectors transformed by each invocation.
@ -30,7 +26,7 @@ public class Vector4AffineTransform
public int Count { get; set; }
/// <summary>
/// Creates identical non-uniform buffers for the current and baseline traversals.
/// Creates a non-uniform input buffer.
/// </summary>
[GlobalSetup]
public void Setup()
@ -41,167 +37,19 @@ public class Vector4AffineTransform
{
this.current[i] = new Vector4(i + .25F, i + .5F, i + .75F, i + 1F);
}
this.baseline = [.. this.current];
}
/// <summary>
/// Executes the operator-driven multiply-then-add traversal.
/// </summary>
[Benchmark]
public void CurrentMultiplyThenAdd()
public void MultiplyThenAdd()
=> Vector4Converters.MultiplyThenAdd(this.current, Multiplier, Offset);
/// <summary>
/// Executes the duplicated multiply-then-add traversal.
/// </summary>
[Benchmark(Baseline = true)]
public void BaselineMultiplyThenAdd()
=> BaselineMultiplyThenAdd(this.baseline, Multiplier, Offset);
/// <summary>
/// Executes the operator-driven add-then-divide traversal.
/// </summary>
[Benchmark]
public void CurrentAddThenDivide()
public void AddThenDivide()
=> Vector4Converters.AddThenDivide(this.current, Offset, Divisor);
/// <summary>
/// Executes the duplicated add-then-divide traversal.
/// </summary>
[Benchmark]
public void BaselineAddThenDivide()
=> BaselineAddThenDivide(this.baseline, Offset, Divisor);
/// <summary>
/// Retains the multiply-then-add traversal being replaced for direct measurement.
/// </summary>
/// <param name="vectors">The vectors to transform.</param>
/// <param name="multiplier">The component-wise multiplier.</param>
/// <param name="offset">The component-wise offset.</param>
internal static void BaselineMultiplyThenAdd(Span<Vector4> vectors, Vector4 multiplier, Vector4 offset)
{
ref Vector4 vectorBase = ref MemoryMarshal.GetReference(vectors);
int index = 0;
if (Vector512.IsHardwareAccelerated)
{
int vectorsPerVector = Vector512<float>.Count / Vector128<float>.Count;
Vector256<float> multiplier256 = Vector256.Create(multiplier.AsVector128(), multiplier.AsVector128());
Vector256<float> offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128());
Vector512<float> multiplier512 = Vector512.Create(multiplier256, multiplier256);
Vector512<float> offset512 = Vector512.Create(offset256, offset256);
for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector)
{
ref Vector512<float> vector = ref Unsafe.As<Vector4, Vector512<float>>(
ref Unsafe.Add(ref vectorBase, (uint)index));
vector = (vector * multiplier512) + offset512;
}
}
if (Vector256.IsHardwareAccelerated)
{
int vectorsPerVector = Vector256<float>.Count / Vector128<float>.Count;
Vector256<float> multiplier256 = Vector256.Create(multiplier.AsVector128(), multiplier.AsVector128());
Vector256<float> offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128());
for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector)
{
ref Vector256<float> vector = ref Unsafe.As<Vector4, Vector256<float>>(
ref Unsafe.Add(ref vectorBase, (uint)index));
vector = (vector * multiplier256) + offset256;
}
}
if (Vector128.IsHardwareAccelerated)
{
Vector128<float> multiplier128 = multiplier.AsVector128();
Vector128<float> offset128 = offset.AsVector128();
for (; index < vectors.Length; index++)
{
ref Vector128<float> vector = ref Unsafe.As<Vector4, Vector128<float>>(
ref Unsafe.Add(ref vectorBase, (uint)index));
vector = (vector * multiplier128) + offset128;
}
return;
}
for (; index < vectors.Length; index++)
{
ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index);
vector = (vector * multiplier) + offset;
}
}
/// <summary>
/// Retains the add-then-divide traversal being replaced for direct measurement.
/// </summary>
/// <param name="vectors">The vectors to transform.</param>
/// <param name="offset">The component-wise offset.</param>
/// <param name="divisor">The component-wise divisor.</param>
internal static void BaselineAddThenDivide(Span<Vector4> vectors, Vector4 offset, Vector4 divisor)
{
ref Vector4 vectorBase = ref MemoryMarshal.GetReference(vectors);
int index = 0;
if (Vector512.IsHardwareAccelerated)
{
int vectorsPerVector = Vector512<float>.Count / Vector128<float>.Count;
Vector256<float> offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128());
Vector256<float> divisor256 = Vector256.Create(divisor.AsVector128(), divisor.AsVector128());
Vector512<float> offset512 = Vector512.Create(offset256, offset256);
Vector512<float> divisor512 = Vector512.Create(divisor256, divisor256);
for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector)
{
ref Vector512<float> vector = ref Unsafe.As<Vector4, Vector512<float>>(
ref Unsafe.Add(ref vectorBase, (uint)index));
vector = (vector + offset512) / divisor512;
}
}
if (Vector256.IsHardwareAccelerated)
{
int vectorsPerVector = Vector256<float>.Count / Vector128<float>.Count;
Vector256<float> offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128());
Vector256<float> divisor256 = Vector256.Create(divisor.AsVector128(), divisor.AsVector128());
for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector)
{
ref Vector256<float> vector = ref Unsafe.As<Vector4, Vector256<float>>(
ref Unsafe.Add(ref vectorBase, (uint)index));
vector = (vector + offset256) / divisor256;
}
}
if (Vector128.IsHardwareAccelerated)
{
Vector128<float> offset128 = offset.AsVector128();
Vector128<float> divisor128 = divisor.AsVector128();
for (; index < vectors.Length; index++)
{
ref Vector128<float> vector = ref Unsafe.As<Vector4, Vector128<float>>(
ref Unsafe.Add(ref vectorBase, (uint)index));
vector = (vector + offset128) / divisor128;
}
return;
}
for (; index < vectors.Length; index++)
{
ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index);
vector = (vector + offset) / divisor;
}
}
}

14
tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs

@ -56,18 +56,4 @@ public class Vector4AffineTransformAssembly
[Benchmark]
public void AddThenDivide()
=> Vector4Converters.AddThenDivide(this.vectors, Offset, Divisor);
/// <summary>
/// Executes the multiply-then-add traversal being replaced for assembly comparison.
/// </summary>
[Benchmark]
public void BaselineMultiplyThenAdd()
=> Vector4AffineTransform.BaselineMultiplyThenAdd(this.vectors, Multiplier, Offset);
/// <summary>
/// Executes the add-then-divide traversal being replaced for assembly comparison.
/// </summary>
[Benchmark]
public void BaselineAddThenDivide()
=> Vector4AffineTransform.BaselineAddThenDivide(this.vectors, Offset, Divisor);
}

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

File diff suppressed because it is too large
Loading…
Cancel
Save