Browse Source

Merge pull request #1404 from SixLabors/js/shuffle-intrinsics

Add 4 Channel Shuffle Intrinsics
js/color-alpha-handling
James Jackson-South 5 years ago
committed by GitHub
parent
commit
65b5a47c1a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 165
      src/ImageSharp/Common/Helpers/IComponentShuffle.cs
  2. 228
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  3. 141
      src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
  4. 64
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs
  5. 64
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs
  6. 64
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs
  7. 32
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude
  8. 90
      src/ImageSharp/PixelFormats/Utils/PixelConverter.cs
  9. 67
      tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleByte4Channel.cs
  10. 68
      tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleFloat4Channel.cs
  11. 15
      tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs
  12. 56
      tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs
  13. 1
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  14. 161
      tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs
  15. 14
      tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
  16. 2
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  17. 60
      tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs
  18. 67
      tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs
  19. 21
      tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
  20. 4
      tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs

165
src/ImageSharp/Common/Helpers/IComponentShuffle.cs

@ -0,0 +1,165 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Defines the contract for methods that allow the shuffling of pixel components.
/// Used for shuffling on platforms that do not support Hardware Intrinsics.
/// </summary>
internal interface IComponentShuffle
{
/// <summary>
/// Gets the shuffle control.
/// </summary>
byte Control { get; }
/// <summary>
/// Shuffle 8-bit integers within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest);
}
internal readonly struct DefaultShuffle4 : IComponentShuffle
{
public DefaultShuffle4(byte p3, byte p2, byte p1, byte p0)
: this(SimdUtils.Shuffle.MmShuffle(p3, p2, p1, p0))
{
}
public DefaultShuffle4(byte control) => this.Control = control;
public byte Control { get; }
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ref byte sBase = ref MemoryMarshal.GetReference(source);
ref byte dBase = ref MemoryMarshal.GetReference(dest);
SimdUtils.Shuffle.InverseMmShuffle(
this.Control,
out int p3,
out int p2,
out int p1,
out int p0);
for (int i = 0; i < source.Length; i += 4)
{
Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, p0 + i);
Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i);
Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i);
Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i);
}
}
}
internal readonly struct WXYZShuffle4 : IComponentShuffle
{
public byte Control => SimdUtils.Shuffle.MmShuffle(2, 1, 0, 3);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ReadOnlySpan<uint> s = MemoryMarshal.Cast<byte, uint>(source);
Span<uint> d = MemoryMarshal.Cast<byte, uint>(dest);
ref uint sBase = ref MemoryMarshal.GetReference(s);
ref uint dBase = ref MemoryMarshal.GetReference(d);
// The JIT can detect and optimize rotation idioms ROTL (Rotate Left)
// and ROTR (Rotate Right) emitting efficient CPU instructions:
// https://github.com/dotnet/coreclr/pull/1830
for (int i = 0; i < s.Length; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// ROTL(8, packed) = [Z Y X W]
Unsafe.Add(ref dBase, i) = (packed << 8) | (packed >> 24);
}
}
}
internal readonly struct WZYXShuffle4 : IComponentShuffle
{
public byte Control => SimdUtils.Shuffle.MmShuffle(0, 1, 2, 3);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ReadOnlySpan<uint> s = MemoryMarshal.Cast<byte, uint>(source);
Span<uint> d = MemoryMarshal.Cast<byte, uint>(dest);
ref uint sBase = ref MemoryMarshal.GetReference(s);
ref uint dBase = ref MemoryMarshal.GetReference(d);
for (int i = 0; i < s.Length; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// REVERSE(packedArgb) = [X Y Z W]
Unsafe.Add(ref dBase, i) = BinaryPrimitives.ReverseEndianness(packed);
}
}
}
internal readonly struct YZWXShuffle4 : IComponentShuffle
{
public byte Control => SimdUtils.Shuffle.MmShuffle(0, 3, 2, 1);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ReadOnlySpan<uint> s = MemoryMarshal.Cast<byte, uint>(source);
Span<uint> d = MemoryMarshal.Cast<byte, uint>(dest);
ref uint sBase = ref MemoryMarshal.GetReference(s);
ref uint dBase = ref MemoryMarshal.GetReference(d);
for (int i = 0; i < s.Length; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// ROTR(8, packedArgb) = [Y Z W X]
Unsafe.Add(ref dBase, i) = (packed >> 8) | (packed << 24);
}
}
}
internal readonly struct ZYXWShuffle4 : IComponentShuffle
{
public byte Control => SimdUtils.Shuffle.MmShuffle(3, 0, 1, 2);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ReadOnlySpan<uint> s = MemoryMarshal.Cast<byte, uint>(source);
Span<uint> d = MemoryMarshal.Cast<byte, uint>(dest);
ref uint sBase = ref MemoryMarshal.GetReference(s);
ref uint dBase = ref MemoryMarshal.GetReference(d);
for (int i = 0; i < s.Length; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// tmp1 = [W 0 Y 0]
// tmp2 = [0 Z 0 X]
// tmp3=ROTL(16, tmp2) = [0 X 0 Z]
// tmp1 + tmp3 = [W X Y Z]
uint tmp1 = packed & 0xFF00FF00;
uint tmp2 = packed & 0x00FF00FF;
uint tmp3 = (tmp2 << 16) | (tmp2 >> 16);
Unsafe.Add(ref dBase, i) = tmp1 + tmp3;
}
}
}
}

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

@ -18,6 +18,234 @@ namespace SixLabors.ImageSharp
public static ReadOnlySpan<byte> PermuteMaskEvenOdd8x32 => new byte[] { 0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 };
/// <summary>
/// Shuffle single-precision (32-bit) floating-point elements in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// </summary>
/// <param name="source">The source span of floats.</param>
/// <param name="dest">The destination span of floats.</param>
/// <param name="control">The byte control.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Shuffle4ChannelReduce(
ref ReadOnlySpan<float> source,
ref Span<float> dest,
byte control)
{
if (Avx.IsSupported || Sse.IsSupported)
{
int remainder = Avx.IsSupported
? ImageMaths.ModuloP2(source.Length, Vector256<float>.Count)
: ImageMaths.ModuloP2(source.Length, Vector128<float>.Count);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)
{
Shuffle4Channel(
source.Slice(0, adjustedCount),
dest.Slice(0, adjustedCount),
control);
source = source.Slice(adjustedCount);
dest = dest.Slice(adjustedCount);
}
}
}
/// <summary>
/// Shuffle 8-bit integers in a within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
/// <param name="control">The byte control.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Shuffle4ChannelReduce(
ref ReadOnlySpan<byte> source,
ref Span<byte> dest,
byte control)
{
if (Avx2.IsSupported || Ssse3.IsSupported)
{
int remainder = Avx2.IsSupported
? ImageMaths.ModuloP2(source.Length, Vector256<byte>.Count)
: ImageMaths.ModuloP2(source.Length, Vector128<byte>.Count);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)
{
Shuffle4Channel(
source.Slice(0, adjustedCount),
dest.Slice(0, adjustedCount),
control);
source = source.Slice(adjustedCount);
dest = dest.Slice(adjustedCount);
}
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void Shuffle4Channel(
ReadOnlySpan<float> source,
Span<float> dest,
byte control)
{
if (Avx.IsSupported)
{
ref Vector256<float> sourceBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref Vector256<float> destBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(dest));
int n = dest.Length / Vector256<float>.Count;
int m = ImageMaths.Modulo4(n);
int u = n - m;
for (int i = 0; i < u; i += 4)
{
ref Vector256<float> vd0 = ref Unsafe.Add(ref destBase, i);
ref Vector256<float> vs0 = ref Unsafe.Add(ref sourceBase, i);
vd0 = Avx.Permute(vs0, control);
Unsafe.Add(ref vd0, 1) = Avx.Permute(Unsafe.Add(ref vs0, 1), control);
Unsafe.Add(ref vd0, 2) = Avx.Permute(Unsafe.Add(ref vs0, 2), control);
Unsafe.Add(ref vd0, 3) = Avx.Permute(Unsafe.Add(ref vs0, 3), control);
}
if (m > 0)
{
for (int i = u; i < n; i++)
{
Unsafe.Add(ref destBase, i) = Avx.Permute(Unsafe.Add(ref sourceBase, i), control);
}
}
}
else
{
// Sse
ref Vector128<float> sourceBase =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(source));
ref Vector128<float> destBase =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(dest));
int n = dest.Length / Vector128<float>.Count;
int m = ImageMaths.Modulo4(n);
int u = n - m;
for (int i = 0; i < u; i += 4)
{
ref Vector128<float> vd0 = ref Unsafe.Add(ref destBase, i);
ref Vector128<float> vs0 = ref Unsafe.Add(ref sourceBase, i);
vd0 = Sse.Shuffle(vs0, vs0, control);
Vector128<float> vs1 = Unsafe.Add(ref vs0, 1);
Unsafe.Add(ref vd0, 1) = Sse.Shuffle(vs1, vs1, control);
Vector128<float> vs2 = Unsafe.Add(ref vs0, 2);
Unsafe.Add(ref vd0, 2) = Sse.Shuffle(vs2, vs2, control);
Vector128<float> vs3 = Unsafe.Add(ref vs0, 3);
Unsafe.Add(ref vd0, 3) = Sse.Shuffle(vs3, vs3, control);
}
if (m > 0)
{
for (int i = u; i < n; i++)
{
Vector128<float> vs = Unsafe.Add(ref sourceBase, i);
Unsafe.Add(ref destBase, i) = Sse.Shuffle(vs, vs, control);
}
}
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void Shuffle4Channel(
ReadOnlySpan<byte> source,
Span<byte> dest,
byte control)
{
if (Avx2.IsSupported)
{
// I've chosen to do this for convenience while we determine what
// shuffle controls to add to the library.
// We can add static ROS instances if need be in the future.
Span<byte> bytes = stackalloc byte[Vector256<byte>.Count];
Shuffle.MmShuffleSpan(ref bytes, control);
Vector256<byte> vcm = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(bytes));
ref Vector256<byte> sourceBase =
ref Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(source));
ref Vector256<byte> destBase =
ref Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(dest));
int n = dest.Length / Vector256<byte>.Count;
int m = ImageMaths.Modulo4(n);
int u = n - m;
for (int i = 0; i < u; i += 4)
{
ref Vector256<byte> vs0 = ref Unsafe.Add(ref sourceBase, i);
ref Vector256<byte> vd0 = ref Unsafe.Add(ref destBase, i);
vd0 = Avx2.Shuffle(vs0, vcm);
Unsafe.Add(ref vd0, 1) = Avx2.Shuffle(Unsafe.Add(ref vs0, 1), vcm);
Unsafe.Add(ref vd0, 2) = Avx2.Shuffle(Unsafe.Add(ref vs0, 2), vcm);
Unsafe.Add(ref vd0, 3) = Avx2.Shuffle(Unsafe.Add(ref vs0, 3), vcm);
}
if (m > 0)
{
for (int i = u; i < n; i++)
{
Unsafe.Add(ref destBase, i) = Avx2.Shuffle(Unsafe.Add(ref sourceBase, i), vcm);
}
}
}
else
{
// Ssse3
Span<byte> bytes = stackalloc byte[Vector128<byte>.Count];
Shuffle.MmShuffleSpan(ref bytes, control);
Vector128<byte> vcm = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(bytes));
ref Vector128<byte> sourceBase =
ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(source));
ref Vector128<byte> destBase =
ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(dest));
int n = dest.Length / Vector128<byte>.Count;
int m = ImageMaths.Modulo4(n);
int u = n - m;
for (int i = 0; i < u; i += 4)
{
ref Vector128<byte> vs0 = ref Unsafe.Add(ref sourceBase, i);
ref Vector128<byte> vd0 = ref Unsafe.Add(ref destBase, i);
vd0 = Ssse3.Shuffle(vs0, vcm);
Unsafe.Add(ref vd0, 1) = Ssse3.Shuffle(Unsafe.Add(ref vs0, 1), vcm);
Unsafe.Add(ref vd0, 2) = Ssse3.Shuffle(Unsafe.Add(ref vs0, 2), vcm);
Unsafe.Add(ref vd0, 3) = Ssse3.Shuffle(Unsafe.Add(ref vs0, 3), vcm);
}
if (m > 0)
{
for (int i = u; i < n; i++)
{
Unsafe.Add(ref destBase, i) = Ssse3.Shuffle(Unsafe.Add(ref sourceBase, i), vcm);
}
}
}
}
/// <summary>
/// Performs a multiplication and an addition of the <see cref="Vector256{T}"/>.
/// </summary>

141
src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs

@ -0,0 +1,141 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp
{
internal static partial class SimdUtils
{
/// <summary>
/// Shuffle single-precision (32-bit) floating-point elements in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// </summary>
/// <param name="source">The source span of floats.</param>
/// <param name="dest">The destination span of floats.</param>
/// <param name="control">The byte control.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Shuffle4Channel(
ReadOnlySpan<float> source,
Span<float> dest,
byte control)
{
VerifyShuffleSpanInput(source, dest);
#if SUPPORTS_RUNTIME_INTRINSICS
HwIntrinsics.Shuffle4ChannelReduce(ref source, ref dest, control);
#endif
// Deal with the remainder:
if (source.Length > 0)
{
ShuffleRemainder4Channel(source, dest, control);
}
}
/// <summary>
/// Shuffle 8-bit integers within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
/// <param name="shuffle">The type of shuffle to perform.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Shuffle4Channel<TShuffle>(
ReadOnlySpan<byte> source,
Span<byte> dest,
TShuffle shuffle)
where TShuffle : struct, IComponentShuffle
{
VerifyShuffleSpanInput(source, dest);
#if SUPPORTS_RUNTIME_INTRINSICS
HwIntrinsics.Shuffle4ChannelReduce(ref source, ref dest, shuffle.Control);
#endif
// Deal with the remainder:
if (source.Length > 0)
{
shuffle.RunFallbackShuffle(source, dest);
}
}
public static void ShuffleRemainder4Channel(
ReadOnlySpan<float> source,
Span<float> dest,
byte control)
{
ref float sBase = ref MemoryMarshal.GetReference(source);
ref float dBase = ref MemoryMarshal.GetReference(dest);
Shuffle.InverseMmShuffle(control, out int p3, out int p2, out int p1, out int p0);
for (int i = 0; i < source.Length; i += 4)
{
Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, p0 + i);
Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i);
Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i);
Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i);
}
}
[Conditional("DEBUG")]
private static void VerifyShuffleSpanInput<T>(ReadOnlySpan<T> source, Span<T> dest)
where T : struct
{
DebugGuard.IsTrue(
source.Length == dest.Length,
nameof(source),
"Input spans must be of same length!");
DebugGuard.IsTrue(
source.Length % 4 == 0,
nameof(source),
"Input spans must be divisiable by 4!");
}
public static class Shuffle
{
[MethodImpl(InliningOptions.ShortMethod)]
public static byte MmShuffle(byte p3, byte p2, byte p1, byte p0)
=> (byte)((p3 << 6) | (p2 << 4) | (p1 << 2) | p0);
[MethodImpl(InliningOptions.ShortMethod)]
public static void MmShuffleSpan(ref Span<byte> span, byte control)
{
InverseMmShuffle(
control,
out int p3,
out int p2,
out int p1,
out int p0);
ref byte spanBase = ref MemoryMarshal.GetReference(span);
for (int i = 0; i < span.Length; i += 4)
{
Unsafe.Add(ref spanBase, i) = (byte)(p0 + i);
Unsafe.Add(ref spanBase, i + 1) = (byte)(p1 + i);
Unsafe.Add(ref spanBase, i + 2) = (byte)(p2 + i);
Unsafe.Add(ref spanBase, i + 3) = (byte)(p3 + i);
}
}
[MethodImpl(InliningOptions.ShortMethod)]
public static void InverseMmShuffle(
byte control,
out int p3,
out int p2,
out int p1,
out int p0)
{
p3 = control >> 6 & 0x3;
p2 = control >> 4 & 0x3;
p1 = control >> 2 & 0x3;
p0 = control >> 0 & 0x3;
}
}
}
}

64
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs

@ -53,66 +53,58 @@ namespace SixLabors.ImageSharp.PixelFormats
Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale));
}
/// <inheritdoc />
public override void ToRgba32(Configuration configuration, ReadOnlySpan<Argb32> sourcePixels, Span<Rgba32> destinationPixels)
public override void ToRgba32(
Configuration configuration,
ReadOnlySpan<Argb32> sourcePixels,
Span<Rgba32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Argb32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Rgba32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromArgb32.ToRgba32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Argb32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Rgba32, byte>(destinationPixels);
PixelConverter.FromArgb32.ToRgba32(source, dest);
}
/// <inheritdoc />
public override void FromRgba32(Configuration configuration, ReadOnlySpan<Rgba32> sourcePixels, Span<Argb32> destinationPixels)
public override void FromRgba32(
Configuration configuration,
ReadOnlySpan<Rgba32> sourcePixels,
Span<Argb32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Rgba32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Argb32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromRgba32.ToArgb32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Rgba32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Argb32, byte>(destinationPixels);
PixelConverter.FromRgba32.ToArgb32(source, dest);
}
/// <inheritdoc />
public override void ToBgra32(Configuration configuration, ReadOnlySpan<Argb32> sourcePixels, Span<Bgra32> destinationPixels)
public override void ToBgra32(
Configuration configuration,
ReadOnlySpan<Argb32> sourcePixels,
Span<Bgra32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Argb32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Bgra32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromArgb32.ToBgra32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Argb32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Bgra32, byte>(destinationPixels);
PixelConverter.FromArgb32.ToBgra32(source, dest);
}
/// <inheritdoc />
public override void FromBgra32(Configuration configuration, ReadOnlySpan<Bgra32> sourcePixels, Span<Argb32> destinationPixels)
public override void FromBgra32(
Configuration configuration,
ReadOnlySpan<Bgra32> sourcePixels,
Span<Argb32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Bgra32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Argb32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromBgra32.ToArgb32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Bgra32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Argb32, byte>(destinationPixels);
PixelConverter.FromBgra32.ToArgb32(source, dest);
}
/// <inheritdoc />

64
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs

@ -53,66 +53,58 @@ namespace SixLabors.ImageSharp.PixelFormats
Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale));
}
/// <inheritdoc />
public override void ToRgba32(Configuration configuration, ReadOnlySpan<Bgra32> sourcePixels, Span<Rgba32> destinationPixels)
public override void ToRgba32(
Configuration configuration,
ReadOnlySpan<Bgra32> sourcePixels,
Span<Rgba32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Bgra32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Rgba32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromBgra32.ToRgba32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Bgra32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Rgba32, byte>(destinationPixels);
PixelConverter.FromBgra32.ToRgba32(source, dest);
}
/// <inheritdoc />
public override void FromRgba32(Configuration configuration, ReadOnlySpan<Rgba32> sourcePixels, Span<Bgra32> destinationPixels)
public override void FromRgba32(
Configuration configuration,
ReadOnlySpan<Rgba32> sourcePixels,
Span<Bgra32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Rgba32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Bgra32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromRgba32.ToBgra32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Rgba32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Bgra32, byte>(destinationPixels);
PixelConverter.FromRgba32.ToBgra32(source, dest);
}
/// <inheritdoc />
public override void ToArgb32(Configuration configuration, ReadOnlySpan<Bgra32> sourcePixels, Span<Argb32> destinationPixels)
public override void ToArgb32(
Configuration configuration,
ReadOnlySpan<Bgra32> sourcePixels,
Span<Argb32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Bgra32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Argb32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromBgra32.ToArgb32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Bgra32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Argb32, byte>(destinationPixels);
PixelConverter.FromBgra32.ToArgb32(source, dest);
}
/// <inheritdoc />
public override void FromArgb32(Configuration configuration, ReadOnlySpan<Argb32> sourcePixels, Span<Bgra32> destinationPixels)
public override void FromArgb32(
Configuration configuration,
ReadOnlySpan<Argb32> sourcePixels,
Span<Bgra32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Argb32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Bgra32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromArgb32.ToBgra32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Argb32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Bgra32, byte>(destinationPixels);
PixelConverter.FromArgb32.ToBgra32(source, dest);
}
/// <inheritdoc />

64
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs

@ -42,66 +42,58 @@ namespace SixLabors.ImageSharp.PixelFormats
}
/// <inheritdoc />
public override void ToArgb32(Configuration configuration, ReadOnlySpan<Rgba32> sourcePixels, Span<Argb32> destinationPixels)
public override void ToArgb32(
Configuration configuration,
ReadOnlySpan<Rgba32> sourcePixels,
Span<Argb32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Rgba32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Argb32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromRgba32.ToArgb32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Rgba32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Argb32, byte>(destinationPixels);
PixelConverter.FromRgba32.ToArgb32(source, dest);
}
/// <inheritdoc />
public override void FromArgb32(Configuration configuration, ReadOnlySpan<Argb32> sourcePixels, Span<Rgba32> destinationPixels)
public override void FromArgb32(
Configuration configuration,
ReadOnlySpan<Argb32> sourcePixels,
Span<Rgba32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Argb32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Rgba32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromArgb32.ToRgba32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Argb32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Rgba32, byte>(destinationPixels);
PixelConverter.FromArgb32.ToRgba32(source, dest);
}
/// <inheritdoc />
public override void ToBgra32(Configuration configuration, ReadOnlySpan<Rgba32> sourcePixels, Span<Bgra32> destinationPixels)
public override void ToBgra32(
Configuration configuration,
ReadOnlySpan<Rgba32> sourcePixels,
Span<Bgra32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Rgba32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Bgra32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromRgba32.ToBgra32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Rgba32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Bgra32, byte>(destinationPixels);
PixelConverter.FromRgba32.ToBgra32(source, dest);
}
/// <inheritdoc />
public override void FromBgra32(Configuration configuration, ReadOnlySpan<Bgra32> sourcePixels, Span<Rgba32> destinationPixels)
public override void FromBgra32(
Configuration configuration,
ReadOnlySpan<Bgra32> sourcePixels,
Span<Rgba32> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<Bgra32,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<Rgba32, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.FromBgra32.ToRgba32(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<Bgra32, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<Rgba32, byte>(destinationPixels);
PixelConverter.FromBgra32.ToRgba32(source, dest);
}
/// <inheritdoc />

32
src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude

@ -88,35 +88,31 @@ using System.Runtime.InteropServices;
{
#>
/// <inheritdoc />
public override void To<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=thisPixelType#>> sourcePixels, Span<<#=otherPixelType#>> destinationPixels)
public override void To<#=otherPixelType#>(
Configuration configuration,
ReadOnlySpan<<#=thisPixelType#>> sourcePixels,
Span<<#=otherPixelType#>> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<<#=thisPixelType#>,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<<#=otherPixelType#>, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.From<#=thisPixelType#>.To<#=otherPixelType#>(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<<#=thisPixelType#>, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<<#=otherPixelType#>, byte>(destinationPixels);
PixelConverter.From<#=thisPixelType#>.To<#=otherPixelType#>(source, dest);
}
/// <inheritdoc />
public override void From<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=otherPixelType#>> sourcePixels, Span<<#=thisPixelType#>> destinationPixels)
public override void From<#=otherPixelType#>(
Configuration configuration,
ReadOnlySpan<<#=otherPixelType#>> sourcePixels,
Span<<#=thisPixelType#>> destinationPixels)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
ref uint sourceRef = ref Unsafe.As<<#=otherPixelType#>,uint>(ref MemoryMarshal.GetReference(sourcePixels));
ref uint destRef = ref Unsafe.As<<#=thisPixelType#>, uint>(ref MemoryMarshal.GetReference(destinationPixels));
for (int i = 0; i < sourcePixels.Length; i++)
{
uint sp = Unsafe.Add(ref sourceRef, i);
Unsafe.Add(ref destRef, i) = PixelConverter.From<#=otherPixelType#>.To<#=thisPixelType#>(sp);
}
ReadOnlySpan<byte> source = MemoryMarshal.Cast<<#=otherPixelType#>, byte>(sourcePixels);
Span<byte> dest = MemoryMarshal.Cast<<#=thisPixelType#>, byte>(destinationPixels);
PixelConverter.From<#=otherPixelType#>.To<#=thisPixelType#>(source, dest);
}
<#+
}

90
src/ImageSharp/PixelFormats/Utils/PixelConverter.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Buffers.Binary;
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.PixelFormats.Utils
@ -21,88 +21,64 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils
public static class FromRgba32
{
/// <summary>
/// Converts a packed <see cref="Rgba32"/> to <see cref="Argb32"/>.
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Rgba32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Argb32"/> pixels.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint ToArgb32(uint packedRgba)
{
// packedRgba = [aa bb gg rr]
// ROTL(8, packedRgba) = [bb gg rr aa]
return (packedRgba << 8) | (packedRgba >> 24);
}
public static void ToArgb32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Channel<WXYZShuffle4>(source, dest, default);
/// <summary>
/// Converts a packed <see cref="Rgba32"/> to <see cref="Bgra32"/>.
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Rgba32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgra32"/> pixels.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint ToBgra32(uint packedRgba)
{
// packedRgba = [aa bb gg rr]
// tmp1 = [aa 00 gg 00]
// tmp2 = [00 bb 00 rr]
// tmp3=ROTL(16, tmp2) = [00 rr 00 bb]
// tmp1 + tmp3 = [aa rr gg bb]
uint tmp1 = packedRgba & 0xFF00FF00;
uint tmp2 = packedRgba & 0x00FF00FF;
uint tmp3 = (tmp2 << 16) | (tmp2 >> 16);
return tmp1 + tmp3;
}
public static void ToBgra32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Channel<ZYXWShuffle4>(source, dest, default);
}
public static class FromArgb32
{
/// <summary>
/// Converts a packed <see cref="Argb32"/> to <see cref="Rgba32"/>.
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Argb32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Rgba32"/> pixels.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint ToRgba32(uint packedArgb)
{
// packedArgb = [bb gg rr aa]
// ROTR(8, packedArgb) = [aa bb gg rr]
return (packedArgb >> 8) | (packedArgb << 24);
}
public static void ToRgba32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Channel<YZWXShuffle4>(source, dest, default);
/// <summary>
/// Converts a packed <see cref="Argb32"/> to <see cref="Bgra32"/>.
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Argb32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgra32"/> pixels.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint ToBgra32(uint packedArgb)
{
// packedArgb = [bb gg rr aa]
// REVERSE(packedArgb) = [aa rr gg bb]
return BinaryPrimitives.ReverseEndianness(packedArgb);
}
public static void ToBgra32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Channel<WZYXShuffle4>(source, dest, default);
}
public static class FromBgra32
{
/// <summary>
/// Converts a packed <see cref="Bgra32"/> to <see cref="Argb32"/>.
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Bgra32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Argb32"/> pixels.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint ToArgb32(uint packedBgra)
{
// packedBgra = [aa rr gg bb]
// REVERSE(packedBgra) = [bb gg rr aa]
return BinaryPrimitives.ReverseEndianness(packedBgra);
}
public static void ToArgb32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Channel<WZYXShuffle4>(source, dest, default);
/// <summary>
/// Converts a packed <see cref="Rgba32"/> to <see cref="Bgra32"/>.
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Bgra32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgra32"/> pixels.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint ToRgba32(uint packedBgra)
{
// packedRgba = [aa rr gg bb]
// tmp1 = [aa 00 gg 00]
// tmp2 = [00 rr 00 bb]
// tmp3=ROTL(16, tmp2) = [00 bb 00 rr]
// tmp1 + tmp3 = [aa bb gg rr]
uint tmp1 = packedBgra & 0xFF00FF00;
uint tmp2 = packedBgra & 0x00FF00FF;
uint tmp3 = (tmp2 << 16) | (tmp2 >> 16);
return tmp1 + tmp3;
}
public static void ToRgba32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Channel<ZYXWShuffle4>(source, dest, default);
}
}
}
}

67
tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleByte4Channel.cs

@ -0,0 +1,67 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using BenchmarkDotNet.Attributes;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class ShuffleByte4Channel
{
private byte[] source;
private byte[] destination;
[GlobalSetup]
public void Setup()
{
this.source = new byte[this.Count];
new Random(this.Count).NextBytes(this.source);
this.destination = new byte[this.Count];
}
[Params(128, 256, 512, 1024, 2048)]
public int Count { get; set; }
[Benchmark]
public void Shuffle4Channel()
{
SimdUtils.Shuffle4Channel<WXYZShuffle4>(this.source, this.destination, default);
}
}
// 2020-10-29
// ##########
//
// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1)
// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
// .NET Core SDK=3.1.403
// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
//
// Runtime=.NET Core 3.1
//
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|--------:|------:|------:|------:|----------:|
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 17.39 ns | 0.187 ns | 0.175 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 128 | 21.72 ns | 0.299 ns | 0.279 ns | 1.25 | 0.02 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 128 | 18.10 ns | 0.346 ns | 0.289 ns | 1.04 | 0.02 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 35.51 ns | 0.711 ns | 0.790 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 256 | 23.90 ns | 0.508 ns | 0.820 ns | 0.69 | 0.02 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 256 | 20.40 ns | 0.133 ns | 0.111 ns | 0.57 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 73.39 ns | 0.310 ns | 0.259 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 512 | 26.10 ns | 0.418 ns | 0.391 ns | 0.36 | 0.01 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 512 | 27.59 ns | 0.556 ns | 0.571 ns | 0.38 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 150.64 ns | 2.903 ns | 2.716 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 1024 | 38.67 ns | 0.801 ns | 1.889 ns | 0.24 | 0.02 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 47.13 ns | 0.948 ns | 1.054 ns | 0.31 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 315.29 ns | 5.206 ns | 6.583 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 2048 | 57.37 ns | 1.152 ns | 1.078 ns | 0.18 | 0.01 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 65.75 ns | 1.198 ns | 1.600 ns | 0.21 | 0.01 | - | - | - | - |
}

68
tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleFloat4Channel.cs

@ -0,0 +1,68 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class ShuffleFloat4Channel
{
private static readonly byte control = default(WXYZShuffle4).Control;
private float[] source;
private float[] destination;
[GlobalSetup]
public void Setup()
{
this.source = new Random(this.Count).GenerateRandomFloatArray(this.Count, 0, 256);
this.destination = new float[this.Count];
}
[Params(128, 256, 512, 1024, 2048)]
public int Count { get; set; }
[Benchmark]
public void Shuffle4Channel()
{
SimdUtils.Shuffle4Channel(this.source, this.destination, control);
}
}
// 2020-10-29
// ##########
//
// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1)
// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
// .NET Core SDK=3.1.403
// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
//
// Runtime=.NET Core 3.1
//
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------------- |------------------- |-------------------------------------------------- |------ |-----------:|----------:|----------:|------:|------:|------:|------:|----------:|
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 63.647 ns | 0.5475 ns | 0.4853 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 128 | 9.818 ns | 0.1457 ns | 0.1292 ns | 0.15 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 128 | 15.267 ns | 0.1005 ns | 0.0940 ns | 0.24 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 125.586 ns | 1.9312 ns | 1.8064 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 256 | 15.878 ns | 0.1983 ns | 0.1758 ns | 0.13 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 256 | 29.170 ns | 0.2925 ns | 0.2442 ns | 0.23 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 263.859 ns | 2.6660 ns | 2.3634 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 512 | 29.452 ns | 0.3334 ns | 0.3118 ns | 0.11 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 512 | 52.912 ns | 0.1932 ns | 0.1713 ns | 0.20 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 495.717 ns | 1.9850 ns | 1.8567 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 1024 | 53.757 ns | 0.3212 ns | 0.2847 ns | 0.11 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 107.815 ns | 1.6201 ns | 1.3528 ns | 0.22 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 980.134 ns | 3.7407 ns | 3.1237 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 2048 | 105.120 ns | 0.6140 ns | 0.5443 ns | 0.11 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 216.473 ns | 2.3268 ns | 2.0627 ns | 0.22 | - | - | - | - |
}

15
tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs

@ -58,25 +58,26 @@ namespace SixLabors.ImageSharp.Benchmarks
{
public HwIntrinsics_SSE_AVX()
{
this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31)
.WithEnvironmentVariables(
new EnvironmentVariable(EnableHWIntrinsic, Off),
new EnvironmentVariable(FeatureSIMD, Off))
.WithId("1. No HwIntrinsics").AsBaseline());
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported)
{
this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31)
.WithId("AVX").AsBaseline());
.WithId("2. AVX"));
}
if (Sse.IsSupported)
{
this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31)
.WithEnvironmentVariables(new EnvironmentVariable(EnableAVX, Off))
.WithId("SSE"));
.WithId("3. SSE"));
}
#endif
this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31)
.WithEnvironmentVariables(
new EnvironmentVariable(EnableHWIntrinsic, Off),
new EnvironmentVariable(FeatureSIMD, Off))
.WithId("No HwIntrinsics"));
}
}
}

56
tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs

@ -168,49 +168,27 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion
[Benchmark]
public void PixelConverter_Rgba32_ToArgb32()
{
ref uint sBase = ref Unsafe.As<Rgba32, uint>(ref this.PermutedRunnerRgbaToArgb.Source[0]);
ref uint dBase = ref Unsafe.As<TestArgb, uint>(ref this.PermutedRunnerRgbaToArgb.Dest[0]);
Span<byte> source = MemoryMarshal.Cast<Rgba32, byte>(this.PermutedRunnerRgbaToArgb.Source);
Span<byte> dest = MemoryMarshal.Cast<TestArgb, byte>(this.PermutedRunnerRgbaToArgb.Dest);
for (int i = 0; i < this.Count; i++)
{
uint s = Unsafe.Add(ref sBase, i);
Unsafe.Add(ref dBase, i) = PixelConverter.FromRgba32.ToArgb32(s);
}
}
[Benchmark]
public void PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer()
{
Span<uint> source = MemoryMarshal.Cast<Rgba32, uint>(this.PermutedRunnerRgbaToArgb.Source);
Span<uint> dest = MemoryMarshal.Cast<TestArgb, uint>(this.PermutedRunnerRgbaToArgb.Dest);
source.CopyTo(dest);
ref uint dBase = ref MemoryMarshal.GetReference(dest);
for (int i = 0; i < this.Count; i++)
{
uint s = Unsafe.Add(ref dBase, i);
Unsafe.Add(ref dBase, i) = PixelConverter.FromRgba32.ToArgb32(s);
}
PixelConverter.FromRgba32.ToArgb32(source, dest);
}
/*
RESULTS:
Method | Count | Mean | Error | StdDev | Scaled | ScaledSD |
---------------------------------------------------------- |------ |-----------:|-----------:|-----------:|-------:|---------:|
ByRef | 256 | 328.7 ns | 6.6141 ns | 6.1868 ns | 1.00 | 0.00 |
ByVal | 256 | 322.0 ns | 4.3541 ns | 4.0728 ns | 0.98 | 0.02 |
FromBytes | 256 | 321.5 ns | 3.3499 ns | 3.1335 ns | 0.98 | 0.02 |
InlineShuffle | 256 | 330.7 ns | 4.2525 ns | 3.9778 ns | 1.01 | 0.02 |
PixelConverter_Rgba32_ToArgb32 | 256 | 167.4 ns | 0.6357 ns | 0.5309 ns | 0.51 | 0.01 |
PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 256 | 196.6 ns | 0.8929 ns | 0.7915 ns | 0.60 | 0.01 |
| | | | | | |
ByRef | 2048 | 2,534.4 ns | 8.2947 ns | 6.9265 ns | 1.00 | 0.00 |
ByVal | 2048 | 2,638.5 ns | 52.6843 ns | 70.3320 ns | 1.04 | 0.03 |
FromBytes | 2048 | 2,517.2 ns | 40.8055 ns | 38.1695 ns | 0.99 | 0.01 |
InlineShuffle | 2048 | 2,546.5 ns | 21.2506 ns | 19.8778 ns | 1.00 | 0.01 |
PixelConverter_Rgba32_ToArgb32 | 2048 | 1,265.7 ns | 5.1397 ns | 4.5562 ns | 0.50 | 0.00 |
PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 2048 | 1,410.3 ns | 11.1939 ns | 9.9231 ns | 0.56 | 0.00 |
*/
| Method | Count | Mean | Error | StdDev | Median | Ratio | RatioSD |
|------------------------------- |------ |------------:|----------:|----------:|------------:|------:|--------:|
| ByRef | 256 | 288.84 ns | 19.601 ns | 52.319 ns | 268.10 ns | 1.00 | 0.00 |
| ByVal | 256 | 267.97 ns | 1.831 ns | 1.713 ns | 267.85 ns | 0.77 | 0.18 |
| FromBytes | 256 | 266.81 ns | 2.427 ns | 2.270 ns | 266.47 ns | 0.76 | 0.18 |
| InlineShuffle | 256 | 291.41 ns | 5.820 ns | 5.444 ns | 290.17 ns | 0.83 | 0.19 |
| PixelConverter_Rgba32_ToArgb32 | 256 | 38.62 ns | 0.431 ns | 0.403 ns | 38.68 ns | 0.11 | 0.03 |
| | | | | | | | |
| ByRef | 2048 | 2,197.69 ns | 15.826 ns | 14.804 ns | 2,197.25 ns | 1.00 | 0.00 |
| ByVal | 2048 | 2,226.81 ns | 44.266 ns | 62.054 ns | 2,197.17 ns | 1.03 | 0.04 |
| FromBytes | 2048 | 2,181.35 ns | 18.033 ns | 16.868 ns | 2,185.97 ns | 0.99 | 0.01 |
| InlineShuffle | 2048 | 2,233.10 ns | 27.673 ns | 24.531 ns | 2,229.78 ns | 1.02 | 0.01 |
| PixelConverter_Rgba32_ToArgb32 | 2048 | 139.90 ns | 2.152 ns | 3.825 ns | 138.70 ns | 0.06 | 0.00 |
*/
}
}

1
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -17,6 +17,7 @@
<ItemGroup>
<Compile Include="..\ImageSharp.Tests\TestImages.cs" Link="Tests\TestImages.cs" />
<Compile Include="..\ImageSharp.Tests\TestUtilities\TestEnvironment.cs" Link="Tests\TestEnvironment.cs" />
<Compile Include="..\ImageSharp.Tests\TestUtilities\TestDataGenerator.cs" Link="Tests\TestDataGenerator.cs" />
</ItemGroup>
<ItemGroup>

161
tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs

@ -0,0 +1,161 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Common
{
public partial class SimdUtilsTests
{
[Theory]
[MemberData(nameof(ArraySizesDivisibleBy4))]
public void BulkShuffleFloat4Channel(int count)
{
static void RunTest(string serialized)
{
// No need to test multiple shuffle controls as the
// pipeline is always the same.
int size = FeatureTestRunner.Deserialize<int>(serialized);
byte control = default(WZYXShuffle4).Control;
TestShuffleFloat4Channel(
size,
(s, d) => SimdUtils.Shuffle4Channel(s.Span, d.Span, control),
control);
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
count,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE);
}
[Theory]
[MemberData(nameof(ArraySizesDivisibleBy4))]
public void BulkShuffleByte4Channel(int count)
{
static void RunTest(string serialized)
{
int size = FeatureTestRunner.Deserialize<int>(serialized);
foreach (var item in ArraySizesDivisibleBy4)
{
// These cannot be expressed as a theory as you cannot
// use RemoteExecutor within generic methods nor pass
// IComponentShuffle to the generic utils method.
foreach (var count in item)
{
WXYZShuffle4 wxyz = default;
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4Channel(s.Span, d.Span, wxyz),
wxyz.Control);
WZYXShuffle4 wzyx = default;
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4Channel(s.Span, d.Span, wzyx),
wzyx.Control);
YZWXShuffle4 yzwx = default;
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4Channel(s.Span, d.Span, yzwx),
yzwx.Control);
ZYXWShuffle4 zyxw = default;
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4Channel(s.Span, d.Span, zyxw),
zyxw.Control);
var xwyz = new DefaultShuffle4(2, 1, 3, 0);
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4Channel(s.Span, d.Span, xwyz),
xwyz.Control);
var yyyy = new DefaultShuffle4(1, 1, 1, 1);
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4Channel(s.Span, d.Span, yyyy),
yyyy.Control);
var wwww = new DefaultShuffle4(3, 3, 3, 3);
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4Channel(s.Span, d.Span, wwww),
wwww.Control);
}
}
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
count,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE);
}
private static void TestShuffleFloat4Channel(
int count,
Action<Memory<float>, Memory<float>> convert,
byte control)
{
float[] source = new Random(count).GenerateRandomFloatArray(count, 0, 256);
var result = new float[count];
float[] expected = new float[count];
SimdUtils.Shuffle.InverseMmShuffle(
control,
out int p3,
out int p2,
out int p1,
out int p0);
for (int i = 0; i < expected.Length; i += 4)
{
expected[i] = source[p0 + i];
expected[i + 1] = source[p1 + i];
expected[i + 2] = source[p2 + i];
expected[i + 3] = source[p3 + i];
}
convert(source, result);
Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5F));
}
private static void TestShuffleByte4Channel(
int count,
Action<Memory<byte>, Memory<byte>> convert,
byte control)
{
byte[] source = new byte[count];
new Random(count).NextBytes(source);
var result = new byte[count];
byte[] expected = new byte[count];
SimdUtils.Shuffle.InverseMmShuffle(
control,
out int p3,
out int p2,
out int p1,
out int p0);
for (int i = 0; i < expected.Length; i += 4)
{
expected[i] = source[p0 + i];
expected[i + 1] = source[p1 + i];
expected[i + 2] = source[p2 + i];
expected[i + 3] = source[p3 + i];
}
convert(source, result);
Assert.Equal(expected, result);
}
}
}

14
tests/ImageSharp.Tests/Common/SimdUtilsTests.cs

@ -13,7 +13,7 @@ using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Common
{
public class SimdUtilsTests
public partial class SimdUtilsTests
{
private ITestOutputHelper Output { get; }
@ -212,14 +212,14 @@ namespace SixLabors.ImageSharp.Tests.Common
static void RunTest(string serialized)
{
TestImpl_BulkConvertByteToNormalizedFloat(
FeatureTestRunner.Deserialize(serialized),
FeatureTestRunner.Deserialize<int>(serialized),
(s, d) => SimdUtils.HwIntrinsics.ByteToNormalizedFloat(s.Span, d.Span));
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE41,
count);
count,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE41);
}
#endif
@ -305,14 +305,14 @@ namespace SixLabors.ImageSharp.Tests.Common
static void RunTest(string serialized)
{
TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(
FeatureTestRunner.Deserialize(serialized),
FeatureTestRunner.Deserialize<int>(serialized),
(s, d) => SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span));
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2,
count);
count,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
#endif

2
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -535,7 +535,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
static void RunTest(string serialized)
{
TestImageProvider<Rgba32> provider =
FeatureTestRunner.Deserialize<TestImageProvider<Rgba32>>(serialized);
FeatureTestRunner.DeserializeForXunit<TestImageProvider<Rgba32>>(serialized);
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{

60
tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs

@ -13,34 +13,49 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
{
public static class ReferenceImplementations
{
public static Rgba32 MakeRgba32(byte r, byte g, byte b, byte a)
public static byte[] MakeRgba32ByteArray(byte r, byte g, byte b, byte a)
{
Rgba32 d = default;
d.R = r;
d.G = g;
d.B = b;
d.A = a;
return d;
var buffer = new byte[256];
for (int i = 0; i < buffer.Length; i += 4)
{
buffer[i] = r;
buffer[i + 1] = g;
buffer[i + 2] = b;
buffer[i + 3] = a;
}
return buffer;
}
public static Argb32 MakeArgb32(byte r, byte g, byte b, byte a)
public static byte[] MakeArgb32ByteArray(byte r, byte g, byte b, byte a)
{
Argb32 d = default;
d.R = r;
d.G = g;
d.B = b;
d.A = a;
return d;
var buffer = new byte[256];
for (int i = 0; i < buffer.Length; i += 4)
{
buffer[i] = a;
buffer[i + 1] = r;
buffer[i + 2] = g;
buffer[i + 3] = b;
}
return buffer;
}
public static Bgra32 MakeBgra32(byte r, byte g, byte b, byte a)
public static byte[] MakeBgra32ByteArray(byte r, byte g, byte b, byte a)
{
Bgra32 d = default;
d.R = r;
d.G = g;
d.B = b;
d.A = a;
return d;
var buffer = new byte[256];
for (int i = 0; i < buffer.Length; i += 4)
{
buffer[i] = b;
buffer[i + 1] = g;
buffer[i + 2] = r;
buffer[i + 3] = a;
}
return buffer;
}
internal static void To<TSourcePixel, TDestinationPixel>(
@ -83,8 +98,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
if (typeof(TDestinationPixel) == typeof(L8))
{
ref L8 l8Ref = ref MemoryMarshal.GetReference(
MemoryMarshal.Cast<TDestinationPixel, L8>(destinationPixels));
ref L8 l8Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<TDestinationPixel, L8>(destinationPixels));
for (int i = 0; i < count; i++)
{
ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i);

67
tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.PixelFormats.Utils;
@ -33,30 +34,28 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
[MemberData(nameof(RgbaData))]
public void ToArgb32(byte r, byte g, byte b, byte a)
{
Rgba32 s = ReferenceImplementations.MakeRgba32(r, g, b, a);
byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a);
var actual = new byte[source.Length];
// Act:
uint actualPacked = PixelConverter.FromRgba32.ToArgb32(s.PackedValue);
PixelConverter.FromRgba32.ToArgb32(source, actual);
// Assert:
uint expectedPacked = ReferenceImplementations.MakeArgb32(r, g, b, a).PackedValue;
byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a);
Assert.Equal(expectedPacked, actualPacked);
Assert.Equal(expected, actual);
}
[Theory]
[MemberData(nameof(RgbaData))]
public void ToBgra32(byte r, byte g, byte b, byte a)
{
Rgba32 s = ReferenceImplementations.MakeRgba32(r, g, b, a);
byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a);
var actual = new byte[source.Length];
// Act:
uint actualPacked = PixelConverter.FromRgba32.ToBgra32(s.PackedValue);
PixelConverter.FromRgba32.ToBgra32(source, actual);
// Assert:
uint expectedPacked = ReferenceImplementations.MakeBgra32(r, g, b, a).PackedValue;
byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a);
Assert.Equal(expectedPacked, actualPacked);
Assert.Equal(expected, actual);
}
}
@ -66,30 +65,28 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
[MemberData(nameof(RgbaData))]
public void ToRgba32(byte r, byte g, byte b, byte a)
{
Argb32 s = ReferenceImplementations.MakeArgb32(r, g, b, a);
byte[] source = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a);
var actual = new byte[source.Length];
// Act:
uint actualPacked = PixelConverter.FromArgb32.ToRgba32(s.PackedValue);
PixelConverter.FromArgb32.ToRgba32(source, actual);
// Assert:
uint expectedPacked = ReferenceImplementations.MakeRgba32(r, g, b, a).PackedValue;
byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a);
Assert.Equal(expectedPacked, actualPacked);
Assert.Equal(expected, actual);
}
[Theory]
[MemberData(nameof(RgbaData))]
public void ToBgra32(byte r, byte g, byte b, byte a)
{
Argb32 s = ReferenceImplementations.MakeArgb32(r, g, b, a);
byte[] source = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a);
var actual = new byte[source.Length];
// Act:
uint actualPacked = PixelConverter.FromArgb32.ToBgra32(s.PackedValue);
PixelConverter.FromArgb32.ToBgra32(source, actual);
// Assert:
uint expectedPacked = ReferenceImplementations.MakeBgra32(r, g, b, a).PackedValue;
byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a);
Assert.Equal(expectedPacked, actualPacked);
Assert.Equal(expected, actual);
}
}
@ -99,30 +96,28 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
[MemberData(nameof(RgbaData))]
public void ToArgb32(byte r, byte g, byte b, byte a)
{
Bgra32 s = ReferenceImplementations.MakeBgra32(r, g, b, a);
byte[] source = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a);
var actual = new byte[source.Length];
// Act:
uint actualPacked = PixelConverter.FromBgra32.ToArgb32(s.PackedValue);
PixelConverter.FromBgra32.ToArgb32(source, actual);
// Assert:
uint expectedPacked = ReferenceImplementations.MakeArgb32(r, g, b, a).PackedValue;
byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a);
Assert.Equal(expectedPacked, actualPacked);
Assert.Equal(expected, actual);
}
[Theory]
[MemberData(nameof(RgbaData))]
public void ToRgba32(byte r, byte g, byte b, byte a)
{
Bgra32 s = ReferenceImplementations.MakeBgra32(r, g, b, a);
byte[] source = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a);
var actual = new byte[source.Length];
// Act:
uint actualPacked = PixelConverter.FromBgra32.ToRgba32(s.PackedValue);
PixelConverter.FromBgra32.ToRgba32(source, actual);
// Assert:
uint expectedPacked = ReferenceImplementations.MakeRgba32(r, g, b, a).PackedValue;
byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a);
Assert.Equal(expectedPacked, actualPacked);
Assert.Equal(expected, actual);
}
}
}

21
tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs

@ -29,17 +29,19 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <param name="value">The string value to deserialize.</param>
/// <returns>The <see cref="T"/> value.</returns>
public static T Deserialize<T>(string value)
public static T DeserializeForXunit<T>(string value)
where T : IXunitSerializable
=> BasicSerializer.Deserialize<T>(value);
/// <summary>
/// Allows the deserialization of integers passed to the feature test.
/// Allows the deserialization of types implementing <see cref="IConvertible"/>
/// passed to the feature test.
/// </summary>
/// <param name="value">The string value to deserialize.</param>
/// <returns>The <see cref="int"/> value.</returns>
public static int Deserialize(string value)
=> Convert.ToInt32(value);
/// <returns>The <typeparamref name="T"/> value.</returns>
public static T Deserialize<T>(string value)
where T : IConvertible
=> (T)Convert.ChangeType(value, typeof(T));
/// <summary>
/// Runs the given test <paramref name="action"/> within an environment
@ -214,12 +216,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities
/// where the given <paramref name="intrinsics"/> features.
/// </summary>
/// <param name="action">The test action to run.</param>
/// <param name="intrinsics">The intrinsics features.</param>
/// <param name="serializable">The value to pass as a parameter to the test action.</param>
public static void RunWithHwIntrinsicsFeature(
/// <param name="intrinsics">The intrinsics features.</param>
public static void RunWithHwIntrinsicsFeature<T>(
Action<string> action,
HwIntrinsics intrinsics,
int serializable)
T serializable,
HwIntrinsics intrinsics)
where T : IConvertible
{
if (!RemoteExecutor.IsSupported)
{

4
tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs

@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
static void AssertHwIntrinsicsFeatureDisabled(string serializable)
{
Assert.NotNull(serializable);
Assert.NotNull(FeatureTestRunner.Deserialize<FakeSerializable>(serializable));
Assert.NotNull(FeatureTestRunner.DeserializeForXunit<FakeSerializable>(serializable));
#if SUPPORTS_RUNTIME_INTRINSICS
Assert.False(Sse.IsSupported);
@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
static void AssertHwIntrinsicsFeatureDisabled(string serializable, string intrinsic)
{
Assert.NotNull(serializable);
Assert.NotNull(FeatureTestRunner.Deserialize<FakeSerializable>(serializable));
Assert.NotNull(FeatureTestRunner.DeserializeForXunit<FakeSerializable>(serializable));
switch ((HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic))
{

Loading…
Cancel
Save