Browse Source

Merge remote-tracking branch 'upstream/main' into tiff-frames-meta

pull/2363/head
James Jackson-South 3 years ago
parent
commit
3fcacc650d
  1. 5
      .gitattributes
  2. 6
      .github/workflows/build-and-test.yml
  3. 4
      README.md
  4. 2
      shared-infrastructure
  5. 2
      src/ImageSharp/Common/Constants.cs
  6. 6
      src/ImageSharp/Common/Helpers/Guard.cs
  7. 76
      src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs
  8. 39
      src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs
  9. 25
      src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs
  10. 34
      src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs
  11. 50
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  12. 281
      src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
  13. 2
      src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Intrinsic.cs
  14. 68
      src/ImageSharp/Formats/Webp/AlphaDecoder.cs
  15. 26
      src/ImageSharp/Formats/Webp/AlphaEncoder.cs
  16. 21
      src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs
  17. 4
      src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs
  18. 7
      src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs
  19. 4
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  20. 38
      src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
  21. 13
      src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
  22. 47
      src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs
  23. 31
      src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs
  24. 5
      src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs
  25. 19
      src/ImageSharp/Formats/Webp/Lossless/CostManager.cs
  26. 58
      src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs
  27. 3
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  28. 10
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  29. 9
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs
  30. 25
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  31. 1
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  32. 220
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  33. 2
      src/ImageSharp/Formats/Webp/WebpEncoder.cs
  34. 12
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
  35. 9
      src/ImageSharp/Formats/Webp/WebpImageInfo.cs
  36. 6
      src/ImageSharp/Formats/Webp/WebpThrowHelper.cs
  37. 3
      src/ImageSharp/ImageExtensions.cs
  38. 35
      src/ImageSharp/ImageFrame{TPixel}.cs
  39. 7
      src/ImageSharp/IndexedImageFrame{TPixel}.cs
  40. 8
      src/ImageSharp/Memory/Buffer2D{T}.cs
  41. 8112
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  42. 79
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  43. 1602
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs
  44. 174
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt
  45. 366
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  46. 88
      src/ImageSharp/PixelFormats/Utils/PixelConverter.cs
  47. 11
      src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs
  48. 2
      src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs
  49. 2
      src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs
  50. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
  51. 4
      src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
  52. 58
      tests/ImageSharp.Benchmarks/Color/Bulk/Pad3Shuffle4Channel.cs
  53. 39
      tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle3Channel.cs
  54. 68
      tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle4Slice3Channel.cs
  55. 39
      tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleByte4Channel.cs
  56. 40
      tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleFloat4Channel.cs
  57. 24
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs
  58. 68
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs
  59. 325
      tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs
  60. 8
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
  61. 73
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs
  62. 135
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs
  63. 17
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
  64. 46
      tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs

5
.gitattributes

@ -64,18 +64,19 @@
# Set explicit file behavior to:
# treat as text
# normalize to Unix-style line endings and
# use a union merge when resoling conflicts
# use a union merge when resolving conflicts
###############################################################################
*.csproj text eol=lf merge=union
*.dbproj text eol=lf merge=union
*.fsproj text eol=lf merge=union
*.ncrunchproject text eol=lf merge=union
*.vbproj text eol=lf merge=union
*.shproj text eol=lf merge=union
###############################################################################
# Set explicit file behavior to:
# treat as text
# normalize to Windows-style line endings and
# use a union merge when resoling conflicts
# use a union merge when resolving conflicts
###############################################################################
*.sln text eol=crlf merge=union
###############################################################################

6
.github/workflows/build-and-test.yml

@ -196,9 +196,9 @@ jobs:
shell: pwsh
run: ./ci-pack.ps1
- name: MyGet Publish
- name: Feedz Publish
shell: pwsh
run: |
dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v2/package
dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v3/index.json
dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/nuget/index.json
dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/symbols
# TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org

4
README.md

@ -54,9 +54,9 @@ For more information, see the [.NET Foundation Code of Conduct](https://dotnetfo
Install stable releases via Nuget; development releases are available via MyGet.
| Package Name | Release (NuGet) | Nightly (MyGet) |
| Package Name | Release (NuGet) | Nightly (Feedz.io) |
|--------------------------------|-----------------|-----------------|
| `SixLabors.ImageSharp` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp/) | [![MyGet](https://img.shields.io/myget/sixlabors/vpre/SixLabors.ImageSharp.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp) |
| `SixLabors.ImageSharp` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp/) | [![feedz.io](https://img.shields.io/badge/endpoint.svg?url=https%3A%2F%2Ff.feedz.io%2Fsixlabors%2Fsixlabors%2Fshield%2FSixLabors.ImageSharp%2Flatest)](https://f.feedz.io/sixlabors/sixlabors/nuget/index.json) |
## Manual build

2
shared-infrastructure

@ -1 +1 @@
Subproject commit 6c52486c512357475cbb92bbb7c4c0af4d85b1db
Subproject commit 9a6cf00d9a3d482bb08211dd8309f4724a2735cb

2
src/ImageSharp/Common/Constants.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp;

6
src/ImageSharp/Common/Helpers/Guard.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp;
@ -17,13 +16,14 @@ internal static partial class Guard
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException"><paramref name="value"/> is not a value type.</exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeValueType<TValue>(TValue value, string parameterName)
public static void MustBeValueType<TValue>(TValue value, [CallerArgumentExpression("value")] string? parameterName = null)
where TValue : notnull
{
if (value.GetType().IsValueType)
{
return;
}
ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName);
ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName!);
}
}

76
src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs

@ -5,6 +5,7 @@ using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static SixLabors.ImageSharp.SimdUtils;
// The JIT can detect and optimize rotation idioms ROTL (Rotate Left)
// and ROTR (Rotate Right) emitting efficient CPU instructions:
@ -18,9 +19,12 @@ namespace SixLabors.ImageSharp;
internal interface IComponentShuffle
{
/// <summary>
/// Gets the shuffle control.
/// Shuffles then slices 8-bit integers within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// </summary>
byte Control { get; }
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest);
/// <summary>
/// Shuffle 8-bit integers within 128-bit lanes in <paramref name="source"/>
@ -42,37 +46,25 @@ internal interface IShuffle4 : IComponentShuffle
internal readonly struct DefaultShuffle4 : IShuffle4
{
private readonly byte p3;
private readonly byte p2;
private readonly byte p1;
private readonly byte p0;
public DefaultShuffle4(byte p3, byte p2, byte p1, byte p0)
public DefaultShuffle4(byte control)
{
DebugGuard.MustBeBetweenOrEqualTo<byte>(p3, 0, 3, nameof(p3));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p2, 0, 3, nameof(p2));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p1, 0, 3, nameof(p1));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p0, 0, 3, nameof(p0));
this.p3 = p3;
this.p2 = p2;
this.p1 = p1;
this.p0 = p0;
this.Control = SimdUtils.Shuffle.MmShuffle(p3, p2, p1, p0);
DebugGuard.MustBeBetweenOrEqualTo<byte>(control, 0, 3, nameof(control));
this.Control = control;
}
public byte Control { get; }
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, this.Control);
[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);
int p3 = this.p3;
int p2 = this.p2;
int p1 = this.p1;
int p0 = this.p0;
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)
{
@ -86,11 +78,9 @@ internal readonly struct DefaultShuffle4 : IShuffle4
internal readonly struct WXYZShuffle4 : IShuffle4
{
public byte Control
{
[MethodImpl(InliningOptions.ShortMethod)]
get => SimdUtils.Shuffle.MmShuffle(2, 1, 0, 3);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle2103);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
@ -112,11 +102,9 @@ internal readonly struct WXYZShuffle4 : IShuffle4
internal readonly struct WZYXShuffle4 : IShuffle4
{
public byte Control
{
[MethodImpl(InliningOptions.ShortMethod)]
get => SimdUtils.Shuffle.MmShuffle(0, 1, 2, 3);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle0123);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
@ -138,11 +126,9 @@ internal readonly struct WZYXShuffle4 : IShuffle4
internal readonly struct YZWXShuffle4 : IShuffle4
{
public byte Control
{
[MethodImpl(InliningOptions.ShortMethod)]
get => SimdUtils.Shuffle.MmShuffle(0, 3, 2, 1);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle0321);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
@ -164,11 +150,9 @@ internal readonly struct YZWXShuffle4 : IShuffle4
internal readonly struct ZYXWShuffle4 : IShuffle4
{
public byte Control
{
[MethodImpl(InliningOptions.ShortMethod)]
get => SimdUtils.Shuffle.MmShuffle(3, 0, 1, 2);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle3012);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
@ -197,11 +181,9 @@ internal readonly struct ZYXWShuffle4 : IShuffle4
internal readonly struct XWZYShuffle4 : IShuffle4
{
public byte Control
{
[MethodImpl(InliningOptions.ShortMethod)]
get => SimdUtils.Shuffle.MmShuffle(1, 2, 3, 0);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle1230);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)

39
src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs

@ -3,6 +3,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static SixLabors.ImageSharp.SimdUtils;
namespace SixLabors.ImageSharp;
@ -13,37 +14,25 @@ internal interface IPad3Shuffle4 : IComponentShuffle
internal readonly struct DefaultPad3Shuffle4 : IPad3Shuffle4
{
private readonly byte p3;
private readonly byte p2;
private readonly byte p1;
private readonly byte p0;
public DefaultPad3Shuffle4(byte p3, byte p2, byte p1, byte p0)
public DefaultPad3Shuffle4(byte control)
{
DebugGuard.MustBeBetweenOrEqualTo<byte>(p3, 0, 3, nameof(p3));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p2, 0, 3, nameof(p2));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p1, 0, 3, nameof(p1));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p0, 0, 3, nameof(p0));
this.p3 = p3;
this.p2 = p2;
this.p1 = p1;
this.p0 = p0;
this.Control = SimdUtils.Shuffle.MmShuffle(p3, p2, p1, p0);
DebugGuard.MustBeBetweenOrEqualTo<byte>(control, 0, 3, nameof(control));
this.Control = control;
}
public byte Control { get; }
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref dest, this.Control);
[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);
int p3 = this.p3;
int p2 = this.p2;
int p1 = this.p1;
int p0 = this.p0;
Shuffle.InverseMMShuffle(this.Control, out int p3, out int p2, out int p1, out int p0);
Span<byte> temp = stackalloc byte[4];
ref byte t = ref MemoryMarshal.GetReference(temp);
@ -51,7 +40,7 @@ internal readonly struct DefaultPad3Shuffle4 : IPad3Shuffle4
for (int i = 0, j = 0; i < source.Length; i += 3, j += 4)
{
ref var s = ref Unsafe.Add(ref sBase, i);
ref byte s = ref Unsafe.Add(ref sBase, i);
tu = Unsafe.As<byte, uint>(ref s) | 0xFF000000;
Unsafe.Add(ref dBase, j) = Unsafe.Add(ref t, p0);
@ -64,11 +53,9 @@ internal readonly struct DefaultPad3Shuffle4 : IPad3Shuffle4
internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4
{
public byte Control
{
[MethodImpl(InliningOptions.ShortMethod)]
get => SimdUtils.Shuffle.MmShuffle(3, 2, 1, 0);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle3210);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)

25
src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs

@ -3,6 +3,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static SixLabors.ImageSharp.SimdUtils;
namespace SixLabors.ImageSharp;
@ -13,33 +14,25 @@ internal interface IShuffle3 : IComponentShuffle
internal readonly struct DefaultShuffle3 : IShuffle3
{
private readonly byte p2;
private readonly byte p1;
private readonly byte p0;
public DefaultShuffle3(byte p2, byte p1, byte p0)
public DefaultShuffle3(byte control)
{
DebugGuard.MustBeBetweenOrEqualTo<byte>(p2, 0, 2, nameof(p2));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p1, 0, 2, nameof(p1));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p0, 0, 2, nameof(p0));
this.p2 = p2;
this.p1 = p1;
this.p0 = p0;
this.Control = SimdUtils.Shuffle.MmShuffle(3, p2, p1, p0);
DebugGuard.MustBeBetweenOrEqualTo<byte>(control, 0, 3, nameof(control));
this.Control = control;
}
public byte Control { get; }
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle3Reduce(ref source, ref dest, this.Control);
[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);
int p2 = this.p2;
int p1 = this.p1;
int p0 = this.p0;
Shuffle.InverseMMShuffle(this.Control, out _, out int p2, out int p1, out int p0);
for (int i = 0; i < source.Length; i += 3)
{

34
src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs

@ -3,6 +3,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static SixLabors.ImageSharp.SimdUtils;
namespace SixLabors.ImageSharp;
@ -13,34 +14,25 @@ internal interface IShuffle4Slice3 : IComponentShuffle
internal readonly struct DefaultShuffle4Slice3 : IShuffle4Slice3
{
private readonly byte p2;
private readonly byte p1;
private readonly byte p0;
public DefaultShuffle4Slice3(byte p3, byte p2, byte p1, byte p0)
public DefaultShuffle4Slice3(byte control)
{
DebugGuard.MustBeBetweenOrEqualTo<byte>(p3, 0, 3, nameof(p3));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p2, 0, 3, nameof(p2));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p1, 0, 3, nameof(p1));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p0, 0, 3, nameof(p0));
this.p2 = p2;
this.p1 = p1;
this.p0 = p0;
this.Control = SimdUtils.Shuffle.MmShuffle(p3, p2, p1, p0);
DebugGuard.MustBeBetweenOrEqualTo<byte>(control, 0, 3, nameof(control));
this.Control = control;
}
public byte Control { get; }
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref dest, this.Control);
[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);
int p2 = this.p2;
int p1 = this.p1;
int p0 = this.p0;
Shuffle.InverseMMShuffle(this.Control, out _, out int p2, out int p1, out int p0);
for (int i = 0, j = 0; i < dest.Length; i += 3, j += 4)
{
@ -53,11 +45,9 @@ internal readonly struct DefaultShuffle4Slice3 : IShuffle4Slice3
internal readonly struct XYZWShuffle4Slice3 : IShuffle4Slice3
{
public byte Control
{
[MethodImpl(InliningOptions.ShortMethod)]
get => SimdUtils.Shuffle.MmShuffle(3, 2, 1, 0);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref dest, Shuffle.MMShuffle3210);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)

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

@ -297,7 +297,7 @@ internal static partial class SimdUtils
// 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);
Shuffle.MMShuffleSpan(ref bytes, control);
Vector256<byte> vshuffle = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(bytes));
ref Vector256<byte> sourceBase =
@ -333,7 +333,7 @@ internal static partial class SimdUtils
{
// Ssse3
Span<byte> bytes = stackalloc byte[Vector128<byte>.Count];
Shuffle.MmShuffleSpan(ref bytes, control);
Shuffle.MMShuffleSpan(ref bytes, control);
Vector128<byte> vshuffle = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(bytes));
ref Vector128<byte> sourceBase =
@ -382,7 +382,7 @@ internal static partial class SimdUtils
Vector128<byte> vmaske = Ssse3.AlignRight(vmasko, vmasko, 12);
Span<byte> bytes = stackalloc byte[Vector128<byte>.Count];
Shuffle.MmShuffleSpan(ref bytes, control);
Shuffle.MMShuffleSpan(ref bytes, control);
Vector128<byte> vshuffle = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(bytes));
ref Vector128<byte> sourceBase =
@ -445,7 +445,7 @@ internal static partial class SimdUtils
Vector128<byte> vfill = Vector128.Create(0xff000000ff000000ul).AsByte();
Span<byte> bytes = stackalloc byte[Vector128<byte>.Count];
Shuffle.MmShuffleSpan(ref bytes, control);
Shuffle.MMShuffleSpan(ref bytes, control);
Vector128<byte> vshuffle = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(bytes));
ref Vector128<byte> sourceBase =
@ -489,7 +489,7 @@ internal static partial class SimdUtils
Vector128<byte> vmaske = Ssse3.AlignRight(vmasko, vmasko, 12);
Span<byte> bytes = stackalloc byte[Vector128<byte>.Count];
Shuffle.MmShuffleSpan(ref bytes, control);
Shuffle.MMShuffleSpan(ref bytes, control);
Vector128<byte> vshuffle = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(bytes));
ref Vector128<byte> sourceBase =
@ -532,7 +532,8 @@ internal static partial class SimdUtils
}
/// <summary>
/// Performs a multiplication and an addition of the <see cref="Vector256{T}"/>.
/// Performs a multiplication and an addition of the <see cref="Vector256{Single}"/>.
/// TODO: Fix. The arguments are in a different order to the FMA intrinsic.
/// </summary>
/// <remarks>ret = (vm0 * vm1) + va</remarks>
/// <param name="va">The vector to add to the intermediate result.</param>
@ -549,22 +550,21 @@ internal static partial class SimdUtils
{
return Fma.MultiplyAdd(vm1, vm0, va);
}
else
{
return Avx.Add(Avx.Multiply(vm0, vm1), va);
}
return Avx.Add(Avx.Multiply(vm0, vm1), va);
}
/// <summary>
/// Performs a multiplication and a substraction of the <see cref="Vector256{T}"/>.
/// Performs a multiplication and a subtraction of the <see cref="Vector256{Single}"/>.
/// TODO: Fix. The arguments are in a different order to the FMA intrinsic.
/// </summary>
/// <remarks>ret = (vm0 * vm1) - vs</remarks>
/// <param name="vs">The vector to substract from the intermediate result.</param>
/// <param name="vs">The vector to subtract from the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector256<float> MultiplySubstract(
public static Vector256<float> MultiplySubtract(
in Vector256<float> vs,
in Vector256<float> vm0,
in Vector256<float> vm1)
@ -573,10 +573,30 @@ internal static partial class SimdUtils
{
return Fma.MultiplySubtract(vm1, vm0, vs);
}
else
return Avx.Subtract(Avx.Multiply(vm0, vm1), vs);
}
/// <summary>
/// Performs a multiplication and a negated addition of the <see cref="Vector256{Single}"/>.
/// </summary>
/// <remarks>ret = c - (a * b)</remarks>
/// <param name="a">The first vector to multiply.</param>
/// <param name="b">The second vector to multiply.</param>
/// <param name="c">The vector to add negated to the intermediate result.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector256<float> MultiplyAddNegated(
in Vector256<float> a,
in Vector256<float> b,
in Vector256<float> c)
{
if (Fma.IsSupported)
{
return Avx.Subtract(Avx.Multiply(vm0, vm1), vs);
return Fma.MultiplyAddNegated(a, b, c);
}
return Avx.Subtract(c, Avx.Multiply(a, b));
}
/// <summary>

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

@ -37,6 +37,7 @@ internal static partial class SimdUtils
/// Shuffle 8-bit integers within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// </summary>
/// <typeparam name="TShuffle">The type of shuffle struct.</typeparam>
/// <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>
@ -49,7 +50,7 @@ internal static partial class SimdUtils
{
VerifyShuffle4SpanInput(source, dest);
HwIntrinsics.Shuffle4Reduce(ref source, ref dest, shuffle.Control);
shuffle.ShuffleReduce(ref source, ref dest);
// Deal with the remainder:
if (source.Length > 0)
@ -62,6 +63,7 @@ internal static partial class SimdUtils
/// Shuffle 8-bit integer triplets within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// </summary>
/// <typeparam name="TShuffle">The type of shuffle struct.</typeparam>
/// <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>
@ -75,7 +77,7 @@ internal static partial class SimdUtils
// Source length should be smaller than dest length, and divisible by 3.
VerifyShuffle3SpanInput(source, dest);
HwIntrinsics.Shuffle3Reduce(ref source, ref dest, shuffle.Control);
shuffle.ShuffleReduce(ref source, ref dest);
// Deal with the remainder:
if (source.Length > 0)
@ -88,6 +90,7 @@ internal static partial class SimdUtils
/// Pads then shuffles 8-bit integers within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// </summary>
/// <typeparam name="TShuffle">The type of shuffle struct.</typeparam>
/// <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>
@ -100,7 +103,7 @@ internal static partial class SimdUtils
{
VerifyPad3Shuffle4SpanInput(source, dest);
HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref dest, shuffle.Control);
shuffle.ShuffleReduce(ref source, ref dest);
// Deal with the remainder:
if (source.Length > 0)
@ -113,6 +116,7 @@ internal static partial class SimdUtils
/// Shuffles then slices 8-bit integers within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// </summary>
/// <typeparam name="TShuffle">The type of shuffle struct.</typeparam>
/// <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>
@ -125,7 +129,7 @@ internal static partial class SimdUtils
{
VerifyShuffle4Slice3SpanInput(source, dest);
HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref dest, shuffle.Control);
shuffle.ShuffleReduce(ref source, ref dest);
// Deal with the remainder:
if (source.Length > 0)
@ -141,7 +145,7 @@ internal static partial class SimdUtils
{
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);
Shuffle.InverseMMShuffle(control, out int p3, out int p2, out int p1, out int p0);
for (int i = 0; i < source.Length; i += 4)
{
@ -153,7 +157,7 @@ internal static partial class SimdUtils
}
[Conditional("DEBUG")]
private static void VerifyShuffle4SpanInput<T>(ReadOnlySpan<T> source, Span<T> dest)
internal static void VerifyShuffle4SpanInput<T>(ReadOnlySpan<T> source, Span<T> dest)
where T : struct
{
DebugGuard.IsTrue(
@ -222,14 +226,271 @@ internal static partial class SimdUtils
public static class Shuffle
{
public const byte MMShuffle0000 = 0b00000000;
public const byte MMShuffle0001 = 0b00000001;
public const byte MMShuffle0002 = 0b00000010;
public const byte MMShuffle0003 = 0b00000011;
public const byte MMShuffle0010 = 0b00000100;
public const byte MMShuffle0011 = 0b00000101;
public const byte MMShuffle0012 = 0b00000110;
public const byte MMShuffle0013 = 0b00000111;
public const byte MMShuffle0020 = 0b00001000;
public const byte MMShuffle0021 = 0b00001001;
public const byte MMShuffle0022 = 0b00001010;
public const byte MMShuffle0023 = 0b00001011;
public const byte MMShuffle0030 = 0b00001100;
public const byte MMShuffle0031 = 0b00001101;
public const byte MMShuffle0032 = 0b00001110;
public const byte MMShuffle0033 = 0b00001111;
public const byte MMShuffle0100 = 0b00010000;
public const byte MMShuffle0101 = 0b00010001;
public const byte MMShuffle0102 = 0b00010010;
public const byte MMShuffle0103 = 0b00010011;
public const byte MMShuffle0110 = 0b00010100;
public const byte MMShuffle0111 = 0b00010101;
public const byte MMShuffle0112 = 0b00010110;
public const byte MMShuffle0113 = 0b00010111;
public const byte MMShuffle0120 = 0b00011000;
public const byte MMShuffle0121 = 0b00011001;
public const byte MMShuffle0122 = 0b00011010;
public const byte MMShuffle0123 = 0b00011011;
public const byte MMShuffle0130 = 0b00011100;
public const byte MMShuffle0131 = 0b00011101;
public const byte MMShuffle0132 = 0b00011110;
public const byte MMShuffle0133 = 0b00011111;
public const byte MMShuffle0200 = 0b00100000;
public const byte MMShuffle0201 = 0b00100001;
public const byte MMShuffle0202 = 0b00100010;
public const byte MMShuffle0203 = 0b00100011;
public const byte MMShuffle0210 = 0b00100100;
public const byte MMShuffle0211 = 0b00100101;
public const byte MMShuffle0212 = 0b00100110;
public const byte MMShuffle0213 = 0b00100111;
public const byte MMShuffle0220 = 0b00101000;
public const byte MMShuffle0221 = 0b00101001;
public const byte MMShuffle0222 = 0b00101010;
public const byte MMShuffle0223 = 0b00101011;
public const byte MMShuffle0230 = 0b00101100;
public const byte MMShuffle0231 = 0b00101101;
public const byte MMShuffle0232 = 0b00101110;
public const byte MMShuffle0233 = 0b00101111;
public const byte MMShuffle0300 = 0b00110000;
public const byte MMShuffle0301 = 0b00110001;
public const byte MMShuffle0302 = 0b00110010;
public const byte MMShuffle0303 = 0b00110011;
public const byte MMShuffle0310 = 0b00110100;
public const byte MMShuffle0311 = 0b00110101;
public const byte MMShuffle0312 = 0b00110110;
public const byte MMShuffle0313 = 0b00110111;
public const byte MMShuffle0320 = 0b00111000;
public const byte MMShuffle0321 = 0b00111001;
public const byte MMShuffle0322 = 0b00111010;
public const byte MMShuffle0323 = 0b00111011;
public const byte MMShuffle0330 = 0b00111100;
public const byte MMShuffle0331 = 0b00111101;
public const byte MMShuffle0332 = 0b00111110;
public const byte MMShuffle0333 = 0b00111111;
public const byte MMShuffle1000 = 0b01000000;
public const byte MMShuffle1001 = 0b01000001;
public const byte MMShuffle1002 = 0b01000010;
public const byte MMShuffle1003 = 0b01000011;
public const byte MMShuffle1010 = 0b01000100;
public const byte MMShuffle1011 = 0b01000101;
public const byte MMShuffle1012 = 0b01000110;
public const byte MMShuffle1013 = 0b01000111;
public const byte MMShuffle1020 = 0b01001000;
public const byte MMShuffle1021 = 0b01001001;
public const byte MMShuffle1022 = 0b01001010;
public const byte MMShuffle1023 = 0b01001011;
public const byte MMShuffle1030 = 0b01001100;
public const byte MMShuffle1031 = 0b01001101;
public const byte MMShuffle1032 = 0b01001110;
public const byte MMShuffle1033 = 0b01001111;
public const byte MMShuffle1100 = 0b01010000;
public const byte MMShuffle1101 = 0b01010001;
public const byte MMShuffle1102 = 0b01010010;
public const byte MMShuffle1103 = 0b01010011;
public const byte MMShuffle1110 = 0b01010100;
public const byte MMShuffle1111 = 0b01010101;
public const byte MMShuffle1112 = 0b01010110;
public const byte MMShuffle1113 = 0b01010111;
public const byte MMShuffle1120 = 0b01011000;
public const byte MMShuffle1121 = 0b01011001;
public const byte MMShuffle1122 = 0b01011010;
public const byte MMShuffle1123 = 0b01011011;
public const byte MMShuffle1130 = 0b01011100;
public const byte MMShuffle1131 = 0b01011101;
public const byte MMShuffle1132 = 0b01011110;
public const byte MMShuffle1133 = 0b01011111;
public const byte MMShuffle1200 = 0b01100000;
public const byte MMShuffle1201 = 0b01100001;
public const byte MMShuffle1202 = 0b01100010;
public const byte MMShuffle1203 = 0b01100011;
public const byte MMShuffle1210 = 0b01100100;
public const byte MMShuffle1211 = 0b01100101;
public const byte MMShuffle1212 = 0b01100110;
public const byte MMShuffle1213 = 0b01100111;
public const byte MMShuffle1220 = 0b01101000;
public const byte MMShuffle1221 = 0b01101001;
public const byte MMShuffle1222 = 0b01101010;
public const byte MMShuffle1223 = 0b01101011;
public const byte MMShuffle1230 = 0b01101100;
public const byte MMShuffle1231 = 0b01101101;
public const byte MMShuffle1232 = 0b01101110;
public const byte MMShuffle1233 = 0b01101111;
public const byte MMShuffle1300 = 0b01110000;
public const byte MMShuffle1301 = 0b01110001;
public const byte MMShuffle1302 = 0b01110010;
public const byte MMShuffle1303 = 0b01110011;
public const byte MMShuffle1310 = 0b01110100;
public const byte MMShuffle1311 = 0b01110101;
public const byte MMShuffle1312 = 0b01110110;
public const byte MMShuffle1313 = 0b01110111;
public const byte MMShuffle1320 = 0b01111000;
public const byte MMShuffle1321 = 0b01111001;
public const byte MMShuffle1322 = 0b01111010;
public const byte MMShuffle1323 = 0b01111011;
public const byte MMShuffle1330 = 0b01111100;
public const byte MMShuffle1331 = 0b01111101;
public const byte MMShuffle1332 = 0b01111110;
public const byte MMShuffle1333 = 0b01111111;
public const byte MMShuffle2000 = 0b10000000;
public const byte MMShuffle2001 = 0b10000001;
public const byte MMShuffle2002 = 0b10000010;
public const byte MMShuffle2003 = 0b10000011;
public const byte MMShuffle2010 = 0b10000100;
public const byte MMShuffle2011 = 0b10000101;
public const byte MMShuffle2012 = 0b10000110;
public const byte MMShuffle2013 = 0b10000111;
public const byte MMShuffle2020 = 0b10001000;
public const byte MMShuffle2021 = 0b10001001;
public const byte MMShuffle2022 = 0b10001010;
public const byte MMShuffle2023 = 0b10001011;
public const byte MMShuffle2030 = 0b10001100;
public const byte MMShuffle2031 = 0b10001101;
public const byte MMShuffle2032 = 0b10001110;
public const byte MMShuffle2033 = 0b10001111;
public const byte MMShuffle2100 = 0b10010000;
public const byte MMShuffle2101 = 0b10010001;
public const byte MMShuffle2102 = 0b10010010;
public const byte MMShuffle2103 = 0b10010011;
public const byte MMShuffle2110 = 0b10010100;
public const byte MMShuffle2111 = 0b10010101;
public const byte MMShuffle2112 = 0b10010110;
public const byte MMShuffle2113 = 0b10010111;
public const byte MMShuffle2120 = 0b10011000;
public const byte MMShuffle2121 = 0b10011001;
public const byte MMShuffle2122 = 0b10011010;
public const byte MMShuffle2123 = 0b10011011;
public const byte MMShuffle2130 = 0b10011100;
public const byte MMShuffle2131 = 0b10011101;
public const byte MMShuffle2132 = 0b10011110;
public const byte MMShuffle2133 = 0b10011111;
public const byte MMShuffle2200 = 0b10100000;
public const byte MMShuffle2201 = 0b10100001;
public const byte MMShuffle2202 = 0b10100010;
public const byte MMShuffle2203 = 0b10100011;
public const byte MMShuffle2210 = 0b10100100;
public const byte MMShuffle2211 = 0b10100101;
public const byte MMShuffle2212 = 0b10100110;
public const byte MMShuffle2213 = 0b10100111;
public const byte MMShuffle2220 = 0b10101000;
public const byte MMShuffle2221 = 0b10101001;
public const byte MMShuffle2222 = 0b10101010;
public const byte MMShuffle2223 = 0b10101011;
public const byte MMShuffle2230 = 0b10101100;
public const byte MMShuffle2231 = 0b10101101;
public const byte MMShuffle2232 = 0b10101110;
public const byte MMShuffle2233 = 0b10101111;
public const byte MMShuffle2300 = 0b10110000;
public const byte MMShuffle2301 = 0b10110001;
public const byte MMShuffle2302 = 0b10110010;
public const byte MMShuffle2303 = 0b10110011;
public const byte MMShuffle2310 = 0b10110100;
public const byte MMShuffle2311 = 0b10110101;
public const byte MMShuffle2312 = 0b10110110;
public const byte MMShuffle2313 = 0b10110111;
public const byte MMShuffle2320 = 0b10111000;
public const byte MMShuffle2321 = 0b10111001;
public const byte MMShuffle2322 = 0b10111010;
public const byte MMShuffle2323 = 0b10111011;
public const byte MMShuffle2330 = 0b10111100;
public const byte MMShuffle2331 = 0b10111101;
public const byte MMShuffle2332 = 0b10111110;
public const byte MMShuffle2333 = 0b10111111;
public const byte MMShuffle3000 = 0b11000000;
public const byte MMShuffle3001 = 0b11000001;
public const byte MMShuffle3002 = 0b11000010;
public const byte MMShuffle3003 = 0b11000011;
public const byte MMShuffle3010 = 0b11000100;
public const byte MMShuffle3011 = 0b11000101;
public const byte MMShuffle3012 = 0b11000110;
public const byte MMShuffle3013 = 0b11000111;
public const byte MMShuffle3020 = 0b11001000;
public const byte MMShuffle3021 = 0b11001001;
public const byte MMShuffle3022 = 0b11001010;
public const byte MMShuffle3023 = 0b11001011;
public const byte MMShuffle3030 = 0b11001100;
public const byte MMShuffle3031 = 0b11001101;
public const byte MMShuffle3032 = 0b11001110;
public const byte MMShuffle3033 = 0b11001111;
public const byte MMShuffle3100 = 0b11010000;
public const byte MMShuffle3101 = 0b11010001;
public const byte MMShuffle3102 = 0b11010010;
public const byte MMShuffle3103 = 0b11010011;
public const byte MMShuffle3110 = 0b11010100;
public const byte MMShuffle3111 = 0b11010101;
public const byte MMShuffle3112 = 0b11010110;
public const byte MMShuffle3113 = 0b11010111;
public const byte MMShuffle3120 = 0b11011000;
public const byte MMShuffle3121 = 0b11011001;
public const byte MMShuffle3122 = 0b11011010;
public const byte MMShuffle3123 = 0b11011011;
public const byte MMShuffle3130 = 0b11011100;
public const byte MMShuffle3131 = 0b11011101;
public const byte MMShuffle3132 = 0b11011110;
public const byte MMShuffle3133 = 0b11011111;
public const byte MMShuffle3200 = 0b11100000;
public const byte MMShuffle3201 = 0b11100001;
public const byte MMShuffle3202 = 0b11100010;
public const byte MMShuffle3203 = 0b11100011;
public const byte MMShuffle3210 = 0b11100100;
public const byte MMShuffle3211 = 0b11100101;
public const byte MMShuffle3212 = 0b11100110;
public const byte MMShuffle3213 = 0b11100111;
public const byte MMShuffle3220 = 0b11101000;
public const byte MMShuffle3221 = 0b11101001;
public const byte MMShuffle3222 = 0b11101010;
public const byte MMShuffle3223 = 0b11101011;
public const byte MMShuffle3230 = 0b11101100;
public const byte MMShuffle3231 = 0b11101101;
public const byte MMShuffle3232 = 0b11101110;
public const byte MMShuffle3233 = 0b11101111;
public const byte MMShuffle3300 = 0b11110000;
public const byte MMShuffle3301 = 0b11110001;
public const byte MMShuffle3302 = 0b11110010;
public const byte MMShuffle3303 = 0b11110011;
public const byte MMShuffle3310 = 0b11110100;
public const byte MMShuffle3311 = 0b11110101;
public const byte MMShuffle3312 = 0b11110110;
public const byte MMShuffle3313 = 0b11110111;
public const byte MMShuffle3320 = 0b11111000;
public const byte MMShuffle3321 = 0b11111001;
public const byte MMShuffle3322 = 0b11111010;
public const byte MMShuffle3323 = 0b11111011;
public const byte MMShuffle3330 = 0b11111100;
public const byte MMShuffle3331 = 0b11111101;
public const byte MMShuffle3332 = 0b11111110;
public const byte MMShuffle3333 = 0b11111111;
[MethodImpl(InliningOptions.ShortMethod)]
public static byte MmShuffle(byte p3, byte p2, byte p1, byte p0)
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)
public static void MMShuffleSpan(ref Span<byte> span, byte control)
{
InverseMmShuffle(
InverseMMShuffle(
control,
out int p3,
out int p2,
@ -248,7 +509,7 @@ internal static partial class SimdUtils
}
[MethodImpl(InliningOptions.ShortMethod)]
public static void InverseMmShuffle(
public static void InverseMMShuffle(
byte control,
out int p3,
out int p2,

2
src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Intrinsic.cs

@ -99,7 +99,7 @@ internal static partial class FloatingPointDCT
var mm256_F_1_4142 = Vector256.Create(1.414213562f);
Vector256<float> tmp13 = Avx.Add(tmp1, tmp3);
Vector256<float> tmp12 = SimdUtils.HwIntrinsics.MultiplySubstract(tmp13, Avx.Subtract(tmp1, tmp3), mm256_F_1_4142);
Vector256<float> tmp12 = SimdUtils.HwIntrinsics.MultiplySubtract(tmp13, Avx.Subtract(tmp1, tmp3), mm256_F_1_4142);
tmp0 = Avx.Add(tmp10, tmp13);
tmp3 = Avx.Subtract(tmp10, tmp13);

68
src/ImageSharp/Formats/Webp/AlphaDecoder.cs

@ -1,8 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
@ -38,7 +38,7 @@ internal class AlphaDecoder : IDisposable
this.LastRow = 0;
int totalPixels = width * height;
var compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03);
WebpAlphaCompressionMethod compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03);
if (compression is not WebpAlphaCompressionMethod.NoCompression and not WebpAlphaCompressionMethod.WebpLosslessCompression)
{
WebpThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found");
@ -59,7 +59,7 @@ internal class AlphaDecoder : IDisposable
if (this.Compressed)
{
var bitReader = new Vp8LBitReader(data);
Vp8LBitReader bitReader = new(data);
this.LosslessDecoder = new WebpLosslessDecoder(bitReader, memoryAllocator, configuration);
this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true);
@ -110,6 +110,7 @@ internal class AlphaDecoder : IDisposable
/// <summary>
/// Gets a value indicating whether the alpha channel uses compression.
/// </summary>
[MemberNotNullWhen(true, nameof(LosslessDecoder))]
private bool Compressed { get; }
/// <summary>
@ -120,7 +121,7 @@ internal class AlphaDecoder : IDisposable
/// <summary>
/// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed.
/// </summary>
private WebpLosslessDecoder LosslessDecoder { get; }
private WebpLosslessDecoder? LosslessDecoder { get; }
/// <summary>
/// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding.
@ -173,17 +174,14 @@ internal class AlphaDecoder : IDisposable
dst = dst[this.Width..];
}
}
else if (this.Use8BDecode)
{
this.LosslessDecoder.DecodeAlphaData(this);
}
else
{
if (this.Use8BDecode)
{
this.LosslessDecoder.DecodeAlphaData(this);
}
else
{
this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span);
this.ExtractAlphaRows(this.Vp8LDec);
}
this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span);
this.ExtractAlphaRows(this.Vp8LDec);
}
}
@ -261,8 +259,7 @@ internal class AlphaDecoder : IDisposable
{
int numRowsToProcess = dec.Height;
int width = dec.Width;
Span<uint> pixels = dec.Pixels.Memory.Span;
Span<uint> input = pixels;
Span<uint> input = dec.Pixels.Memory.Span;
Span<byte> output = this.Alpha.Memory.Span;
// Extract alpha (which is stored in the green plane).
@ -327,7 +324,7 @@ internal class AlphaDecoder : IDisposable
ref byte srcRef = ref MemoryMarshal.GetReference(input);
for (i = 1; i + 8 <= width; i += 8)
{
var a0 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref srcRef, i)), 0);
Vector128<long> a0 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref srcRef, i)), 0);
Vector128<byte> a1 = Sse2.Add(a0.AsByte(), last.AsByte());
Vector128<byte> a2 = Sse2.ShiftLeftLogical128BitLane(a1, 1);
Vector128<byte> a3 = Sse2.Add(a1, a2);
@ -365,32 +362,29 @@ internal class AlphaDecoder : IDisposable
{
HorizontalUnfilter(null, input, dst, width);
}
else
else if (Avx2.IsSupported)
{
if (Avx2.IsSupported)
nint i;
int maxPos = width & ~31;
for (i = 0; i < maxPos; i += 32)
{
nint i;
int maxPos = width & ~31;
for (i = 0; i < maxPos; i += 32)
{
Vector256<int> a0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i));
Vector256<int> b0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i));
Vector256<byte> c0 = Avx2.Add(a0.AsByte(), b0.AsByte());
ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i);
Unsafe.As<byte, Vector256<byte>>(ref outputRef) = c0;
}
Vector256<int> a0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i));
Vector256<int> b0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i));
Vector256<byte> c0 = Avx2.Add(a0.AsByte(), b0.AsByte());
ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i);
Unsafe.As<byte, Vector256<byte>>(ref outputRef) = c0;
}
for (; i < width; i++)
{
dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]);
}
for (; i < width; i++)
{
dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]);
}
else
}
else
{
for (int i = 0; i < width; i++)
{
for (int i = 0; i < width; i++)
{
dst[i] = (byte)(prev[i] + input[i]);
}
dst[i] = (byte)(prev[i] + input[i]);
}
}
}

26
src/ImageSharp/Formats/Webp/AlphaEncoder.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
@ -13,10 +12,8 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
/// Methods for encoding the alpha data of a VP8 image.
/// </summary>
internal class AlphaEncoder : IDisposable
internal static class AlphaEncoder
{
private IMemoryOwner<byte> alphaData;
/// <summary>
/// Encodes the alpha channel data.
/// Data is either compressed as lossless webp image or uncompressed.
@ -29,12 +26,18 @@ internal class AlphaEncoder : IDisposable
/// <param name="compress">Indicates, if the data should be compressed with the lossless webp compression.</param>
/// <param name="size">The size in bytes of the alpha data.</param>
/// <returns>The encoded alpha data.</returns>
public IMemoryOwner<byte> EncodeAlpha<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator, bool skipMetadata, bool compress, out int size)
public static IMemoryOwner<byte> EncodeAlpha<TPixel>(
Image<TPixel> image,
Configuration configuration,
MemoryAllocator memoryAllocator,
bool skipMetadata,
bool compress,
out int size)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
this.alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
IMemoryOwner<byte> alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
if (compress)
{
@ -55,15 +58,15 @@ internal class AlphaEncoder : IDisposable
// The transparency information will be stored in the green channel of the ARGB quadruplet.
// The green channel is allowed extra transformation steps in the specification -- unlike the other channels,
// that can improve compression.
using Image<Rgba32> alphaAsImage = DispatchAlphaToGreen(image, this.alphaData.GetSpan());
using Image<Rgba32> alphaAsImage = DispatchAlphaToGreen(image, alphaData.GetSpan());
size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, this.alphaData);
size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, alphaData);
return this.alphaData;
return alphaData;
}
size = width * height;
return this.alphaData;
return alphaData;
}
/// <summary>
@ -128,7 +131,4 @@ internal class AlphaEncoder : IDisposable
return alphaDataBuffer;
}
/// <inheritdoc/>
public void Dispose() => this.alphaData?.Dispose();
}

21
src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using SixLabors.ImageSharp.Memory;
@ -14,10 +13,16 @@ internal abstract class BitReaderBase : IDisposable
{
private bool isDisposed;
protected BitReaderBase(IMemoryOwner<byte> data)
=> this.Data = data;
protected BitReaderBase(Stream inputStream, int imageDataSize, MemoryAllocator memoryAllocator)
=> this.Data = ReadImageDataFromStream(inputStream, imageDataSize, memoryAllocator);
/// <summary>
/// Gets or sets the raw encoded image data.
/// Gets the raw encoded image data.
/// </summary>
public IMemoryOwner<byte> Data { get; set; }
public IMemoryOwner<byte> Data { get; }
/// <summary>
/// Copies the raw encoded image data from the stream into a byte array.
@ -25,11 +30,13 @@ internal abstract class BitReaderBase : IDisposable
/// <param name="input">The input stream.</param>
/// <param name="bytesToRead">Number of bytes to read as indicated from the chunk size.</param>
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator)
protected static IMemoryOwner<byte> ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator)
{
this.Data = memoryAllocator.Allocate<byte>(bytesToRead);
Span<byte> dataSpan = this.Data.Memory.Span;
IMemoryOwner<byte> data = memoryAllocator.Allocate<byte>(bytesToRead);
Span<byte> dataSpan = data.Memory.Span;
input.Read(dataSpan[..bytesToRead], 0, bytesToRead);
return data;
}
protected virtual void Dispose(bool disposing)
@ -41,7 +48,7 @@ internal abstract class BitReaderBase : IDisposable
if (disposing)
{
this.Data?.Dispose();
this.Data.Dispose();
}
this.isDisposed = true;

4
src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs

@ -57,12 +57,12 @@ internal class Vp8BitReader : BitReaderBase
/// <param name="partitionLength">The partition length.</param>
/// <param name="startPos">Start index in the data array. Defaults to 0.</param>
public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0)
: base(inputStream, (int)imageDataSize, memoryAllocator)
{
Guard.MustBeLessThan(imageDataSize, int.MaxValue, nameof(imageDataSize));
this.ImageDataSize = imageDataSize;
this.PartitionLength = partitionLength;
this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator);
this.InitBitreader(partitionLength, startPos);
}
@ -73,8 +73,8 @@ internal class Vp8BitReader : BitReaderBase
/// <param name="partitionLength">The partition length.</param>
/// <param name="startPos">Start index in the data array. Defaults to 0.</param>
public Vp8BitReader(IMemoryOwner<byte> imageData, uint partitionLength, int startPos = 0)
: base(imageData)
{
this.Data = imageData;
this.ImageDataSize = (uint)imageData.Memory.Length;
this.PartitionLength = partitionLength;
this.InitBitreader(partitionLength, startPos);

7
src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs

@ -63,8 +63,8 @@ internal class Vp8LBitReader : BitReaderBase
/// </summary>
/// <param name="data">Lossless compressed image data.</param>
public Vp8LBitReader(IMemoryOwner<byte> data)
: base(data)
{
this.Data = data;
this.len = data.Memory.Length;
this.value = 0;
this.bitPos = 0;
@ -88,11 +88,10 @@ internal class Vp8LBitReader : BitReaderBase
/// <param name="imageDataSize">The raw image data size in bytes.</param>
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator)
: base(inputStream, (int)imageDataSize, memoryAllocator)
{
long length = imageDataSize;
this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator);
this.len = length;
this.value = 0;
this.bitPos = 0;
@ -193,7 +192,7 @@ internal class Vp8LBitReader : BitReaderBase
[MethodImpl(InliningOptions.ShortMethod)]
private void ShiftBytes()
{
System.Span<byte> dataSpan = this.Data.Memory.Span;
System.Span<byte> dataSpan = this.Data!.Memory.Span;
while (this.bitPos >= 8 && this.pos < this.len)
{
this.value >>= 8;

4
src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

@ -123,7 +123,7 @@ internal abstract class BitWriterBase
/// <param name="stream">The stream to write to.</param>
/// <param name="metadataBytes">The metadata profile's bytes.</param>
/// <param name="chunkType">The chuck type to write.</param>
protected void WriteMetadataProfile(Stream stream, byte[] metadataBytes, WebpChunkType chunkType)
protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpChunkType chunkType)
{
DebugGuard.NotNull(metadataBytes, nameof(metadataBytes));
@ -207,7 +207,7 @@ internal abstract class BitWriterBase
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, byte[] iccProfileBytes, uint width, uint height, bool hasAlpha)
protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, byte[]? iccProfileBytes, uint width, uint height, bool hasAlpha)
{
if (width > MaxDimension || height > MaxDimension)
{

38
src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
@ -58,7 +57,8 @@ internal class Vp8BitWriter : BitWriterBase
/// Initializes a new instance of the <see cref="Vp8BitWriter"/> class.
/// </summary>
/// <param name="expectedSize">The expected size in bytes.</param>
public Vp8BitWriter(int expectedSize)
/// <param name="enc">The Vp8Encoder.</param>
public Vp8BitWriter(int expectedSize, Vp8Encoder enc)
: base(expectedSize)
{
this.range = 255 - 1;
@ -67,15 +67,9 @@ internal class Vp8BitWriter : BitWriterBase
this.nbBits = -8;
this.pos = 0;
this.maxPos = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="Vp8BitWriter"/> class.
/// </summary>
/// <param name="expectedSize">The expected size in bytes.</param>
/// <param name="enc">The Vp8Encoder.</param>
public Vp8BitWriter(int expectedSize, Vp8Encoder enc)
: this(expectedSize) => this.enc = enc;
this.enc = enc;
}
/// <inheritdoc/>
public override int NumBytes() => (int)this.pos;
@ -414,9 +408,9 @@ internal class Vp8BitWriter : BitWriterBase
/// <param name="alphaDataIsCompressed">Indicates, if the alpha data is compressed.</param>
public void WriteEncodedImageToStream(
Stream stream,
ExifProfile exifProfile,
XmpProfile xmpProfile,
IccProfile iccProfile,
ExifProfile? exifProfile,
XmpProfile? xmpProfile,
IccProfile? iccProfile,
uint width,
uint height,
bool hasAlpha,
@ -424,22 +418,22 @@ internal class Vp8BitWriter : BitWriterBase
bool alphaDataIsCompressed)
{
bool isVp8X = false;
byte[] exifBytes = null;
byte[] xmpBytes = null;
byte[] iccProfileBytes = null;
byte[]? exifBytes = null;
byte[]? xmpBytes = null;
byte[]? iccProfileBytes = null;
uint riffSize = 0;
if (exifProfile != null)
{
isVp8X = true;
exifBytes = exifProfile.ToByteArray();
riffSize += MetadataChunkSize(exifBytes);
riffSize += MetadataChunkSize(exifBytes!);
}
if (xmpProfile != null)
{
isVp8X = true;
xmpBytes = xmpProfile.Data;
riffSize += MetadataChunkSize(xmpBytes);
riffSize += MetadataChunkSize(xmpBytes!);
}
if (iccProfile != null)
@ -465,7 +459,7 @@ internal class Vp8BitWriter : BitWriterBase
int mbSize = this.enc.Mbw * this.enc.Mbh;
int expectedSize = mbSize * 7 / 8;
var bitWriterPartZero = new Vp8BitWriter(expectedSize);
Vp8BitWriter bitWriterPartZero = new(expectedSize, this.enc);
// Partition #0 with header and partition sizes.
uint size0 = this.GeneratePartition0(bitWriterPartZero);
@ -676,9 +670,9 @@ internal class Vp8BitWriter : BitWriterBase
bool isVp8X,
uint width,
uint height,
ExifProfile exifProfile,
XmpProfile xmpProfile,
byte[] iccProfileBytes,
ExifProfile? exifProfile,
XmpProfile? xmpProfile,
byte[]? iccProfileBytes,
bool hasAlpha,
Span<byte> alphaData,
bool alphaDataIsCompressed)

13
src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
@ -138,25 +137,25 @@ internal class Vp8LBitWriter : BitWriterBase
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, IccProfile iccProfile, uint width, uint height, bool hasAlpha)
public void WriteEncodedImageToStream(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha)
{
bool isVp8X = false;
byte[] exifBytes = null;
byte[] xmpBytes = null;
byte[] iccBytes = null;
byte[]? exifBytes = null;
byte[]? xmpBytes = null;
byte[]? iccBytes = null;
uint riffSize = 0;
if (exifProfile != null)
{
isVp8X = true;
exifBytes = exifProfile.ToByteArray();
riffSize += MetadataChunkSize(exifBytes);
riffSize += MetadataChunkSize(exifBytes!);
}
if (xmpProfile != null)
{
isVp8X = true;
xmpBytes = xmpProfile.Data;
riffSize += MetadataChunkSize(xmpBytes);
riffSize += MetadataChunkSize(xmpBytes!);
}
if (iccProfile != null)

47
src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using SixLabors.ImageSharp.Memory;
@ -50,7 +49,7 @@ internal static class BackwardReferenceEncoder
int lz77TypeBest = 0;
double bitCostBest = -1;
int cacheBitsInitial = cacheBits;
Vp8LHashChain hashChainBox = null;
Vp8LHashChain? hashChainBox = null;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1)
@ -101,7 +100,7 @@ internal static class BackwardReferenceEncoder
// Improve on simple LZ77 but only for high quality (TraceBackwards is costly).
if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25)
{
Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox;
Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox!;
BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst);
var histo = new Vp8LHistogram(worst, cacheBits);
double bitCostTrace = histo.EstimateBits(stats, bitsEntropy);
@ -140,8 +139,7 @@ internal static class BackwardReferenceEncoder
for (int i = 0; i <= WebpConstants.MaxColorCacheBits; i++)
{
histos[i] = new Vp8LHistogram(paletteCodeBits: i);
colorCache[i] = new ColorCache();
colorCache[i].Init(i);
colorCache[i] = new ColorCache(i);
}
// Find the cacheBits giving the lowest entropy.
@ -274,11 +272,11 @@ internal static class BackwardReferenceEncoder
double offsetCost = -1;
int firstOffsetIsConstant = -1; // initialized with 'impossible' value.
int reach = 0;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
costModel.Build(xSize, cacheBits, refs);
@ -375,12 +373,12 @@ internal static class BackwardReferenceEncoder
private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan<uint> bgra, int cacheBits, Span<ushort> chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs)
{
bool useColorCache = cacheBits > 0;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
int i = 0;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
backwardRefs.Refs.Clear();
@ -396,7 +394,7 @@ internal static class BackwardReferenceEncoder
{
for (int k = 0; k < len; k++)
{
colorCache.Insert(bgra[i + k]);
colorCache!.Insert(bgra[i + k]);
}
}
@ -405,7 +403,7 @@ internal static class BackwardReferenceEncoder
else
{
PixOrCopy v;
int idx = useColorCache ? colorCache.Contains(bgra[i]) : -1;
int idx = useColorCache ? colorCache!.Contains(bgra[i]) : -1;
if (idx >= 0)
{
// useColorCache is true and color cache contains bgra[i]
@ -416,7 +414,7 @@ internal static class BackwardReferenceEncoder
{
if (useColorCache)
{
colorCache.Insert(bgra[i]);
colorCache!.Insert(bgra[i]);
}
v = PixOrCopy.CreateLiteral(bgra[i]);
@ -430,7 +428,7 @@ internal static class BackwardReferenceEncoder
private static void AddSingleLiteralWithCostModel(
ReadOnlySpan<uint> bgra,
ColorCache colorCache,
ColorCache? colorCache,
CostModel costModel,
int idx,
bool useColorCache,
@ -440,7 +438,7 @@ internal static class BackwardReferenceEncoder
{
double costVal = prevCost;
uint color = bgra[idx];
int ix = useColorCache ? colorCache.Contains(color) : -1;
int ix = useColorCache ? colorCache!.Contains(color) : -1;
if (ix >= 0)
{
double mul0 = 0.68;
@ -451,7 +449,7 @@ internal static class BackwardReferenceEncoder
double mul1 = 0.82;
if (useColorCache)
{
colorCache.Insert(color);
colorCache!.Insert(color);
}
costVal += costModel.GetLiteralCost(color) * mul1;
@ -469,10 +467,10 @@ internal static class BackwardReferenceEncoder
int iLastCheck = -1;
bool useColorCache = cacheBits > 0;
int pixCount = xSize * ySize;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
refs.Refs.Clear();
@ -529,7 +527,7 @@ internal static class BackwardReferenceEncoder
{
for (j = i; j < i + len; j++)
{
colorCache.Insert(bgra[j]);
colorCache!.Insert(bgra[j]);
}
}
}
@ -725,11 +723,11 @@ internal static class BackwardReferenceEncoder
{
int pixelCount = xSize * ySize;
bool useColorCache = cacheBits > 0;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
refs.Refs.Clear();
@ -757,7 +755,7 @@ internal static class BackwardReferenceEncoder
{
for (int k = 0; k < prevRowLen; ++k)
{
colorCache.Insert(bgra[i + k]);
colorCache!.Insert(bgra[i + k]);
}
}
@ -777,8 +775,7 @@ internal static class BackwardReferenceEncoder
private static void BackwardRefsWithLocalCache(ReadOnlySpan<uint> bgra, int cacheBits, Vp8LBackwardRefs refs)
{
int pixelIndex = 0;
var colorCache = new ColorCache();
colorCache.Init(cacheBits);
ColorCache colorCache = new(cacheBits);
for (int idx = 0; idx < refs.Refs.Count; idx++)
{
PixOrCopy v = refs.Refs[idx];
@ -825,12 +822,12 @@ internal static class BackwardReferenceEncoder
}
}
private static void AddSingleLiteral(uint pixel, bool useColorCache, ColorCache colorCache, Vp8LBackwardRefs refs)
private static void AddSingleLiteral(uint pixel, bool useColorCache, ColorCache? colorCache, Vp8LBackwardRefs refs)
{
PixOrCopy v;
if (useColorCache)
{
int key = colorCache.GetIndex(pixel);
int key = colorCache!.GetIndex(pixel);
if (colorCache.Lookup(key) == pixel)
{
v = PixOrCopy.CreateCacheIdx(key);

31
src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Runtime.CompilerServices;
@ -14,31 +13,31 @@ internal class ColorCache
private const uint HashMul = 0x1e35a7bdu;
/// <summary>
/// Gets the color entries.
/// Initializes a new instance of the <see cref="ColorCache"/> class.
/// </summary>
public uint[] Colors { get; private set; }
/// <param name="hashBits">The hashBits determine the size of cache. It will be 1 left shifted by hashBits.</param>
public ColorCache(int hashBits)
{
int hashSize = 1 << hashBits;
this.Colors = new uint[hashSize];
this.HashBits = hashBits;
this.HashShift = 32 - hashBits;
}
/// <summary>
/// Gets the hash shift: 32 - hashBits.
/// Gets the color entries.
/// </summary>
public int HashShift { get; private set; }
public uint[] Colors { get; }
/// <summary>
/// Gets the hash bits.
/// Gets the hash shift: 32 - hashBits.
/// </summary>
public int HashBits { get; private set; }
public int HashShift { get; }
/// <summary>
/// Initializes a new color cache.
/// Gets the hash bits.
/// </summary>
/// <param name="hashBits">The hashBits determine the size of cache. It will be 1 left shifted by hashBits.</param>
public void Init(int hashBits)
{
int hashSize = 1 << hashBits;
this.Colors = new uint[hashSize];
this.HashBits = hashBits;
this.HashShift = 32 - hashBits;
}
public int HashBits { get; }
/// <summary>
/// Inserts a new color into the cache.

5
src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Diagnostics;
@ -33,7 +32,7 @@ internal class CostInterval
public int Index { get; set; }
public CostInterval Previous { get; set; }
public CostInterval? Previous { get; set; }
public CostInterval Next { get; set; }
public CostInterval? Next { get; set; }
}

19
src/ImageSharp/Formats/Webp/Lossless/CostManager.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using SixLabors.ImageSharp.Memory;
@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless;
/// </summary>
internal sealed class CostManager : IDisposable
{
private CostInterval head;
private CostInterval? head;
private const int FreeIntervalsStartCount = 25;
@ -103,10 +102,10 @@ internal sealed class CostManager : IDisposable
/// <param name="doCleanIntervals">If 'doCleanIntervals' is true, intervals that end before 'i' will be popped.</param>
public void UpdateCostAtIndex(int i, bool doCleanIntervals)
{
CostInterval current = this.head;
CostInterval? current = this.head;
while (current != null && current.Start <= i)
{
CostInterval next = current.Next;
CostInterval? next = current.Next;
if (current.End <= i)
{
if (doCleanIntervals)
@ -155,7 +154,7 @@ internal sealed class CostManager : IDisposable
return;
}
CostInterval interval = this.head;
CostInterval? interval = this.head;
for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++)
{
// Define the intersection of the ith interval with the new one.
@ -163,7 +162,7 @@ internal sealed class CostManager : IDisposable
int end = position + (this.CacheIntervals[i].End > len ? len : this.CacheIntervals[i].End);
float cost = (float)(distanceCost + this.CacheIntervals[i].Cost);
CostInterval intervalNext;
CostInterval? intervalNext;
for (; interval != null && interval.Start < end; interval = intervalNext)
{
intervalNext = interval.Next;
@ -225,7 +224,7 @@ internal sealed class CostManager : IDisposable
/// Pop an interval from the manager.
/// </summary>
/// <param name="interval">The interval to remove.</param>
private void PopInterval(CostInterval interval)
private void PopInterval(CostInterval? interval)
{
if (interval == null)
{
@ -240,7 +239,7 @@ internal sealed class CostManager : IDisposable
this.freeIntervals.Push(interval);
}
private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end)
private void InsertInterval(CostInterval? intervalIn, float cost, int position, int start, int end)
{
if (start >= end)
{
@ -271,7 +270,7 @@ internal sealed class CostManager : IDisposable
/// it was orphaned (which can be NULL), set it at the right place in the list
/// of intervals using the start_ ordering and the previous interval as a hint.
/// </summary>
private void PositionOrphanInterval(CostInterval current, CostInterval previous)
private void PositionOrphanInterval(CostInterval current, CostInterval? previous)
{
previous ??= this.head;
@ -292,7 +291,7 @@ internal sealed class CostManager : IDisposable
/// <summary>
/// Given two intervals, make 'prev' be the previous one of 'next' in 'manager'.
/// </summary>
private void ConnectIntervals(CostInterval prev, CostInterval next)
private void ConnectIntervals(CostInterval? prev, CostInterval? next)
{
if (prev != null)
{

58
src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs

@ -96,7 +96,7 @@ internal static unsafe class LosslessUtils
{
if (Avx2.IsSupported)
{
var addGreenToBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255);
Vector256<byte> addGreenToBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255);
int numPixels = pixelData.Length;
nint i;
for (i = 0; i <= numPixels - 8; i += 8)
@ -115,7 +115,7 @@ internal static unsafe class LosslessUtils
}
else if (Ssse3.IsSupported)
{
var addGreenToBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255);
Vector128<byte> addGreenToBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255);
int numPixels = pixelData.Length;
nint i;
for (i = 0; i <= numPixels - 4; i += 4)
@ -138,13 +138,11 @@ internal static unsafe class LosslessUtils
nint i;
for (i = 0; i <= numPixels - 4; i += 4)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte();
Vector128<ushort> a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g
Vector128<ushort> b = Sse2.ShuffleLow(a, mmShuffle_2200);
Vector128<ushort> c = Sse2.ShuffleHigh(b, mmShuffle_2200); // 0g0g
Vector128<ushort> b = Sse2.ShuffleLow(a, SimdUtils.Shuffle.MMShuffle2200);
Vector128<ushort> c = Sse2.ShuffleHigh(b, SimdUtils.Shuffle.MMShuffle2200); // 0g0g
Vector128<byte> output = Sse2.Add(input.AsByte(), c.AsByte());
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
}
@ -178,7 +176,7 @@ internal static unsafe class LosslessUtils
{
if (Avx2.IsSupported)
{
var subtractGreenFromBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255);
Vector256<byte> subtractGreenFromBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255);
int numPixels = pixelData.Length;
nint i;
for (i = 0; i <= numPixels - 8; i += 8)
@ -197,7 +195,7 @@ internal static unsafe class LosslessUtils
}
else if (Ssse3.IsSupported)
{
var subtractGreenFromBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255);
Vector128<byte> subtractGreenFromBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255);
int numPixels = pixelData.Length;
nint i;
for (i = 0; i <= numPixels - 4; i += 4)
@ -220,13 +218,11 @@ internal static unsafe class LosslessUtils
nint i;
for (i = 0; i <= numPixels - 4; i += 4)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte();
Vector128<ushort> a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g
Vector128<ushort> b = Sse2.ShuffleLow(a, mmShuffle_2200);
Vector128<ushort> c = Sse2.ShuffleHigh(b, mmShuffle_2200); // 0g0g
Vector128<ushort> b = Sse2.ShuffleLow(a, SimdUtils.Shuffle.MMShuffle2200);
Vector128<ushort> c = Sse2.ShuffleHigh(b, SimdUtils.Shuffle.MMShuffle2200); // 0g0g
Vector128<byte> output = Sse2.Subtract(input.AsByte(), c.AsByte());
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
}
@ -334,7 +330,7 @@ internal static unsafe class LosslessUtils
while (y < yEnd)
{
int predRowIdx = predRowIdxStart;
var m = default(Vp8LMultipliers);
Vp8LMultipliers m = default;
int srcSafeEnd = pixelPos + safeWidth;
int srcEnd = pixelPos + width;
while (pixelPos < srcSafeEnd)
@ -371,21 +367,19 @@ internal static unsafe class LosslessUtils
{
if (Avx2.IsSupported && numPixels >= 8)
{
var transformColorAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
var transformColorRedBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
Vector256<byte> transformColorAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector256<byte> transformColorRedBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
Vector256<int> multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector256<int> multsb2 = MkCst32(Cst5b(m.RedToBlue), 0);
nint idx;
for (idx = 0; idx <= numPixels - 8; idx += 8)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector256<uint> input = Unsafe.As<uint, Vector256<uint>>(ref pos);
Vector256<byte> a = Avx2.And(input.AsByte(), transformColorAlphaGreenMask256);
Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), SimdUtils.Shuffle.MMShuffle2200);
Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), SimdUtils.Shuffle.MMShuffle2200);
Vector256<short> d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector256<short> e = Avx2.ShiftLeftLogical(input.AsInt16(), 8);
Vector256<short> f = Avx2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16());
@ -403,20 +397,18 @@ internal static unsafe class LosslessUtils
}
else if (Sse2.IsSupported)
{
var transformColorAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
var transformColorRedBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
Vector128<byte> transformColorAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector128<byte> transformColorRedBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
Vector128<int> multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector128<int> multsb2 = MkCst16(Cst5b(m.RedToBlue), 0);
nint idx;
for (idx = 0; idx <= numPixels - 4; idx += 4)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector128<uint> input = Unsafe.As<uint, Vector128<uint>>(ref pos);
Vector128<byte> a = Sse2.And(input.AsByte(), transformColorAlphaGreenMask);
Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), SimdUtils.Shuffle.MMShuffle2200);
Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), SimdUtils.Shuffle.MMShuffle2200);
Vector128<short> d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector128<short> e = Sse2.ShiftLeftLogical(input.AsInt16(), 8);
Vector128<short> f = Sse2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16());
@ -465,19 +457,17 @@ internal static unsafe class LosslessUtils
{
if (Avx2.IsSupported && pixelData.Length >= 8)
{
var transformColorInverseAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector256<byte> transformColorInverseAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector256<int> multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector256<int> multsb2 = MkCst32(Cst5b(m.RedToBlue), 0);
nint idx;
for (idx = 0; idx <= pixelData.Length - 8; idx += 8)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector256<uint> input = Unsafe.As<uint, Vector256<uint>>(ref pos);
Vector256<byte> a = Avx2.And(input.AsByte(), transformColorInverseAlphaGreenMask256);
Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), SimdUtils.Shuffle.MMShuffle2200);
Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), SimdUtils.Shuffle.MMShuffle2200);
Vector256<short> d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector256<byte> e = Avx2.Add(input.AsByte(), d.AsByte());
Vector256<short> f = Avx2.ShiftLeftLogical(e.AsInt16(), 8);
@ -496,20 +486,18 @@ internal static unsafe class LosslessUtils
}
else if (Sse2.IsSupported)
{
var transformColorInverseAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector128<byte> transformColorInverseAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector128<int> multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector128<int> multsb2 = MkCst16(Cst5b(m.RedToBlue), 0);
nint idx;
for (idx = 0; idx <= pixelData.Length - 4; idx += 4)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector128<uint> input = Unsafe.As<uint, Vector128<uint>>(ref pos);
Vector128<byte> a = Sse2.And(input.AsByte(), transformColorInverseAlphaGreenMask);
Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), SimdUtils.Shuffle.MMShuffle2200);
Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), SimdUtils.Shuffle.MMShuffle2200);
Vector128<short> d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector128<byte> e = Sse2.Add(input.AsByte(), d.AsByte());
Vector128<short> f = Sse2.ShiftLeftLogical(e.AsInt16(), 8);

3
src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs

@ -158,10 +158,9 @@ internal sealed class WebpLosslessDecoder
// Finish setting up the color-cache.
if (isColorCachePresent)
{
decoder.Metadata.ColorCache = new ColorCache();
decoder.Metadata.ColorCache = new ColorCache(colorCacheBits);
colorCacheSize = 1 << colorCacheBits;
decoder.Metadata.ColorCacheSize = colorCacheSize;
decoder.Metadata.ColorCache.Init(colorCacheBits);
}
else
{

10
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -348,12 +348,18 @@ internal class Vp8Encoder : IDisposable
// Extract and encode alpha channel data, if present.
int alphaDataSize = 0;
bool alphaCompressionSucceeded = false;
using AlphaEncoder alphaEncoder = new();
Span<byte> alphaData = Span<byte>.Empty;
if (hasAlpha)
{
// TODO: This can potentially run in an separate task.
IMemoryOwner<byte> encodedAlphaData = alphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.skipMetadata, this.alphaCompression, out alphaDataSize);
using IMemoryOwner<byte> encodedAlphaData = AlphaEncoder.EncodeAlpha(
image,
this.configuration,
this.memoryAllocator,
this.skipMetadata,
this.alphaCompression,
out alphaDataSize);
alphaData = encodedAlphaData.GetSpan();
if (alphaDataSize < pixelCount)
{

9
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs

@ -521,9 +521,8 @@ internal static unsafe class Vp8Encoding
{
// *in01 = 00 01 10 11 02 03 12 13
// *in23 = 20 21 30 31 22 23 32 33
const byte mmShuffle_2301 = 0b_10_11_00_01;
Vector128<short> shuf01_p = Sse2.ShuffleHigh(row01, mmShuffle_2301);
Vector128<short> shuf32_p = Sse2.ShuffleHigh(row23, mmShuffle_2301);
Vector128<short> shuf01_p = Sse2.ShuffleHigh(row01, SimdUtils.Shuffle.MMShuffle2301);
Vector128<short> shuf32_p = Sse2.ShuffleHigh(row23, SimdUtils.Shuffle.MMShuffle2301);
// 00 01 10 11 03 02 13 12
// 20 21 30 31 23 22 33 32
@ -555,9 +554,7 @@ internal static unsafe class Vp8Encoding
Vector128<short> shi = Sse2.UnpackHigh(s03, s12); // 2 3 2 3 2 3
Vector128<int> v23 = Sse2.UnpackHigh(slo.AsInt32(), shi.AsInt32());
out01 = Sse2.UnpackLow(slo.AsInt32(), shi.AsInt32());
const byte mmShuffle_1032 = 0b_01_00_11_10;
out32 = Sse2.Shuffle(v23, mmShuffle_1032);
out32 = Sse2.Shuffle(v23, SimdUtils.Shuffle.MMShuffle1032);
}
public static void FTransformPass2SSE2(Vector128<int> v01, Vector128<int> v32, Span<short> output)

25
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Runtime.CompilerServices;
@ -46,17 +45,17 @@ internal class WebpAnimationDecoder : IDisposable
/// <summary>
/// The abstract metadata.
/// </summary>
private ImageMetadata metadata;
private ImageMetadata? metadata;
/// <summary>
/// The gif specific metadata.
/// </summary>
private WebpMetadata webpMetadata;
private WebpMetadata? webpMetadata;
/// <summary>
/// The alpha data, if an ALPH chunk is present.
/// </summary>
private IMemoryOwner<byte> alphaData;
private IMemoryOwner<byte>? alphaData;
/// <summary>
/// Initializes a new instance of the <see cref="WebpAnimationDecoder"/> class.
@ -83,8 +82,8 @@ internal class WebpAnimationDecoder : IDisposable
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, WebpFeatures features, uint width, uint height, uint completeDataSize)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
ImageFrame<TPixel> previousFrame = null;
Image<TPixel>? image = null;
ImageFrame<TPixel>? previousFrame = null;
this.metadata = new ImageMetadata();
this.webpMetadata = this.metadata.GetWebpMetadata();
@ -99,12 +98,12 @@ internal class WebpAnimationDecoder : IDisposable
switch (chunkType)
{
case WebpChunkType.Animation:
uint dataSize = this.ReadFrame(stream, ref image, ref previousFrame, width, height, features.AnimationBackgroundColor.Value);
uint dataSize = this.ReadFrame(stream, ref image, ref previousFrame, width, height, features.AnimationBackgroundColor!.Value);
remainingBytes -= (int)dataSize;
break;
case WebpChunkType.Xmp:
case WebpChunkType.Exif:
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image.Metadata, false, this.buffer);
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, false, this.buffer);
break;
default:
WebpThrowHelper.ThrowImageFormatException("Read unexpected webp chunk data");
@ -117,7 +116,7 @@ internal class WebpAnimationDecoder : IDisposable
}
}
return image;
return image!;
}
/// <summary>
@ -130,7 +129,7 @@ internal class WebpAnimationDecoder : IDisposable
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="backgroundColor">The default background color of the canvas in.</param>
private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, uint width, uint height, Color backgroundColor)
private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? image, ref ImageFrame<TPixel>? previousFrame, uint width, uint height, Color backgroundColor)
where TPixel : unmanaged, IPixel<TPixel>
{
AnimationFrameData frameData = this.ReadFrameHeader(stream);
@ -146,7 +145,7 @@ internal class WebpAnimationDecoder : IDisposable
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
}
WebpImageInfo webpInfo = null;
WebpImageInfo? webpInfo = null;
WebpFeatures features = new();
switch (chunkType)
{
@ -163,7 +162,7 @@ internal class WebpAnimationDecoder : IDisposable
break;
}
ImageFrame<TPixel> currentFrame = null;
ImageFrame<TPixel>? currentFrame = null;
ImageFrame<TPixel> imageFrame;
if (previousFrame is null)
{
@ -175,7 +174,7 @@ internal class WebpAnimationDecoder : IDisposable
}
else
{
currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection.
currentFrame = image!.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection.
SetFrameMetadata(currentFrame.Metadata, frameData.Duration);

1
src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Webp.BitReader;

220
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Buffers.Binary;
@ -9,9 +8,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp;
@ -41,35 +38,20 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// </summary>
private readonly uint maxFrames;
/// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary>
private ImageMetadata metadata;
/// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary>
private IMemoryOwner<byte> alphaData;
private IMemoryOwner<byte>? alphaData;
/// <summary>
/// Used for allocating memory during the decoding operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The stream to decode from.
/// </summary>
private BufferedReadStream currentStream;
/// <summary>
/// The webp specific metadata.
/// </summary>
private WebpMetadata webpMetadata;
/// <summary>
/// Information about the webp image.
/// </summary>
private WebpImageInfo webImageInfo;
private WebpImageInfo? webImageInfo;
/// <summary>
/// Initializes a new instance of the <see cref="WebpDecoderCore"/> class.
@ -88,25 +70,24 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
public DecoderOptions Options { get; }
/// <inheritdoc/>
public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height);
public Size Dimensions => new((int)this.webImageInfo!.Width, (int)this.webImageInfo.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
Image<TPixel>? image = null;
try
{
this.metadata = new ImageMetadata();
this.currentStream = stream;
ImageMetadata metadata = new();
uint fileSize = this.ReadImageHeader();
uint fileSize = this.ReadImageHeader(stream);
using (this.webImageInfo = this.ReadVp8Info())
using (this.webImageInfo = this.ReadVp8Info(stream, metadata))
{
if (this.webImageInfo.Features is { Animation: true })
{
using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.configuration, this.maxFrames);
using WebpAnimationDecoder animationDecoder = new(this.memoryAllocator, this.configuration, this.maxFrames);
return animationDecoder.Decode<TPixel>(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
}
@ -115,23 +96,23 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
WebpThrowHelper.ThrowNotSupportedException("Animations are not supported");
}
image = new Image<TPixel>(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata);
image = new Image<TPixel>(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.webImageInfo.IsLossless)
{
var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration);
WebpLosslessDecoder losslessDecoder = new(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration);
losslessDecoder.Decode(pixels, image.Width, image.Height);
}
else
{
var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration);
WebpLossyDecoder lossyDecoder = new(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration);
lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData);
}
// There can be optional chunks after the image data, like EXIF and XMP.
if (this.webImageInfo.Features != null)
{
this.ParseOptionalChunks(this.webImageInfo.Features);
this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features);
}
return image;
@ -147,34 +128,35 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.currentStream = stream;
this.ReadImageHeader(stream);
this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info(true))
ImageMetadata metadata = new();
using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true))
{
return new ImageInfo(
new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel),
new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height),
this.metadata);
metadata);
}
}
/// <summary>
/// Reads and skips over the image header.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <returns>The file size in bytes.</returns>
private uint ReadImageHeader()
private uint ReadImageHeader(BufferedReadStream stream)
{
// Skip FourCC header, we already know its a RIFF file at this point.
this.currentStream.Skip(4);
stream.Skip(4);
// Read file size.
// The size of the file in bytes starting at offset 8.
// The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC.
uint fileSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
uint fileSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
// Skip 'WEBP' from the header.
this.currentStream.Skip(4);
stream.Skip(4);
return fileSize;
}
@ -182,42 +164,43 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads information present in the image header, about the image content and how to decode the image.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <returns>Information about the webp image.</returns>
private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false)
private WebpImageInfo ReadVp8Info(BufferedReadStream stream, ImageMetadata metadata, bool ignoreAlpha = false)
{
this.metadata = new ImageMetadata();
this.webpMetadata = this.metadata.GetFormatMetadata(WebpFormat.Instance);
WebpMetadata webpMetadata = metadata.GetFormatMetadata(WebpFormat.Instance);
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer);
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
var features = new WebpFeatures();
WebpFeatures features = new();
switch (chunkType)
{
case WebpChunkType.Vp8:
this.webpMetadata.FileFormat = WebpFileFormatType.Lossy;
return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, this.currentStream, this.buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossy;
return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features);
case WebpChunkType.Vp8L:
this.webpMetadata.FileFormat = WebpFileFormatType.Lossless;
return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, this.currentStream, this.buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossless;
return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features);
case WebpChunkType.Vp8X:
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(this.currentStream, this.buffer, features);
while (this.currentStream.Position < this.currentStream.Length)
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, this.buffer, features);
while (stream.Position < stream.Length)
{
chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer);
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
if (chunkType == WebpChunkType.Vp8)
{
this.webpMetadata.FileFormat = WebpFileFormatType.Lossy;
webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, this.currentStream, this.buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossy;
webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features);
}
else if (chunkType == WebpChunkType.Vp8L)
{
this.webpMetadata.FileFormat = WebpFileFormatType.Lossless;
webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, this.currentStream, this.buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossless;
webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features);
}
else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType))
{
bool isAnimationChunk = this.ParseOptionalExtendedChunks(chunkType, features, ignoreAlpha);
bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha);
if (isAnimationChunk)
{
return webpInfos;
@ -226,8 +209,8 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
else
{
// Ignore unknown chunks.
uint chunkSize = this.ReadChunkSize();
this.currentStream.Skip((int)chunkSize);
uint chunkSize = this.ReadChunkSize(stream);
stream.Skip((int)chunkSize);
}
}
@ -242,32 +225,39 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="chunkType">The chunk type.</param>
/// <param name="features">The webp image features.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <returns>true, if its a alpha chunk.</returns>
private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features, bool ignoreAlpha)
private bool ParseOptionalExtendedChunks(
BufferedReadStream stream,
ImageMetadata metadata,
WebpChunkType chunkType,
WebpFeatures features,
bool ignoreAlpha)
{
switch (chunkType)
{
case WebpChunkType.Iccp:
this.ReadIccProfile();
this.ReadIccProfile(stream, metadata);
break;
case WebpChunkType.Exif:
this.ReadExifProfile();
this.ReadExifProfile(stream, metadata);
break;
case WebpChunkType.Xmp:
this.ReadXmpProfile();
this.ReadXmpProfile(stream, metadata);
break;
case WebpChunkType.AnimationParameter:
this.ReadAnimationParameters(features);
this.ReadAnimationParameters(stream, features);
return true;
case WebpChunkType.Alpha:
this.ReadAlphaData(features, ignoreAlpha);
this.ReadAlphaData(stream, features, ignoreAlpha);
break;
default:
WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header");
@ -280,32 +270,34 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the optional metadata EXIF of XMP profiles, which can follow the image data.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="features">The webp features.</param>
private void ParseOptionalChunks(WebpFeatures features)
private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features)
{
if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData))
{
return;
}
long streamLength = this.currentStream.Length;
while (this.currentStream.Position < streamLength)
long streamLength = stream.Length;
while (stream.Position < streamLength)
{
// Read chunk header.
WebpChunkType chunkType = this.ReadChunkType();
if (chunkType == WebpChunkType.Exif && this.metadata.ExifProfile == null)
WebpChunkType chunkType = this.ReadChunkType(stream);
if (chunkType == WebpChunkType.Exif && metadata.ExifProfile == null)
{
this.ReadExifProfile();
this.ReadExifProfile(stream, metadata);
}
else if (chunkType == WebpChunkType.Xmp && this.metadata.XmpProfile == null)
else if (chunkType == WebpChunkType.Xmp && metadata.XmpProfile == null)
{
this.ReadXmpProfile();
this.ReadXmpProfile(stream, metadata);
}
else
{
// Skip duplicate XMP or EXIF chunk.
uint chunkLength = this.ReadChunkSize();
this.currentStream.Skip((int)chunkLength);
uint chunkLength = this.ReadChunkSize(stream);
stream.Skip((int)chunkLength);
}
}
}
@ -313,76 +305,80 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the EXIF profile from the stream.
/// </summary>
private void ReadExifProfile()
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata)
{
uint exifChunkSize = this.ReadChunkSize();
uint exifChunkSize = this.ReadChunkSize(stream);
if (this.skipMetadata)
{
this.currentStream.Skip((int)exifChunkSize);
stream.Skip((int)exifChunkSize);
}
else
{
byte[] exifData = new byte[exifChunkSize];
int bytesRead = this.currentStream.Read(exifData, 0, (int)exifChunkSize);
int bytesRead = stream.Read(exifData, 0, (int)exifChunkSize);
if (bytesRead != exifChunkSize)
{
// Ignore invalid chunk.
return;
}
var profile = new ExifProfile(exifData);
this.metadata.ExifProfile = profile;
metadata.ExifProfile = new(exifData);
}
}
/// <summary>
/// Reads the XMP profile the stream.
/// </summary>
private void ReadXmpProfile()
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata)
{
uint xmpChunkSize = this.ReadChunkSize();
uint xmpChunkSize = this.ReadChunkSize(stream);
if (this.skipMetadata)
{
this.currentStream.Skip((int)xmpChunkSize);
stream.Skip((int)xmpChunkSize);
}
else
{
byte[] xmpData = new byte[xmpChunkSize];
int bytesRead = this.currentStream.Read(xmpData, 0, (int)xmpChunkSize);
int bytesRead = stream.Read(xmpData, 0, (int)xmpChunkSize);
if (bytesRead != xmpChunkSize)
{
// Ignore invalid chunk.
return;
}
var profile = new XmpProfile(xmpData);
this.metadata.XmpProfile = profile;
metadata.XmpProfile = new(xmpData);
}
}
/// <summary>
/// Reads the ICCP chunk from the stream.
/// </summary>
private void ReadIccProfile()
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata)
{
uint iccpChunkSize = this.ReadChunkSize();
uint iccpChunkSize = this.ReadChunkSize(stream);
if (this.skipMetadata)
{
this.currentStream.Skip((int)iccpChunkSize);
stream.Skip((int)iccpChunkSize);
}
else
{
byte[] iccpData = new byte[iccpChunkSize];
int bytesRead = this.currentStream.Read(iccpData, 0, (int)iccpChunkSize);
int bytesRead = stream.Read(iccpData, 0, (int)iccpChunkSize);
if (bytesRead != iccpChunkSize)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk");
}
var profile = new IccProfile(iccpData);
IccProfile profile = new(iccpData);
if (profile.CheckIsValid())
{
this.metadata.IccProfile = profile;
metadata.IccProfile = profile;
}
}
}
@ -390,17 +386,18 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the animation parameters chunk from the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The webp features.</param>
private void ReadAnimationParameters(WebpFeatures features)
private void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features)
{
features.Animation = true;
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
byte blue = (byte)this.currentStream.ReadByte();
byte green = (byte)this.currentStream.ReadByte();
byte red = (byte)this.currentStream.ReadByte();
byte alpha = (byte)this.currentStream.ReadByte();
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
byte blue = (byte)stream.ReadByte();
byte green = (byte)stream.ReadByte();
byte red = (byte)stream.ReadByte();
byte alpha = (byte)stream.ReadByte();
features.AnimationBackgroundColor = new Color(new Rgba32(red, green, blue, alpha));
int bytesRead = this.currentStream.Read(this.buffer, 0, 2);
int bytesRead = stream.Read(this.buffer, 0, 2);
if (bytesRead != 2)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count");
@ -412,22 +409,23 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the alpha data chunk data from the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The features.</param>
/// <param name="ignoreAlpha">if set to true, skips the chunk data.</param>
private void ReadAlphaData(WebpFeatures features, bool ignoreAlpha)
private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha)
{
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
if (ignoreAlpha)
{
this.currentStream.Skip((int)alphaChunkSize);
stream.Skip((int)alphaChunkSize);
return;
}
features.AlphaChunkHeader = (byte)this.currentStream.ReadByte();
features.AlphaChunkHeader = (byte)stream.ReadByte();
int alphaDataSize = (int)(alphaChunkSize - 1);
this.alphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
Span<byte> alphaData = this.alphaData.GetSpan();
int bytesRead = this.currentStream.Read(alphaData, 0, alphaDataSize);
int bytesRead = stream.Read(alphaData, 0, alphaDataSize);
if (bytesRead != alphaDataSize)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the alpha data from the stream");
@ -437,15 +435,15 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Identifies the chunk type from the chunk.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
private WebpChunkType ReadChunkType()
private WebpChunkType ReadChunkType(BufferedReadStream stream)
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
if (stream.Read(this.buffer, 0, 4) == 4)
{
var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
return chunkType;
return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
}
throw new ImageFormatException("Invalid Webp data.");
@ -455,10 +453,12 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload,
/// so the chunk size will be increased by 1 in those cases.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <returns>The chunk size in bytes.</returns>
private uint ReadChunkSize()
/// <exception cref="ImageFormatException">Invalid data.</exception>
private uint ReadChunkSize(BufferedReadStream stream)
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
if (stream.Read(this.buffer, 0, 4) == 4)
{
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1;

2
src/ImageSharp/Formats/Webp/WebpEncoder.cs

@ -82,7 +82,7 @@ public sealed class WebpEncoder : ImageEncoder
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
WebpEncoderCore encoder = new(this, image.GetMemoryAllocator());
WebpEncoderCore encoder = new(this, image.GetConfiguration());
encoder.Encode(image, stream, cancellationToken);
}
}

12
src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Memory;
@ -81,16 +79,17 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
/// <summary>
/// The global configuration.
/// </summary>
private Configuration configuration;
private readonly Configuration configuration;
/// <summary>
/// Initializes a new instance of the <see cref="WebpEncoderCore"/> class.
/// </summary>
/// <param name="encoder">The encoder with options.</param>
/// <param name="memoryAllocator">The memory manager.</param>
public WebpEncoderCore(WebpEncoder encoder, MemoryAllocator memoryAllocator)
/// <param name="configuration">The global configuration.</param>
public WebpEncoderCore(WebpEncoder encoder, Configuration configuration)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.alphaCompression = encoder.UseAlphaCompression;
this.fileFormat = encoder.FileFormat;
this.quality = encoder.Quality;
@ -117,7 +116,6 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
this.configuration = image.GetConfiguration();
bool lossless;
if (this.fileFormat is not null)
{

9
src/ImageSharp/Formats/Webp/WebpImageInfo.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Formats.Webp.BitReader;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
@ -36,7 +35,7 @@ internal class WebpImageInfo : IDisposable
/// <summary>
/// Gets or sets additional features present in a VP8X image.
/// </summary>
public WebpFeatures Features { get; set; }
public WebpFeatures? Features { get; set; }
/// <summary>
/// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1.
@ -46,17 +45,17 @@ internal class WebpImageInfo : IDisposable
/// <summary>
/// Gets or sets the VP8 frame header.
/// </summary>
public Vp8FrameHeader Vp8FrameHeader { get; set; }
public Vp8FrameHeader? Vp8FrameHeader { get; set; }
/// <summary>
/// Gets or sets the VP8L bitreader. Will be <see langword="null"/>, if its not a lossless image.
/// </summary>
public Vp8LBitReader Vp8LBitReader { get; set; }
public Vp8LBitReader? Vp8LBitReader { get; set; }
/// <summary>
/// Gets or sets the VP8 bitreader. Will be <see langword="null"/>, if its not a lossy image.
/// </summary>
public Vp8BitReader Vp8BitReader { get; set; }
public Vp8BitReader? Vp8BitReader { get; set; }
/// <inheritdoc/>
public void Dispose()

6
src/ImageSharp/Formats/Webp/WebpThrowHelper.cs

@ -1,15 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Webp;
internal static class WebpThrowHelper
{
[DoesNotReturn]
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
[DoesNotReturn]
public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage);
[DoesNotReturn]
public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage);
[DoesNotReturn]
public static void ThrowInvalidImageDimensions(string errorMessage) => throw new InvalidImageContentException(errorMessage);
}

3
src/ImageSharp/ImageExtensions.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Globalization;
using System.Text;
@ -180,6 +179,6 @@ public static partial class ImageExtensions
// Always available.
stream.TryGetBuffer(out ArraySegment<byte> buffer);
return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(buffer.Array, 0, (int)stream.Length)}";
return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(buffer.Array ?? Array.Empty<byte>(), 0, (int)stream.Length)}";
}
}

35
src/ImageSharp/ImageFrame{TPixel}.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -145,7 +144,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
}
/// <inheritdoc/>
public Buffer2D<TPixel> PixelBuffer { get; private set; }
public Buffer2D<TPixel> PixelBuffer { get; }
/// <summary>
/// Gets or sets the pixel at the specified position.
@ -183,7 +182,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
try
{
var accessor = new PixelAccessor<TPixel>(this.PixelBuffer);
PixelAccessor<TPixel> accessor = new(this.PixelBuffer);
processPixels(accessor);
}
finally
@ -211,8 +210,8 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
try
{
var accessor1 = new PixelAccessor<TPixel>(this.PixelBuffer);
var accessor2 = new PixelAccessor<TPixel2>(frame2.PixelBuffer);
PixelAccessor<TPixel> accessor1 = new(this.PixelBuffer);
PixelAccessor<TPixel2> accessor2 = new(frame2.PixelBuffer);
processPixels(accessor1, accessor2);
}
finally
@ -247,9 +246,9 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
try
{
var accessor1 = new PixelAccessor<TPixel>(this.PixelBuffer);
var accessor2 = new PixelAccessor<TPixel2>(frame2.PixelBuffer);
var accessor3 = new PixelAccessor<TPixel3>(frame3.PixelBuffer);
PixelAccessor<TPixel> accessor1 = new(this.PixelBuffer);
PixelAccessor<TPixel2> accessor2 = new(frame2.PixelBuffer);
PixelAccessor<TPixel3> accessor3 = new(frame3.PixelBuffer);
processPixels(accessor1, accessor2, accessor3);
}
finally
@ -310,6 +309,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
/// Copies the pixels to a <see cref="Buffer2D{TPixel}"/> of the same size.
/// </summary>
/// <param name="target">The target pixel buffer accessor.</param>
/// <exception cref="ArgumentException">ImageFrame{TPixel}.CopyTo(): target must be of the same size!</exception>
internal void CopyTo(Buffer2D<TPixel> target)
{
if (this.Size() != target.Size())
@ -342,8 +342,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
if (disposing)
{
this.PixelBuffer?.Dispose();
this.PixelBuffer = null;
this.PixelBuffer.Dispose();
}
this.isDisposed = true;
@ -379,14 +378,14 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
/// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>The <see cref="ImageFrame{TPixel}"/></returns>
internal ImageFrame<TPixel> Clone(Configuration configuration) => new ImageFrame<TPixel>(configuration, this);
internal ImageFrame<TPixel> Clone(Configuration configuration) => new(configuration, this);
/// <summary>
/// Returns a copy of the image frame in the given pixel format.
/// </summary>
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <returns>The <see cref="ImageFrame{TPixel2}"/></returns>
internal ImageFrame<TPixel2> CloneAs<TPixel2>()
internal ImageFrame<TPixel2>? CloneAs<TPixel2>()
where TPixel2 : unmanaged, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.GetConfiguration());
/// <summary>
@ -400,11 +399,11 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
{
if (typeof(TPixel2) == typeof(TPixel))
{
return this.Clone(configuration) as ImageFrame<TPixel2>;
return (this.Clone(configuration) as ImageFrame<TPixel2>)!;
}
var target = new ImageFrame<TPixel2>(configuration, this.Width, this.Height, this.Metadata.DeepClone());
var operation = new RowIntervalOperation<TPixel2>(this.PixelBuffer, target.PixelBuffer, configuration);
ImageFrame<TPixel2> target = new(configuration, this.Width, this.Height, this.Metadata.DeepClone());
RowIntervalOperation<TPixel2> operation = new(this.PixelBuffer, target.PixelBuffer, configuration);
ParallelRowIterator.IterateRowIntervals(
configuration,
@ -447,14 +446,12 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
}
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowArgumentOutOfRangeException(string paramName)
{
throw new ArgumentOutOfRangeException(paramName);
}
private static void ThrowArgumentOutOfRangeException(string paramName) => throw new ArgumentOutOfRangeException(paramName);
/// <summary>
/// A <see langword="struct"/> implementing the clone logic for <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel2">The type of the target pixel format.</typeparam>
private readonly struct RowIntervalOperation<TPixel2> : IRowIntervalOperation
where TPixel2 : unmanaged, IPixel<TPixel2>
{

7
src/ImageSharp/IndexedImageFrame{TPixel}.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Runtime.CompilerServices;
@ -18,8 +17,8 @@ namespace SixLabors.ImageSharp;
public sealed class IndexedImageFrame<TPixel> : IPixelSource, IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
private Buffer2D<byte> pixelBuffer;
private IMemoryOwner<TPixel> paletteOwner;
private readonly Buffer2D<byte> pixelBuffer;
private readonly IMemoryOwner<TPixel> paletteOwner;
private bool isDisposed;
/// <summary>
@ -109,8 +108,6 @@ public sealed class IndexedImageFrame<TPixel> : IPixelSource, IDisposable
this.isDisposed = true;
this.pixelBuffer.Dispose();
this.paletteOwner.Dispose();
this.pixelBuffer = null;
this.paletteOwner = null;
}
}
}

8
src/ImageSharp/Memory/Buffer2D{T}.cs

@ -55,6 +55,8 @@ public sealed class Buffer2D<T> : IDisposable
/// </remarks>
internal MemoryGroup<T> FastMemoryGroup { get; private set; }
internal bool IsDisposed { get; private set; }
/// <summary>
/// Gets a reference to the element at the specified position.
/// </summary>
@ -79,7 +81,11 @@ public sealed class Buffer2D<T> : IDisposable
/// <summary>
/// Disposes the <see cref="Buffer2D{T}"/> instance
/// </summary>
public void Dispose() => this.FastMemoryGroup.Dispose();
public void Dispose()
{
this.FastMemoryGroup.Dispose();
this.IsDisposed = true;
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.

8112
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs

File diff suppressed because it is too large

79
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt

@ -13,6 +13,10 @@
// <auto-generated />
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
@ -86,18 +90,85 @@ var blenders = new []{
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount)
{
amount = Numerics.Clamp(amount, 0, 1);
for (int i = 0; i < destination.Length; i++)
if (Avx2.IsSupported && destination.Length >= 2)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (IntPtr)((uint)destination.Length / 2u));
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
Vector256<float> opacity = Vector256.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{
for (int i = 0; i < destination.Length; i++)
if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (IntPtr)((uint)destination.Length / 2u));
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1));
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
}

1602
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs

File diff suppressed because it is too large

174
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt

@ -15,6 +15,8 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
@ -31,11 +33,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return source;
}
/// <summary>
/// Returns the result of the "<#=blender#>Src compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>Src(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl);
/// <summary>
/// Returns the result of the "<#=blender#>SrcAtop" compositing equation.
/// </summary>
@ -46,7 +59,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Atop(backdrop, source, <#=blender#>(backdrop, source));
}
/// <summary>
/// Returns the result of the "<#=blender#>SrcAtop" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcAtop(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl);
return Atop(backdrop, source, <#=blender#>(backdrop, source));
}
@ -61,7 +89,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Over(backdrop, source, <#=blender#>(backdrop, source));
}
/// <summary>
/// Returns the result of the "<#=blender#>SrcOver" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcOver(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl);
return Over(backdrop, source, <#=blender#>(backdrop, source));
}
@ -76,11 +119,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return In(backdrop, source);
}
/// <summary>
/// Returns the result of the "<#=blender#>SrcIn" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcIn(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl));
/// <summary>
/// Returns the result of the "<#=blender#>SrcOut" compositing equation.
/// </summary>
@ -91,11 +145,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Out(backdrop, source);
}
/// <summary>
/// Returns the result of the "<#=blender#>SrcOut" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcOut(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl));
/// <summary>
/// Returns the result of the "<#=blender#>Dest" compositing equation.
/// </summary>
@ -109,6 +174,19 @@ internal static partial class PorterDuffFunctions
return backdrop;
}
/// <summary>
/// Returns the result of the "<#=blender#>Dest" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>Dest(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{
return backdrop;
}
/// <summary>
/// Returns the result of the "<#=blender#>DestAtop" compositing equation.
/// </summary>
@ -119,7 +197,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Atop(source, backdrop, <#=blender#>(source, backdrop));
}
/// <summary>
/// Returns the result of the "<#=blender#>DestAtop" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestAtop(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl);
return Atop(source, backdrop, <#=blender#>(source, backdrop));
}
@ -134,7 +227,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Over(source, backdrop, <#=blender#>(source, backdrop));
}
/// <summary>
/// Returns the result of the "<#=blender#>DestOver" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestOver(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl);
return Over(source, backdrop, <#=blender#>(source, backdrop));
}
@ -149,11 +257,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return In(source, backdrop);
}
/// <summary>
/// Returns the result of the "<#=blender#>DestIn" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestIn(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop);
/// <summary>
/// Returns the result of the "<#=blender#>DestOut" compositing equation.
/// </summary>
@ -164,11 +283,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Out(source, backdrop);
}
/// <summary>
/// Returns the result of the "<#=blender#>DestOut" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestOut(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop);
/// <summary>
/// Returns the result of the "<#=blender#>Xor" compositing equation.
/// </summary>
@ -179,11 +309,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Xor(backdrop, source);
}
/// <summary>
/// Returns the result of the "<#=blender#>Xor" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>Xor(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl));
/// <summary>
/// Returns the result of the "<#=blender#>Clear" compositing equation.
/// </summary>
@ -194,11 +335,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Clear(backdrop, source);
}
/// <summary>
/// Returns the result of the "<#=blender#>Clear" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>Clear(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl));
<#} #>
<# void GenerateGenericPixelBlender(string blender, string composer) { #>

366
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs

@ -3,6 +3,8 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
@ -19,6 +21,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
/// </remarks>
internal static partial class PorterDuffFunctions
{
private const int BlendAlphaControl = 0b_10_00_10_00;
private const int ShuffleAlphaControl = 0b_11_11_11_11;
/// <summary>
/// Returns the result of the "Normal" compositing equation.
/// </summary>
@ -27,9 +32,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Normal(Vector4 backdrop, Vector4 source)
{
return source;
}
=> source;
/// <summary>
/// Returns the result of the "Normal" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Normal(Vector256<float> backdrop, Vector256<float> source)
=> source;
/// <summary>
/// Returns the result of the "Multiply" compositing equation.
@ -39,9 +52,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Multiply(Vector4 backdrop, Vector4 source)
{
return backdrop * source;
}
=> backdrop * source;
/// <summary>
/// Returns the result of the "Multiply" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Multiply(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Multiply(backdrop, source);
/// <summary>
/// Returns the result of the "Add" compositing equation.
@ -51,9 +72,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Add(Vector4 backdrop, Vector4 source)
{
return Vector4.Min(Vector4.One, backdrop + source);
}
=> Vector4.Min(Vector4.One, backdrop + source);
/// <summary>
/// Returns the result of the "Add" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Add(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Min(Vector256.Create(1F), Avx.Add(backdrop, source));
/// <summary>
/// Returns the result of the "Subtract" compositing equation.
@ -63,9 +92,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Subtract(Vector4 backdrop, Vector4 source)
{
return Vector4.Max(Vector4.Zero, backdrop - source);
}
=> Vector4.Max(Vector4.Zero, backdrop - source);
/// <summary>
/// Returns the result of the "Subtract" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Subtract(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Max(Vector256<float>.Zero, Avx.Subtract(backdrop, source));
/// <summary>
/// Returns the result of the "Screen" compositing equation.
@ -75,8 +112,19 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Screen(Vector4 backdrop, Vector4 source)
=> Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source));
/// <summary>
/// Returns the result of the "Screen" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Screen(Vector256<float> backdrop, Vector256<float> source)
{
return Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source));
Vector256<float> vOne = Vector256.Create(1F);
return SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Subtract(vOne, backdrop), Avx.Subtract(vOne, source), vOne);
}
/// <summary>
@ -87,9 +135,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Darken(Vector4 backdrop, Vector4 source)
{
return Vector4.Min(backdrop, source);
}
=> Vector4.Min(backdrop, source);
/// <summary>
/// Returns the result of the "Darken" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Darken(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Min(backdrop, source);
/// <summary>
/// Returns the result of the "Lighten" compositing equation.
@ -98,10 +154,17 @@ internal static partial class PorterDuffFunctions
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Lighten(Vector4 backdrop, Vector4 source)
{
return Vector4.Max(backdrop, source);
}
public static Vector4 Lighten(Vector4 backdrop, Vector4 source) => Vector4.Max(backdrop, source);
/// <summary>
/// Returns the result of the "Lighten" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Lighten(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Max(backdrop, source);
/// <summary>
/// Returns the result of the "Overlay" compositing equation.
@ -119,6 +182,19 @@ internal static partial class PorterDuffFunctions
return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0));
}
/// <summary>
/// Returns the result of the "Overlay" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Overlay(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> color = OverlayValueFunction(backdrop, source);
return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256<float>.Zero, BlendAlphaControl));
}
/// <summary>
/// Returns the result of the "HardLight" compositing equation.
/// </summary>
@ -136,15 +212,44 @@ internal static partial class PorterDuffFunctions
}
/// <summary>
/// Helper function for Overlay andHardLight modes
/// Returns the result of the "HardLight" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> HardLight(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> color = OverlayValueFunction(source, backdrop);
return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256<float>.Zero, BlendAlphaControl));
}
/// <summary>
/// Helper function for Overlay and HardLight modes
/// </summary>
/// <param name="backdrop">Backdrop color element</param>
/// <param name="source">Source color element</param>
/// <returns>Overlay value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float OverlayValueFunction(float backdrop, float source)
=> backdrop <= 0.5f ? (2 * backdrop * source) : 1 - (2 * (1 - source) * (1 - backdrop));
/// <summary>
/// Helper function for Overlay and HardLight modes
/// </summary>
/// <param name="backdrop">Backdrop color element</param>
/// <param name="source">Source color element</param>
/// <returns>Overlay value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> OverlayValueFunction(Vector256<float> backdrop, Vector256<float> source)
{
return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - (2 * (1 - source) * (1 - backdrop));
Vector256<float> vOne = Vector256.Create(1F);
Vector256<float> left = Avx.Multiply(Avx.Add(backdrop, backdrop), source);
Vector256<float> vOneMinusSource = Avx.Subtract(vOne, source);
Vector256<float> right = SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Add(vOneMinusSource, vOneMinusSource), Avx.Subtract(vOne, backdrop), vOne);
Vector256<float> cmp = Avx.CompareGreaterThan(backdrop, Vector256.Create(.5F));
return Avx.BlendVariable(left, right, cmp);
}
/// <summary>
@ -158,21 +263,53 @@ internal static partial class PorterDuffFunctions
public static Vector4 Over(Vector4 destination, Vector4 source, Vector4 blend)
{
// calculate weights
float blendW = destination.W * source.W;
float dstW = destination.W - blendW;
float srcW = source.W - blendW;
Vector4 sW = PermuteW(source);
Vector4 dW = PermuteW(destination);
Vector4 blendW = sW * dW;
Vector4 dstW = dW - blendW;
Vector4 srcW = sW - blendW;
// calculate final alpha
float alpha = dstW + source.W;
Vector4 alpha = dstW + sW;
// calculate final color
Vector4 color = (destination * dstW) + (source * srcW) + (blend * blendW);
// unpremultiply
color /= MathF.Max(alpha, Constants.Epsilon);
color.W = alpha;
color /= Vector4.Max(alpha, new(Constants.Epsilon));
return WithW(color, alpha);
}
/// <summary>
/// Returns the result of the "Over" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="blend">The amount to blend. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Over(Vector256<float> destination, Vector256<float> source, Vector256<float> blend)
{
// calculate weights
Vector256<float> sW = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> dW = Avx.Permute(destination, ShuffleAlphaControl);
Vector256<float> blendW = Avx.Multiply(sW, dW);
Vector256<float> dstW = Avx.Subtract(dW, blendW);
Vector256<float> srcW = Avx.Subtract(sW, blendW);
// calculate final alpha
Vector256<float> alpha = Avx.Add(dstW, sW);
// calculate final color
Vector256<float> color = Avx.Multiply(destination, dstW);
color = SimdUtils.HwIntrinsics.MultiplyAdd(color, source, srcW);
color = SimdUtils.HwIntrinsics.MultiplyAdd(color, blend, blendW);
return color;
// unpremultiply
color = Avx.Divide(color, Avx.Max(alpha, Vector256.Create(Constants.Epsilon)));
return Avx.Blend(color, alpha, BlendAlphaControl);
}
/// <summary>
@ -186,20 +323,47 @@ internal static partial class PorterDuffFunctions
public static Vector4 Atop(Vector4 destination, Vector4 source, Vector4 blend)
{
// calculate weights
float blendW = destination.W * source.W;
float dstW = destination.W - blendW;
Vector4 sW = PermuteW(source);
Vector4 dW = PermuteW(destination);
Vector4 blendW = sW * dW;
Vector4 dstW = dW - blendW;
// calculate final alpha
float alpha = destination.W;
Vector4 alpha = dW;
// calculate final color
Vector4 color = (destination * dstW) + (blend * blendW);
// unpremultiply
color /= MathF.Max(alpha, Constants.Epsilon);
color.W = alpha;
color /= Vector4.Max(alpha, new(Constants.Epsilon));
return WithW(color, alpha);
}
return color;
/// <summary>
/// Returns the result of the "Atop" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="blend">The amount to blend. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Atop(Vector256<float> destination, Vector256<float> source, Vector256<float> blend)
{
// calculate final alpha
Vector256<float> alpha = Avx.Permute(destination, ShuffleAlphaControl);
// calculate weights
Vector256<float> sW = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> blendW = Avx.Multiply(sW, alpha);
Vector256<float> dstW = Avx.Subtract(alpha, blendW);
// calculate final color
Vector256<float> color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(blend, blendW), destination, dstW);
// unpremultiply
color = Avx.Divide(color, Avx.Max(alpha, Vector256.Create(Constants.Epsilon)));
return Avx.Blend(color, alpha, BlendAlphaControl);
}
/// <summary>
@ -211,13 +375,33 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 In(Vector4 destination, Vector4 source)
{
float alpha = destination.W * source.W;
Vector4 sW = PermuteW(source);
Vector4 dW = PermuteW(destination);
Vector4 alpha = dW * sW;
Vector4 color = source * alpha; // premultiply
color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply
color.W = alpha;
color /= Vector4.Max(alpha, new(Constants.Epsilon)); // unpremultiply
return WithW(color, alpha);
}
return color;
/// <summary>
/// Returns the result of the "In" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> In(Vector256<float> destination, Vector256<float> source)
{
// calculate alpha
Vector256<float> alpha = Avx.Permute(Avx.Multiply(source, destination), ShuffleAlphaControl);
// premultiply
Vector256<float> color = Avx.Multiply(source, alpha);
// unpremultiply
color = Avx.Divide(color, Avx.Max(alpha, Vector256.Create(Constants.Epsilon)));
return Avx.Blend(color, alpha, BlendAlphaControl);
}
/// <summary>
@ -229,13 +413,33 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Out(Vector4 destination, Vector4 source)
{
float alpha = (1 - destination.W) * source.W;
Vector4 sW = PermuteW(source);
Vector4 dW = PermuteW(destination);
Vector4 alpha = (Vector4.One - dW) * sW;
Vector4 color = source * alpha; // premultiply
color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply
color.W = alpha;
color /= Vector4.Max(alpha, new(Constants.Epsilon)); // unpremultiply
return WithW(color, alpha);
}
return color;
/// <summary>
/// Returns the result of the "Out" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Out(Vector256<float> destination, Vector256<float> source)
{
// calculate alpha
Vector256<float> alpha = Avx.Permute(Avx.Multiply(source, Avx.Subtract(Vector256.Create(1F), destination)), ShuffleAlphaControl);
// premultiply
Vector256<float> color = Avx.Multiply(source, alpha);
// unpremultiply
color = Avx.Divide(color, Avx.Max(alpha, Vector256.Create(Constants.Epsilon)));
return Avx.Blend(color, alpha, BlendAlphaControl);
}
/// <summary>
@ -247,22 +451,80 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Xor(Vector4 destination, Vector4 source)
{
float srcW = 1 - destination.W;
float dstW = 1 - source.W;
Vector4 sW = PermuteW(source);
Vector4 dW = PermuteW(destination);
Vector4 srcW = Vector4.One - dW;
Vector4 dstW = Vector4.One - sW;
Vector4 alpha = (sW * srcW) + (dW * dstW);
Vector4 color = (sW * source * srcW) + (dW * destination * dstW);
// unpremultiply
color /= Vector4.Max(alpha, new(Constants.Epsilon));
return WithW(color, alpha);
}
/// <summary>
/// Returns the result of the "XOr" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Xor(Vector256<float> destination, Vector256<float> source)
{
// calculate weights
Vector256<float> sW = Avx.Shuffle(source, source, ShuffleAlphaControl);
Vector256<float> dW = Avx.Shuffle(destination, destination, ShuffleAlphaControl);
Vector256<float> vOne = Vector256.Create(1F);
Vector256<float> srcW = Avx.Subtract(vOne, dW);
Vector256<float> dstW = Avx.Subtract(vOne, sW);
float alpha = (source.W * srcW) + (destination.W * dstW);
Vector4 color = (source.W * source * srcW) + (destination.W * destination * dstW);
// calculate alpha
Vector256<float> alpha = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(dW, dstW), sW, srcW);
Vector256<float> color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(Avx.Multiply(dW, destination), dstW), Avx.Multiply(sW, source), srcW);
// unpremultiply
color /= MathF.Max(alpha, Constants.Epsilon);
color.W = alpha;
color = Avx.Divide(color, Avx.Max(alpha, Vector256.Create(Constants.Epsilon)));
return Avx.Blend(color, alpha, BlendAlphaControl);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Clear(Vector4 backdrop, Vector4 source) => Vector4.Zero;
return color;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> Clear(Vector256<float> backdrop, Vector256<float> source) => Vector256<float>.Zero;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 WithW(Vector4 value, Vector4 w)
{
if (Sse41.IsSupported)
{
return Sse41.Insert(value.AsVector128(), w.AsVector128(), 0b11_11_0000).AsVector4();
}
if (Sse.IsSupported)
{
// Create tmp as <w[3], w[0], value[2], value[0]>
// Then return <value[0], value[1], tmp[2], tmp[0]> (which is <value[0], value[1], value[2], w[3]>)
Vector128<float> tmp = Sse.Shuffle(w.AsVector128(), value.AsVector128(), 0b00_10_00_11);
return Sse.Shuffle(value.AsVector128(), tmp, 0b00_10_01_00).AsVector4();
}
value.W = w.W;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Clear(Vector4 backdrop, Vector4 source)
private static Vector4 PermuteW(Vector4 value)
{
return Vector4.Zero;
if (Sse.IsSupported)
{
return Sse.Shuffle(value.AsVector128(), value.AsVector128(), 0b11111111).AsVector4();
}
return new(value.W);
}
}

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

@ -29,6 +29,8 @@ internal static class PixelConverter
/// <see cref="Rgba32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Argb32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToArgb32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<WXYZShuffle4>(source, dest, default);
@ -38,6 +40,8 @@ internal static class PixelConverter
/// <see cref="Rgba32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgra32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToBgra32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<ZYXWShuffle4>(source, dest, default);
@ -47,6 +51,8 @@ internal static class PixelConverter
/// <see cref="Argb32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Abgr32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToAbgr32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<WZYXShuffle4>(source, dest, default);
@ -56,6 +62,8 @@ internal static class PixelConverter
/// <see cref="Rgba32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Rgb24"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToRgb24(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Slice3<XYZWShuffle4Slice3>(source, dest, default);
@ -65,9 +73,11 @@ internal static class PixelConverter
/// <see cref="Rgba32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgr24"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToBgr24(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(3, 0, 1, 2));
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle3012));
}
/// <summary>
@ -82,6 +92,8 @@ internal static class PixelConverter
/// <see cref="Argb32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Rgba32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToRgba32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<YZWXShuffle4>(source, dest, default);
@ -91,6 +103,8 @@ internal static class PixelConverter
/// <see cref="Argb32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgra32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToBgra32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<WZYXShuffle4>(source, dest, default);
@ -100,6 +114,8 @@ internal static class PixelConverter
/// <see cref="Argb32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Abgr32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToAbgr32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<XWZYShuffle4>(source, dest, default);
@ -109,18 +125,22 @@ internal static class PixelConverter
/// <see cref="Argb32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Rgb24"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToRgb24(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 3, 2, 1));
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0321));
/// <summary>
/// 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="Bgr24"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToBgr24(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 1, 2, 3));
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0123));
}
/// <summary>
@ -135,6 +155,8 @@ internal static class PixelConverter
/// <see cref="Bgra32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Argb32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToArgb32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<WZYXShuffle4>(source, dest, default);
@ -144,6 +166,8 @@ internal static class PixelConverter
/// <see cref="Bgra32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Rgba32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToRgba32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<ZYXWShuffle4>(source, dest, default);
@ -153,6 +177,8 @@ internal static class PixelConverter
/// <see cref="Bgra32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Abgr32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToAbgr32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<WXYZShuffle4>(source, dest, default);
@ -162,15 +188,19 @@ internal static class PixelConverter
/// <see cref="Argb32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Rgb24"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToRgb24(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(3, 0, 1, 2));
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle3012));
/// <summary>
/// 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="Bgr24"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToBgr24(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Slice3<XYZWShuffle4Slice3>(source, dest, default);
@ -188,6 +218,8 @@ internal static class PixelConverter
/// <see cref="Abgr32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Argb32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToArgb32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<XWZYShuffle4>(source, dest, default);
@ -197,6 +229,8 @@ internal static class PixelConverter
/// <see cref="Abgr32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgra32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToRgba32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<WZYXShuffle4>(source, dest, default);
@ -206,6 +240,8 @@ internal static class PixelConverter
/// <see cref="Abgr32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgra32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToBgra32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4<YZWXShuffle4>(source, dest, default);
@ -215,18 +251,22 @@ internal static class PixelConverter
/// <see cref="Abgr32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Rgb24"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToRgb24(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 1, 2, 3));
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0123));
/// <summary>
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Abgr32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgr24"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToBgr24(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 3, 2, 1));
=> SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0321));
}
/// <summary>
@ -241,6 +281,8 @@ internal static class PixelConverter
/// <see cref="Rgb24"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Rgba32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToRgba32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Pad3Shuffle4<XYZWPad3Shuffle4>(source, dest, default);
@ -250,36 +292,44 @@ internal static class PixelConverter
/// <see cref="Rgba32"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Argb32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToArgb32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(2, 1, 0, 3));
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle2103));
/// <summary>
/// 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>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToBgra32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(3, 0, 1, 2));
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle3012));
/// <summary>
/// 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>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToAbgr32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(0, 1, 2, 3));
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle0123));
/// <summary>
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Rgb24"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgr24"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToBgr24(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(0, 1, 2));
=> SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(SimdUtils.Shuffle.MMShuffle3012));
}
/// <summary>
@ -294,24 +344,30 @@ internal static class PixelConverter
/// <see cref="Bgr24"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Argb32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToArgb32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(0, 1, 2, 3));
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle0123));
/// <summary>
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Bgr24"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgra32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToRgba32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(3, 0, 1, 2));
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle3012));
/// <summary>
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Bgr24"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgra32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToBgra32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Pad3Shuffle4<XYZWPad3Shuffle4>(source, dest, default);
@ -321,17 +377,21 @@ internal static class PixelConverter
/// <see cref="Bgr24"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Abgr32"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToAbgr32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(2, 1, 0, 3));
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle2103));
/// <summary>
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Bgr24"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Rgb24"/> pixels.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToRgb24(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(0, 1, 2));
=> SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(SimdUtils.Shuffle.MMShuffle3012));
}
}

11
src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@ -157,9 +156,9 @@ public static partial class ProcessingExtensions
Guard.NotNull(operation, nameof(operation));
source.EnsureNotDisposed();
var visitor = new ProcessingVisitor(configuration, operation, false);
ProcessingVisitor visitor = new(configuration, operation, false);
source.AcceptVisitor(visitor);
return visitor.ResultImage;
return visitor.GetResultImage();
}
/// <summary>
@ -275,6 +274,8 @@ public static partial class ProcessingExtensions
private readonly bool mutate;
private Image? resultImage;
public ProcessingVisitor(Configuration configuration, Action<IImageProcessingContext> operation, bool mutate)
{
this.configuration = configuration;
@ -282,7 +283,7 @@ public static partial class ProcessingExtensions
this.mutate = mutate;
}
public Image ResultImage { get; private set; }
public Image GetResultImage() => this.resultImage!;
public void Visit<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
@ -291,7 +292,7 @@ public static partial class ProcessingExtensions
this.configuration.ImageOperationsProvider.CreateImageProcessingContext(this.configuration, image, this.mutate);
this.operation(operationsRunner);
this.ResultImage = operationsRunner.GetResultImage();
this.resultImage = operationsRunner.GetResultImage();
}
}
}

2
src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs

@ -19,7 +19,7 @@ public class AffineTransformProcessor : CloningImageProcessor
public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions)
{
Guard.NotNull(sampler, nameof(sampler));
Guard.MustBeValueType(sampler, nameof(sampler));
Guard.MustBeValueType(sampler);
if (TransformUtils.IsDegenerate(matrix))
{

2
src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs

@ -19,7 +19,7 @@ public sealed class ProjectiveTransformProcessor : CloningImageProcessor
public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions)
{
Guard.NotNull(sampler, nameof(sampler));
Guard.MustBeValueType(sampler, nameof(sampler));
Guard.MustBeValueType(sampler);
if (TransformUtils.IsDegenerate(matrix))
{

2
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs

@ -17,7 +17,7 @@ public class ResizeProcessor : CloningImageProcessor
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(options.Sampler, nameof(options.Sampler));
Guard.MustBeValueType(options.Sampler, nameof(options.Sampler));
Guard.MustBeValueType(options.Sampler);
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options);

4
src/ImageSharp/Processing/ProjectiveTransformBuilder.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing;
/// </summary>
public class ProjectiveTransformBuilder
{
private readonly List<Func<Size, Matrix4x4>> matrixFactories = new List<Func<Size, Matrix4x4>>();
private readonly List<Func<Size, Matrix4x4>> matrixFactories = new();
/// <summary>
/// Prepends a matrix that performs a tapering projective transform.
@ -313,7 +313,7 @@ public class ProjectiveTransformBuilder
Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle));
// Translate the origin matrix to cater for source rectangle offsets.
var matrix = Matrix4x4.CreateTranslation(new Vector3(-sourceRectangle.Location, 0));
Matrix4x4 matrix = Matrix4x4.CreateTranslation(new Vector3(-sourceRectangle.Location, 0));
Size size = sourceRectangle.Size;

58
tests/ImageSharp.Benchmarks/Color/Bulk/Pad3Shuffle4Channel.cs

@ -8,8 +8,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class Pad3Shuffle4Channel
{
private static readonly DefaultPad3Shuffle4 Control = new DefaultPad3Shuffle4(1, 0, 3, 2);
private static readonly XYZWPad3Shuffle4 ControlFast = default;
private static readonly DefaultPad3Shuffle4 Control = new(SimdUtils.Shuffle.MMShuffle1032);
private byte[] source;
private byte[] destination;
@ -26,15 +25,11 @@ public class Pad3Shuffle4Channel
[Benchmark]
public void Pad3Shuffle4()
{
SimdUtils.Pad3Shuffle4(this.source, this.destination, Control);
}
=> SimdUtils.Pad3Shuffle4(this.source, this.destination, Control);
[Benchmark]
public void Pad3Shuffle4FastFallback()
{
SimdUtils.Pad3Shuffle4(this.source, this.destination, ControlFast);
}
=> SimdUtils.Pad3Shuffle4(this.source, this.destination, default(XYZWPad3Shuffle4));
}
// 2020-10-30
@ -83,3 +78,50 @@ public class Pad3Shuffle4Channel
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 220.37 ns | 1.601 ns | 1.419 ns | 220.13 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 1536 | 111.54 ns | 2.173 ns | 2.901 ns | 111.27 ns | 0.51 | 0.01 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 110.23 ns | 0.456 ns | 0.427 ns | 110.25 ns | 0.50 | 0.00 | - | - | - | - |
// 2023-02-21
// ##########
//
// BenchmarkDotNet=v0.13.0, OS=Windows 10.0.22621
// 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores
// .NET SDK= 7.0.103
// [Host] : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 1. No HwIntrinsics : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 2. SSE : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 3. AVX : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// Runtime=.NET 6.0
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:|
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 57.45 ns | 0.126 ns | 0.118 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 96 | 14.70 ns | 0.105 ns | 0.098 ns | 0.26 | - | - | - | - |
// | Pad3Shuffle4 | 3. AVX | Empty | 96 | 14.63 ns | 0.070 ns | 0.062 ns | 0.25 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 12.08 ns | 0.028 ns | 0.025 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 96 | 14.04 ns | 0.050 ns | 0.044 ns | 1.16 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 96 | 13.90 ns | 0.086 ns | 0.080 ns | 1.15 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 202.67 ns | 2.010 ns | 1.678 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 384 | 25.54 ns | 0.060 ns | 0.053 ns | 0.13 | - | - | - | - |
// | Pad3Shuffle4 | 3. AVX | Empty | 384 | 25.72 ns | 0.139 ns | 0.130 ns | 0.13 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 60.35 ns | 0.080 ns | 0.071 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 384 | 25.18 ns | 0.388 ns | 0.324 ns | 0.42 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 384 | 26.21 ns | 0.067 ns | 0.059 ns | 0.43 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 393.88 ns | 1.353 ns | 1.199 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 768 | 39.44 ns | 0.230 ns | 0.204 ns | 0.10 | - | - | - | - |
// | Pad3Shuffle4 | 3. AVX | Empty | 768 | 39.51 ns | 0.108 ns | 0.101 ns | 0.10 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 112.02 ns | 0.140 ns | 0.131 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 768 | 38.60 ns | 0.091 ns | 0.080 ns | 0.34 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 768 | 38.18 ns | 0.100 ns | 0.084 ns | 0.34 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 777.95 ns | 1.719 ns | 1.342 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 1536 | 73.11 ns | 0.090 ns | 0.075 ns | 0.09 | - | - | - | - |
// | Pad3Shuffle4 | 3. AVX | Empty | 1536 | 73.41 ns | 0.125 ns | 0.117 ns | 0.09 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 218.14 ns | 0.377 ns | 0.334 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 1536 | 72.55 ns | 1.418 ns | 1.184 ns | 0.33 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 1536 | 73.15 ns | 0.330 ns | 0.292 ns | 0.34 | - | - | - | - |

39
tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle3Channel.cs

@ -1,14 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using Iced.Intel;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class Shuffle3Channel
{
private static readonly DefaultShuffle3 Control = new DefaultShuffle3(1, 0, 2);
private static readonly DefaultShuffle3 Control = new(SimdUtils.Shuffle.MMShuffle3102);
private byte[] source;
private byte[] destination;
@ -25,9 +27,7 @@ public class Shuffle3Channel
[Benchmark]
public void Shuffle3()
{
SimdUtils.Shuffle3(this.source, this.destination, Control);
}
=> SimdUtils.Shuffle3(this.source, this.destination, Control);
}
// 2020-11-02
@ -60,3 +60,34 @@ public class Shuffle3Channel
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 773.70 ns | 5.516 ns | 4.890 ns | 772.96 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle3 | 2. AVX | Empty | 1536 | 190.41 ns | 1.090 ns | 0.851 ns | 190.38 ns | 0.25 | 0.00 | - | - | - | - |
// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 190.94 ns | 0.985 ns | 0.769 ns | 190.85 ns | 0.25 | 0.00 | - | - | - | - |
// 2023-02-21
// ##########
//
// BenchmarkDotNet=v0.13.0, OS=Windows 10.0.22621
// 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores
// .NET SDK= 7.0.103
// [Host] : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 1. No HwIntrinsics : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 2. SSE : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 3. AVX : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// Runtime=.NET 6.0
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |--------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:|
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 44.55 ns | 0.564 ns | 0.528 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 96 | 15.46 ns | 0.064 ns | 0.060 ns | 0.35 | - | - | - | - |
// | Shuffle3 | 3. AVX | Empty | 96 | 15.18 ns | 0.056 ns | 0.053 ns | 0.34 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 155.68 ns | 0.539 ns | 0.504 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 384 | 30.04 ns | 0.100 ns | 0.089 ns | 0.19 | - | - | - | - |
// | Shuffle3 | 3. AVX | Empty | 384 | 29.70 ns | 0.061 ns | 0.054 ns | 0.19 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 302.76 ns | 1.023 ns | 0.957 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 768 | 50.24 ns | 0.098 ns | 0.092 ns | 0.17 | - | - | - | - |
// | Shuffle3 | 3. AVX | Empty | 768 | 49.28 ns | 0.156 ns | 0.131 ns | 0.16 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 596.53 ns | 2.675 ns | 2.503 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 1536 | 94.09 ns | 0.312 ns | 0.260 ns | 0.16 | - | - | - | - |
// | Shuffle3 | 3. AVX | Empty | 1536 | 93.57 ns | 0.196 ns | 0.183 ns | 0.16 | - | - | - | - |

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

@ -1,15 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using Iced.Intel;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class Shuffle4Slice3Channel
{
private static readonly DefaultShuffle4Slice3 Control = new DefaultShuffle4Slice3(1, 0, 3, 2);
private static readonly XYZWShuffle4Slice3 ControlFast = default;
private static readonly DefaultShuffle4Slice3 Control = new(SimdUtils.Shuffle.MMShuffle1032);
private byte[] source;
private byte[] destination;
@ -26,15 +27,11 @@ public class Shuffle4Slice3Channel
[Benchmark]
public void Shuffle4Slice3()
{
SimdUtils.Shuffle4Slice3(this.source, this.destination, Control);
}
=> SimdUtils.Shuffle4Slice3(this.source, this.destination, Control);
[Benchmark]
public void Shuffle4Slice3FastFallback()
{
SimdUtils.Shuffle4Slice3(this.source, this.destination, ControlFast);
}
=> SimdUtils.Shuffle4Slice3(this.source, this.destination, default(XYZWShuffle4Slice3));
}
// 2020-10-29
@ -91,3 +88,58 @@ public class Shuffle4Slice3Channel
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 382.97 ns | 1.064 ns | 0.831 ns | 382.87 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 2048 | 126.93 ns | 0.382 ns | 0.339 ns | 126.94 ns | 0.33 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 149.36 ns | 1.875 ns | 1.754 ns | 149.33 ns | 0.39 | 0.00 | - | - | - | - |
// 2023-02-21
// ##########
//
// BenchmarkDotNet=v0.13.0, OS=Windows 10.0.22621
// 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores
// .NET SDK= 7.0.103
// [Host] : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 1. No HwIntrinsics : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 2. SSE : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 3. AVX : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
//
// Runtime=.NET 6.0
//
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |--------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:|
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 45.59 ns | 0.166 ns | 0.147 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 128 | 15.62 ns | 0.056 ns | 0.052 ns | 0.34 | - | - | - | - |
// | Shuffle4Slice3 | 3. AVX | Empty | 128 | 16.37 ns | 0.047 ns | 0.040 ns | 0.36 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 13.23 ns | 0.028 ns | 0.026 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 128 | 14.41 ns | 0.013 ns | 0.012 ns | 1.09 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 128 | 14.70 ns | 0.050 ns | 0.047 ns | 1.11 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 85.48 ns | 0.192 ns | 0.179 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 256 | 19.18 ns | 0.230 ns | 0.204 ns | 0.22 | - | - | - | - |
// | Shuffle4Slice3 | 3. AVX | Empty | 256 | 18.66 ns | 0.017 ns | 0.015 ns | 0.22 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 24.34 ns | 0.078 ns | 0.073 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 256 | 18.58 ns | 0.061 ns | 0.057 ns | 0.76 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 256 | 19.23 ns | 0.018 ns | 0.016 ns | 0.79 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 165.31 ns | 0.742 ns | 0.694 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 512 | 28.10 ns | 0.077 ns | 0.068 ns | 0.17 | - | - | - | - |
// | Shuffle4Slice3 | 3. AVX | Empty | 512 | 28.99 ns | 0.018 ns | 0.014 ns | 0.18 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 53.45 ns | 0.270 ns | 0.226 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 512 | 27.50 ns | 0.034 ns | 0.028 ns | 0.51 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 512 | 28.76 ns | 0.017 ns | 0.015 ns | 0.54 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 323.87 ns | 0.549 ns | 0.487 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 40.81 ns | 0.056 ns | 0.050 ns | 0.13 | - | - | - | - |
// | Shuffle4Slice3 | 3. AVX | Empty | 1024 | 39.95 ns | 0.075 ns | 0.067 ns | 0.12 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 101.37 ns | 0.080 ns | 0.067 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 40.72 ns | 0.049 ns | 0.041 ns | 0.40 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 1024 | 39.78 ns | 0.029 ns | 0.027 ns | 0.39 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 642.95 ns | 2.067 ns | 1.933 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 73.19 ns | 0.082 ns | 0.077 ns | 0.11 | - | - | - | - |
// | Shuffle4Slice3 | 3. AVX | Empty | 2048 | 69.83 ns | 0.319 ns | 0.267 ns | 0.11 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 196.85 ns | 0.238 ns | 0.211 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 72.89 ns | 0.117 ns | 0.098 ns | 0.37 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 2048 | 69.59 ns | 0.073 ns | 0.061 ns | 0.35 | - | - | - | - |

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

@ -24,9 +24,7 @@ public class ShuffleByte4Channel
[Benchmark]
public void Shuffle4Channel()
{
SimdUtils.Shuffle4<WXYZShuffle4>(this.source, this.destination, default);
}
=> SimdUtils.Shuffle4<WXYZShuffle4>(this.source, this.destination, default);
}
// 2020-10-29
@ -63,3 +61,38 @@ public class ShuffleByte4Channel
// | 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 | - | - | - | - |
// 2023-02-21
// ##########
//
// BenchmarkDotNet=v0.13.0, OS=Windows 10.0.22621
// 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores
// .NET SDK= 7.0.103
// [Host] : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 1. No HwIntrinsics : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 2. SSE : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 3. AVX : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
//
// Runtime=.NET 6.0
//
// | 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 | 10.76 ns | 0.033 ns | 0.029 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 128 | 11.39 ns | 0.045 ns | 0.040 ns | 1.06 | 0.01 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 128 | 14.05 ns | 0.029 ns | 0.024 ns | 1.31 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 32.09 ns | 0.655 ns | 1.000 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 256 | 14.03 ns | 0.047 ns | 0.041 ns | 0.44 | 0.02 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 256 | 15.18 ns | 0.052 ns | 0.043 ns | 0.48 | 0.03 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 59.26 ns | 0.084 ns | 0.070 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 512 | 18.80 ns | 0.036 ns | 0.034 ns | 0.32 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 512 | 17.69 ns | 0.038 ns | 0.034 ns | 0.30 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 112.48 ns | 0.285 ns | 0.253 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 31.57 ns | 0.041 ns | 0.036 ns | 0.28 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 1024 | 28.41 ns | 0.068 ns | 0.064 ns | 0.25 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 218.59 ns | 0.303 ns | 0.283 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 53.04 ns | 0.106 ns | 0.099 ns | 0.24 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 2048 | 34.74 ns | 0.061 ns | 0.054 ns | 0.16 | 0.00 | - | - | - | - |

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

@ -9,7 +9,6 @@ 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;
@ -25,9 +24,7 @@ public class ShuffleFloat4Channel
[Benchmark]
public void Shuffle4Channel()
{
SimdUtils.Shuffle4(this.source, this.destination, Control);
}
=> SimdUtils.Shuffle4(this.source, this.destination, SimdUtils.Shuffle.MMShuffle2103);
}
// 2020-10-29
@ -64,3 +61,38 @@ public class ShuffleFloat4Channel
// | 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 | - | - | - | - |
// 2023-02-21
// ##########
//
// BenchmarkDotNet=v0.13.0, OS=Windows 10.0.22621
// 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores
// .NET SDK= 7.0.103
// [Host] : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 1. No HwIntrinsics : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 2. SSE : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
// 3. AVX : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT
//
// Runtime=.NET 6.0
//
// | 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 | 57.819 ns | 0.2360 ns | 0.1970 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 128 | 11.564 ns | 0.0234 ns | 0.0195 ns | 0.20 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 128 | 7.770 ns | 0.0696 ns | 0.0617 ns | 0.13 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 105.282 ns | 0.2713 ns | 0.2405 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 256 | 19.867 ns | 0.0393 ns | 0.0348 ns | 0.19 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 256 | 17.586 ns | 0.0582 ns | 0.0544 ns | 0.17 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 200.799 ns | 0.5678 ns | 0.5033 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 512 | 41.137 ns | 0.1524 ns | 0.1351 ns | 0.20 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 512 | 24.040 ns | 0.0445 ns | 0.0395 ns | 0.12 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 401.046 ns | 0.5865 ns | 0.5199 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 94.904 ns | 0.4633 ns | 0.4334 ns | 0.24 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 1024 | 68.456 ns | 0.1192 ns | 0.0996 ns | 0.17 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 772.297 ns | 0.6270 ns | 0.5558 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 184.561 ns | 0.4319 ns | 0.4040 ns | 0.24 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 2048 | 133.634 ns | 1.7864 ns | 1.8345 ns | 0.17 | - | - | - | - |

24
tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs

@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Benchmarks;
public class PorterDuffBulkVsPixel
{
private Configuration Configuration => Configuration.Default;
private static Configuration Configuration => Configuration.Default;
private void BulkVectorConvert<TPixel>(
private static void BulkVectorConvert<TPixel>(
Span<TPixel> destination,
Span<TPixel> background,
Span<TPixel> source,
@ -31,18 +31,18 @@ public class PorterDuffBulkVsPixel
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, background, backgroundSpan);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, source, sourceSpan);
PixelOperations<TPixel>.Instance.ToVector4(Configuration, background, backgroundSpan);
PixelOperations<TPixel>.Instance.ToVector4(Configuration, source, sourceSpan);
for (int i = 0; i < destination.Length; i++)
{
destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination);
PixelOperations<TPixel>.Instance.FromVector4Destructive(Configuration, destinationSpan, destination);
}
private void BulkPixelConvert<TPixel>(
private static void BulkPixelConvert<TPixel>(
Span<TPixel> destination,
Span<TPixel> background,
Span<TPixel> source,
@ -60,9 +60,9 @@ public class PorterDuffBulkVsPixel
}
[Benchmark(Description = "ImageSharp BulkVectorConvert")]
public Size BulkVectorConvert()
public static Size BulkVectorConvert()
{
using var image = new Image<Rgba32>(800, 800);
using Image<Rgba32> image = new(800, 800);
using IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width);
amounts.GetSpan().Fill(1);
@ -70,23 +70,23 @@ public class PorterDuffBulkVsPixel
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> span = pixels.DangerousGetRowSpan(y);
this.BulkVectorConvert(span, span, span, amounts.GetSpan());
BulkVectorConvert(span, span, span, amounts.GetSpan());
}
return new Size(image.Width, image.Height);
}
[Benchmark(Description = "ImageSharp BulkPixelConvert")]
public Size BulkPixelConvert()
public static Size BulkPixelConvert()
{
using var image = new Image<Rgba32>(800, 800);
using Image<Rgba32> image = new(800, 800);
using IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width);
amounts.GetSpan().Fill(1);
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> span = pixels.DangerousGetRowSpan(y);
this.BulkPixelConvert(span, span, span, amounts.GetSpan());
BulkPixelConvert(span, span, span, amounts.GetSpan());
}
return new Size(image.Width, image.Height);

68
tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs

@ -0,0 +1,68 @@
// 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 BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
namespace SixLabors.ImageSharp.Benchmarks.PixelBlenders;
public class PorterDuffBulkVsSingleVector
{
private Vector4[] backdrop;
private Vector4[] source;
[GlobalSetup]
public void Setup()
{
this.backdrop = new Vector4[8 * 20];
this.source = new Vector4[8 * 20];
FillRandom(this.backdrop);
FillRandom(this.source);
}
private static void FillRandom(Vector4[] arr)
{
Random rng = new();
for (int i = 0; i < arr.Length; i++)
{
arr[i].X = rng.NextSingle();
arr[i].Y = rng.NextSingle();
arr[i].Z = rng.NextSingle();
arr[i].W = rng.NextSingle();
}
}
[Benchmark(Description = "Scalar", Baseline = true)]
public Vector4 OverlayValueFunction_Scalar()
{
Vector4 result = default;
for (int i = 0; i < this.backdrop.Length; i++)
{
result = PorterDuffFunctions.NormalSrcOver(this.backdrop[i], this.source[i], .5F);
}
return result;
}
[Benchmark(Description = "Avx")]
public Vector256<float> OverlayValueFunction_Avx()
{
ref Vector256<float> backdrop = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference<Vector4>(this.backdrop));
ref Vector256<float> source = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference<Vector4>(this.source));
Vector256<float> result = default;
Vector256<float> opacity = Vector256.Create(.5F);
int count = this.backdrop.Length / 2;
for (int i = 0; i < count; i++)
{
result = PorterDuffFunctions.NormalSrcOver(Unsafe.Add(ref backdrop, i), Unsafe.Add(ref source, i), opacity);
}
return result;
}
}

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

@ -7,6 +7,271 @@ namespace SixLabors.ImageSharp.Tests.Common;
public partial class SimdUtilsTests
{
public static readonly TheoryData<byte, byte> MMShuffleData = new()
{
{ SimdUtils.Shuffle.MMShuffle(0, 0, 0, 0), SimdUtils.Shuffle.MMShuffle0000 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 0, 1), SimdUtils.Shuffle.MMShuffle0001 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 0, 2), SimdUtils.Shuffle.MMShuffle0002 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 0, 3), SimdUtils.Shuffle.MMShuffle0003 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 1, 0), SimdUtils.Shuffle.MMShuffle0010 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 1, 1), SimdUtils.Shuffle.MMShuffle0011 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 1, 2), SimdUtils.Shuffle.MMShuffle0012 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 1, 3), SimdUtils.Shuffle.MMShuffle0013 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 2, 0), SimdUtils.Shuffle.MMShuffle0020 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 2, 1), SimdUtils.Shuffle.MMShuffle0021 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 2, 2), SimdUtils.Shuffle.MMShuffle0022 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 2, 3), SimdUtils.Shuffle.MMShuffle0023 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 3, 0), SimdUtils.Shuffle.MMShuffle0030 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 3, 1), SimdUtils.Shuffle.MMShuffle0031 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 3, 2), SimdUtils.Shuffle.MMShuffle0032 },
{ SimdUtils.Shuffle.MMShuffle(0, 0, 3, 3), SimdUtils.Shuffle.MMShuffle0033 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 0, 0), SimdUtils.Shuffle.MMShuffle0100 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 0, 1), SimdUtils.Shuffle.MMShuffle0101 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 0, 2), SimdUtils.Shuffle.MMShuffle0102 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 0, 3), SimdUtils.Shuffle.MMShuffle0103 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 1, 0), SimdUtils.Shuffle.MMShuffle0110 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 1, 1), SimdUtils.Shuffle.MMShuffle0111 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 1, 2), SimdUtils.Shuffle.MMShuffle0112 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 1, 3), SimdUtils.Shuffle.MMShuffle0113 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 2, 0), SimdUtils.Shuffle.MMShuffle0120 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 2, 1), SimdUtils.Shuffle.MMShuffle0121 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 2, 2), SimdUtils.Shuffle.MMShuffle0122 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 2, 3), SimdUtils.Shuffle.MMShuffle0123 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 3, 0), SimdUtils.Shuffle.MMShuffle0130 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 3, 1), SimdUtils.Shuffle.MMShuffle0131 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 3, 2), SimdUtils.Shuffle.MMShuffle0132 },
{ SimdUtils.Shuffle.MMShuffle(0, 1, 3, 3), SimdUtils.Shuffle.MMShuffle0133 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 0, 0), SimdUtils.Shuffle.MMShuffle0200 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 0, 1), SimdUtils.Shuffle.MMShuffle0201 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 0, 2), SimdUtils.Shuffle.MMShuffle0202 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 0, 3), SimdUtils.Shuffle.MMShuffle0203 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 1, 0), SimdUtils.Shuffle.MMShuffle0210 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 1, 1), SimdUtils.Shuffle.MMShuffle0211 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 1, 2), SimdUtils.Shuffle.MMShuffle0212 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 1, 3), SimdUtils.Shuffle.MMShuffle0213 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 2, 0), SimdUtils.Shuffle.MMShuffle0220 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 2, 1), SimdUtils.Shuffle.MMShuffle0221 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 2, 2), SimdUtils.Shuffle.MMShuffle0222 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 2, 3), SimdUtils.Shuffle.MMShuffle0223 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 3, 0), SimdUtils.Shuffle.MMShuffle0230 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 3, 1), SimdUtils.Shuffle.MMShuffle0231 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 3, 2), SimdUtils.Shuffle.MMShuffle0232 },
{ SimdUtils.Shuffle.MMShuffle(0, 2, 3, 3), SimdUtils.Shuffle.MMShuffle0233 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 0, 0), SimdUtils.Shuffle.MMShuffle0300 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 0, 1), SimdUtils.Shuffle.MMShuffle0301 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 0, 2), SimdUtils.Shuffle.MMShuffle0302 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 0, 3), SimdUtils.Shuffle.MMShuffle0303 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 1, 0), SimdUtils.Shuffle.MMShuffle0310 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 1, 1), SimdUtils.Shuffle.MMShuffle0311 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 1, 2), SimdUtils.Shuffle.MMShuffle0312 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 1, 3), SimdUtils.Shuffle.MMShuffle0313 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 2, 0), SimdUtils.Shuffle.MMShuffle0320 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 2, 1), SimdUtils.Shuffle.MMShuffle0321 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 2, 2), SimdUtils.Shuffle.MMShuffle0322 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 2, 3), SimdUtils.Shuffle.MMShuffle0323 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 3, 0), SimdUtils.Shuffle.MMShuffle0330 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 3, 1), SimdUtils.Shuffle.MMShuffle0331 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 3, 2), SimdUtils.Shuffle.MMShuffle0332 },
{ SimdUtils.Shuffle.MMShuffle(0, 3, 3, 3), SimdUtils.Shuffle.MMShuffle0333 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 0, 0), SimdUtils.Shuffle.MMShuffle1000 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 0, 1), SimdUtils.Shuffle.MMShuffle1001 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 0, 2), SimdUtils.Shuffle.MMShuffle1002 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 0, 3), SimdUtils.Shuffle.MMShuffle1003 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 1, 0), SimdUtils.Shuffle.MMShuffle1010 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 1, 1), SimdUtils.Shuffle.MMShuffle1011 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 1, 2), SimdUtils.Shuffle.MMShuffle1012 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 1, 3), SimdUtils.Shuffle.MMShuffle1013 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 2, 0), SimdUtils.Shuffle.MMShuffle1020 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 2, 1), SimdUtils.Shuffle.MMShuffle1021 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 2, 2), SimdUtils.Shuffle.MMShuffle1022 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 2, 3), SimdUtils.Shuffle.MMShuffle1023 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 3, 0), SimdUtils.Shuffle.MMShuffle1030 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 3, 1), SimdUtils.Shuffle.MMShuffle1031 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 3, 2), SimdUtils.Shuffle.MMShuffle1032 },
{ SimdUtils.Shuffle.MMShuffle(1, 0, 3, 3), SimdUtils.Shuffle.MMShuffle1033 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 0, 0), SimdUtils.Shuffle.MMShuffle1100 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 0, 1), SimdUtils.Shuffle.MMShuffle1101 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 0, 2), SimdUtils.Shuffle.MMShuffle1102 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 0, 3), SimdUtils.Shuffle.MMShuffle1103 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 1, 0), SimdUtils.Shuffle.MMShuffle1110 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 1, 1), SimdUtils.Shuffle.MMShuffle1111 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 1, 2), SimdUtils.Shuffle.MMShuffle1112 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 1, 3), SimdUtils.Shuffle.MMShuffle1113 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 2, 0), SimdUtils.Shuffle.MMShuffle1120 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 2, 1), SimdUtils.Shuffle.MMShuffle1121 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 2, 2), SimdUtils.Shuffle.MMShuffle1122 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 2, 3), SimdUtils.Shuffle.MMShuffle1123 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 3, 0), SimdUtils.Shuffle.MMShuffle1130 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 3, 1), SimdUtils.Shuffle.MMShuffle1131 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 3, 2), SimdUtils.Shuffle.MMShuffle1132 },
{ SimdUtils.Shuffle.MMShuffle(1, 1, 3, 3), SimdUtils.Shuffle.MMShuffle1133 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 0, 0), SimdUtils.Shuffle.MMShuffle1200 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 0, 1), SimdUtils.Shuffle.MMShuffle1201 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 0, 2), SimdUtils.Shuffle.MMShuffle1202 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 0, 3), SimdUtils.Shuffle.MMShuffle1203 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 1, 0), SimdUtils.Shuffle.MMShuffle1210 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 1, 1), SimdUtils.Shuffle.MMShuffle1211 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 1, 2), SimdUtils.Shuffle.MMShuffle1212 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 1, 3), SimdUtils.Shuffle.MMShuffle1213 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 2, 0), SimdUtils.Shuffle.MMShuffle1220 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 2, 1), SimdUtils.Shuffle.MMShuffle1221 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 2, 2), SimdUtils.Shuffle.MMShuffle1222 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 2, 3), SimdUtils.Shuffle.MMShuffle1223 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 3, 0), SimdUtils.Shuffle.MMShuffle1230 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 3, 1), SimdUtils.Shuffle.MMShuffle1231 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 3, 2), SimdUtils.Shuffle.MMShuffle1232 },
{ SimdUtils.Shuffle.MMShuffle(1, 2, 3, 3), SimdUtils.Shuffle.MMShuffle1233 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 0, 0), SimdUtils.Shuffle.MMShuffle1300 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 0, 1), SimdUtils.Shuffle.MMShuffle1301 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 0, 2), SimdUtils.Shuffle.MMShuffle1302 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 0, 3), SimdUtils.Shuffle.MMShuffle1303 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 1, 0), SimdUtils.Shuffle.MMShuffle1310 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 1, 1), SimdUtils.Shuffle.MMShuffle1311 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 1, 2), SimdUtils.Shuffle.MMShuffle1312 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 1, 3), SimdUtils.Shuffle.MMShuffle1313 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 2, 0), SimdUtils.Shuffle.MMShuffle1320 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 2, 1), SimdUtils.Shuffle.MMShuffle1321 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 2, 2), SimdUtils.Shuffle.MMShuffle1322 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 2, 3), SimdUtils.Shuffle.MMShuffle1323 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 3, 0), SimdUtils.Shuffle.MMShuffle1330 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 3, 1), SimdUtils.Shuffle.MMShuffle1331 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 3, 2), SimdUtils.Shuffle.MMShuffle1332 },
{ SimdUtils.Shuffle.MMShuffle(1, 3, 3, 3), SimdUtils.Shuffle.MMShuffle1333 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 0, 0), SimdUtils.Shuffle.MMShuffle2000 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 0, 1), SimdUtils.Shuffle.MMShuffle2001 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 0, 2), SimdUtils.Shuffle.MMShuffle2002 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 0, 3), SimdUtils.Shuffle.MMShuffle2003 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 1, 0), SimdUtils.Shuffle.MMShuffle2010 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 1, 1), SimdUtils.Shuffle.MMShuffle2011 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 1, 2), SimdUtils.Shuffle.MMShuffle2012 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 1, 3), SimdUtils.Shuffle.MMShuffle2013 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 2, 0), SimdUtils.Shuffle.MMShuffle2020 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 2, 1), SimdUtils.Shuffle.MMShuffle2021 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 2, 2), SimdUtils.Shuffle.MMShuffle2022 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 2, 3), SimdUtils.Shuffle.MMShuffle2023 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 3, 0), SimdUtils.Shuffle.MMShuffle2030 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 3, 1), SimdUtils.Shuffle.MMShuffle2031 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 3, 2), SimdUtils.Shuffle.MMShuffle2032 },
{ SimdUtils.Shuffle.MMShuffle(2, 0, 3, 3), SimdUtils.Shuffle.MMShuffle2033 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 0, 0), SimdUtils.Shuffle.MMShuffle2100 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 0, 1), SimdUtils.Shuffle.MMShuffle2101 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 0, 2), SimdUtils.Shuffle.MMShuffle2102 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 0, 3), SimdUtils.Shuffle.MMShuffle2103 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 1, 0), SimdUtils.Shuffle.MMShuffle2110 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 1, 1), SimdUtils.Shuffle.MMShuffle2111 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 1, 2), SimdUtils.Shuffle.MMShuffle2112 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 1, 3), SimdUtils.Shuffle.MMShuffle2113 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 2, 0), SimdUtils.Shuffle.MMShuffle2120 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 2, 1), SimdUtils.Shuffle.MMShuffle2121 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 2, 2), SimdUtils.Shuffle.MMShuffle2122 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 2, 3), SimdUtils.Shuffle.MMShuffle2123 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 3, 0), SimdUtils.Shuffle.MMShuffle2130 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 3, 1), SimdUtils.Shuffle.MMShuffle2131 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 3, 2), SimdUtils.Shuffle.MMShuffle2132 },
{ SimdUtils.Shuffle.MMShuffle(2, 1, 3, 3), SimdUtils.Shuffle.MMShuffle2133 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 0, 0), SimdUtils.Shuffle.MMShuffle2200 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 0, 1), SimdUtils.Shuffle.MMShuffle2201 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 0, 2), SimdUtils.Shuffle.MMShuffle2202 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 0, 3), SimdUtils.Shuffle.MMShuffle2203 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 1, 0), SimdUtils.Shuffle.MMShuffle2210 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 1, 1), SimdUtils.Shuffle.MMShuffle2211 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 1, 2), SimdUtils.Shuffle.MMShuffle2212 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 1, 3), SimdUtils.Shuffle.MMShuffle2213 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 2, 0), SimdUtils.Shuffle.MMShuffle2220 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 2, 1), SimdUtils.Shuffle.MMShuffle2221 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 2, 2), SimdUtils.Shuffle.MMShuffle2222 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 2, 3), SimdUtils.Shuffle.MMShuffle2223 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 3, 0), SimdUtils.Shuffle.MMShuffle2230 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 3, 1), SimdUtils.Shuffle.MMShuffle2231 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 3, 2), SimdUtils.Shuffle.MMShuffle2232 },
{ SimdUtils.Shuffle.MMShuffle(2, 2, 3, 3), SimdUtils.Shuffle.MMShuffle2233 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 0, 0), SimdUtils.Shuffle.MMShuffle2300 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 0, 1), SimdUtils.Shuffle.MMShuffle2301 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 0, 2), SimdUtils.Shuffle.MMShuffle2302 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 0, 3), SimdUtils.Shuffle.MMShuffle2303 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 1, 0), SimdUtils.Shuffle.MMShuffle2310 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 1, 1), SimdUtils.Shuffle.MMShuffle2311 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 1, 2), SimdUtils.Shuffle.MMShuffle2312 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 1, 3), SimdUtils.Shuffle.MMShuffle2313 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 2, 0), SimdUtils.Shuffle.MMShuffle2320 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 2, 1), SimdUtils.Shuffle.MMShuffle2321 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 2, 2), SimdUtils.Shuffle.MMShuffle2322 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 2, 3), SimdUtils.Shuffle.MMShuffle2323 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 3, 0), SimdUtils.Shuffle.MMShuffle2330 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 3, 1), SimdUtils.Shuffle.MMShuffle2331 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 3, 2), SimdUtils.Shuffle.MMShuffle2332 },
{ SimdUtils.Shuffle.MMShuffle(2, 3, 3, 3), SimdUtils.Shuffle.MMShuffle2333 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 0, 0), SimdUtils.Shuffle.MMShuffle3000 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 0, 1), SimdUtils.Shuffle.MMShuffle3001 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 0, 2), SimdUtils.Shuffle.MMShuffle3002 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 0, 3), SimdUtils.Shuffle.MMShuffle3003 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 1, 0), SimdUtils.Shuffle.MMShuffle3010 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 1, 1), SimdUtils.Shuffle.MMShuffle3011 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 1, 2), SimdUtils.Shuffle.MMShuffle3012 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 1, 3), SimdUtils.Shuffle.MMShuffle3013 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 2, 0), SimdUtils.Shuffle.MMShuffle3020 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 2, 1), SimdUtils.Shuffle.MMShuffle3021 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 2, 2), SimdUtils.Shuffle.MMShuffle3022 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 2, 3), SimdUtils.Shuffle.MMShuffle3023 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 3, 0), SimdUtils.Shuffle.MMShuffle3030 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 3, 1), SimdUtils.Shuffle.MMShuffle3031 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 3, 2), SimdUtils.Shuffle.MMShuffle3032 },
{ SimdUtils.Shuffle.MMShuffle(3, 0, 3, 3), SimdUtils.Shuffle.MMShuffle3033 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 0, 0), SimdUtils.Shuffle.MMShuffle3100 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 0, 1), SimdUtils.Shuffle.MMShuffle3101 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 0, 2), SimdUtils.Shuffle.MMShuffle3102 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 0, 3), SimdUtils.Shuffle.MMShuffle3103 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 1, 0), SimdUtils.Shuffle.MMShuffle3110 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 1, 1), SimdUtils.Shuffle.MMShuffle3111 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 1, 2), SimdUtils.Shuffle.MMShuffle3112 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 1, 3), SimdUtils.Shuffle.MMShuffle3113 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 2, 0), SimdUtils.Shuffle.MMShuffle3120 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 2, 1), SimdUtils.Shuffle.MMShuffle3121 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 2, 2), SimdUtils.Shuffle.MMShuffle3122 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 2, 3), SimdUtils.Shuffle.MMShuffle3123 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 3, 0), SimdUtils.Shuffle.MMShuffle3130 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 3, 1), SimdUtils.Shuffle.MMShuffle3131 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 3, 2), SimdUtils.Shuffle.MMShuffle3132 },
{ SimdUtils.Shuffle.MMShuffle(3, 1, 3, 3), SimdUtils.Shuffle.MMShuffle3133 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 0, 0), SimdUtils.Shuffle.MMShuffle3200 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 0, 1), SimdUtils.Shuffle.MMShuffle3201 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 0, 2), SimdUtils.Shuffle.MMShuffle3202 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 0, 3), SimdUtils.Shuffle.MMShuffle3203 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 1, 0), SimdUtils.Shuffle.MMShuffle3210 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 1, 1), SimdUtils.Shuffle.MMShuffle3211 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 1, 2), SimdUtils.Shuffle.MMShuffle3212 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 1, 3), SimdUtils.Shuffle.MMShuffle3213 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 2, 0), SimdUtils.Shuffle.MMShuffle3220 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 2, 1), SimdUtils.Shuffle.MMShuffle3221 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 2, 2), SimdUtils.Shuffle.MMShuffle3222 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 2, 3), SimdUtils.Shuffle.MMShuffle3223 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 3, 0), SimdUtils.Shuffle.MMShuffle3230 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 3, 1), SimdUtils.Shuffle.MMShuffle3231 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 3, 2), SimdUtils.Shuffle.MMShuffle3232 },
{ SimdUtils.Shuffle.MMShuffle(3, 2, 3, 3), SimdUtils.Shuffle.MMShuffle3233 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 0, 0), SimdUtils.Shuffle.MMShuffle3300 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 0, 1), SimdUtils.Shuffle.MMShuffle3301 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 0, 2), SimdUtils.Shuffle.MMShuffle3302 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 0, 3), SimdUtils.Shuffle.MMShuffle3303 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 1, 0), SimdUtils.Shuffle.MMShuffle3310 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 1, 1), SimdUtils.Shuffle.MMShuffle3311 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 1, 2), SimdUtils.Shuffle.MMShuffle3312 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 1, 3), SimdUtils.Shuffle.MMShuffle3313 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 2, 0), SimdUtils.Shuffle.MMShuffle3320 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 2, 1), SimdUtils.Shuffle.MMShuffle3321 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 2, 2), SimdUtils.Shuffle.MMShuffle3322 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 2, 3), SimdUtils.Shuffle.MMShuffle3323 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 3, 0), SimdUtils.Shuffle.MMShuffle3330 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 3, 1), SimdUtils.Shuffle.MMShuffle3331 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 3, 2), SimdUtils.Shuffle.MMShuffle3332 },
{ SimdUtils.Shuffle.MMShuffle(3, 3, 3, 3), SimdUtils.Shuffle.MMShuffle3333 }
};
[Theory]
[MemberData(nameof(MMShuffleData))]
public void MMShuffleConstantsAreCorrect(byte expected, byte actual)
=> Assert.Equal(expected, actual);
[Theory]
[MemberData(nameof(ArraySizesDivisibleBy4))]
public void BulkShuffleFloat4Channel(int count)
@ -16,7 +281,7 @@ public partial class SimdUtilsTests
// 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;
const byte control = SimdUtils.Shuffle.MMShuffle0123;
TestShuffleFloat4Channel(
size,
@ -45,39 +310,39 @@ public partial class SimdUtilsTests
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wxyz),
wxyz.Control);
SimdUtils.Shuffle.MMShuffle2103);
WZYXShuffle4 wzyx = default;
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wzyx),
wzyx.Control);
SimdUtils.Shuffle.MMShuffle0123);
YZWXShuffle4 yzwx = default;
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yzwx),
yzwx.Control);
SimdUtils.Shuffle.MMShuffle0321);
ZYXWShuffle4 zyxw = default;
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4(s.Span, d.Span, zyxw),
zyxw.Control);
SimdUtils.Shuffle.MMShuffle3012);
var xwyz = new DefaultShuffle4(2, 1, 3, 0);
DefaultShuffle4 xwyz = new(SimdUtils.Shuffle.MMShuffle2130);
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4(s.Span, d.Span, xwyz),
xwyz.Control);
var yyyy = new DefaultShuffle4(1, 1, 1, 1);
DefaultShuffle4 yyyy = new(SimdUtils.Shuffle.MMShuffle1111);
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yyyy),
yyyy.Control);
var wwww = new DefaultShuffle4(3, 3, 3, 3);
DefaultShuffle4 wwww = new(SimdUtils.Shuffle.MMShuffle3333);
TestShuffleByte4Channel(
size,
(s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wwww),
@ -101,25 +366,25 @@ public partial class SimdUtilsTests
// These cannot be expressed as a theory as you cannot
// use RemoteExecutor within generic methods nor pass
// IShuffle3 to the generic utils method.
var zyx = new DefaultShuffle3(0, 1, 2);
DefaultShuffle3 zyx = new(SimdUtils.Shuffle.MMShuffle3012);
TestShuffleByte3Channel(
size,
(s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zyx),
zyx.Control);
var xyz = new DefaultShuffle3(2, 1, 0);
DefaultShuffle3 xyz = new(SimdUtils.Shuffle.MMShuffle3210);
TestShuffleByte3Channel(
size,
(s, d) => SimdUtils.Shuffle3(s.Span, d.Span, xyz),
xyz.Control);
var yyy = new DefaultShuffle3(1, 1, 1);
DefaultShuffle3 yyy = new(SimdUtils.Shuffle.MMShuffle3111);
TestShuffleByte3Channel(
size,
(s, d) => SimdUtils.Shuffle3(s.Span, d.Span, yyy),
yyy.Control);
var zzz = new DefaultShuffle3(2, 2, 2);
DefaultShuffle3 zzz = new(SimdUtils.Shuffle.MMShuffle3222);
TestShuffleByte3Channel(
size,
(s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zzz),
@ -147,21 +412,21 @@ public partial class SimdUtilsTests
TestPad3Shuffle4Channel(
size,
(s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xyzw),
xyzw.Control);
SimdUtils.Shuffle.MMShuffle3210);
var xwyz = new DefaultPad3Shuffle4(2, 1, 3, 0);
DefaultPad3Shuffle4 xwyz = new(SimdUtils.Shuffle.MMShuffle2130);
TestPad3Shuffle4Channel(
size,
(s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xwyz),
xwyz.Control);
var yyyy = new DefaultPad3Shuffle4(1, 1, 1, 1);
DefaultPad3Shuffle4 yyyy = new(SimdUtils.Shuffle.MMShuffle1111);
TestPad3Shuffle4Channel(
size,
(s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, yyyy),
yyyy.Control);
var wwww = new DefaultPad3Shuffle4(3, 3, 3, 3);
DefaultPad3Shuffle4 wwww = new(SimdUtils.Shuffle.MMShuffle3333);
TestPad3Shuffle4Channel(
size,
(s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, wwww),
@ -189,21 +454,21 @@ public partial class SimdUtilsTests
TestShuffle4Slice3Channel(
size,
(s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xyzw),
xyzw.Control);
SimdUtils.Shuffle.MMShuffle3210);
var xwyz = new DefaultShuffle4Slice3(2, 1, 3, 0);
DefaultShuffle4Slice3 xwyz = new(SimdUtils.Shuffle.MMShuffle2130);
TestShuffle4Slice3Channel(
size,
(s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xwyz),
xwyz.Control);
var yyyy = new DefaultShuffle4Slice3(1, 1, 1, 1);
DefaultShuffle4Slice3 yyyy = new(SimdUtils.Shuffle.MMShuffle1111);
TestShuffle4Slice3Channel(
size,
(s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, yyyy),
yyyy.Control);
var wwww = new DefaultShuffle4Slice3(3, 3, 3, 3);
DefaultShuffle4Slice3 wwww = new(SimdUtils.Shuffle.MMShuffle3333);
TestShuffle4Slice3Channel(
size,
(s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, wwww),
@ -222,11 +487,11 @@ public partial class SimdUtilsTests
byte control)
{
float[] source = new Random(count).GenerateRandomFloatArray(count, 0, 256);
var result = new float[count];
float[] result = new float[count];
float[] expected = new float[count];
SimdUtils.Shuffle.InverseMmShuffle(
SimdUtils.Shuffle.InverseMMShuffle(
control,
out int p3,
out int p2,
@ -253,11 +518,11 @@ public partial class SimdUtilsTests
{
byte[] source = new byte[count];
new Random(count).NextBytes(source);
var result = new byte[count];
byte[] result = new byte[count];
byte[] expected = new byte[count];
SimdUtils.Shuffle.InverseMmShuffle(
SimdUtils.Shuffle.InverseMMShuffle(
control,
out int p3,
out int p2,
@ -284,11 +549,11 @@ public partial class SimdUtilsTests
{
byte[] source = new byte[count];
new Random(count).NextBytes(source);
var result = new byte[count];
byte[] result = new byte[count];
byte[] expected = new byte[count];
SimdUtils.Shuffle.InverseMmShuffle(
SimdUtils.Shuffle.InverseMMShuffle(
control,
out int _,
out int p2,
@ -315,11 +580,11 @@ public partial class SimdUtilsTests
byte[] source = new byte[count];
new Random(count).NextBytes(source);
var result = new byte[count * 4 / 3];
byte[] result = new byte[count * 4 / 3];
byte[] expected = new byte[result.Length];
SimdUtils.Shuffle.InverseMmShuffle(
SimdUtils.Shuffle.InverseMMShuffle(
control,
out int p3,
out int p2,
@ -366,11 +631,11 @@ public partial class SimdUtilsTests
byte[] source = new byte[count];
new Random(count).NextBytes(source);
var result = new byte[count * 3 / 4];
byte[] result = new byte[count * 3 / 4];
byte[] expected = new byte[result.Length];
SimdUtils.Shuffle.InverseMmShuffle(
SimdUtils.Shuffle.InverseMMShuffle(
control,
out int _,
out int p2,

8
tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs

@ -182,12 +182,12 @@ public abstract partial class ImageFrameCollectionTests
new[] { imageFrame1, imageFrame2 });
IPixelSource<Rgba32>[] framesSnapShot = collection.OfType<IPixelSource<Rgba32>>().ToArray();
Assert.All(framesSnapShot, f => Assert.False(f.PixelBuffer.IsDisposed));
collection.Dispose();
Assert.All(
framesSnapShot,
f => // The pixel source of the frame is null after its been disposed.
Assert.Null(f.PixelBuffer));
Assert.All(framesSnapShot, f => Assert.True(f.PixelBuffer.IsDisposed));
}
[Theory]

73
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs

@ -1,59 +1,66 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders;
public class PorterDuffCompositorTests
{
// TODO: Add other modes to compare.
public static readonly TheoryData<PixelAlphaCompositionMode> CompositingOperators =
new TheoryData<PixelAlphaCompositionMode>
{
PixelAlphaCompositionMode.Src,
PixelAlphaCompositionMode.SrcAtop,
PixelAlphaCompositionMode.SrcOver,
PixelAlphaCompositionMode.SrcIn,
PixelAlphaCompositionMode.SrcOut,
PixelAlphaCompositionMode.Dest,
PixelAlphaCompositionMode.DestAtop,
PixelAlphaCompositionMode.DestOver,
PixelAlphaCompositionMode.DestIn,
PixelAlphaCompositionMode.DestOut,
PixelAlphaCompositionMode.Clear,
PixelAlphaCompositionMode.Xor
};
new()
{
PixelAlphaCompositionMode.Src,
PixelAlphaCompositionMode.SrcAtop,
PixelAlphaCompositionMode.SrcOver,
PixelAlphaCompositionMode.SrcIn,
PixelAlphaCompositionMode.SrcOut,
PixelAlphaCompositionMode.Dest,
PixelAlphaCompositionMode.DestAtop,
PixelAlphaCompositionMode.DestOver,
PixelAlphaCompositionMode.DestIn,
PixelAlphaCompositionMode.DestOut,
PixelAlphaCompositionMode.Clear,
PixelAlphaCompositionMode.Xor
};
[Theory]
[WithFile(TestImages.Png.PDDest, nameof(CompositingOperators), PixelTypes.Rgba32)]
public void PorterDuffOutputIsCorrect(TestImageProvider<Rgba32> provider, PixelAlphaCompositionMode mode)
{
var srcFile = TestFile.Create(TestImages.Png.PDSrc);
using (Image<Rgba32> src = srcFile.CreateRgba32Image())
using (Image<Rgba32> dest = provider.GetImage())
static void RunTest(string providerDump, string alphaMode)
{
var options = new GraphicsOptions
TestImageProvider<Rgba32> provider
= BasicSerializer.Deserialize<TestImageProvider<Rgba32>>(providerDump);
TestFile srcFile = TestFile.Create(TestImages.Png.PDSrc);
using Image<Rgba32> src = srcFile.CreateRgba32Image();
using Image<Rgba32> dest = provider.GetImage();
GraphicsOptions options = new()
{
Antialias = false,
AlphaCompositionMode = mode
AlphaCompositionMode = Enum.Parse<PixelAlphaCompositionMode>(alphaMode)
};
using (Image<Rgba32> res = dest.Clone(x => x.DrawImage(src, options)))
{
string combinedMode = mode.ToString();
if (combinedMode != "Src" && combinedMode.StartsWith("Src"))
{
combinedMode = combinedMode.Substring(3);
}
using Image<Rgba32> res = dest.Clone(x => x.DrawImage(src, options));
string combinedMode = alphaMode;
res.DebugSave(provider, combinedMode);
res.CompareToReferenceOutput(provider, combinedMode);
if (combinedMode != "Src" && combinedMode.StartsWith("Src", StringComparison.OrdinalIgnoreCase))
{
combinedMode = combinedMode[3..];
}
res.DebugSave(provider, combinedMode);
res.CompareToReferenceOutput(provider, combinedMode);
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX,
provider,
mode.ToString());
}
}

135
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
using SixLabors.ImageSharp.Tests.TestUtilities;
@ -9,7 +10,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders;
public class PorterDuffFunctionsTests
{
public static TheoryData<TestVector4, TestVector4, float, TestVector4> NormalBlendFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
private static readonly ApproximateFloatComparer FloatComparer = new(.000001F);
public static TheoryData<TestVector4, TestVector4, float, TestVector4> NormalBlendFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }
@ -23,7 +26,19 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected, actual);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> MultiplyFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(NormalBlendFunctionData))]
public void NormalBlendFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.NormalSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> MultiplyFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) },
@ -38,22 +53,46 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> AddFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(MultiplyFunctionData))]
public void MultiplyFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.MultiplySrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> AddFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) },
{ new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2075676f, .2075676f, .2075676f, .37f) }
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(0.24324325f, 0.24324325f, 0.24324325f, .37f) }
};
[Theory]
[MemberData(nameof(AddFunctionData))]
public void AddFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector4 actual = PorterDuffFunctions.MultiplySrcOver((Vector4)back, source, amount);
Vector4 actual = PorterDuffFunctions.AddSrcOver((Vector4)back, source, amount);
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> SubtractFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(AddFunctionData))]
public void AddFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.AddSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> SubtractFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(0, 0, 0, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) },
@ -68,7 +107,19 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> ScreenFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(SubtractFunctionData))]
public void SubtractFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.SubtractSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> ScreenFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) },
@ -83,7 +134,19 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> DarkenFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(ScreenFunctionData))]
public void ScreenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.ScreenSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> DarkenFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) },
@ -98,7 +161,19 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> LightenFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(DarkenFunctionData))]
public void DarkenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.DarkenSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> LightenFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) },
@ -113,7 +188,19 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> OverlayFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(LightenFunctionData))]
public void LightenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.LightenSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> OverlayFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) },
@ -128,7 +215,19 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> HardLightFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(OverlayFunctionData))]
public void OverlayFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.OverlaySrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> HardLightFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1f) },
@ -142,4 +241,16 @@ public class PorterDuffFunctionsTests
Vector4 actual = PorterDuffFunctions.HardLightSrcOver((Vector4)back, source, amount);
VectorAssert.Equal(expected, actual, 5);
}
[Theory]
[MemberData(nameof(HardLightFunctionData))]
public void HardLightFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.HardLightSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
}

17
tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

@ -1,7 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests;
@ -14,7 +16,8 @@ internal readonly struct ApproximateFloatComparer :
IEqualityComparer<Vector2>,
IEqualityComparer<IPixel>,
IEqualityComparer<Vector4>,
IEqualityComparer<ColorMatrix>
IEqualityComparer<ColorMatrix>,
IEqualityComparer<Vector256<float>>
{
private readonly float epsilon;
@ -72,4 +75,16 @@ internal readonly struct ApproximateFloatComparer :
/// <inheritdoc/>
public int GetHashCode(ColorMatrix obj) => obj.GetHashCode();
public bool Equals(Vector256<float> x, Vector256<float> y)
=> this.Equals(x.GetElement(0), y.GetElement(0))
&& this.Equals(x.GetElement(1), y.GetElement(1))
&& this.Equals(x.GetElement(2), y.GetElement(2))
&& this.Equals(x.GetElement(3), y.GetElement(3))
&& this.Equals(x.GetElement(4), y.GetElement(4))
&& this.Equals(x.GetElement(5), y.GetElement(5))
&& this.Equals(x.GetElement(6), y.GetElement(6))
&& this.Equals(x.GetElement(7), y.GetElement(7));
public int GetHashCode([DisallowNull] Vector256<float> obj) => obj.GetHashCode();
}

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

@ -257,6 +257,52 @@ public static class FeatureTestRunner
}
}
/// <summary>
/// Runs the given test <paramref name="action"/> within an environment
/// 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="arg1">The value to pass as a parameter to the test action.</param>
/// <param name="arg2">The second value to pass as a parameter to the test action.</param>
public static void RunWithHwIntrinsicsFeature<T>(
Action<string, string> action,
HwIntrinsics intrinsics,
T arg1,
string arg2)
where T : IXunitSerializable
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
RemoteExecutor.Invoke(
action,
BasicSerializer.Serialize(arg1),
arg2,
new RemoteInvokeOptions
{
StartInfo = processStartInfo
})
.Dispose();
}
else
{
// Since we are running using the default architecture there is no
// point creating the overhead of running the action in a separate process.
action(BasicSerializer.Serialize(arg1), arg2);
}
}
}
/// <summary>
/// Runs the given test <paramref name="action"/> within an environment
/// where the given <paramref name="intrinsics"/> features.

Loading…
Cancel
Save