mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
115 changed files with 12602 additions and 173906 deletions
@ -1 +1 @@ |
|||||
Subproject commit 7ac5703452348d9295db31fc0912c2bd9e419dc9 |
Subproject commit 74b7f32b8e41fdf8fe2f3eda54fd5a82ebbedfbc |
||||
@ -1,36 +1,26 @@ |
|||||
// Copyright (c) Six Labors.
|
// Copyright (c) Six Labors.
|
||||
// Licensed under the Six Labors Split License.
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
// The JIT can detect and optimize rotation idioms ROTL (Rotate Left)
|
using System.Runtime.Intrinsics; |
||||
// and ROTR (Rotate Right) emitting efficient CPU instructions:
|
|
||||
// https://github.com/dotnet/coreclr/pull/1830
|
|
||||
namespace SixLabors.ImageSharp; |
namespace SixLabors.ImageSharp; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Defines the contract for methods that allow the shuffling of pixel components.
|
/// Defines a stateless operation over packed pixel components.
|
||||
/// Used for shuffling on platforms that do not support Hardware Intrinsics.
|
|
||||
/// </summary>
|
/// </summary>
|
||||
internal interface IComponentShuffle |
internal interface IComponentShuffle |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Shuffles then slices 8-bit integers in <paramref name="source"/>
|
/// Reorders one packed pixel.
|
||||
/// using a byte control and store the results in <paramref name="destination"/>.
|
|
||||
/// If successful, this method will reduce the length of <paramref name="source"/> length
|
|
||||
/// by the shuffle amount.
|
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="source">The source span of bytes.</param>
|
/// <param name="source">The source components, with the first component in the least-significant byte.</param>
|
||||
/// <param name="destination">The destination span of bytes.</param>
|
/// <returns>The reordered packed components.</returns>
|
||||
void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination); |
public static abstract uint Invoke(uint source); |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Shuffle 8-bit integers in <paramref name="source"/>
|
/// Reorders the packed pixels in a 128-bit vector.
|
||||
/// using the control and store the results in <paramref name="destination"/>.
|
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="source">The source span of bytes.</param>
|
/// <param name="source">The source pixels.</param>
|
||||
/// <param name="destination">The destination span of bytes.</param>
|
/// <returns>The reordered pixels.</returns>
|
||||
/// <remarks>
|
public static abstract Vector128<byte> Invoke(Vector128<byte> source); |
||||
/// Implementation can assume that source.Length is less or equal than destination.Length.
|
|
||||
/// Loops should iterate using source.Length.
|
|
||||
/// </remarks>
|
|
||||
void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination); |
|
||||
} |
} |
||||
|
|||||
@ -1,99 +1,95 @@ |
|||||
// Copyright (c) Six Labors.
|
// Copyright (c) Six Labors.
|
||||
// Licensed under the Six Labors Split License.
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using System.Runtime.CompilerServices; |
using System.Runtime.CompilerServices; |
||||
using System.Runtime.InteropServices; |
using System.Runtime.Intrinsics; |
||||
using static SixLabors.ImageSharp.SimdUtils; |
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
namespace SixLabors.ImageSharp; |
namespace SixLabors.ImageSharp; |
||||
|
|
||||
/// <inheritdoc/>
|
/// <summary>
|
||||
|
/// Defines a stateless operation that reorders a three-component pixel after adding opaque alpha.
|
||||
|
/// </summary>
|
||||
internal interface IPad3Shuffle4 : IComponentShuffle |
internal interface IPad3Shuffle4 : IComponentShuffle |
||||
{ |
{ |
||||
} |
} |
||||
|
|
||||
internal readonly struct DefaultPad3Shuffle4([ConstantExpected] byte control) : IPad3Shuffle4 |
/// <summary>
|
||||
|
/// Preserves XYZ order and appends opaque W.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4 |
||||
{ |
{ |
||||
public byte Control { get; } = control; |
/// <inheritdoc />
|
||||
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
[MethodImpl(InliningOptions.ShortMethod)] |
||||
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination) |
public static uint Invoke(uint source) => source; |
||||
#pragma warning disable CA1857 // A constant is expected for the parameter
|
|
||||
=> HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref destination, this.Control); |
|
||||
#pragma warning restore CA1857 // A constant is expected for the parameter
|
|
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<byte> Invoke(Vector128<byte> source) => source; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reorders padded XYZW components to WXYZ.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct WXYZPad3Shuffle4 : IPad3Shuffle4 |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
[MethodImpl(InliningOptions.ShortMethod)] |
||||
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination) |
public static uint Invoke(uint source) |
||||
{ |
|
||||
ref byte sBase = ref MemoryMarshal.GetReference(source); |
// The scalar pipeline has already appended opaque W, so the four-component
|
||||
ref byte dBase = ref MemoryMarshal.GetReference(destination); |
// WXYZ operator performs the complete remaining permutation.
|
||||
|
=> WXYZShuffle4.Invoke(source); |
||||
SimdUtils.Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0); |
|
||||
|
/// <inheritdoc />
|
||||
for (nuint i = 0, j = 0; i < (uint)source.Length; i += 3, j += 4) |
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
{ |
public static Vector128<byte> Invoke(Vector128<byte> source) |
||||
// Expanding 3-byte pixels to 4 bytes can overwrite the next source
|
|
||||
// triplet when spans overlap. Assemble the padded pixel first, then
|
// Each four-byte group is an XYZW pixel with opaque W. Selecting [3, 0, 1, 2]
|
||||
// shuffle from the staged uint.
|
// produces WXYZ, and offsets 4, 8, and 12 repeat that rotation for the next pixels.
|
||||
uint packed = |
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14)); |
||||
Unsafe.Add(ref sBase, i + 0u) | |
|
||||
((uint)Unsafe.Add(ref sBase, i + 1u) << 8) | |
|
||||
((uint)Unsafe.Add(ref sBase, i + 2u) << 16) | |
|
||||
0xFF000000; |
|
||||
|
|
||||
ref byte pBase = ref Unsafe.As<uint, byte>(ref packed); |
|
||||
|
|
||||
Unsafe.Add(ref dBase, j + 0u) = Unsafe.Add(ref pBase, p0); |
|
||||
Unsafe.Add(ref dBase, j + 1u) = Unsafe.Add(ref pBase, p1); |
|
||||
Unsafe.Add(ref dBase, j + 2u) = Unsafe.Add(ref pBase, p2); |
|
||||
Unsafe.Add(ref dBase, j + 3u) = Unsafe.Add(ref pBase, p3); |
|
||||
} |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4 |
/// <summary>
|
||||
|
/// Reorders padded XYZW components to WZYX.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct WZYXPad3Shuffle4 : IPad3Shuffle4 |
||||
{ |
{ |
||||
|
/// <inheritdoc />
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
[MethodImpl(InliningOptions.ShortMethod)] |
||||
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination) |
public static uint Invoke(uint source) |
||||
=> HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle3210); |
|
||||
|
|
||||
|
// The scalar pipeline has already appended opaque W, so the four-component
|
||||
|
// WZYX operator performs the complete remaining permutation.
|
||||
|
=> WZYXShuffle4.Invoke(source); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<byte> Invoke(Vector128<byte> source) |
||||
|
|
||||
|
// Each four-byte group is an XYZW pixel with opaque W. Selecting [3, 2, 1, 0]
|
||||
|
// produces WZYX, and offsets 4, 8, and 12 repeat that reversal for the next pixels.
|
||||
|
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reorders padded XYZW components to ZYXW.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct ZYXWPad3Shuffle4 : IPad3Shuffle4 |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
[MethodImpl(InliningOptions.ShortMethod)] |
||||
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination) |
public static uint Invoke(uint source) |
||||
{ |
|
||||
ref byte sBase = ref MemoryMarshal.GetReference(source); |
// The scalar pipeline has already appended opaque W, so the four-component
|
||||
ref byte dBase = ref MemoryMarshal.GetReference(destination); |
// ZYXW operator performs the complete remaining permutation.
|
||||
|
=> ZYXWShuffle4.Invoke(source); |
||||
ref byte sEnd = ref Unsafe.Add(ref sBase, (uint)source.Length); |
|
||||
ref byte sLoopEnd = ref Unsafe.Subtract(ref sEnd, 4); |
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) |
public static Vector128<byte> Invoke(Vector128<byte> source) |
||||
{ |
|
||||
// The fast scalar path reads one extra byte past the source triplet.
|
// Each four-byte group is an XYZW pixel with opaque W. Selecting [2, 1, 0, 3]
|
||||
// Keep that widened read in a local before writing the expanded pixel
|
// exchanges X and Z to produce ZYXW, with offsets 4, 8, and 12 covering the next pixels.
|
||||
// so overlapping destinations cannot change what was read.
|
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15)); |
||||
uint packed = Unsafe.As<byte, uint>(ref sBase) | 0xFF000000; |
|
||||
|
|
||||
Unsafe.As<byte, uint>(ref dBase) = packed; |
|
||||
|
|
||||
sBase = ref Unsafe.Add(ref sBase, 3); |
|
||||
dBase = ref Unsafe.Add(ref dBase, 4); |
|
||||
} |
|
||||
|
|
||||
while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) |
|
||||
{ |
|
||||
// The final triplet cannot use the widened read above, so assemble
|
|
||||
// the same padded uint byte-by-byte before the overlapping store.
|
|
||||
uint packed = |
|
||||
Unsafe.Add(ref sBase, 0u) | |
|
||||
((uint)Unsafe.Add(ref sBase, 1u) << 8) | |
|
||||
((uint)Unsafe.Add(ref sBase, 2u) << 16) | |
|
||||
0xFF000000; |
|
||||
|
|
||||
Unsafe.As<byte, uint>(ref dBase) = packed; |
|
||||
|
|
||||
sBase = ref Unsafe.Add(ref sBase, 3); |
|
||||
dBase = ref Unsafe.Add(ref dBase, 4); |
|
||||
} |
|
||||
} |
|
||||
} |
} |
||||
|
|||||
@ -1,51 +1,38 @@ |
|||||
// Copyright (c) Six Labors.
|
// Copyright (c) Six Labors.
|
||||
// Licensed under the Six Labors Split License.
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using System.Runtime.CompilerServices; |
using System.Runtime.CompilerServices; |
||||
using System.Runtime.InteropServices; |
using System.Runtime.Intrinsics; |
||||
using static SixLabors.ImageSharp.SimdUtils; |
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
namespace SixLabors.ImageSharp; |
namespace SixLabors.ImageSharp; |
||||
|
|
||||
/// <inheritdoc/>
|
/// <summary>
|
||||
|
/// Identifies a stateless three-component shuffle operator.
|
||||
|
/// </summary>
|
||||
internal interface IShuffle3 : IComponentShuffle |
internal interface IShuffle3 : IComponentShuffle |
||||
{ |
{ |
||||
} |
} |
||||
|
|
||||
internal readonly struct DefaultShuffle3([ConstantExpected] byte control) : IShuffle3 |
/// <summary>
|
||||
|
/// Reorders XYZ components to ZYX.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct ZYXShuffle3 : IShuffle3 |
||||
{ |
{ |
||||
public byte Control { get; } = control; |
/// <inheritdoc />
|
||||
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination) |
|
||||
#pragma warning disable CA1857 // A constant is expected for the parameter
|
|
||||
=> HwIntrinsics.Shuffle3Reduce(ref source, ref destination, this.Control); |
|
||||
#pragma warning restore CA1857 // A constant is expected for the parameter
|
|
||||
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
[MethodImpl(InliningOptions.ShortMethod)] |
||||
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination) |
public static uint Invoke(uint source) |
||||
{ |
|
||||
ref byte sBase = ref MemoryMarshal.GetReference(source); |
|
||||
ref byte dBase = ref MemoryMarshal.GetReference(destination); |
|
||||
|
|
||||
SimdUtils.Shuffle.InverseMMShuffle(this.Control, out _, out uint p2, out uint p1, out uint p0); |
|
||||
|
|
||||
for (nuint i = 0; i < (uint)source.Length; i += 3) |
// The scalar tail is staged as XYZW with an unused W byte. Reusing the four-component
|
||||
{ |
// ZYXW operator produces ZYX in the low three bytes consumed by the caller.
|
||||
// The scalar remainder can run in-place after the vector body. Load
|
=> ZYXWShuffle4.Invoke(source); |
||||
// the full 3-byte pixel into a register-sized value before stores so
|
|
||||
// channel swaps cannot corrupt later reads from the same pixel.
|
|
||||
uint packed = |
|
||||
Unsafe.Add(ref sBase, i + 0u) | |
|
||||
((uint)Unsafe.Add(ref sBase, i + 1u) << 8) | |
|
||||
((uint)Unsafe.Add(ref sBase, i + 2u) << 16); |
|
||||
|
|
||||
ref byte pBase = ref Unsafe.As<uint, byte>(ref packed); |
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<byte> Invoke(Vector128<byte> source) |
||||
|
|
||||
Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0); |
// Each four-byte group is a temporary XYZW pixel created by the shuffle pipeline.
|
||||
Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1); |
// Selecting [2, 1, 0, 3] produces ZYXW, and offsets 4, 8, and 12 repeat that
|
||||
Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2); |
// permutation for the next pixels. The pipeline subsequently discards every W byte.
|
||||
} |
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15)); |
||||
} |
|
||||
} |
} |
||||
|
|||||
@ -0,0 +1,93 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
internal static partial class TensorPrimitives_ |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Computes the element-wise sum of the values in <paramref name="x"/> and <paramref name="y"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <param name="x">The first addends.</param>
|
||||
|
/// <param name="y">The second addends.</param>
|
||||
|
/// <param name="destination">The destination for the sums.</param>
|
||||
|
/// <exception cref="ArgumentException"><paramref name="x"/> and <paramref name="y"/> do not have the same length.</exception>
|
||||
|
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than the input spans.</exception>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// An input and <paramref name="destination"/> overlap without beginning at the same memory location.
|
||||
|
/// </exception>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Add<T>(ReadOnlySpan<T> x, ReadOnlySpan<T> y, Span<T> destination) |
||||
|
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T> |
||||
|
=> InvokeSpanSpanIntoSpan<T, AddOperator<T>>(x, y, destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Computes the element-wise sum of the values in <paramref name="x"/> and the scalar <paramref name="y"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <param name="x">The first addends.</param>
|
||||
|
/// <param name="y">The scalar second addend.</param>
|
||||
|
/// <param name="destination">The destination for the sums.</param>
|
||||
|
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
||||
|
/// </exception>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Add<T>(ReadOnlySpan<T> x, T y, Span<T> destination) |
||||
|
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T> |
||||
|
=> InvokeSpanScalarIntoSpan<T, AddOperator<T>>(x, y, destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds corresponding values.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
private readonly struct AddOperator<T> : IBinaryOperator<T> |
||||
|
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether this operation supports vector execution.
|
||||
|
/// </summary>
|
||||
|
public static bool Vectorizable => true; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds scalar values.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first addend.</param>
|
||||
|
/// <param name="y">The second addend.</param>
|
||||
|
/// <returns>The sum.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static T Invoke(T x, T y) => x + y; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds 128-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first addends.</param>
|
||||
|
/// <param name="y">The second addends.</param>
|
||||
|
/// <returns>The sums.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y) => x + y; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds 256-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first addends.</param>
|
||||
|
/// <param name="y">The second addends.</param>
|
||||
|
/// <returns>The sums.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y) => x + y; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds 512-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first addends.</param>
|
||||
|
/// <param name="y">The second addends.</param>
|
||||
|
/// <returns>The sums.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y) => x + y; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,322 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
internal static partial class TensorPrimitives_ |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Computes the element-wise result of clamping <paramref name="x"/> to the inclusive range specified
|
||||
|
/// by <paramref name="min"/> and <paramref name="max"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <param name="x">The values to clamp.</param>
|
||||
|
/// <param name="min">The inclusive lower bound.</param>
|
||||
|
/// <param name="max">The inclusive upper bound.</param>
|
||||
|
/// <param name="destination">The destination for the clamped values.</param>
|
||||
|
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
||||
|
/// </exception>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Clamp<T>(ReadOnlySpan<T> x, T min, T max, Span<T> destination) |
||||
|
where T : INumber<T> |
||||
|
=> InvokeSpanScalarScalarIntoSpan<T, ClampOperator<T>>(x, min, max, destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clamps single-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The values to clamp.</param>
|
||||
|
/// <param name="min">The inclusive lower bounds.</param>
|
||||
|
/// <param name="max">The inclusive upper bounds.</param>
|
||||
|
/// <returns>The clamped values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<float> ClampSingle( |
||||
|
Vector128<float> value, |
||||
|
Vector128<float> min, |
||||
|
Vector128<float> max) |
||||
|
{ |
||||
|
// Unlike the native x86 min/max instructions, the normalized runtime operations propagate a NaN in the
|
||||
|
// first operand and select negative zero when equal values have different signs.
|
||||
|
Vector128<float> maximum = Vector128.ConditionalSelect( |
||||
|
Vector128.LessThan(min, value) |
||||
|
| ~Vector128.Equals(value, value) |
||||
|
| (Vector128.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), |
||||
|
value, |
||||
|
min); |
||||
|
|
||||
|
return Vector128.ConditionalSelect( |
||||
|
Vector128.LessThan(maximum, max) |
||||
|
| ~Vector128.Equals(maximum, maximum) |
||||
|
| (Vector128.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), |
||||
|
maximum, |
||||
|
max); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clamps single-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The values to clamp.</param>
|
||||
|
/// <param name="min">The inclusive lower bounds.</param>
|
||||
|
/// <param name="max">The inclusive upper bounds.</param>
|
||||
|
/// <returns>The clamped values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<float> ClampSingle( |
||||
|
Vector256<float> value, |
||||
|
Vector256<float> min, |
||||
|
Vector256<float> max) |
||||
|
{ |
||||
|
Vector256<float> maximum = Vector256.ConditionalSelect( |
||||
|
Vector256.LessThan(min, value) |
||||
|
| ~Vector256.Equals(value, value) |
||||
|
| (Vector256.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), |
||||
|
value, |
||||
|
min); |
||||
|
|
||||
|
return Vector256.ConditionalSelect( |
||||
|
Vector256.LessThan(maximum, max) |
||||
|
| ~Vector256.Equals(maximum, maximum) |
||||
|
| (Vector256.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), |
||||
|
maximum, |
||||
|
max); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clamps single-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The values to clamp.</param>
|
||||
|
/// <param name="min">The inclusive lower bounds.</param>
|
||||
|
/// <param name="max">The inclusive upper bounds.</param>
|
||||
|
/// <returns>The clamped values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<float> ClampSingle( |
||||
|
Vector512<float> value, |
||||
|
Vector512<float> min, |
||||
|
Vector512<float> max) |
||||
|
{ |
||||
|
Vector512<float> maximum = Vector512.ConditionalSelect( |
||||
|
Vector512.LessThan(min, value) |
||||
|
| ~Vector512.Equals(value, value) |
||||
|
| (Vector512.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), |
||||
|
value, |
||||
|
min); |
||||
|
|
||||
|
return Vector512.ConditionalSelect( |
||||
|
Vector512.LessThan(maximum, max) |
||||
|
| ~Vector512.Equals(maximum, maximum) |
||||
|
| (Vector512.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), |
||||
|
maximum, |
||||
|
max); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clamps double-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The values to clamp.</param>
|
||||
|
/// <param name="min">The inclusive lower bounds.</param>
|
||||
|
/// <param name="max">The inclusive upper bounds.</param>
|
||||
|
/// <returns>The clamped values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<double> ClampDouble( |
||||
|
Vector128<double> value, |
||||
|
Vector128<double> min, |
||||
|
Vector128<double> max) |
||||
|
{ |
||||
|
Vector128<double> maximum = Vector128.ConditionalSelect( |
||||
|
Vector128.LessThan(min, value) |
||||
|
| ~Vector128.Equals(value, value) |
||||
|
| (Vector128.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), |
||||
|
value, |
||||
|
min); |
||||
|
|
||||
|
return Vector128.ConditionalSelect( |
||||
|
Vector128.LessThan(maximum, max) |
||||
|
| ~Vector128.Equals(maximum, maximum) |
||||
|
| (Vector128.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), |
||||
|
maximum, |
||||
|
max); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clamps double-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The values to clamp.</param>
|
||||
|
/// <param name="min">The inclusive lower bounds.</param>
|
||||
|
/// <param name="max">The inclusive upper bounds.</param>
|
||||
|
/// <returns>The clamped values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<double> ClampDouble( |
||||
|
Vector256<double> value, |
||||
|
Vector256<double> min, |
||||
|
Vector256<double> max) |
||||
|
{ |
||||
|
Vector256<double> maximum = Vector256.ConditionalSelect( |
||||
|
Vector256.LessThan(min, value) |
||||
|
| ~Vector256.Equals(value, value) |
||||
|
| (Vector256.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), |
||||
|
value, |
||||
|
min); |
||||
|
|
||||
|
return Vector256.ConditionalSelect( |
||||
|
Vector256.LessThan(maximum, max) |
||||
|
| ~Vector256.Equals(maximum, maximum) |
||||
|
| (Vector256.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), |
||||
|
maximum, |
||||
|
max); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clamps double-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The values to clamp.</param>
|
||||
|
/// <param name="min">The inclusive lower bounds.</param>
|
||||
|
/// <param name="max">The inclusive upper bounds.</param>
|
||||
|
/// <returns>The clamped values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<double> ClampDouble( |
||||
|
Vector512<double> value, |
||||
|
Vector512<double> min, |
||||
|
Vector512<double> max) |
||||
|
{ |
||||
|
Vector512<double> maximum = Vector512.ConditionalSelect( |
||||
|
Vector512.LessThan(min, value) |
||||
|
| ~Vector512.Equals(value, value) |
||||
|
| (Vector512.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), |
||||
|
value, |
||||
|
min); |
||||
|
|
||||
|
return Vector512.ConditionalSelect( |
||||
|
Vector512.LessThan(maximum, max) |
||||
|
| ~Vector512.Equals(maximum, maximum) |
||||
|
| (Vector512.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), |
||||
|
maximum, |
||||
|
max); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clamps values using the complete runtime tensor contract, including signed-zero correction.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
private readonly struct ClampOperator<T> : ITernaryOperator<T> |
||||
|
where T : INumber<T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether this operation supports vector execution.
|
||||
|
/// </summary>
|
||||
|
public static bool Vectorizable => true; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clamps a scalar value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The value.</param>
|
||||
|
/// <param name="min">The inclusive lower bound.</param>
|
||||
|
/// <param name="max">The inclusive upper bound.</param>
|
||||
|
/// <returns>The clamped value.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static T Invoke(T x, T min, T max) |
||||
|
=> Vector128<T>.IsSupported ? T.Min(T.Max(x, min), max) : T.Clamp(x, min, max); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clamps a 128-bit vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The values.</param>
|
||||
|
/// <param name="min">The inclusive lower bounds.</param>
|
||||
|
/// <param name="max">The inclusive upper bounds.</param>
|
||||
|
/// <returns>The clamped values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> min, Vector128<T> max) |
||||
|
{ |
||||
|
if (typeof(T) == typeof(float)) |
||||
|
{ |
||||
|
Vector128<float> result = ClampSingle( |
||||
|
Unsafe.As<Vector128<T>, Vector128<float>>(ref x), |
||||
|
Unsafe.As<Vector128<T>, Vector128<float>>(ref min), |
||||
|
Unsafe.As<Vector128<T>, Vector128<float>>(ref max)); |
||||
|
|
||||
|
return Unsafe.As<Vector128<float>, Vector128<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(double)) |
||||
|
{ |
||||
|
Vector128<double> result = ClampDouble( |
||||
|
Unsafe.As<Vector128<T>, Vector128<double>>(ref x), |
||||
|
Unsafe.As<Vector128<T>, Vector128<double>>(ref min), |
||||
|
Unsafe.As<Vector128<T>, Vector128<double>>(ref max)); |
||||
|
|
||||
|
return Unsafe.As<Vector128<double>, Vector128<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
return Vector128_.Clamp(x, min, max); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clamps a 256-bit vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The values.</param>
|
||||
|
/// <param name="min">The inclusive lower bounds.</param>
|
||||
|
/// <param name="max">The inclusive upper bounds.</param>
|
||||
|
/// <returns>The clamped values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> min, Vector256<T> max) |
||||
|
{ |
||||
|
if (typeof(T) == typeof(float)) |
||||
|
{ |
||||
|
Vector256<float> result = ClampSingle( |
||||
|
Unsafe.As<Vector256<T>, Vector256<float>>(ref x), |
||||
|
Unsafe.As<Vector256<T>, Vector256<float>>(ref min), |
||||
|
Unsafe.As<Vector256<T>, Vector256<float>>(ref max)); |
||||
|
|
||||
|
return Unsafe.As<Vector256<float>, Vector256<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(double)) |
||||
|
{ |
||||
|
Vector256<double> result = ClampDouble( |
||||
|
Unsafe.As<Vector256<T>, Vector256<double>>(ref x), |
||||
|
Unsafe.As<Vector256<T>, Vector256<double>>(ref min), |
||||
|
Unsafe.As<Vector256<T>, Vector256<double>>(ref max)); |
||||
|
|
||||
|
return Unsafe.As<Vector256<double>, Vector256<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
return Vector256_.Clamp(x, min, max); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clamps a 512-bit vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The values.</param>
|
||||
|
/// <param name="min">The inclusive lower bounds.</param>
|
||||
|
/// <param name="max">The inclusive upper bounds.</param>
|
||||
|
/// <returns>The clamped values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> min, Vector512<T> max) |
||||
|
{ |
||||
|
if (typeof(T) == typeof(float)) |
||||
|
{ |
||||
|
Vector512<float> result = ClampSingle( |
||||
|
Unsafe.As<Vector512<T>, Vector512<float>>(ref x), |
||||
|
Unsafe.As<Vector512<T>, Vector512<float>>(ref min), |
||||
|
Unsafe.As<Vector512<T>, Vector512<float>>(ref max)); |
||||
|
|
||||
|
return Unsafe.As<Vector512<float>, Vector512<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(double)) |
||||
|
{ |
||||
|
Vector512<double> result = ClampDouble( |
||||
|
Unsafe.As<Vector512<T>, Vector512<double>>(ref x), |
||||
|
Unsafe.As<Vector512<T>, Vector512<double>>(ref min), |
||||
|
Unsafe.As<Vector512<T>, Vector512<double>>(ref max)); |
||||
|
|
||||
|
return Unsafe.As<Vector512<double>, Vector512<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
return Vector512_.Clamp(x, min, max); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
internal static partial class TensorPrimitives_ |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Computes the element-wise result of dividing the values in <paramref name="x"/> by <paramref name="y"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <param name="x">The dividend values.</param>
|
||||
|
/// <param name="y">The divisor.</param>
|
||||
|
/// <param name="destination">The destination for the quotient values.</param>
|
||||
|
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
||||
|
/// </exception>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Divide<T>(ReadOnlySpan<T> x, T y, Span<T> destination) |
||||
|
where T : IDivisionOperators<T, T, T> |
||||
|
=> InvokeSpanScalarIntoSpanForDivision<T, DivideOperator<T>>(x, y, destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Determines whether <typeparamref name="T"/> has the same vector division support as <see cref="int"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <returns><see langword="true"/> when <typeparamref name="T"/> is a 32-bit signed native integer type.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static bool IsInt32Like<T>() |
||||
|
=> typeof(T) == typeof(int) || (IntPtr.Size == 4 && typeof(T) == typeof(nint)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Divides values by a scalar.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
private readonly struct DivideOperator<T> : IBinaryOperator<T> |
||||
|
where T : IDivisionOperators<T, T, T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether this operation supports vector execution.
|
||||
|
/// </summary>
|
||||
|
public static bool Vectorizable => typeof(T) == typeof(float) |
||||
|
|| typeof(T) == typeof(double) |
||||
|
|| (Vector256.IsHardwareAccelerated && IsInt32Like<T>()); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Divides scalar values.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The dividend.</param>
|
||||
|
/// <param name="y">The divisor.</param>
|
||||
|
/// <returns>The quotient.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static T Invoke(T x, T y) => x / y; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Divides 128-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The dividends.</param>
|
||||
|
/// <param name="y">The divisors.</param>
|
||||
|
/// <returns>The quotients.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y) => x / y; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Divides 256-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The dividends.</param>
|
||||
|
/// <param name="y">The divisors.</param>
|
||||
|
/// <returns>The quotients.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y) => x / y; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Divides 512-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The dividends.</param>
|
||||
|
/// <param name="y">The divisors.</param>
|
||||
|
/// <returns>The quotients.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y) => x / y; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,900 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Provides compatibility implementations for tensor operations that are not available on every target framework.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The API shape follows <c>System.Numerics.Tensors.TensorPrimitives</c> so call sites can move to the runtime
|
||||
|
/// implementation when ImageSharp no longer supports target frameworks that predate it.
|
||||
|
/// </remarks>
|
||||
|
internal static partial class TensorPrimitives_ |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines an element-wise binary operation.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
private interface IBinaryOperator<T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the operation supports vector execution.
|
||||
|
/// </summary>
|
||||
|
public static abstract bool Vectorizable { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to scalar values.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first value.</param>
|
||||
|
/// <param name="y">The second value.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract T Invoke(T x, T y); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to 128-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first vector.</param>
|
||||
|
/// <param name="y">The second vector.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract Vector128<T> Invoke(Vector128<T> x, Vector128<T> y); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to 256-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first vector.</param>
|
||||
|
/// <param name="y">The second vector.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract Vector256<T> Invoke(Vector256<T> x, Vector256<T> y); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to 512-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first vector.</param>
|
||||
|
/// <param name="y">The second vector.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract Vector512<T> Invoke(Vector512<T> x, Vector512<T> y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines an element-wise ternary operation.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
private interface ITernaryOperator<T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the operation supports vector execution.
|
||||
|
/// </summary>
|
||||
|
public static abstract bool Vectorizable { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to scalar values.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first value.</param>
|
||||
|
/// <param name="y">The second value.</param>
|
||||
|
/// <param name="z">The third value.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract T Invoke(T x, T y, T z); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to 128-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first vector.</param>
|
||||
|
/// <param name="y">The second vector.</param>
|
||||
|
/// <param name="z">The third vector.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract Vector128<T> Invoke(Vector128<T> x, Vector128<T> y, Vector128<T> z); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to 256-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first vector.</param>
|
||||
|
/// <param name="y">The second vector.</param>
|
||||
|
/// <param name="z">The third vector.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract Vector256<T> Invoke(Vector256<T> x, Vector256<T> y, Vector256<T> z); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to 512-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first vector.</param>
|
||||
|
/// <param name="y">The second vector.</param>
|
||||
|
/// <param name="z">The third vector.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract Vector512<T> Invoke(Vector512<T> x, Vector512<T> y, Vector512<T> z); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Validates that an input and destination are either disjoint or begin at the same memory location.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <param name="input">The input values.</param>
|
||||
|
/// <param name="destination">The destination values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void ValidateInputOutputSpanNonOverlapping<T>(ReadOnlySpan<T> input, Span<T> destination) |
||||
|
{ |
||||
|
// Runtime TensorPrimitives permits exact same-start overlap for in-place operation. A shifted overlap is
|
||||
|
// rejected because forward SIMD stores could overwrite input elements before a later load consumes them.
|
||||
|
if (!Unsafe.AreSame(ref MemoryMarshal.GetReference(input), ref MemoryMarshal.GetReference(destination)) |
||||
|
&& input.Overlaps(destination)) |
||||
|
{ |
||||
|
ThrowInputAndDestinationSpanMustNotOverlap(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Throws when input spans do not have the same length.
|
||||
|
/// </summary>
|
||||
|
[DoesNotReturn] |
||||
|
private static void ThrowSpansMustHaveSameLength() |
||||
|
=> throw new ArgumentException("Input span arguments must all have the same length."); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Throws when the destination cannot hold every result.
|
||||
|
/// </summary>
|
||||
|
[DoesNotReturn] |
||||
|
private static void ThrowDestinationTooShort() |
||||
|
=> throw new ArgumentException("Destination is too short.", "destination"); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Throws when an input and destination overlap without beginning at the same memory location.
|
||||
|
/// </summary>
|
||||
|
[DoesNotReturn] |
||||
|
private static void ThrowInputAndDestinationSpanMustNotOverlap() |
||||
|
=> throw new ArgumentException( |
||||
|
"The destination span may only overlap with an input span if the two spans start at the same memory location.", |
||||
|
"destination"); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Performs an element-wise binary operation between two spans.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="x">The first input values.</param>
|
||||
|
/// <param name="y">The second input values.</param>
|
||||
|
/// <param name="destination">The destination values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeSpanSpanIntoSpan<T, TOperator>( |
||||
|
ReadOnlySpan<T> x, |
||||
|
ReadOnlySpan<T> y, |
||||
|
Span<T> destination) |
||||
|
where TOperator : struct, IBinaryOperator<T> |
||||
|
{ |
||||
|
if (x.Length != y.Length) |
||||
|
{ |
||||
|
ThrowSpansMustHaveSameLength(); |
||||
|
} |
||||
|
|
||||
|
if (x.Length > destination.Length) |
||||
|
{ |
||||
|
ThrowDestinationTooShort(); |
||||
|
} |
||||
|
|
||||
|
ValidateInputOutputSpanNonOverlapping(x, destination); |
||||
|
ValidateInputOutputSpanNonOverlapping(y, destination); |
||||
|
|
||||
|
ref T xRef = ref MemoryMarshal.GetReference(x); |
||||
|
ref T yRef = ref MemoryMarshal.GetReference(y); |
||||
|
ref T destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
nuint length = (uint)x.Length; |
||||
|
|
||||
|
// Runtime main selects the widest supported pipeline once one complete vector is available.
|
||||
|
// Each pipeline preloads its final inputs when a tail overlaps so same-start in-place operation remains correct.
|
||||
|
if (TOperator.Vectorizable |
||||
|
&& Vector512.IsHardwareAccelerated |
||||
|
&& Vector512<T>.IsSupported |
||||
|
&& length >= (uint)Vector512<T>.Count) |
||||
|
{ |
||||
|
InvokeVectorized512<T, TOperator>(ref xRef, ref yRef, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count) |
||||
|
{ |
||||
|
InvokeVectorized256<T, TOperator>(ref xRef, ref yRef, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count) |
||||
|
{ |
||||
|
InvokeVectorized128<T, TOperator>(ref xRef, ref yRef, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (nuint i = 0; i < length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), Unsafe.Add(ref yRef, i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Performs an element-wise binary operation between a span and a scalar.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="x">The input values.</param>
|
||||
|
/// <param name="y">The scalar input.</param>
|
||||
|
/// <param name="destination">The destination values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeSpanScalarIntoSpan<T, TOperator>( |
||||
|
ReadOnlySpan<T> x, |
||||
|
T y, |
||||
|
Span<T> destination) |
||||
|
where TOperator : struct, IBinaryOperator<T> |
||||
|
{ |
||||
|
if (x.Length > destination.Length) |
||||
|
{ |
||||
|
ThrowDestinationTooShort(); |
||||
|
} |
||||
|
|
||||
|
ValidateInputOutputSpanNonOverlapping(x, destination); |
||||
|
|
||||
|
ref T xRef = ref MemoryMarshal.GetReference(x); |
||||
|
ref T destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
nuint length = (uint)x.Length; |
||||
|
|
||||
|
// Runtime main selects the widest supported pipeline once one complete vector is available.
|
||||
|
if (TOperator.Vectorizable |
||||
|
&& Vector512.IsHardwareAccelerated |
||||
|
&& Vector512<T>.IsSupported |
||||
|
&& length >= (uint)Vector512<T>.Count) |
||||
|
{ |
||||
|
InvokeVectorized512<T, TOperator>(ref xRef, y, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count) |
||||
|
{ |
||||
|
InvokeVectorized256<T, TOperator>(ref xRef, y, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count) |
||||
|
{ |
||||
|
InvokeVectorized128<T, TOperator>(ref xRef, y, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (nuint i = 0; i < length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Performs element-wise division using the runtime tensor width-selection order.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The division operation to apply.</typeparam>
|
||||
|
/// <param name="x">The input values.</param>
|
||||
|
/// <param name="y">The scalar divisor.</param>
|
||||
|
/// <param name="destination">The destination values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeSpanScalarIntoSpanForDivision<T, TOperator>( |
||||
|
ReadOnlySpan<T> x, |
||||
|
T y, |
||||
|
Span<T> destination) |
||||
|
where TOperator : struct, IBinaryOperator<T> |
||||
|
{ |
||||
|
if (x.Length > destination.Length) |
||||
|
{ |
||||
|
ThrowDestinationTooShort(); |
||||
|
} |
||||
|
|
||||
|
ValidateInputOutputSpanNonOverlapping(x, destination); |
||||
|
|
||||
|
ref T xRef = ref MemoryMarshal.GetReference(x); |
||||
|
ref T destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
nuint length = (uint)x.Length; |
||||
|
|
||||
|
// Runtime main selects the widest supported pipeline once one complete vector is available.
|
||||
|
if (TOperator.Vectorizable |
||||
|
&& Vector512.IsHardwareAccelerated |
||||
|
&& Vector512<T>.IsSupported |
||||
|
&& length >= (uint)Vector512<T>.Count) |
||||
|
{ |
||||
|
InvokeVectorized512<T, TOperator>(ref xRef, y, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count) |
||||
|
{ |
||||
|
InvokeVectorized256<T, TOperator>(ref xRef, y, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Four values fill one 128-bit float vector. Processing exactly one packed prefix before the scalar
|
||||
|
// remainder avoids the overlapping second vector that regresses the common seven-element normalization.
|
||||
|
if (TOperator.Vectorizable |
||||
|
&& Vector128.IsHardwareAccelerated |
||||
|
&& Vector128<T>.IsSupported |
||||
|
&& length >= (uint)Vector128<T>.Count) |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector128<T>.Count; |
||||
|
Vector128<T> yVector = Vector128.Create(y); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef), yVector).StoreUnsafe(ref destinationRef); |
||||
|
|
||||
|
for (nuint i = vectorCount; i < length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y); |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (nuint i = 0; i < length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Performs an element-wise ternary operation between a span and two scalars.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="x">The input values.</param>
|
||||
|
/// <param name="y">The first scalar input.</param>
|
||||
|
/// <param name="z">The second scalar input.</param>
|
||||
|
/// <param name="destination">The destination values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeSpanScalarScalarIntoSpan<T, TOperator>( |
||||
|
ReadOnlySpan<T> x, |
||||
|
T y, |
||||
|
T z, |
||||
|
Span<T> destination) |
||||
|
where TOperator : struct, ITernaryOperator<T> |
||||
|
{ |
||||
|
if (x.Length > destination.Length) |
||||
|
{ |
||||
|
ThrowDestinationTooShort(); |
||||
|
} |
||||
|
|
||||
|
ValidateInputOutputSpanNonOverlapping(x, destination); |
||||
|
|
||||
|
ref T xRef = ref MemoryMarshal.GetReference(x); |
||||
|
ref T destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
nuint length = (uint)x.Length; |
||||
|
|
||||
|
// This dispatch mirrors the runtime pipeline: large inputs use the widest available registers while
|
||||
|
// short inputs fall through to a width that fits, keeping the operator contract identical at every length.
|
||||
|
if (TOperator.Vectorizable && Vector512.IsHardwareAccelerated && Vector512<T>.IsSupported && length >= (uint)Vector512<T>.Count) |
||||
|
{ |
||||
|
InvokeVectorized512<T, TOperator>(ref xRef, y, z, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count) |
||||
|
{ |
||||
|
InvokeVectorized256<T, TOperator>(ref xRef, y, z, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count) |
||||
|
{ |
||||
|
InvokeVectorized128<T, TOperator>(ref xRef, y, z, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (nuint i = 0; i < length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y, z); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a binary operation between two spans with 128-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first element of the first input.</param>
|
||||
|
/// <param name="yRef">The first element of the second input.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeVectorized128<T, TOperator>( |
||||
|
ref T xRef, |
||||
|
ref T yRef, |
||||
|
ref T destinationRef, |
||||
|
nuint length) |
||||
|
where TOperator : struct, IBinaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector128<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
|
||||
|
// When a tail exists, both final inputs are loaded before any stores. This permits either source to also
|
||||
|
// be the destination when the tail starts inside the range written by the preceding full vector.
|
||||
|
Vector128<T> end = default; |
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke( |
||||
|
Vector128.LoadUnsafe(ref xRef, length - vectorCount), |
||||
|
Vector128.LoadUnsafe(ref yRef, length - vectorCount)); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), Vector128.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a binary operation between two spans with 256-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first element of the first input.</param>
|
||||
|
/// <param name="yRef">The first element of the second input.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeVectorized256<T, TOperator>( |
||||
|
ref T xRef, |
||||
|
ref T yRef, |
||||
|
ref T destinationRef, |
||||
|
nuint length) |
||||
|
where TOperator : struct, IBinaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector256<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
Vector256<T> end = default; |
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke( |
||||
|
Vector256.LoadUnsafe(ref xRef, length - vectorCount), |
||||
|
Vector256.LoadUnsafe(ref yRef, length - vectorCount)); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), Vector256.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a binary operation between two spans with 512-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first element of the first input.</param>
|
||||
|
/// <param name="yRef">The first element of the second input.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeVectorized512<T, TOperator>( |
||||
|
ref T xRef, |
||||
|
ref T yRef, |
||||
|
ref T destinationRef, |
||||
|
nuint length) |
||||
|
where TOperator : struct, IBinaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector512<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
Vector512<T> end = default; |
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke( |
||||
|
Vector512.LoadUnsafe(ref xRef, length - vectorCount), |
||||
|
Vector512.LoadUnsafe(ref yRef, length - vectorCount)); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), Vector512.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a binary operation between a span and a scalar with 128-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first input element.</param>
|
||||
|
/// <param name="y">The scalar input.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeVectorized128<T, TOperator>( |
||||
|
ref T xRef, |
||||
|
T y, |
||||
|
ref T destinationRef, |
||||
|
nuint length) |
||||
|
where TOperator : struct, IBinaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector128<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
Vector128<T> yVector = Vector128.Create(y); |
||||
|
|
||||
|
// When a tail exists, preloading its final vector is required for in-place operation because it must
|
||||
|
// observe the original values before an earlier overlapping store writes them.
|
||||
|
Vector128<T> end = default; |
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke( |
||||
|
Vector128.LoadUnsafe(ref xRef, length - vectorCount), |
||||
|
yVector); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a binary operation with 256-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first input element.</param>
|
||||
|
/// <param name="y">The scalar input.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeVectorized256<T, TOperator>( |
||||
|
ref T xRef, |
||||
|
T y, |
||||
|
ref T destinationRef, |
||||
|
nuint length) |
||||
|
where TOperator : struct, IBinaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector256<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
Vector256<T> yVector = Vector256.Create(y); |
||||
|
Vector256<T> end = default; |
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke( |
||||
|
Vector256.LoadUnsafe(ref xRef, length - vectorCount), |
||||
|
yVector); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a binary operation with 512-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first input element.</param>
|
||||
|
/// <param name="y">The scalar input.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeVectorized512<T, TOperator>( |
||||
|
ref T xRef, |
||||
|
T y, |
||||
|
ref T destinationRef, |
||||
|
nuint length) |
||||
|
where TOperator : struct, IBinaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector512<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
Vector512<T> yVector = Vector512.Create(y); |
||||
|
Vector512<T> end = default; |
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke( |
||||
|
Vector512.LoadUnsafe(ref xRef, length - vectorCount), |
||||
|
yVector); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a ternary operation with 128-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first input element.</param>
|
||||
|
/// <param name="y">The first scalar input.</param>
|
||||
|
/// <param name="z">The second scalar input.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeVectorized128<T, TOperator>( |
||||
|
ref T xRef, |
||||
|
T y, |
||||
|
T z, |
||||
|
ref T destinationRef, |
||||
|
nuint length) |
||||
|
where TOperator : struct, ITernaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector128<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
Vector128<T> yVector = Vector128.Create(y); |
||||
|
Vector128<T> zVector = Vector128.Create(z); |
||||
|
Vector128<T> end = default; |
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke( |
||||
|
Vector128.LoadUnsafe(ref xRef, length - vectorCount), |
||||
|
yVector, |
||||
|
zVector); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a ternary operation with 256-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first input element.</param>
|
||||
|
/// <param name="y">The first scalar input.</param>
|
||||
|
/// <param name="z">The second scalar input.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeVectorized256<T, TOperator>( |
||||
|
ref T xRef, |
||||
|
T y, |
||||
|
T z, |
||||
|
ref T destinationRef, |
||||
|
nuint length) |
||||
|
where TOperator : struct, ITernaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector256<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
Vector256<T> yVector = Vector256.Create(y); |
||||
|
Vector256<T> zVector = Vector256.Create(z); |
||||
|
Vector256<T> end = default; |
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke( |
||||
|
Vector256.LoadUnsafe(ref xRef, length - vectorCount), |
||||
|
yVector, |
||||
|
zVector); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a ternary operation with 512-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first input element.</param>
|
||||
|
/// <param name="y">The first scalar input.</param>
|
||||
|
/// <param name="z">The second scalar input.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeVectorized512<T, TOperator>( |
||||
|
ref T xRef, |
||||
|
T y, |
||||
|
T z, |
||||
|
ref T destinationRef, |
||||
|
nuint length) |
||||
|
where TOperator : struct, ITernaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector512<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
Vector512<T> yVector = Vector512.Create(y); |
||||
|
Vector512<T> zVector = Vector512.Create(z); |
||||
|
Vector512<T> end = default; |
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke( |
||||
|
Vector512.LoadUnsafe(ref xRef, length - vectorCount), |
||||
|
yVector, |
||||
|
zVector); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,249 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
internal static partial class TensorPrimitives_ |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Computes the element-wise maximum of the values in <paramref name="x"/> and <paramref name="y"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <param name="x">The values to compare.</param>
|
||||
|
/// <param name="y">The value to compare with each element.</param>
|
||||
|
/// <param name="destination">The destination for the maximum values.</param>
|
||||
|
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
||||
|
/// </exception>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Max<T>(ReadOnlySpan<T> x, T y, Span<T> destination) |
||||
|
where T : INumber<T> |
||||
|
=> InvokeSpanScalarIntoSpan<T, MaxOperator<T>>(x, y, destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects maximum single-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first values.</param>
|
||||
|
/// <param name="y">The second values.</param>
|
||||
|
/// <returns>The maximum values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<float> MaxSingle(Vector128<float> x, Vector128<float> y) |
||||
|
{ |
||||
|
// The .NET 8 operation already handles ordered unequal values. Correct its second-operand result for a
|
||||
|
// first-operand NaN, then use bitwise AND for equal values so positive zero wins regardless of operand order.
|
||||
|
Vector128<float> result = Vector128.Max(x, y); |
||||
|
result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result); |
||||
|
|
||||
|
return Vector128.ConditionalSelect( |
||||
|
Vector128.Equals(x, y), |
||||
|
x & y, |
||||
|
result); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects maximum single-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first values.</param>
|
||||
|
/// <param name="y">The second values.</param>
|
||||
|
/// <returns>The maximum values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<float> MaxSingle(Vector256<float> x, Vector256<float> y) |
||||
|
{ |
||||
|
Vector256<float> result = Vector256.Max(x, y); |
||||
|
result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result); |
||||
|
|
||||
|
return Vector256.ConditionalSelect( |
||||
|
Vector256.Equals(x, y), |
||||
|
x & y, |
||||
|
result); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects maximum single-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first values.</param>
|
||||
|
/// <param name="y">The second values.</param>
|
||||
|
/// <returns>The maximum values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<float> MaxSingle(Vector512<float> x, Vector512<float> y) |
||||
|
{ |
||||
|
Vector512<float> result = Vector512.Max(x, y); |
||||
|
result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result); |
||||
|
|
||||
|
return Vector512.ConditionalSelect( |
||||
|
Vector512.Equals(x, y), |
||||
|
x & y, |
||||
|
result); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects maximum double-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first values.</param>
|
||||
|
/// <param name="y">The second values.</param>
|
||||
|
/// <returns>The maximum values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<double> MaxDouble(Vector128<double> x, Vector128<double> y) |
||||
|
{ |
||||
|
Vector128<double> result = Vector128.Max(x, y); |
||||
|
result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result); |
||||
|
|
||||
|
return Vector128.ConditionalSelect( |
||||
|
Vector128.Equals(x, y), |
||||
|
x & y, |
||||
|
result); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects maximum double-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first values.</param>
|
||||
|
/// <param name="y">The second values.</param>
|
||||
|
/// <returns>The maximum values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<double> MaxDouble(Vector256<double> x, Vector256<double> y) |
||||
|
{ |
||||
|
Vector256<double> result = Vector256.Max(x, y); |
||||
|
result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result); |
||||
|
|
||||
|
return Vector256.ConditionalSelect( |
||||
|
Vector256.Equals(x, y), |
||||
|
x & y, |
||||
|
result); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects maximum double-precision values with the normalized runtime semantics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first values.</param>
|
||||
|
/// <param name="y">The second values.</param>
|
||||
|
/// <returns>The maximum values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<double> MaxDouble(Vector512<double> x, Vector512<double> y) |
||||
|
{ |
||||
|
Vector512<double> result = Vector512.Max(x, y); |
||||
|
result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result); |
||||
|
|
||||
|
return Vector512.ConditionalSelect( |
||||
|
Vector512.Equals(x, y), |
||||
|
x & y, |
||||
|
result); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the maximum corresponding values.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
private readonly struct MaxOperator<T> : IBinaryOperator<T> |
||||
|
where T : INumber<T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether this operation supports vector execution.
|
||||
|
/// </summary>
|
||||
|
public static bool Vectorizable => true; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the maximum scalar value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first value.</param>
|
||||
|
/// <param name="y">The second value.</param>
|
||||
|
/// <returns>The maximum value.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static T Invoke(T x, T y) => T.Max(x, y); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the maximum values from 128-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first values.</param>
|
||||
|
/// <param name="y">The second values.</param>
|
||||
|
/// <returns>The maximum values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y) |
||||
|
{ |
||||
|
if (typeof(T) == typeof(float)) |
||||
|
{ |
||||
|
Vector128<float> result = MaxSingle( |
||||
|
Unsafe.As<Vector128<T>, Vector128<float>>(ref x), |
||||
|
Unsafe.As<Vector128<T>, Vector128<float>>(ref y)); |
||||
|
|
||||
|
return Unsafe.As<Vector128<float>, Vector128<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(double)) |
||||
|
{ |
||||
|
Vector128<double> result = MaxDouble( |
||||
|
Unsafe.As<Vector128<T>, Vector128<double>>(ref x), |
||||
|
Unsafe.As<Vector128<T>, Vector128<double>>(ref y)); |
||||
|
|
||||
|
return Unsafe.As<Vector128<double>, Vector128<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
return Vector128.Max(x, y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the maximum values from 256-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first values.</param>
|
||||
|
/// <param name="y">The second values.</param>
|
||||
|
/// <returns>The maximum values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y) |
||||
|
{ |
||||
|
if (typeof(T) == typeof(float)) |
||||
|
{ |
||||
|
Vector256<float> result = MaxSingle( |
||||
|
Unsafe.As<Vector256<T>, Vector256<float>>(ref x), |
||||
|
Unsafe.As<Vector256<T>, Vector256<float>>(ref y)); |
||||
|
|
||||
|
return Unsafe.As<Vector256<float>, Vector256<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(double)) |
||||
|
{ |
||||
|
Vector256<double> result = MaxDouble( |
||||
|
Unsafe.As<Vector256<T>, Vector256<double>>(ref x), |
||||
|
Unsafe.As<Vector256<T>, Vector256<double>>(ref y)); |
||||
|
|
||||
|
return Unsafe.As<Vector256<double>, Vector256<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
return Vector256.Max(x, y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the maximum values from 512-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The first values.</param>
|
||||
|
/// <param name="y">The second values.</param>
|
||||
|
/// <returns>The maximum values.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y) |
||||
|
{ |
||||
|
if (typeof(T) == typeof(float)) |
||||
|
{ |
||||
|
Vector512<float> result = MaxSingle( |
||||
|
Unsafe.As<Vector512<T>, Vector512<float>>(ref x), |
||||
|
Unsafe.As<Vector512<T>, Vector512<float>>(ref y)); |
||||
|
|
||||
|
return Unsafe.As<Vector512<float>, Vector512<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(double)) |
||||
|
{ |
||||
|
Vector512<double> result = MaxDouble( |
||||
|
Unsafe.As<Vector512<T>, Vector512<double>>(ref x), |
||||
|
Unsafe.As<Vector512<T>, Vector512<double>>(ref y)); |
||||
|
|
||||
|
return Unsafe.As<Vector512<double>, Vector512<T>>(ref result); |
||||
|
} |
||||
|
|
||||
|
return Vector512.Max(x, y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
internal static partial class TensorPrimitives_ |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Computes the element-wise product of the values in <paramref name="x"/> and <paramref name="y"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <param name="x">The multiplicands.</param>
|
||||
|
/// <param name="y">The multiplier.</param>
|
||||
|
/// <param name="destination">The destination for the products.</param>
|
||||
|
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
||||
|
/// </exception>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Multiply<T>(ReadOnlySpan<T> x, T y, Span<T> destination) |
||||
|
where T : IMultiplyOperators<T, T, T>, IMultiplicativeIdentity<T, T> |
||||
|
=> InvokeSpanScalarIntoSpan<T, MultiplyOperator<T>>(x, y, destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Multiplies corresponding values.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
private readonly struct MultiplyOperator<T> : IBinaryOperator<T> |
||||
|
where T : IMultiplyOperators<T, T, T>, IMultiplicativeIdentity<T, T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether this operation supports vector execution.
|
||||
|
/// </summary>
|
||||
|
public static bool Vectorizable => true; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Multiplies scalar values.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The multiplicand.</param>
|
||||
|
/// <param name="y">The multiplier.</param>
|
||||
|
/// <returns>The product.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static T Invoke(T x, T y) => x * y; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Multiplies 128-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The multiplicands.</param>
|
||||
|
/// <param name="y">The multipliers.</param>
|
||||
|
/// <returns>The products.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y) => x * y; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Multiplies 256-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The multiplicands.</param>
|
||||
|
/// <param name="y">The multipliers.</param>
|
||||
|
/// <returns>The products.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y) => x * y; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Multiplies 512-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The multiplicands.</param>
|
||||
|
/// <param name="y">The multipliers.</param>
|
||||
|
/// <returns>The products.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y) => x * y; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,332 @@ |
|||||
|
// 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; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
internal static partial class TensorPrimitives_ |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines an element-wise unary operation.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
private interface IUnaryOperator<T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the operation supports vector execution.
|
||||
|
/// </summary>
|
||||
|
public static abstract bool Vectorizable { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to a scalar value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The input value.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract T Invoke(T x); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to a 128-bit vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The input vector.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract Vector128<T> Invoke(Vector128<T> x); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to a 256-bit vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The input vector.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract Vector256<T> Invoke(Vector256<T> x); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the operation to a 512-bit vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The input vector.</param>
|
||||
|
/// <returns>The operation result.</returns>
|
||||
|
public static abstract Vector512<T> Invoke(Vector512<T> x); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Computes the element-wise negation of the values in <paramref name="x"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <param name="x">The values to negate.</param>
|
||||
|
/// <param name="destination">The destination for the negated values.</param>
|
||||
|
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
||||
|
/// </exception>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Negate<T>(ReadOnlySpan<T> x, Span<T> destination) |
||||
|
where T : IUnaryNegationOperators<T, T> |
||||
|
=> InvokeSpanIntoSpan<T, NegateOperator<T>>(x, destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Performs an element-wise unary operation over a span.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="x">The input values.</param>
|
||||
|
/// <param name="destination">The destination values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeSpanIntoSpan<T, TOperator>(ReadOnlySpan<T> x, Span<T> destination) |
||||
|
where TOperator : struct, IUnaryOperator<T> |
||||
|
{ |
||||
|
if (x.Length > destination.Length) |
||||
|
{ |
||||
|
ThrowDestinationTooShort(); |
||||
|
} |
||||
|
|
||||
|
ValidateInputOutputSpanNonOverlapping(x, destination); |
||||
|
|
||||
|
ref T xRef = ref MemoryMarshal.GetReference(x); |
||||
|
ref T destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
nuint length = (uint)x.Length; |
||||
|
|
||||
|
// Runtime main selects the widest supported pipeline once one complete vector is available.
|
||||
|
if (TOperator.Vectorizable |
||||
|
&& Vector512.IsHardwareAccelerated |
||||
|
&& Vector512<T>.IsSupported |
||||
|
&& length >= (uint)Vector512<T>.Count) |
||||
|
{ |
||||
|
InvokeUnaryVectorized512<T, TOperator>(ref xRef, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count) |
||||
|
{ |
||||
|
InvokeUnaryVectorized256<T, TOperator>(ref xRef, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count) |
||||
|
{ |
||||
|
InvokeUnaryVectorized128<T, TOperator>(ref xRef, ref destinationRef, length); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (nuint i = 0; i < length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a unary operation with 128-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first input element.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeUnaryVectorized128<T, TOperator>(ref T xRef, ref T destinationRef, nuint length) |
||||
|
where TOperator : struct, IUnaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector128<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
|
||||
|
// The final vector overlaps the preceding store when the length is not a vector multiple. Loading it
|
||||
|
// before any stores preserves same-start in-place operation because it captures the original tail.
|
||||
|
Vector128<T> end = default; |
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, length - vectorCount)); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a unary operation with 256-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first input element.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeUnaryVectorized256<T, TOperator>(ref T xRef, ref T destinationRef, nuint length) |
||||
|
where TOperator : struct, IUnaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector256<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
Vector256<T> end = default; |
||||
|
|
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, length - vectorCount)); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a unary operation with 512-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
||||
|
/// <param name="xRef">The first input element.</param>
|
||||
|
/// <param name="destinationRef">The first destination element.</param>
|
||||
|
/// <param name="length">The number of elements to process.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void InvokeUnaryVectorized512<T, TOperator>(ref T xRef, ref T destinationRef, nuint length) |
||||
|
where TOperator : struct, IUnaryOperator<T> |
||||
|
{ |
||||
|
nuint vectorCount = (uint)Vector512<T>.Count; |
||||
|
nuint vectorsPerLoop = vectorCount * 8; |
||||
|
nuint index = 0; |
||||
|
Vector512<T> end = default; |
||||
|
|
||||
|
if ((length % vectorCount) != 0) |
||||
|
{ |
||||
|
end = TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, length - vectorCount)); |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorsPerLoop) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
||||
|
|
||||
|
index += vectorsPerLoop; |
||||
|
} |
||||
|
|
||||
|
while ((length - index) >= vectorCount) |
||||
|
{ |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); |
||||
|
index += vectorCount; |
||||
|
} |
||||
|
|
||||
|
if (index != length) |
||||
|
{ |
||||
|
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Implements element-wise negation for scalar and SIMD inputs.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
private readonly struct NegateOperator<T> : IUnaryOperator<T> |
||||
|
where T : IUnaryNegationOperators<T, T> |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public static bool Vectorizable => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static T Invoke(T x) => -x; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static Vector128<T> Invoke(Vector128<T> x) |
||||
|
{ |
||||
|
if (typeof(T) == typeof(float)) |
||||
|
{ |
||||
|
// IEEE-754 negation toggles the sign bit. Expressing that operation explicitly avoids the
|
||||
|
// subtraction-based ARM64 code generated by .NET 8 for generic vector negation, which loses
|
||||
|
// the sign when +0F is negated and therefore differs from both scalar and runtime-main behavior.
|
||||
|
return x ^ Vector128.Create(-0F).As<float, T>(); |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(double)) |
||||
|
{ |
||||
|
// Double-precision values use the same sign-bit representation, with the sign in bit 63.
|
||||
|
return x ^ Vector128.Create(-0D).As<double, T>(); |
||||
|
} |
||||
|
|
||||
|
return -x; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static Vector256<T> Invoke(Vector256<T> x) |
||||
|
{ |
||||
|
if (typeof(T) == typeof(float)) |
||||
|
{ |
||||
|
// Keep the operation bitwise at every width so ARM64 preserves signed zero exactly.
|
||||
|
return x ^ Vector256.Create(-0F).As<float, T>(); |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(double)) |
||||
|
{ |
||||
|
return x ^ Vector256.Create(-0D).As<double, T>(); |
||||
|
} |
||||
|
|
||||
|
return -x; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static Vector512<T> Invoke(Vector512<T> x) |
||||
|
{ |
||||
|
if (typeof(T) == typeof(float)) |
||||
|
{ |
||||
|
// Vector512 can be hardware accelerated directly or decomposed by the runtime; the explicit
|
||||
|
// bit operation provides identical IEEE-754 behavior in either case.
|
||||
|
return x ^ Vector512.Create(-0F).As<float, T>(); |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(double)) |
||||
|
{ |
||||
|
return x ^ Vector512.Create(-0D).As<double, T>(); |
||||
|
} |
||||
|
|
||||
|
return -x; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,168 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements inverted JPEG CMYK conversion for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct CmykOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.Cmyk; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 4; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) |
||||
|
{ |
||||
|
// Adobe-style CMYK stores inverted component samples. Multiplying K by scale twice folds the
|
||||
|
// two sample-domain divisions into one factor before it modulates the C, M, and Y planes.
|
||||
|
float scaledK = c3 * scale * scale; |
||||
|
c0 *= scaledK; |
||||
|
c1 *= scaledK; |
||||
|
c2 *= scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector128<float> c0, ref Vector128<float> c1, ref Vector128<float> c2, Vector128<float> c3, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale) |
||||
|
{ |
||||
|
// Each K lane supplies the common modulation factor for the corresponding C, M, and Y lanes.
|
||||
|
Vector128<float> scaledK = c3 * scale * scale; |
||||
|
c0 *= scaledK; |
||||
|
c1 *= scaledK; |
||||
|
c2 *= scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector256<float> c0, ref Vector256<float> c1, ref Vector256<float> c2, Vector256<float> c3, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale) |
||||
|
{ |
||||
|
// Eight independent CMYK samples remain lane-aligned throughout the modulation.
|
||||
|
Vector256<float> scaledK = c3 * scale * scale; |
||||
|
c0 *= scaledK; |
||||
|
c1 *= scaledK; |
||||
|
c2 *= scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector512<float> c0, ref Vector512<float> c1, ref Vector512<float> c2, Vector512<float> c3, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale) |
||||
|
{ |
||||
|
// Sixteen independent CMYK samples remain lane-aligned throughout the modulation.
|
||||
|
Vector512<float> scaledK = c3 * scale * scale; |
||||
|
c0 *= scaledK; |
||||
|
c1 *= scaledK; |
||||
|
c2 *= scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) |
||||
|
{ |
||||
|
float c = maximumValue - r; |
||||
|
float m = maximumValue - g; |
||||
|
float y = maximumValue - b; |
||||
|
float k = MathF.Min(c, MathF.Min(m, y)); |
||||
|
|
||||
|
// Pure black makes the chromatic divisor zero. In that case chromatic ink is defined as zero;
|
||||
|
// otherwise remove K and normalize the remaining C, M, and Y contributions.
|
||||
|
if (k >= maximumValue) |
||||
|
{ |
||||
|
c = 0; |
||||
|
m = 0; |
||||
|
y = 0; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// The same remaining range normalizes every chromatic channel. Computing its reciprocal once
|
||||
|
// replaces three divisions with one division and three multiplies.
|
||||
|
float reciprocal = 1F / (maximumValue - k); |
||||
|
c = (c - k) * reciprocal; |
||||
|
m = (m - k) * reciprocal; |
||||
|
y = (y - k) * reciprocal; |
||||
|
} |
||||
|
|
||||
|
// JPEG CMYK is inverted, including K, so normalized chromatic values are reflected around max.
|
||||
|
c0 = maximumValue - (c * maximumValue); |
||||
|
c1 = maximumValue - (m * maximumValue); |
||||
|
c2 = maximumValue - (y * maximumValue); |
||||
|
c3 = maximumValue - k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3) |
||||
|
{ |
||||
|
Vector128<float> c = maximumValue - r; |
||||
|
Vector128<float> m = maximumValue - g; |
||||
|
Vector128<float> y = maximumValue - b; |
||||
|
Vector128<float> k = Vector128.Min(c, Vector128.Min(m, y)); |
||||
|
|
||||
|
// The all-bits mask clears the undefined zero-divisor result for pure-black lanes without a branch.
|
||||
|
Vector128<float> nonBlack = ~Vector128.Equals(k, maximumValue); |
||||
|
Vector128<float> reciprocal = Vector128<float>.One / (maximumValue - k); |
||||
|
c = ((c - k) * reciprocal) & nonBlack; |
||||
|
m = ((m - k) * reciprocal) & nonBlack; |
||||
|
y = ((y - k) * reciprocal) & nonBlack; |
||||
|
|
||||
|
c0 = maximumValue - (c * maximumValue); |
||||
|
c1 = maximumValue - (m * maximumValue); |
||||
|
c2 = maximumValue - (y * maximumValue); |
||||
|
c3 = maximumValue - k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3) |
||||
|
{ |
||||
|
Vector256<float> c = maximumValue - r; |
||||
|
Vector256<float> m = maximumValue - g; |
||||
|
Vector256<float> y = maximumValue - b; |
||||
|
Vector256<float> k = Vector256.Min(c, Vector256.Min(m, y)); |
||||
|
|
||||
|
// Masking preserves lane independence when a vector mixes pure black with chromatic pixels.
|
||||
|
Vector256<float> nonBlack = ~Vector256.Equals(k, maximumValue); |
||||
|
Vector256<float> reciprocal = Vector256<float>.One / (maximumValue - k); |
||||
|
c = ((c - k) * reciprocal) & nonBlack; |
||||
|
m = ((m - k) * reciprocal) & nonBlack; |
||||
|
y = ((y - k) * reciprocal) & nonBlack; |
||||
|
|
||||
|
c0 = maximumValue - (c * maximumValue); |
||||
|
c1 = maximumValue - (m * maximumValue); |
||||
|
c2 = maximumValue - (y * maximumValue); |
||||
|
c3 = maximumValue - k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3) |
||||
|
{ |
||||
|
Vector512<float> c = maximumValue - r; |
||||
|
Vector512<float> m = maximumValue - g; |
||||
|
Vector512<float> y = maximumValue - b; |
||||
|
Vector512<float> k = Vector512.Min(c, Vector512.Min(m, y)); |
||||
|
|
||||
|
// AVX-512 still uses a full floating-point mask value here because bitwise clearing exactly matches
|
||||
|
// the narrower operator semantics and lets the JIT select the most suitable native instructions.
|
||||
|
Vector512<float> nonBlack = ~Vector512.Equals(k, maximumValue); |
||||
|
Vector512<float> reciprocal = Vector512<float>.One / (maximumValue - k); |
||||
|
c = ((c - k) * reciprocal) & nonBlack; |
||||
|
m = ((m - k) * reciprocal) & nonBlack; |
||||
|
y = ((y - k) * reciprocal) & nonBlack; |
||||
|
|
||||
|
c0 = maximumValue - (c * maximumValue); |
||||
|
c1 = maximumValue - (m * maximumValue); |
||||
|
c2 = maximumValue - (y * maximumValue); |
||||
|
c3 = maximumValue - k; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,116 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Buffers; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.ColorProfiles; |
|
||||
using SixLabors.ImageSharp.ColorProfiles.Icc; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class CmykScalar : JpegColorConverterScalar |
|
||||
{ |
|
||||
public CmykScalar(int precision) |
|
||||
: base(JpegColorSpace.Cmyk, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) => |
|
||||
ConvertToRgbInPlace(values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
Span<float> c3 = values.Component3; |
|
||||
|
|
||||
float scale = 1 / (maxValue * maxValue); |
|
||||
for (int i = 0; i < c0.Length; i++) |
|
||||
{ |
|
||||
float c = c0[i]; |
|
||||
float m = c1[i]; |
|
||||
float y = c2[i]; |
|
||||
float k = c3[i]; |
|
||||
|
|
||||
k *= scale; |
|
||||
c0[i] = c * k; |
|
||||
c1[i] = m * k; |
|
||||
c2[i] = y * k; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
Span<float> c = values.Component0; |
|
||||
Span<float> m = values.Component1; |
|
||||
Span<float> y = values.Component2; |
|
||||
Span<float> k = values.Component3; |
|
||||
|
|
||||
for (int i = 0; i < c.Length; i++) |
|
||||
{ |
|
||||
float ctmp = 255f - rLane[i]; |
|
||||
float mtmp = 255f - gLane[i]; |
|
||||
float ytmp = 255f - bLane[i]; |
|
||||
float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); |
|
||||
|
|
||||
if (ktmp >= 255f) |
|
||||
{ |
|
||||
ctmp = 0f; |
|
||||
mtmp = 0f; |
|
||||
ytmp = 0f; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
ctmp = (ctmp - ktmp) / (255f - ktmp); |
|
||||
mtmp = (mtmp - ktmp) / (255f - ktmp); |
|
||||
ytmp = (ytmp - ktmp) / (255f - ktmp); |
|
||||
} |
|
||||
|
|
||||
c[i] = maxValue - (ctmp * maxValue); |
|
||||
m[i] = maxValue - (mtmp * maxValue); |
|
||||
y[i] = maxValue - (ytmp * maxValue); |
|
||||
k[i] = maxValue - ktmp; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4); |
|
||||
Span<float> packed = memoryOwner.Memory.Span; |
|
||||
|
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
Span<float> c3 = values.Component3; |
|
||||
|
|
||||
PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); |
|
||||
|
|
||||
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed); |
|
||||
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length]; |
|
||||
|
|
||||
ColorConversionOptions options = new() |
|
||||
{ |
|
||||
SourceIccProfile = profile, |
|
||||
TargetIccProfile = CompactSrgbV4Profile.Profile, |
|
||||
}; |
|
||||
ColorProfileConverter converter = new(options); |
|
||||
converter.Convert<Cmyk, Rgb>(source, destination); |
|
||||
|
|
||||
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,100 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class CmykVector128 : JpegColorConverterVector128 |
|
||||
{ |
|
||||
public CmykVector128(int precision) |
|
||||
: base(JpegColorSpace.Cmyk, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector128<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector128<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector128<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector128<float> c3Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector128<float> scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue)); |
|
||||
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector128<float> c = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector128<float> m = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector128<float> y = ref Unsafe.Add(ref c2Base, i); |
|
||||
Vector128<float> k = Unsafe.Add(ref c3Base, i); |
|
||||
|
|
||||
k *= scale; |
|
||||
c *= k; |
|
||||
m *= k; |
|
||||
y *= k; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector128<float> destC = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector128<float> destM = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector128<float> destY = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector128<float> destK = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
ref Vector128<float> srcR = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector128<float> srcG = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector128<float> srcB = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
Vector128<float> scale = Vector128.Create(maxValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector128<float> ctmp = scale - Unsafe.Add(ref srcR, i); |
|
||||
Vector128<float> mtmp = scale - Unsafe.Add(ref srcG, i); |
|
||||
Vector128<float> ytmp = scale - Unsafe.Add(ref srcB, i); |
|
||||
Vector128<float> ktmp = Vector128.Min(ctmp, Vector128.Min(mtmp, ytmp)); |
|
||||
|
|
||||
Vector128<float> kMask = ~Vector128.Equals(ktmp, scale); |
|
||||
Vector128<float> divisor = scale - ktmp; |
|
||||
|
|
||||
ctmp = ((ctmp - ktmp) / divisor) & kMask; |
|
||||
mtmp = ((mtmp - ktmp) / divisor) & kMask; |
|
||||
ytmp = ((ytmp - ktmp) / divisor) & kMask; |
|
||||
|
|
||||
Unsafe.Add(ref destC, i) = scale - (ctmp * scale); |
|
||||
Unsafe.Add(ref destM, i) = scale - (mtmp * scale); |
|
||||
Unsafe.Add(ref destY, i) = scale - (ytmp * scale); |
|
||||
Unsafe.Add(ref destK, i) = scale - ktmp; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,100 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class CmykVector256 : JpegColorConverterVector256 |
|
||||
{ |
|
||||
public CmykVector256(int precision) |
|
||||
: base(JpegColorSpace.Cmyk, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector256<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector256<float> c3Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector256<float> scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); |
|
||||
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i); |
|
||||
Vector256<float> k = Unsafe.Add(ref c3Base, i); |
|
||||
|
|
||||
k *= scale; |
|
||||
c *= k; |
|
||||
m *= k; |
|
||||
y *= k; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector256<float> destC = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> destM = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> destY = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector256<float> destK = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
ref Vector256<float> srcR = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector256<float> srcG = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector256<float> srcB = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
Vector256<float> scale = Vector256.Create(maxValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector256<float> ctmp = scale - Unsafe.Add(ref srcR, i); |
|
||||
Vector256<float> mtmp = scale - Unsafe.Add(ref srcG, i); |
|
||||
Vector256<float> ytmp = scale - Unsafe.Add(ref srcB, i); |
|
||||
Vector256<float> ktmp = Vector256.Min(ctmp, Vector256.Min(mtmp, ytmp)); |
|
||||
|
|
||||
Vector256<float> kMask = ~Vector256.Equals(ktmp, scale); |
|
||||
Vector256<float> divisor = scale - ktmp; |
|
||||
|
|
||||
ctmp = ((ctmp - ktmp) / divisor) & kMask; |
|
||||
mtmp = ((mtmp - ktmp) / divisor) & kMask; |
|
||||
ytmp = ((ytmp - ktmp) / divisor) & kMask; |
|
||||
|
|
||||
Unsafe.Add(ref destC, i) = scale - (ctmp * scale); |
|
||||
Unsafe.Add(ref destM, i) = scale - (mtmp * scale); |
|
||||
Unsafe.Add(ref destY, i) = scale - (ytmp * scale); |
|
||||
Unsafe.Add(ref destK, i) = scale - ktmp; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,108 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class CmykVector512 : JpegColorConverterVector512 |
|
||||
{ |
|
||||
public CmykVector512(int precision) |
|
||||
: base(JpegColorSpace.Cmyk, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector512<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector512<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector512<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector512<float> c3Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector512<float> scale = Vector512.Create(1 / (this.MaximumValue * this.MaximumValue)); |
|
||||
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector512<float> c = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector512<float> m = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector512<float> y = ref Unsafe.Add(ref c2Base, i); |
|
||||
Vector512<float> k = Unsafe.Add(ref c3Base, i); |
|
||||
|
|
||||
k *= scale; |
|
||||
c *= k; |
|
||||
m *= k; |
|
||||
y *= k; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) |
|
||||
=> CmykScalar.ConvertToRgbInPlace(values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> CmykScalar.ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector512<float> destC = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector512<float> destM = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector512<float> destY = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector512<float> destK = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
ref Vector512<float> srcR = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector512<float> srcG = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector512<float> srcB = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
Vector512<float> scale = Vector512.Create(maxValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector512<float> ctmp = scale - Unsafe.Add(ref srcR, i); |
|
||||
Vector512<float> mtmp = scale - Unsafe.Add(ref srcG, i); |
|
||||
Vector512<float> ytmp = scale - Unsafe.Add(ref srcB, i); |
|
||||
Vector512<float> ktmp = Vector512.Min(ctmp, Vector512.Min(mtmp, ytmp)); |
|
||||
|
|
||||
Vector512<float> kMask = ~Vector512.Equals(ktmp, scale); |
|
||||
Vector512<float> divisor = scale - ktmp; |
|
||||
|
|
||||
ctmp = ((ctmp - ktmp) / divisor) & kMask; |
|
||||
mtmp = ((mtmp - ktmp) / divisor) & kMask; |
|
||||
ytmp = ((ytmp - ktmp) / divisor) & kMask; |
|
||||
|
|
||||
Unsafe.Add(ref destC, i) = scale - (ctmp * scale); |
|
||||
Unsafe.Add(ref destM, i) = scale - (mtmp * scale); |
|
||||
Unsafe.Add(ref destY, i) = scale - (ytmp * scale); |
|
||||
Unsafe.Add(ref destK, i) = scale - ktmp; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,117 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements grayscale expansion and RGB luminance reduction for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct GrayScaleOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.Grayscale; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 1; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) |
||||
|
{ |
||||
|
// JPEG stores luminance in the integer sample domain. Normalize it once, then duplicate the
|
||||
|
// same value into all three RGB planes. Keeping it local also prevents potentially aliasing
|
||||
|
// byref stores from forcing the JIT to reload c0 between assignments.
|
||||
|
float luminance = c0 * scale; |
||||
|
c0 = luminance; |
||||
|
c1 = luminance; |
||||
|
c2 = luminance; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector128<float> c0, ref Vector128<float> c1, ref Vector128<float> c2, Vector128<float> c3, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale) |
||||
|
{ |
||||
|
// Each XMM lane is one independent luminance sample. Reusing the normalized vector for R, G,
|
||||
|
// and B avoids recomputing the scale and keeps it live across potentially aliasing byref stores.
|
||||
|
Vector128<float> luminance = c0 * scale; |
||||
|
c0 = luminance; |
||||
|
c1 = luminance; |
||||
|
c2 = luminance; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector256<float> c0, ref Vector256<float> c1, ref Vector256<float> c2, Vector256<float> c3, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale) |
||||
|
{ |
||||
|
// Eight luminance samples occupy the YMM lanes. The local retains the normalized vector across
|
||||
|
// all three output stores even when the destination planes alias.
|
||||
|
Vector256<float> luminance = c0 * scale; |
||||
|
c0 = luminance; |
||||
|
c1 = luminance; |
||||
|
c2 = luminance; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector512<float> c0, ref Vector512<float> c1, ref Vector512<float> c2, Vector512<float> c3, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale) |
||||
|
{ |
||||
|
// Sixteen luminance samples occupy the ZMM lanes. The local retains the normalized vector across
|
||||
|
// all three output stores without shuffles, interleaving, or source reloads.
|
||||
|
Vector512<float> luminance = c0 * scale; |
||||
|
c0 = luminance; |
||||
|
c1 = luminance; |
||||
|
c2 = luminance; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) |
||||
|
{ |
||||
|
// Rec.601 luma weights operate directly in the encoder sample domain. Only c0 is stored for a
|
||||
|
// one-component model; the remaining out values exist solely to satisfy the common operator shape.
|
||||
|
c0 = (0.299F * r) + (0.587F * g) + (0.114F * b); |
||||
|
c1 = 0; |
||||
|
c2 = 0; |
||||
|
c3 = 0; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3) |
||||
|
{ |
||||
|
// The nested estimate gives each pixel the same multiply-add grouping as the scalar Rec.601 formula.
|
||||
|
c0 = Vector128_.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)); |
||||
|
c1 = default; |
||||
|
c2 = default; |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3) |
||||
|
{ |
||||
|
// YMM lanes evaluate the same Rec.601 equation independently, with no horizontal lane reduction.
|
||||
|
c0 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)); |
||||
|
c1 = default; |
||||
|
c2 = default; |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3) |
||||
|
{ |
||||
|
// ZMM lanes retain the same arithmetic order as narrower paths so only SIMD width changes.
|
||||
|
c0 = Vector512_.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)); |
||||
|
c1 = default; |
||||
|
c2 = default; |
||||
|
c3 = default; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,97 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Buffers; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.ColorProfiles; |
|
||||
using SixLabors.ImageSharp.ColorProfiles.Icc; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class GrayScaleScalar : JpegColorConverterScalar |
|
||||
{ |
|
||||
public GrayScaleScalar(int precision) |
|
||||
: base(JpegColorSpace.Grayscale, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
=> ConvertToRgbInPlace(in values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgbScalar(values, rLane, gLane, bLane); |
|
||||
|
|
||||
internal static void ConvertToRgbInPlace(in ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); |
|
||||
ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); |
|
||||
ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); |
|
||||
|
|
||||
float scale = 1F / maxValue; |
|
||||
for (nuint i = 0; i < (nuint)values.Component0.Length; i++) |
|
||||
{ |
|
||||
float c = Unsafe.Add(ref c0Base, i) * scale; |
|
||||
|
|
||||
Unsafe.Add(ref c0Base, i) = c; |
|
||||
Unsafe.Add(ref c1Base, i) = c; |
|
||||
Unsafe.Add(ref c2Base, i) = c; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3); |
|
||||
Span<float> packed = memoryOwner.Memory.Span; |
|
||||
|
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
|
|
||||
ref float c0Base = ref MemoryMarshal.GetReference(c0); |
|
||||
ref float c1Base = ref MemoryMarshal.GetReference(c1); |
|
||||
ref float c2Base = ref MemoryMarshal.GetReference(c2); |
|
||||
|
|
||||
float scale = 1F / maxValue; |
|
||||
for (nuint i = 0; i < (nuint)values.Component0.Length; i++) |
|
||||
{ |
|
||||
ref float c = ref Unsafe.Add(ref c0Base, i); |
|
||||
c *= scale; |
|
||||
} |
|
||||
|
|
||||
Span<Y> source = MemoryMarshal.Cast<float, Y>(values.Component0); |
|
||||
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed); |
|
||||
|
|
||||
ColorConversionOptions options = new() |
|
||||
{ |
|
||||
SourceIccProfile = profile, |
|
||||
TargetIccProfile = CompactSrgbV4Profile.Profile, |
|
||||
}; |
|
||||
ColorProfileConverter converter = new(options); |
|
||||
converter.Convert<Y, Rgb>(source, destination); |
|
||||
|
|
||||
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2); |
|
||||
} |
|
||||
|
|
||||
internal static void ConvertFromRgbScalar(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
Span<float> c0 = values.Component0; |
|
||||
|
|
||||
for (int i = 0; i < c0.Length; i++) |
|
||||
{ |
|
||||
// luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
c0[i] = (float)((0.299f * rLane[i]) + (0.587f * gLane[i]) + (0.114f * bLane[i])); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,81 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class GrayScaleVector128 : JpegColorConverterVector128 |
|
||||
{ |
|
||||
public GrayScaleVector128(int precision) |
|
||||
: base(JpegColorSpace.Grayscale, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector128<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
|
|
||||
ref Vector128<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
|
|
||||
ref Vector128<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector128<float> scale = Vector128.Create(1 / this.MaximumValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector128<float> c = Unsafe.Add(ref c0Base, i) * scale; |
|
||||
|
|
||||
Unsafe.Add(ref c0Base, i) = c; |
|
||||
Unsafe.Add(ref c1Base, i) = c; |
|
||||
Unsafe.Add(ref c2Base, i) = c; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector128<float> destLuminance = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
|
|
||||
ref Vector128<float> srcRed = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector128<float> srcGreen = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector128<float> srcBlue = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector128<float> f0299 = Vector128.Create(0.299f); |
|
||||
Vector128<float> f0587 = Vector128.Create(0.587f); |
|
||||
Vector128<float> f0114 = Vector128.Create(0.114f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector128<float> r = ref Unsafe.Add(ref srcRed, i); |
|
||||
ref Vector128<float> g = ref Unsafe.Add(ref srcGreen, i); |
|
||||
ref Vector128<float> b = ref Unsafe.Add(ref srcBlue, i); |
|
||||
|
|
||||
// luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
Unsafe.Add(ref destLuminance, i) = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,81 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class GrayScaleVector256 : JpegColorConverterVector256 |
|
||||
{ |
|
||||
public GrayScaleVector256(int precision) |
|
||||
: base(JpegColorSpace.Grayscale, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector256<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
|
|
||||
ref Vector256<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
|
|
||||
ref Vector256<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector256<float> scale = Vector256.Create(1 / this.MaximumValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector256<float> c = Unsafe.Add(ref c0Base, i) * scale; |
|
||||
|
|
||||
Unsafe.Add(ref c0Base, i) = c; |
|
||||
Unsafe.Add(ref c1Base, i) = c; |
|
||||
Unsafe.Add(ref c2Base, i) = c; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector256<float> destLuminance = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
|
|
||||
ref Vector256<float> srcRed = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector256<float> srcGreen = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector256<float> srcBlue = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector256<float> f0299 = Vector256.Create(0.299f); |
|
||||
Vector256<float> f0587 = Vector256.Create(0.587f); |
|
||||
Vector256<float> f0114 = Vector256.Create(0.114f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector256<float> r = ref Unsafe.Add(ref srcRed, i); |
|
||||
ref Vector256<float> g = ref Unsafe.Add(ref srcGreen, i); |
|
||||
ref Vector256<float> b = ref Unsafe.Add(ref srcBlue, i); |
|
||||
|
|
||||
// luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
Unsafe.Add(ref destLuminance, i) = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,89 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class GrayScaleVector512 : JpegColorConverterVector512 |
|
||||
{ |
|
||||
public GrayScaleVector512(int precision) |
|
||||
: base(JpegColorSpace.Grayscale, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector512<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
|
|
||||
ref Vector512<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
|
|
||||
ref Vector512<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector512<float> scale = Vector512.Create(1 / this.MaximumValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector512<float> c = Unsafe.Add(ref c0Base, i) * scale; |
|
||||
|
|
||||
Unsafe.Add(ref c0Base, i) = c; |
|
||||
Unsafe.Add(ref c1Base, i) = c; |
|
||||
Unsafe.Add(ref c2Base, i) = c; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector512<float> destLuminance = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
|
|
||||
ref Vector512<float> srcRed = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector512<float> srcGreen = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector512<float> srcBlue = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector512<float> f0299 = Vector512.Create(0.299f); |
|
||||
Vector512<float> f0587 = Vector512.Create(0.587f); |
|
||||
Vector512<float> f0114 = Vector512.Create(0.114f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector512<float> r = ref Unsafe.Add(ref srcRed, i); |
|
||||
ref Vector512<float> g = ref Unsafe.Add(ref srcGreen, i); |
|
||||
ref Vector512<float> b = ref Unsafe.Add(ref srcBlue, i); |
|
||||
|
|
||||
// luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
Unsafe.Add(ref destLuminance, i) = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) |
|
||||
=> GrayScaleScalar.ConvertToRgbInPlace(in values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> GrayScaleScalar.ConvertFromRgbScalar(values, rLane, gLane, bLane); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,449 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the color-model-specific arithmetic used by <see cref="JpegColorConverter{TOperator}"/>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Each overload describes the same lane-wise transform. The generic traversal selects the widest
|
||||
|
/// available overload and the JIT resolves these static interface calls for each closed converter type.
|
||||
|
/// </remarks>
|
||||
|
internal interface IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the JPEG color space handled by the operator.
|
||||
|
/// </summary>
|
||||
|
public static abstract JpegColorSpace ColorSpace { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of component planes used by the color space.
|
||||
|
/// </summary>
|
||||
|
public static abstract int ComponentCount { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts one JPEG sample to normalized RGB.
|
||||
|
/// </summary>
|
||||
|
/// <param name="c0">The first component, replaced by red.</param>
|
||||
|
/// <param name="c1">The second component, replaced by green.</param>
|
||||
|
/// <param name="c2">The third component, replaced by blue.</param>
|
||||
|
/// <param name="c3">The fourth component, or zero for a three-component color space.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/>.</param>
|
||||
|
public static abstract void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts four JPEG samples to normalized RGB.
|
||||
|
/// </summary>
|
||||
|
/// <param name="c0">The first component lanes, replaced by red.</param>
|
||||
|
/// <param name="c1">The second component lanes, replaced by green.</param>
|
||||
|
/// <param name="c2">The third component lanes, replaced by blue.</param>
|
||||
|
/// <param name="c3">The fourth component lanes, or zero for a three-component color space.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
public static abstract void ConvertToRgb(ref Vector128<float> c0, ref Vector128<float> c1, ref Vector128<float> c2, Vector128<float> c3, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts eight JPEG samples to normalized RGB.
|
||||
|
/// </summary>
|
||||
|
/// <param name="c0">The first component lanes, replaced by red.</param>
|
||||
|
/// <param name="c1">The second component lanes, replaced by green.</param>
|
||||
|
/// <param name="c2">The third component lanes, replaced by blue.</param>
|
||||
|
/// <param name="c3">The fourth component lanes, or zero for a three-component color space.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
public static abstract void ConvertToRgb(ref Vector256<float> c0, ref Vector256<float> c1, ref Vector256<float> c2, Vector256<float> c3, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts sixteen JPEG samples to normalized RGB.
|
||||
|
/// </summary>
|
||||
|
/// <param name="c0">The first component lanes, replaced by red.</param>
|
||||
|
/// <param name="c1">The second component lanes, replaced by green.</param>
|
||||
|
/// <param name="c2">The third component lanes, replaced by blue.</param>
|
||||
|
/// <param name="c3">The fourth component lanes, or zero for a three-component color space.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
public static abstract void ConvertToRgb(ref Vector512<float> c0, ref Vector512<float> c1, ref Vector512<float> c2, Vector512<float> c3, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts one RGB sample to JPEG components.
|
||||
|
/// </summary>
|
||||
|
/// <param name="r">The red value.</param>
|
||||
|
/// <param name="g">The green value.</param>
|
||||
|
/// <param name="b">The blue value.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/>.</param>
|
||||
|
/// <param name="c0">The first converted component.</param>
|
||||
|
/// <param name="c1">The second converted component.</param>
|
||||
|
/// <param name="c2">The third converted component.</param>
|
||||
|
/// <param name="c3">The fourth converted component, if used.</param>
|
||||
|
public static abstract void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts four RGB samples to JPEG components.
|
||||
|
/// </summary>
|
||||
|
/// <param name="r">The red lanes.</param>
|
||||
|
/// <param name="g">The green lanes.</param>
|
||||
|
/// <param name="b">The blue lanes.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
/// <param name="c0">The first converted component lanes.</param>
|
||||
|
/// <param name="c1">The second converted component lanes.</param>
|
||||
|
/// <param name="c2">The third converted component lanes.</param>
|
||||
|
/// <param name="c3">The fourth converted component lanes, if used.</param>
|
||||
|
public static abstract void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts eight RGB samples to JPEG components.
|
||||
|
/// </summary>
|
||||
|
/// <param name="r">The red lanes.</param>
|
||||
|
/// <param name="g">The green lanes.</param>
|
||||
|
/// <param name="b">The blue lanes.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
/// <param name="c0">The first converted component lanes.</param>
|
||||
|
/// <param name="c1">The second converted component lanes.</param>
|
||||
|
/// <param name="c2">The third converted component lanes.</param>
|
||||
|
/// <param name="c3">The fourth converted component lanes, if used.</param>
|
||||
|
public static abstract void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts sixteen RGB samples to JPEG components.
|
||||
|
/// </summary>
|
||||
|
/// <param name="r">The red lanes.</param>
|
||||
|
/// <param name="g">The green lanes.</param>
|
||||
|
/// <param name="b">The blue lanes.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
/// <param name="c0">The first converted component lanes.</param>
|
||||
|
/// <param name="c1">The second converted component lanes.</param>
|
||||
|
/// <param name="c2">The third converted component lanes.</param>
|
||||
|
/// <param name="c3">The fourth converted component lanes, if used.</param>
|
||||
|
public static abstract void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a JPEG color model using a single operator-driven traversal for all SIMD widths.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TOperator">The color-model-specific arithmetic.</typeparam>
|
||||
|
internal sealed class JpegColorConverter<TOperator> : JpegColorConverterBase |
||||
|
where TOperator : struct, IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="JpegColorConverter{TOperator}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="precision">The precision in bits.</param>
|
||||
|
public JpegColorConverter(int precision) |
||||
|
: base(TOperator.ColorSpace, precision) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool IsAvailable => true; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override int ElementsPerBatch |
||||
|
=> Vector512.IsHardwareAccelerated ? Vector512<float>.Count : Vector256.IsHardwareAccelerated ? Vector256<float>.Count : Vector128.IsHardwareAccelerated ? Vector128<float>.Count : 1; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void ConvertToRgbInPlace(in ComponentValues values) |
||||
|
{ |
||||
|
// JPEG component processors own equally sized planar buffers. Capturing their first elements
|
||||
|
// as byrefs lets every width share the same offset without introducing Span bounds checks in
|
||||
|
// the hot loops. Component3 may be empty; its byref is only dereferenced for four-component operators.
|
||||
|
ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); |
||||
|
ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); |
||||
|
ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); |
||||
|
ref float c3Base = ref MemoryMarshal.GetReference(values.Component3); |
||||
|
|
||||
|
int length = values.Component0.Length; |
||||
|
int i = 0; |
||||
|
float scale = 1F / this.MaximumValue; |
||||
|
|
||||
|
// Descending widths keep one traversal while allowing an AVX-512 machine to process
|
||||
|
// an eight-pixel JPEG block with AVX2 rather than sending the entire block to scalar code.
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// Subtracting the lane count turns the loop condition into a single signed comparison.
|
||||
|
// A negative value naturally skips this width, and i <= end proves every unaligned
|
||||
|
// 64-byte reinterpretation remains entirely inside its component buffer.
|
||||
|
int oneVectorFromEnd = length - Vector512<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// Precision-derived values are broadcast only when this width has work. Keeping them outside
|
||||
|
// the loop avoids repeated setup without penalizing rows handled entirely by narrower widths.
|
||||
|
Vector512<float> maximumValue = Vector512.Create(this.MaximumValue); |
||||
|
Vector512<float> halfValue = Vector512.Create(this.HalfValue); |
||||
|
Vector512<float> scaleVector = Vector512.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
||||
|
{ |
||||
|
ref Vector512<float> c0 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c0Base, i)); |
||||
|
ref Vector512<float> c1 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c1Base, i)); |
||||
|
ref Vector512<float> c2 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c2Base, i)); |
||||
|
|
||||
|
// ComponentCount is a static property on the closed operator type, so the JIT removes
|
||||
|
// this choice. Three-component models never dereference the empty Component3 byref.
|
||||
|
Vector512<float> c3 = TOperator.ComponentCount == 4 ? Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c3Base, i)) : default; |
||||
|
|
||||
|
// c0-c2 alias the planar source vectors and are replaced in place with normalized RGB.
|
||||
|
// c3 is passed by value because the fourth JPEG component must remain unchanged.
|
||||
|
TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scaleVector); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// The shared offset continues where AVX-512 stopped. At this point fewer than sixteen
|
||||
|
// samples remain, so this stage consumes the complete eight-sample remainder when present.
|
||||
|
int oneVectorFromEnd = length - Vector256<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// YMM precision state is materialized only for an eight-sample remainder or an AVX2-only loop.
|
||||
|
Vector256<float> maximumValue = Vector256.Create(this.MaximumValue); |
||||
|
Vector256<float> halfValue = Vector256.Create(this.HalfValue); |
||||
|
Vector256<float> scaleVector = Vector256.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
||||
|
{ |
||||
|
ref Vector256<float> c0 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c0Base, i)); |
||||
|
ref Vector256<float> c1 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c1Base, i)); |
||||
|
ref Vector256<float> c2 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c2Base, i)); |
||||
|
|
||||
|
// The closed operator makes this a compile-time color-model choice, not a per-vector
|
||||
|
// runtime abstraction or interface dispatch.
|
||||
|
Vector256<float> c3 = TOperator.ComponentCount == 4 ? Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c3Base, i)) : default; |
||||
|
|
||||
|
TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scaleVector); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// SSE/AdvSimd handles the final four complete samples. This also gives non-AVX machines
|
||||
|
// the same traversal without duplicating the control flow for another register width.
|
||||
|
int oneVectorFromEnd = length - Vector128<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// XMM state is likewise created only when four samples remain for this stage.
|
||||
|
Vector128<float> maximumValue = Vector128.Create(this.MaximumValue); |
||||
|
Vector128<float> halfValue = Vector128.Create(this.HalfValue); |
||||
|
Vector128<float> scaleVector = Vector128.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
||||
|
{ |
||||
|
ref Vector128<float> c0 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c0Base, i)); |
||||
|
ref Vector128<float> c1 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c1Base, i)); |
||||
|
ref Vector128<float> c2 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c2Base, i)); |
||||
|
|
||||
|
// As at the wider stages, the fourth vector is loaded only for CMYK-shaped operators.
|
||||
|
Vector128<float> c3 = TOperator.ComponentCount == 4 ? Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c3Base, i)) : default; |
||||
|
|
||||
|
TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scaleVector); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Fewer than four samples remain after the SIMD cascade. Processing from the shared offset
|
||||
|
// guarantees each sample is visited exactly once for arbitrary test lengths and JPEG block rows.
|
||||
|
for (; i < length; i++) |
||||
|
{ |
||||
|
float c3 = TOperator.ComponentCount == 4 ? Unsafe.Add(ref c3Base, i) : 0; |
||||
|
|
||||
|
TOperator.ConvertToRgb(ref Unsafe.Add(ref c0Base, i), ref Unsafe.Add(ref c1Base, i), ref Unsafe.Add(ref c2Base, i), c3, this.MaximumValue, this.HalfValue, scale); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
||||
|
{ |
||||
|
// The encoder supplies equally sized RGB planes and destination component planes. Byrefs preserve
|
||||
|
// contiguous access and allow the same proven vector boundary to govern every participating lane.
|
||||
|
// Component3 is empty for three-component formats and is only written by four-component operators.
|
||||
|
ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); |
||||
|
ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); |
||||
|
ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); |
||||
|
ref float c3Base = ref MemoryMarshal.GetReference(values.Component3); |
||||
|
ref float rBase = ref MemoryMarshal.GetReference(rLane); |
||||
|
ref float gBase = ref MemoryMarshal.GetReference(gLane); |
||||
|
ref float bBase = ref MemoryMarshal.GetReference(bLane); |
||||
|
|
||||
|
int length = values.Component0.Length; |
||||
|
int i = 0; |
||||
|
float scale = 1F / this.MaximumValue; |
||||
|
|
||||
|
// Each vector overload returns planar component vectors. Storing them here keeps the
|
||||
|
// operator concerned only with color arithmetic and preserves contiguous lane access.
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// The end offset proves all three 64-byte RGB reads and all component writes are in range.
|
||||
|
// A short row yields a negative end and falls through to the next supported width.
|
||||
|
int oneVectorFromEnd = length - Vector512<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// Operators receive width-matched precision state only when this width has work, keeping
|
||||
|
// invariant broadcasts outside the loop without charging narrower or scalar rows for them.
|
||||
|
Vector512<float> maximumValue = Vector512.Create(this.MaximumValue); |
||||
|
Vector512<float> halfValue = Vector512.Create(this.HalfValue); |
||||
|
Vector512<float> scaleVector = Vector512.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
||||
|
{ |
||||
|
Vector512<float> r = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref rBase, i)); |
||||
|
Vector512<float> g = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref gBase, i)); |
||||
|
Vector512<float> b = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref bBase, i)); |
||||
|
|
||||
|
TOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scaleVector, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3); |
||||
|
|
||||
|
// Outputs remain planar: each vector contains sixteen consecutive samples from one
|
||||
|
// JPEG component. Static count checks prevent grayscale from touching absent planes
|
||||
|
// while disappearing completely from three- and four-component specializations.
|
||||
|
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c0Base, i)) = c0; |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 2) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c1Base, i)) = c1; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 3) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c2Base, i)) = c2; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 4) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c3Base, i)) = c3; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// Continue from the AVX-512 offset so an eight-sample tail stays vectorized on AVX-512 CPUs.
|
||||
|
int oneVectorFromEnd = length - Vector256<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// Materialize YMM state only for an eight-sample remainder or an AVX2-only loop.
|
||||
|
Vector256<float> maximumValue = Vector256.Create(this.MaximumValue); |
||||
|
Vector256<float> halfValue = Vector256.Create(this.HalfValue); |
||||
|
Vector256<float> scaleVector = Vector256.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
||||
|
{ |
||||
|
Vector256<float> r = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref rBase, i)); |
||||
|
Vector256<float> g = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref gBase, i)); |
||||
|
Vector256<float> b = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref bBase, i)); |
||||
|
|
||||
|
TOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scaleVector, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3); |
||||
|
|
||||
|
// Static count checks write only planes owned by this color model.
|
||||
|
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c0Base, i)) = c0; |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 2) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c1Base, i)) = c1; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 3) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c2Base, i)) = c2; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 4) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c3Base, i)) = c3; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// The final SIMD stage consumes four complete RGB samples on SSE or AdvSimd hardware.
|
||||
|
int oneVectorFromEnd = length - Vector128<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// Materialize XMM state only when the final SIMD stage can consume four samples.
|
||||
|
Vector128<float> maximumValue = Vector128.Create(this.MaximumValue); |
||||
|
Vector128<float> halfValue = Vector128.Create(this.HalfValue); |
||||
|
Vector128<float> scaleVector = Vector128.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
||||
|
{ |
||||
|
Vector128<float> r = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref rBase, i)); |
||||
|
Vector128<float> g = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref gBase, i)); |
||||
|
Vector128<float> b = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref bBase, i)); |
||||
|
|
||||
|
TOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scaleVector, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3); |
||||
|
|
||||
|
// Four results are stored only for the planes represented by the closed operator.
|
||||
|
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c0Base, i)) = c0; |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 2) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c1Base, i)) = c1; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 3) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c2Base, i)) = c2; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 4) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c3Base, i)) = c3; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Scalar conversion is reserved for the zero-to-three samples that cannot fill Vector128.
|
||||
|
for (; i < length; i++) |
||||
|
{ |
||||
|
TOperator.ConvertFromRgb(Unsafe.Add(ref rBase, i), Unsafe.Add(ref gBase, i), Unsafe.Add(ref bBase, i), this.MaximumValue, this.HalfValue, scale, out float c0, out float c1, out float c2, out float c3); |
||||
|
|
||||
|
Unsafe.Add(ref c0Base, i) = c0; |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 2) |
||||
|
{ |
||||
|
Unsafe.Add(ref c1Base, i) = c1; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 3) |
||||
|
{ |
||||
|
Unsafe.Add(ref c2Base, i) = c2; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 4) |
||||
|
{ |
||||
|
Unsafe.Add(ref c3Base, i) = c3; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,305 @@ |
|||||
|
// 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 SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Normalizes three planar component lanes and interleaves them into packed XYZ values.
|
||||
|
/// </summary>
|
||||
|
/// <param name="xLane">The planar X components.</param>
|
||||
|
/// <param name="yLane">The planar Y components.</param>
|
||||
|
/// <param name="zLane">The planar Z components.</param>
|
||||
|
/// <param name="packed">The destination ordered as consecutive XYZ triples.</param>
|
||||
|
/// <param name="scale">The normalization factor applied to every component.</param>
|
||||
|
public static void PackedNormalizeInterleave3(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, Span<float> packed, float scale) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(packed.Length % 3 == 0, "Packed length must be divisible by 3."); |
||||
|
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); |
||||
|
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); |
||||
|
DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 3, xLane.Length, nameof(packed)); |
||||
|
|
||||
|
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
||||
|
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
||||
|
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
||||
|
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
||||
|
int i = 0; |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector128<float> scaleVector = Vector128.Create(scale); |
||||
|
int oneVectorFromEnd = xLane.Length - Vector128<float>.Count; |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
||||
|
{ |
||||
|
// Each source vector contains four consecutive samples from one plane:
|
||||
|
// x = [X0 X1 X2 X3]
|
||||
|
// y = [Y0 Y1 Y2 Y3]
|
||||
|
// z = [Z0 Z1 Z2 Z3]
|
||||
|
// Shifting X by one sample supplies the value that follows each XYZ triple:
|
||||
|
// shiftedX = [X1 X2 X3 0]
|
||||
|
// The transpose therefore produces overlapping rows [Xn Yn Zn Xn+1].
|
||||
|
// AlignRight joins those rows into three complete destination vectors, avoiding
|
||||
|
// the scalar-sized stores that writing four independent Vector3 values requires.
|
||||
|
Vector128<float> x = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref xLaneRef, i)) * scaleVector; |
||||
|
Vector128<float> y = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref yLaneRef, i)) * scaleVector; |
||||
|
Vector128<float> z = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref zLaneRef, i)) * scaleVector; |
||||
|
Vector128<float> shiftedX = Vector128_.ShiftRightBytesInVector(x.AsByte(), sizeof(float)).AsSingle(); |
||||
|
|
||||
|
Transpose4(x, y, z, shiftedX, out Vector128<float> pixel0, out Vector128<float> pixel1, out Vector128<float> pixel2, out Vector128<float> pixel3); |
||||
|
|
||||
|
// Dropping pixel2.X lets [Y2] complete [Y1 Z1 X2] from pixel1.
|
||||
|
Vector128<byte> shiftedPixel2 = Vector128_.ShiftRightBytesInVector(pixel2.AsByte(), sizeof(float)); |
||||
|
Vector128<float> packed1 = Vector128_.AlignRight(shiftedPixel2, pixel1.AsByte(), sizeof(float)).AsSingle(); |
||||
|
|
||||
|
// Dropping pixel3.X leaves [Y3 Z3] to complete [Z2 X3] from pixel2.
|
||||
|
Vector128<byte> shiftedPixel3 = Vector128_.ShiftRightBytesInVector(pixel3.AsByte(), sizeof(float)); |
||||
|
Vector128<float> packed2 = Vector128_.AlignRight(shiftedPixel3, pixel2.AsByte(), sizeof(float) * 2).AsSingle(); |
||||
|
|
||||
|
ref Vector128<float> destination = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, (uint)i * 3)); |
||||
|
|
||||
|
destination = pixel0; |
||||
|
Unsafe.Add(ref destination, 1) = packed1; |
||||
|
Unsafe.Add(ref destination, 2) = packed2; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Fewer than four pixels remain after SIMD, or every pixel reaches this path
|
||||
|
// when the runtime cannot accelerate the cross-vector transpose.
|
||||
|
for (; i < xLane.Length; i++) |
||||
|
{ |
||||
|
nuint sourceOffset = (uint)i; |
||||
|
nuint packedOffset = sourceOffset * 3; |
||||
|
Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, sourceOffset) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, sourceOffset) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, sourceOffset) * scale; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Deinterleaves packed XYZ values into three planar component lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="packed">The source ordered as consecutive XYZ triples.</param>
|
||||
|
/// <param name="xLane">The destination X components.</param>
|
||||
|
/// <param name="yLane">The destination Y components.</param>
|
||||
|
/// <param name="zLane">The destination Z components.</param>
|
||||
|
public static void UnpackDeinterleave3(ReadOnlySpan<Vector3> packed, Span<float> xLane, Span<float> yLane, Span<float> zLane) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(packed.Length == xLane.Length, nameof(packed), "Channels must be of same size!"); |
||||
|
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); |
||||
|
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); |
||||
|
|
||||
|
ref float packedRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Vector3, float>(packed)); |
||||
|
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
||||
|
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
||||
|
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
||||
|
int i = 0; |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = packed.Length - Vector128<float>.Count; |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
||||
|
{ |
||||
|
// A Vector3 occupies twelve contiguous bytes, so a sixteen-byte load beginning
|
||||
|
// at one pixel also reads the X component of the following pixel:
|
||||
|
// pixel0 = [X0 Y0 Z0 X1]
|
||||
|
// pixel1 = [X1 Y1 Z1 X2]
|
||||
|
// The transpose discards this fourth column, making the overlap useful padding
|
||||
|
// and avoiding two insert instructions per pixel. The final row needs explicit
|
||||
|
// zero padding only when pixel3 is the last element in the source span.
|
||||
|
nuint packedOffset = (uint)i * 3; |
||||
|
Vector128<float> pixel0 = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, packedOffset)); |
||||
|
Vector128<float> pixel1 = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, packedOffset + 3)); |
||||
|
Vector128<float> pixel2 = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, packedOffset + 6)); |
||||
|
ref float pixel3Ref = ref Unsafe.Add(ref packedRef, packedOffset + 9); |
||||
|
Vector128<float> pixel3 = i + Vector128<float>.Count < packed.Length ? Unsafe.As<float, Vector128<float>>(ref pixel3Ref) : Unsafe.As<float, Vector3>(ref pixel3Ref).AsVector128(); |
||||
|
|
||||
|
Transpose4(pixel0, pixel1, pixel2, pixel3, out Vector128<float> x, out Vector128<float> y, out Vector128<float> z, out _); |
||||
|
|
||||
|
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref xLaneRef, i)) = x; |
||||
|
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref yLaneRef, i)) = y; |
||||
|
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref zLaneRef, i)) = z; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// The scalar remainder preserves the original scatter behavior for zero to
|
||||
|
// three pixels and provides the complete fallback on unsupported hardware.
|
||||
|
for (; i < packed.Length; i++) |
||||
|
{ |
||||
|
nuint packedOffset = (uint)i * 3; |
||||
|
Unsafe.Add(ref xLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset); |
||||
|
Unsafe.Add(ref yLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 1); |
||||
|
Unsafe.Add(ref zLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 2); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Normalizes four planar component lanes and interleaves them into packed XYZW values.
|
||||
|
/// </summary>
|
||||
|
/// <param name="xLane">The planar X components.</param>
|
||||
|
/// <param name="yLane">The planar Y components.</param>
|
||||
|
/// <param name="zLane">The planar Z components.</param>
|
||||
|
/// <param name="wLane">The planar W components.</param>
|
||||
|
/// <param name="packed">The destination ordered as consecutive XYZW groups.</param>
|
||||
|
/// <param name="maxValue">The maximum component value used to normalize each component.</param>
|
||||
|
public static void PackedNormalizeInterleave4(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, ReadOnlySpan<float> wLane, Span<float> packed, float maxValue) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4."); |
||||
|
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); |
||||
|
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); |
||||
|
DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!"); |
||||
|
DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed)); |
||||
|
|
||||
|
float scale = 1F / maxValue; |
||||
|
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
||||
|
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
||||
|
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
||||
|
ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); |
||||
|
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
||||
|
int i = 0; |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector128<float> scaleVector = Vector128.Create(scale); |
||||
|
int oneVectorFromEnd = xLane.Length - Vector128<float>.Count; |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
||||
|
{ |
||||
|
// Four planar vectors form the rows of a 4x4 matrix. Transposition
|
||||
|
// converts them into four complete [Xn Yn Zn Wn] pixel vectors, so
|
||||
|
// normalization and interleaving require only four loads, four
|
||||
|
// multiplies, the register transpose, and four contiguous stores.
|
||||
|
Vector128<float> x = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref xLaneRef, i)) * scaleVector; |
||||
|
Vector128<float> y = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref yLaneRef, i)) * scaleVector; |
||||
|
Vector128<float> z = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref zLaneRef, i)) * scaleVector; |
||||
|
Vector128<float> w = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref wLaneRef, i)) * scaleVector; |
||||
|
|
||||
|
Transpose4(x, y, z, w, out Vector128<float> pixel0, out Vector128<float> pixel1, out Vector128<float> pixel2, out Vector128<float> pixel3); |
||||
|
|
||||
|
ref Vector128<float> destination = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, (uint)i * 4)); |
||||
|
|
||||
|
destination = pixel0; |
||||
|
Unsafe.Add(ref destination, 1) = pixel1; |
||||
|
Unsafe.Add(ref destination, 2) = pixel2; |
||||
|
Unsafe.Add(ref destination, 3) = pixel3; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Process the zero-to-three trailing pixels with the same normalization
|
||||
|
// and component order as the vector transpose.
|
||||
|
for (; i < xLane.Length; i++) |
||||
|
{ |
||||
|
nuint sourceOffset = (uint)i; |
||||
|
nuint packedOffset = sourceOffset * 4; |
||||
|
Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, sourceOffset) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, sourceOffset) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, sourceOffset) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 3) = Unsafe.Add(ref wLaneRef, sourceOffset) * scale; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Inverts and normalizes four planar component lanes before interleaving them into packed XYZW values.
|
||||
|
/// </summary>
|
||||
|
/// <param name="xLane">The inverted planar X components.</param>
|
||||
|
/// <param name="yLane">The inverted planar Y components.</param>
|
||||
|
/// <param name="zLane">The inverted planar Z components.</param>
|
||||
|
/// <param name="wLane">The inverted planar W components.</param>
|
||||
|
/// <param name="packed">The destination ordered as consecutive conventional XYZW groups.</param>
|
||||
|
/// <param name="maxValue">The maximum component value used for inversion and normalization.</param>
|
||||
|
public static void PackedInvertNormalizeInterleave4(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, ReadOnlySpan<float> wLane, Span<float> packed, float maxValue) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4."); |
||||
|
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); |
||||
|
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); |
||||
|
DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!"); |
||||
|
DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed)); |
||||
|
|
||||
|
float scale = 1F / maxValue; |
||||
|
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
||||
|
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
||||
|
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
||||
|
ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); |
||||
|
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
||||
|
int i = 0; |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector128<float> maximumVector = Vector128.Create(maxValue); |
||||
|
Vector128<float> scaleVector = Vector128.Create(scale); |
||||
|
int oneVectorFromEnd = xLane.Length - Vector128<float>.Count; |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
||||
|
{ |
||||
|
// Adobe JPEG stores all four components inverted in the sample
|
||||
|
// domain. Reflecting and normalizing the planar vectors before the
|
||||
|
// transpose keeps both arithmetic operations lane-wise and leaves
|
||||
|
// the transpose responsible only for the planar-to-packed layout.
|
||||
|
Vector128<float> x = (maximumVector - Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref xLaneRef, i))) * scaleVector; |
||||
|
Vector128<float> y = (maximumVector - Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref yLaneRef, i))) * scaleVector; |
||||
|
Vector128<float> z = (maximumVector - Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref zLaneRef, i))) * scaleVector; |
||||
|
Vector128<float> w = (maximumVector - Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref wLaneRef, i))) * scaleVector; |
||||
|
|
||||
|
Transpose4(x, y, z, w, out Vector128<float> pixel0, out Vector128<float> pixel1, out Vector128<float> pixel2, out Vector128<float> pixel3); |
||||
|
|
||||
|
ref Vector128<float> destination = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, (uint)i * 4)); |
||||
|
|
||||
|
destination = pixel0; |
||||
|
Unsafe.Add(ref destination, 1) = pixel1; |
||||
|
Unsafe.Add(ref destination, 2) = pixel2; |
||||
|
Unsafe.Add(ref destination, 3) = pixel3; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Preserve the original operation order for the zero-to-three trailing pixels:
|
||||
|
// subtract in the sample domain, then multiply by the reciprocal maximum.
|
||||
|
for (; i < xLane.Length; i++) |
||||
|
{ |
||||
|
nuint sourceOffset = (uint)i; |
||||
|
nuint packedOffset = sourceOffset * 4; |
||||
|
Unsafe.Add(ref packedRef, packedOffset) = (maxValue - Unsafe.Add(ref xLaneRef, sourceOffset)) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 1) = (maxValue - Unsafe.Add(ref yLaneRef, sourceOffset)) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 2) = (maxValue - Unsafe.Add(ref zLaneRef, sourceOffset)) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 3) = (maxValue - Unsafe.Add(ref wLaneRef, sourceOffset)) * scale; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transposes four four-lane rows into four four-lane columns.
|
||||
|
/// </summary>
|
||||
|
/// <param name="row0">The first matrix row.</param>
|
||||
|
/// <param name="row1">The second matrix row.</param>
|
||||
|
/// <param name="row2">The third matrix row.</param>
|
||||
|
/// <param name="row3">The fourth matrix row.</param>
|
||||
|
/// <param name="column0">The first matrix column.</param>
|
||||
|
/// <param name="column1">The second matrix column.</param>
|
||||
|
/// <param name="column2">The third matrix column.</param>
|
||||
|
/// <param name="column3">The fourth matrix column.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void Transpose4(Vector128<float> row0, Vector128<float> row1, Vector128<float> row2, Vector128<float> row3, out Vector128<float> column0, out Vector128<float> column1, out Vector128<float> column2, out Vector128<float> column3) |
||||
|
{ |
||||
|
// The first unpack interleaves adjacent 32-bit lanes from rows 0/1 and 2/3:
|
||||
|
// row01Low = [r0c0 r1c0 r0c1 r1c1]
|
||||
|
// row23Low = [r2c0 r3c0 r2c1 r3c1]
|
||||
|
// A second unpack treats each adjacent pair as one 64-bit lane and combines
|
||||
|
// the row01 and row23 pairs into complete columns. The integer views only
|
||||
|
// expose the cross-platform unpack helpers; every floating-point bit is preserved.
|
||||
|
Vector128<int> row01Low = Vector128_.UnpackLow(row0.AsInt32(), row1.AsInt32()); |
||||
|
Vector128<int> row01High = Vector128_.UnpackHigh(row0.AsInt32(), row1.AsInt32()); |
||||
|
Vector128<int> row23Low = Vector128_.UnpackLow(row2.AsInt32(), row3.AsInt32()); |
||||
|
Vector128<int> row23High = Vector128_.UnpackHigh(row2.AsInt32(), row3.AsInt32()); |
||||
|
|
||||
|
column0 = Vector128_.UnpackLow(row01Low.AsInt64(), row23Low.AsInt64()).AsSingle(); |
||||
|
column1 = Vector128_.UnpackHigh(row01Low.AsInt64(), row23Low.AsInt64()).AsSingle(); |
||||
|
column2 = Vector128_.UnpackLow(row01High.AsInt64(), row23High.AsInt64()).AsSingle(); |
||||
|
column3 = Vector128_.UnpackHigh(row01High.AsInt64(), row23High.AsInt64()).AsSingle(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements direct JPEG RGB normalization and planar RGB copying for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct RgbOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.RGB; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 3; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) |
||||
|
{ |
||||
|
// The JPEG planes already represent R, G, and B. Conversion therefore consists only of moving
|
||||
|
// each integer-domain sample into the normalized floating-point domain consumed by pixel packing.
|
||||
|
c0 *= scale; |
||||
|
c1 *= scale; |
||||
|
c2 *= scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector128<float> c0, ref Vector128<float> c1, ref Vector128<float> c2, Vector128<float> c3, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale) |
||||
|
{ |
||||
|
// Four samples from each planar channel remain in their lanes while sharing one normalization vector.
|
||||
|
c0 *= scale; |
||||
|
c1 *= scale; |
||||
|
c2 *= scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector256<float> c0, ref Vector256<float> c1, ref Vector256<float> c2, Vector256<float> c3, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale) |
||||
|
{ |
||||
|
// Eight samples per plane are normalized independently without channel shuffles.
|
||||
|
c0 *= scale; |
||||
|
c1 *= scale; |
||||
|
c2 *= scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector512<float> c0, ref Vector512<float> c1, ref Vector512<float> c2, Vector512<float> c3, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale) |
||||
|
{ |
||||
|
// Sixteen samples per plane are normalized independently without changing planar ordering.
|
||||
|
c0 *= scale; |
||||
|
c1 *= scale; |
||||
|
c2 *= scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) |
||||
|
{ |
||||
|
// Encoder RGB lanes already use the JPEG sample domain, so the direct color model copies them.
|
||||
|
c0 = r; |
||||
|
c1 = g; |
||||
|
c2 = b; |
||||
|
c3 = 0; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3) |
||||
|
{ |
||||
|
// The planar vectors map one-to-one to JPEG components; the fourth result is statically discarded.
|
||||
|
c0 = r; |
||||
|
c1 = g; |
||||
|
c2 = b; |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3) |
||||
|
{ |
||||
|
// The planar vectors map one-to-one to JPEG components; no arithmetic or rearrangement is required.
|
||||
|
c0 = r; |
||||
|
c1 = g; |
||||
|
c2 = b; |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3) |
||||
|
{ |
||||
|
// The widest path is likewise a register-to-register planar copy for sixteen pixels.
|
||||
|
c0 = r; |
||||
|
c1 = g; |
||||
|
c2 = b; |
||||
|
c3 = default; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,84 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Buffers; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.ColorProfiles; |
|
||||
using SixLabors.ImageSharp.ColorProfiles.Icc; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class RgbScalar : JpegColorConverterScalar |
|
||||
{ |
|
||||
public RgbScalar(int precision) |
|
||||
: base(JpegColorSpace.RGB, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
=> ConvertToRgbInPlace(values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgb(values, rLane, gLane, bLane); |
|
||||
|
|
||||
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3); |
|
||||
Span<float> packed = memoryOwner.Memory.Span; |
|
||||
|
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
|
|
||||
PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maxValue); |
|
||||
|
|
||||
Span<Rgb> source = MemoryMarshal.Cast<float, Rgb>(packed); |
|
||||
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed); |
|
||||
|
|
||||
ColorConversionOptions options = new() |
|
||||
{ |
|
||||
SourceIccProfile = profile, |
|
||||
TargetIccProfile = CompactSrgbV4Profile.Profile, |
|
||||
}; |
|
||||
ColorProfileConverter converter = new(options); |
|
||||
converter.Convert<Rgb, Rgb>(source, destination); |
|
||||
|
|
||||
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2); |
|
||||
} |
|
||||
|
|
||||
internal static void ConvertToRgbInPlace(ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); |
|
||||
ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); |
|
||||
ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); |
|
||||
|
|
||||
float scale = 1F / maxValue; |
|
||||
|
|
||||
for (nuint i = 0; i < (nuint)values.Component0.Length; i++) |
|
||||
{ |
|
||||
Unsafe.Add(ref c0Base, i) *= scale; |
|
||||
Unsafe.Add(ref c1Base, i) *= scale; |
|
||||
Unsafe.Add(ref c2Base, i) *= scale; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
internal static void ConvertFromRgb(ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
// TODO: This doesn't seem correct. We should be scaling to the maximum value here.
|
|
||||
rLane.CopyTo(values.Component0); |
|
||||
gLane.CopyTo(values.Component1); |
|
||||
bLane.CopyTo(values.Component2); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,56 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class RgbVector128 : JpegColorConverterVector128 |
|
||||
{ |
|
||||
public RgbVector128(int precision) |
|
||||
: base(JpegColorSpace.RGB, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector128<float> rBase = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector128<float> gBase = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector128<float> bBase = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector128<float> scale = Vector128.Create(1 / this.MaximumValue); |
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector128<float> r = ref Unsafe.Add(ref rBase, i); |
|
||||
ref Vector128<float> g = ref Unsafe.Add(ref gBase, i); |
|
||||
ref Vector128<float> b = ref Unsafe.Add(ref bBase, i); |
|
||||
r *= scale; |
|
||||
g *= scale; |
|
||||
b *= scale; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
rLane.CopyTo(values.Component0); |
|
||||
gLane.CopyTo(values.Component1); |
|
||||
bLane.CopyTo(values.Component2); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,56 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class RgbVector256 : JpegColorConverterVector256 |
|
||||
{ |
|
||||
public RgbVector256(int precision) |
|
||||
: base(JpegColorSpace.RGB, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector256<float> rBase = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> gBase = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> bBase = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector256<float> scale = Vector256.Create(1 / this.MaximumValue); |
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector256<float> r = ref Unsafe.Add(ref rBase, i); |
|
||||
ref Vector256<float> g = ref Unsafe.Add(ref gBase, i); |
|
||||
ref Vector256<float> b = ref Unsafe.Add(ref bBase, i); |
|
||||
r *= scale; |
|
||||
g *= scale; |
|
||||
b *= scale; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
rLane.CopyTo(values.Component0); |
|
||||
gLane.CopyTo(values.Component1); |
|
||||
bLane.CopyTo(values.Component2); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,64 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class RgbVector512 : JpegColorConverterVector512 |
|
||||
{ |
|
||||
public RgbVector512(int precision) |
|
||||
: base(JpegColorSpace.RGB, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector512<float> rBase = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector512<float> gBase = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector512<float> bBase = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector512<float> scale = Vector512.Create(1 / this.MaximumValue); |
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector512<float> r = ref Unsafe.Add(ref rBase, i); |
|
||||
ref Vector512<float> g = ref Unsafe.Add(ref gBase, i); |
|
||||
ref Vector512<float> b = ref Unsafe.Add(ref bBase, i); |
|
||||
r *= scale; |
|
||||
g *= scale; |
|
||||
b *= scale; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
rLane.CopyTo(values.Component0); |
|
||||
gLane.CopyTo(values.Component1); |
|
||||
bLane.CopyTo(values.Component2); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) |
|
||||
=> RgbScalar.ConvertToRgbInPlace(values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> RgbScalar.ConvertFromRgb(values, rLane, gLane, bLane); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,154 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements non-inverted TIFF JPEG CMYK conversion for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct TiffCmykOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.TiffCmyk; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 4; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) |
||||
|
{ |
||||
|
// TIFF stores conventional CMYK rather than Adobe's inverted representation. Normalize every
|
||||
|
// component, invert C/M/Y, and let the remaining light after K modulate each RGB channel.
|
||||
|
float k = 1F - (c3 * scale); |
||||
|
c0 = (1F - (c0 * scale)) * k; |
||||
|
c1 = (1F - (c1 * scale)) * k; |
||||
|
c2 = (1F - (c2 * scale)) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector128<float> c0, ref Vector128<float> c1, ref Vector128<float> c2, Vector128<float> c3, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale) |
||||
|
{ |
||||
|
// K remains lane-aligned with its C/M/Y sample while one-minus performs the non-inverted CMYK mapping.
|
||||
|
Vector128<float> k = Vector128<float>.One - (c3 * scale); |
||||
|
c0 = (Vector128<float>.One - (c0 * scale)) * k; |
||||
|
c1 = (Vector128<float>.One - (c1 * scale)) * k; |
||||
|
c2 = (Vector128<float>.One - (c2 * scale)) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector256<float> c0, ref Vector256<float> c1, ref Vector256<float> c2, Vector256<float> c3, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale) |
||||
|
{ |
||||
|
// Eight conventional CMYK samples convert independently without channel rearrangement.
|
||||
|
Vector256<float> k = Vector256<float>.One - (c3 * scale); |
||||
|
c0 = (Vector256<float>.One - (c0 * scale)) * k; |
||||
|
c1 = (Vector256<float>.One - (c1 * scale)) * k; |
||||
|
c2 = (Vector256<float>.One - (c2 * scale)) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector512<float> c0, ref Vector512<float> c1, ref Vector512<float> c2, Vector512<float> c3, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale) |
||||
|
{ |
||||
|
// Sixteen conventional CMYK samples convert independently without channel rearrangement.
|
||||
|
Vector512<float> k = Vector512<float>.One - (c3 * scale); |
||||
|
c0 = (Vector512<float>.One - (c0 * scale)) * k; |
||||
|
c1 = (Vector512<float>.One - (c1 * scale)) * k; |
||||
|
c2 = (Vector512<float>.One - (c2 * scale)) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) |
||||
|
{ |
||||
|
float c = maximumValue - r; |
||||
|
float m = maximumValue - g; |
||||
|
float y = maximumValue - b; |
||||
|
float k = MathF.Min(c, MathF.Min(m, y)); |
||||
|
|
||||
|
// Removing the shared black contribution requires division by the remaining range. Pure black
|
||||
|
// consumes that range completely, so its chromatic components are defined as zero.
|
||||
|
if (k >= maximumValue) |
||||
|
{ |
||||
|
c = 0; |
||||
|
m = 0; |
||||
|
y = 0; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// One reciprocal normalizes C, M, and Y against their shared remaining range.
|
||||
|
float reciprocal = 1F / (maximumValue - k); |
||||
|
c = (c - k) * reciprocal; |
||||
|
m = (m - k) * reciprocal; |
||||
|
y = (y - k) * reciprocal; |
||||
|
} |
||||
|
|
||||
|
// TIFF stores conventional CMYK: scale normalized C/M/Y back into the sample domain and retain K.
|
||||
|
c0 = c * maximumValue; |
||||
|
c1 = m * maximumValue; |
||||
|
c2 = y * maximumValue; |
||||
|
c3 = k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3) |
||||
|
{ |
||||
|
Vector128<float> c = maximumValue - r; |
||||
|
Vector128<float> m = maximumValue - g; |
||||
|
Vector128<float> y = maximumValue - b; |
||||
|
Vector128<float> k = Vector128.Min(c, Vector128.Min(m, y)); |
||||
|
|
||||
|
// The all-bits mask clears the undefined zero-divisor result only in pure-black lanes.
|
||||
|
Vector128<float> nonBlack = ~Vector128.Equals(k, maximumValue); |
||||
|
Vector128<float> reciprocal = Vector128<float>.One / (maximumValue - k); |
||||
|
c0 = (((c - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c1 = (((m - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c2 = (((y - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c3 = k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3) |
||||
|
{ |
||||
|
Vector256<float> c = maximumValue - r; |
||||
|
Vector256<float> m = maximumValue - g; |
||||
|
Vector256<float> y = maximumValue - b; |
||||
|
Vector256<float> k = Vector256.Min(c, Vector256.Min(m, y)); |
||||
|
|
||||
|
// Eight lanes independently clear the pure-black singularity before returning conventional CMYK.
|
||||
|
Vector256<float> nonBlack = ~Vector256.Equals(k, maximumValue); |
||||
|
Vector256<float> reciprocal = Vector256<float>.One / (maximumValue - k); |
||||
|
c0 = (((c - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c1 = (((m - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c2 = (((y - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c3 = k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3) |
||||
|
{ |
||||
|
Vector512<float> c = maximumValue - r; |
||||
|
Vector512<float> m = maximumValue - g; |
||||
|
Vector512<float> y = maximumValue - b; |
||||
|
Vector512<float> k = Vector512.Min(c, Vector512.Min(m, y)); |
||||
|
|
||||
|
// Sixteen lanes retain the same branchless singularity handling and component layout.
|
||||
|
Vector512<float> nonBlack = ~Vector512.Equals(k, maximumValue); |
||||
|
Vector512<float> reciprocal = Vector512<float>.One / (maximumValue - k); |
||||
|
c0 = (((c - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c1 = (((m - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c2 = (((y - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c3 = k; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,118 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Buffers; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.ColorProfiles; |
|
||||
using SixLabors.ImageSharp.ColorProfiles.Icc; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Color converter for tiff images, which use the jpeg compression and CMYK colorspace.
|
|
||||
/// </summary>
|
|
||||
internal sealed class TiffCmykScalar : JpegColorConverterScalar |
|
||||
{ |
|
||||
public TiffCmykScalar(int precision) |
|
||||
: base(JpegColorSpace.TiffCmyk, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
=> ConvertToRgbInPlace(in values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
Span<float> c3 = values.Component3; |
|
||||
|
|
||||
float scale = 1 / maxValue; |
|
||||
for (int i = 0; i < c0.Length; i++) |
|
||||
{ |
|
||||
float c = c0[i] * scale; |
|
||||
float m = c1[i] * scale; |
|
||||
float y = c2[i] * scale; |
|
||||
float k = 1 - (c3[i] * scale); |
|
||||
|
|
||||
c0[i] = (1 - c) * k; |
|
||||
c1[i] = (1 - m) * k; |
|
||||
c2[i] = (1 - y) * k; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
Span<float> c = values.Component0; |
|
||||
Span<float> m = values.Component1; |
|
||||
Span<float> y = values.Component2; |
|
||||
Span<float> k = values.Component3; |
|
||||
|
|
||||
for (int i = 0; i < c.Length; i++) |
|
||||
{ |
|
||||
float ctmp = 255F - rLane[i]; |
|
||||
float mtmp = 255F - gLane[i]; |
|
||||
float ytmp = 255F - bLane[i]; |
|
||||
float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); |
|
||||
|
|
||||
if (ktmp >= 255F) |
|
||||
{ |
|
||||
ctmp = 0F; |
|
||||
mtmp = 0F; |
|
||||
ytmp = 0F; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
float divisor = 1 / (255F - ktmp); |
|
||||
ctmp = (ctmp - ktmp) * divisor; |
|
||||
mtmp = (mtmp - ktmp) * divisor; |
|
||||
ytmp = (ytmp - ktmp) * divisor; |
|
||||
} |
|
||||
|
|
||||
c[i] = ctmp * maxValue; |
|
||||
m[i] = mtmp * maxValue; |
|
||||
y[i] = ytmp * maxValue; |
|
||||
k[i] = ktmp; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4); |
|
||||
Span<float> packed = memoryOwner.Memory.Span; |
|
||||
|
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
Span<float> c3 = values.Component3; |
|
||||
|
|
||||
PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); |
|
||||
|
|
||||
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed); |
|
||||
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length]; |
|
||||
|
|
||||
ColorConversionOptions options = new() |
|
||||
{ |
|
||||
SourceIccProfile = profile, |
|
||||
TargetIccProfile = CompactSrgbV4Profile.Profile, |
|
||||
}; |
|
||||
ColorProfileConverter converter = new(options); |
|
||||
converter.Convert<Cmyk, Rgb>(source, destination); |
|
||||
|
|
||||
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,99 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class TiffCmykVector128 : JpegColorConverterVector128 |
|
||||
{ |
|
||||
public TiffCmykVector128(int precision) |
|
||||
: base(JpegColorSpace.TiffCmyk, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector128<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector128<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector128<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector128<float> c3Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
Vector128<float> scale = Vector128.Create(1 / this.MaximumValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector128<float> c = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector128<float> m = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector128<float> y = ref Unsafe.Add(ref c2Base, i); |
|
||||
Vector128<float> k = Unsafe.Add(ref c3Base, i); |
|
||||
|
|
||||
k = Vector128<float>.One - (k * scale); |
|
||||
c = (Vector128<float>.One - (c * scale)) * k; |
|
||||
m = (Vector128<float>.One - (m * scale)) * k; |
|
||||
y = (Vector128<float>.One - (y * scale)) * k; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector128<float> destC = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector128<float> destM = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector128<float> destY = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector128<float> destK = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
ref Vector128<float> srcR = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector128<float> srcG = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector128<float> srcB = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
Vector128<float> scale = Vector128.Create(maxValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector128<float> ctmp = scale - Unsafe.Add(ref srcR, i); |
|
||||
Vector128<float> mtmp = scale - Unsafe.Add(ref srcG, i); |
|
||||
Vector128<float> ytmp = scale - Unsafe.Add(ref srcB, i); |
|
||||
Vector128<float> ktmp = Vector128.Min(ctmp, Vector128.Min(mtmp, ytmp)); |
|
||||
|
|
||||
Vector128<float> kMask = ~Vector128.Equals(ktmp, scale); |
|
||||
Vector128<float> divisor = Vector128<float>.One / (scale - ktmp); |
|
||||
|
|
||||
ctmp = ((ctmp - ktmp) * divisor) & kMask; |
|
||||
mtmp = ((mtmp - ktmp) * divisor) & kMask; |
|
||||
ytmp = ((ytmp - ktmp) * divisor) & kMask; |
|
||||
|
|
||||
Unsafe.Add(ref destC, i) = ctmp * scale; |
|
||||
Unsafe.Add(ref destM, i) = mtmp * scale; |
|
||||
Unsafe.Add(ref destY, i) = ytmp * scale; |
|
||||
Unsafe.Add(ref destK, i) = ktmp; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,99 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class TiffCmykVector256 : JpegColorConverterVector256 |
|
||||
{ |
|
||||
public TiffCmykVector256(int precision) |
|
||||
: base(JpegColorSpace.TiffCmyk, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector256<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector256<float> c3Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
Vector256<float> scale = Vector256.Create(1 / this.MaximumValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i); |
|
||||
Vector256<float> k = Unsafe.Add(ref c3Base, i); |
|
||||
|
|
||||
k = Vector256<float>.One - (k * scale); |
|
||||
c = (Vector256<float>.One - (c * scale)) * k; |
|
||||
m = (Vector256<float>.One - (m * scale)) * k; |
|
||||
y = (Vector256<float>.One - (y * scale)) * k; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector256<float> destC = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> destM = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> destY = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector256<float> destK = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
ref Vector256<float> srcR = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector256<float> srcG = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector256<float> srcB = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
Vector256<float> scale = Vector256.Create(maxValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector256<float> ctmp = scale - Unsafe.Add(ref srcR, i); |
|
||||
Vector256<float> mtmp = scale - Unsafe.Add(ref srcG, i); |
|
||||
Vector256<float> ytmp = scale - Unsafe.Add(ref srcB, i); |
|
||||
Vector256<float> ktmp = Vector256.Min(ctmp, Vector256.Min(mtmp, ytmp)); |
|
||||
|
|
||||
Vector256<float> kMask = ~Vector256.Equals(ktmp, scale); |
|
||||
Vector256<float> divisor = Vector256<float>.One / (scale - ktmp); |
|
||||
|
|
||||
ctmp = ((ctmp - ktmp) * divisor) & kMask; |
|
||||
mtmp = ((mtmp - ktmp) * divisor) & kMask; |
|
||||
ytmp = ((ytmp - ktmp) * divisor) & kMask; |
|
||||
|
|
||||
Unsafe.Add(ref destC, i) = ctmp * scale; |
|
||||
Unsafe.Add(ref destM, i) = mtmp * scale; |
|
||||
Unsafe.Add(ref destY, i) = ytmp * scale; |
|
||||
Unsafe.Add(ref destK, i) = ktmp; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,108 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class TiffCmykVector512 : JpegColorConverterVector512 |
|
||||
{ |
|
||||
public TiffCmykVector512(int precision) |
|
||||
: base(JpegColorSpace.TiffCmyk, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector512<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector512<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector512<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector512<float> c3Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector512<float> scale = Vector512.Create(1 / this.MaximumValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector512<float> c = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector512<float> m = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector512<float> y = ref Unsafe.Add(ref c2Base, i); |
|
||||
Vector512<float> k = Unsafe.Add(ref c3Base, i); |
|
||||
|
|
||||
k = Vector512<float>.One - (k * scale); |
|
||||
c = (Vector512<float>.One - (c * scale)) * k; |
|
||||
m = (Vector512<float>.One - (m * scale)) * k; |
|
||||
y = (Vector512<float>.One - (y * scale)) * k; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) |
|
||||
=> TiffCmykScalar.ConvertToRgbInPlace(values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> TiffCmykScalar.ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector512<float> destC = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector512<float> destM = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector512<float> destY = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector512<float> destK = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
ref Vector512<float> srcR = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector512<float> srcG = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector512<float> srcB = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
Vector512<float> scale = Vector512.Create(maxValue); |
|
||||
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector512<float> ctmp = scale - Unsafe.Add(ref srcR, i); |
|
||||
Vector512<float> mtmp = scale - Unsafe.Add(ref srcG, i); |
|
||||
Vector512<float> ytmp = scale - Unsafe.Add(ref srcB, i); |
|
||||
Vector512<float> ktmp = Vector512.Min(ctmp, Vector512.Min(mtmp, ytmp)); |
|
||||
|
|
||||
Vector512<float> kMask = ~Vector512.Equals(ktmp, scale); |
|
||||
Vector512<float> divisor = Vector512<float>.One / (scale - ktmp); |
|
||||
|
|
||||
ctmp = ((ctmp - ktmp) * divisor) & kMask; |
|
||||
mtmp = ((mtmp - ktmp) * divisor) & kMask; |
|
||||
ytmp = ((ytmp - ktmp) * divisor) & kMask; |
|
||||
|
|
||||
Unsafe.Add(ref destC, i) = ctmp * scale; |
|
||||
Unsafe.Add(ref destM, i) = mtmp * scale; |
|
||||
Unsafe.Add(ref destY, i) = ytmp * scale; |
|
||||
Unsafe.Add(ref destK, i) = ktmp; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,182 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements non-inverted TIFF JPEG YccK conversion for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct TiffYccKOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
private const float SourceScale = 1F / 255F; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.TiffYccK; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 4; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) |
||||
|
{ |
||||
|
float y = c0 * scale; |
||||
|
float cb = (c1 - halfValue) * scale; |
||||
|
float cr = (c2 - halfValue) * scale; |
||||
|
float k = 1F - (c3 * scale); |
||||
|
|
||||
|
// TIFF YccK is non-inverted: decode normalized YCbCr without integer rounding, then let the
|
||||
|
// remaining light after K modulate all three channels.
|
||||
|
c0 = (y + (YCbCrOperator.RCrMult * cr)) * k; |
||||
|
c1 = (y - (YCbCrOperator.GCbMult * cb) - (YCbCrOperator.GCrMult * cr)) * k; |
||||
|
c2 = (y + (YCbCrOperator.BCbMult * cb)) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector128<float> c0, ref Vector128<float> c1, ref Vector128<float> c2, Vector128<float> c3, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale) |
||||
|
{ |
||||
|
Vector128<float> y = c0 * scale; |
||||
|
Vector128<float> cb = (c1 - halfValue) * scale; |
||||
|
Vector128<float> cr = (c2 - halfValue) * scale; |
||||
|
Vector128<float> k = Vector128<float>.One - (c3 * scale); |
||||
|
|
||||
|
// Four lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
|
||||
|
c0 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y) * k; |
||||
|
c1 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y)) * k; |
||||
|
c2 = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector256<float> c0, ref Vector256<float> c1, ref Vector256<float> c2, Vector256<float> c3, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale) |
||||
|
{ |
||||
|
Vector256<float> y = c0 * scale; |
||||
|
Vector256<float> cb = (c1 - halfValue) * scale; |
||||
|
Vector256<float> cr = (c2 - halfValue) * scale; |
||||
|
Vector256<float> k = Vector256<float>.One - (c3 * scale); |
||||
|
|
||||
|
// Eight lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
|
||||
|
c0 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y) * k; |
||||
|
c1 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y)) * k; |
||||
|
c2 = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector512<float> c0, ref Vector512<float> c1, ref Vector512<float> c2, Vector512<float> c3, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale) |
||||
|
{ |
||||
|
Vector512<float> y = c0 * scale; |
||||
|
Vector512<float> cb = (c1 - halfValue) * scale; |
||||
|
Vector512<float> cr = (c2 - halfValue) * scale; |
||||
|
Vector512<float> k = Vector512<float>.One - (c3 * scale); |
||||
|
|
||||
|
// Sixteen lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
|
||||
|
c0 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y) * k; |
||||
|
c1 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y)) * k; |
||||
|
c2 = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) |
||||
|
{ |
||||
|
r *= SourceScale; |
||||
|
g *= SourceScale; |
||||
|
b *= SourceScale; |
||||
|
float k = 1F - MathF.Max(r, MathF.Max(g, b)); |
||||
|
|
||||
|
// Dividing by the brightest channel removes K before YCbCr projection. Pure black has no
|
||||
|
// chromatic direction, so it maps to zero luma and the neutral chroma midpoint.
|
||||
|
if (k >= 1F) |
||||
|
{ |
||||
|
c0 = 0; |
||||
|
c1 = halfValue; |
||||
|
c2 = halfValue; |
||||
|
c3 = maximumValue; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
float divisor = 1F / (1F - k); |
||||
|
r *= divisor; |
||||
|
g *= divisor; |
||||
|
b *= divisor; |
||||
|
c0 = ((0.299F * r) + (0.587F * g) + (0.114F * b)) * maximumValue; |
||||
|
c1 = halfValue + (((-0.168736F * r) + (-0.331264F * g) + (0.5F * b)) * maximumValue); |
||||
|
c2 = halfValue + (((0.5F * r) + (-0.418688F * g) + (-0.081312F * b)) * maximumValue); |
||||
|
c3 = k * maximumValue; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3) |
||||
|
{ |
||||
|
Vector128<float> sourceScale = Vector128.Create(SourceScale); |
||||
|
r *= sourceScale; |
||||
|
g *= sourceScale; |
||||
|
b *= sourceScale; |
||||
|
Vector128<float> k = Vector128<float>.One - Vector128.Max(r, Vector128.Max(g, b)); |
||||
|
|
||||
|
// The mask assigns no chromatic direction to pure-black lanes while preserving neighboring pixels.
|
||||
|
Vector128<float> nonBlack = ~Vector128.Equals(k, Vector128<float>.One); |
||||
|
Vector128<float> divisor = Vector128<float>.One / (Vector128<float>.One - k); |
||||
|
r = (r * divisor) & nonBlack; |
||||
|
g = (g * divisor) & nonBlack; |
||||
|
b = (b * divisor) & nonBlack; |
||||
|
c0 = Vector128_.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)) * maximumValue; |
||||
|
c1 = halfValue + (Vector128_.MultiplyAddEstimate(Vector128.Create(-0.168736F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b)) * maximumValue); |
||||
|
c2 = halfValue + (Vector128_.MultiplyAddEstimate(Vector128.Create(0.5F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b)) * maximumValue); |
||||
|
c3 = k * maximumValue; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3) |
||||
|
{ |
||||
|
Vector256<float> sourceScale = Vector256.Create(SourceScale); |
||||
|
r *= sourceScale; |
||||
|
g *= sourceScale; |
||||
|
b *= sourceScale; |
||||
|
Vector256<float> k = Vector256<float>.One - Vector256.Max(r, Vector256.Max(g, b)); |
||||
|
|
||||
|
// Eight lanes normalize chromatic direction independently and retain neutral chroma for black.
|
||||
|
Vector256<float> nonBlack = ~Vector256.Equals(k, Vector256<float>.One); |
||||
|
Vector256<float> divisor = Vector256<float>.One / (Vector256<float>.One - k); |
||||
|
r = (r * divisor) & nonBlack; |
||||
|
g = (g * divisor) & nonBlack; |
||||
|
b = (b * divisor) & nonBlack; |
||||
|
c0 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)) * maximumValue; |
||||
|
c1 = halfValue + (Vector256_.MultiplyAddEstimate(Vector256.Create(-0.168736F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b)) * maximumValue); |
||||
|
c2 = halfValue + (Vector256_.MultiplyAddEstimate(Vector256.Create(0.5F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b)) * maximumValue); |
||||
|
c3 = k * maximumValue; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3) |
||||
|
{ |
||||
|
Vector512<float> sourceScale = Vector512.Create(SourceScale); |
||||
|
r *= sourceScale; |
||||
|
g *= sourceScale; |
||||
|
b *= sourceScale; |
||||
|
Vector512<float> k = Vector512<float>.One - Vector512.Max(r, Vector512.Max(g, b)); |
||||
|
|
||||
|
// Sixteen lanes normalize chromatic direction independently and retain neutral chroma for black.
|
||||
|
Vector512<float> nonBlack = ~Vector512.Equals(k, Vector512<float>.One); |
||||
|
Vector512<float> divisor = Vector512<float>.One / (Vector512<float>.One - k); |
||||
|
r = (r * divisor) & nonBlack; |
||||
|
g = (g * divisor) & nonBlack; |
||||
|
b = (b * divisor) & nonBlack; |
||||
|
c0 = Vector512_.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)) * maximumValue; |
||||
|
c1 = halfValue + (Vector512_.MultiplyAddEstimate(Vector512.Create(-0.168736F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b)) * maximumValue); |
||||
|
c2 = halfValue + (Vector512_.MultiplyAddEstimate(Vector512.Create(0.5F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)) * maximumValue); |
||||
|
c3 = k * maximumValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,153 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Buffers; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.ColorProfiles; |
|
||||
using SixLabors.ImageSharp.ColorProfiles.Icc; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Color converter for tiff images, which use the jpeg compression and CMYK colorspace.
|
|
||||
/// </summary>
|
|
||||
internal sealed class TiffYccKScalar : JpegColorConverterScalar |
|
||||
{ |
|
||||
// Derived from ITU-T Rec. T.871
|
|
||||
internal const float RCrMult = 1.402f; |
|
||||
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); |
|
||||
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); |
|
||||
internal const float BCbMult = 1.772f; |
|
||||
|
|
||||
public TiffYccKScalar(int precision) |
|
||||
: base(JpegColorSpace.TiffYccK, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
=> ConvertToRgbInPlace(in values, this.MaximumValue, this.HalfValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) |
|
||||
{ |
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
Span<float> c3 = values.Component3; |
|
||||
|
|
||||
float scale = 1F / maxValue; |
|
||||
halfValue *= scale; |
|
||||
|
|
||||
for (int i = 0; i < values.Component0.Length; i++) |
|
||||
{ |
|
||||
float y = c0[i] * scale; |
|
||||
float cb = (c1[i] * scale) - halfValue; |
|
||||
float cr = (c2[i] * scale) - halfValue; |
|
||||
float scaledK = 1 - (c3[i] * scale); |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
c0[i] = (y + (RCrMult * cr)) * scaledK; |
|
||||
c1[i] = (y - (GCbMult * cb) - (GCrMult * cr)) * scaledK; |
|
||||
c2[i] = (y + (BCbMult * cb)) * scaledK; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
Span<float> y = values.Component0; |
|
||||
Span<float> cb = values.Component1; |
|
||||
Span<float> cr = values.Component2; |
|
||||
Span<float> k = values.Component3; |
|
||||
|
|
||||
for (int i = 0; i < cr.Length; i++) |
|
||||
{ |
|
||||
// Scale down to [0-1]
|
|
||||
const float divisor = 1F / 255F; |
|
||||
float r = rLane[i] * divisor; |
|
||||
float g = gLane[i] * divisor; |
|
||||
float b = bLane[i] * divisor; |
|
||||
|
|
||||
float ytmp; |
|
||||
float cbtmp; |
|
||||
float crtmp; |
|
||||
float ktmp = 1F - MathF.Max(r, MathF.Max(g, b)); |
|
||||
|
|
||||
if (ktmp >= 1F) |
|
||||
{ |
|
||||
ytmp = 0F; |
|
||||
cbtmp = 0.5F; |
|
||||
crtmp = 0.5F; |
|
||||
ktmp = maxValue; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
float kmask = 1F / (1F - ktmp); |
|
||||
r *= kmask; |
|
||||
g *= kmask; |
|
||||
b *= kmask; |
|
||||
|
|
||||
// Scale to [0-maxValue]
|
|
||||
ytmp = ((0.299f * r) + (0.587f * g) + (0.114f * b)) * maxValue; |
|
||||
cbtmp = halfValue - (((0.168736f * r) - (0.331264f * g) + (0.5f * b)) * maxValue); |
|
||||
crtmp = halfValue + (((0.5f * r) - (0.418688f * g) - (0.081312f * b)) * maxValue); |
|
||||
ktmp *= maxValue; |
|
||||
} |
|
||||
|
|
||||
y[i] = ytmp; |
|
||||
cb[i] = cbtmp; |
|
||||
cr[i] = crtmp; |
|
||||
k[i] = ktmp; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4); |
|
||||
Span<float> packed = memoryOwner.Memory.Span; |
|
||||
|
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
Span<float> c3 = values.Component3; |
|
||||
|
|
||||
PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); |
|
||||
|
|
||||
ColorProfileConverter converter = new(); |
|
||||
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed); |
|
||||
|
|
||||
// YccK is not a defined ICC color space � it's a JPEG-specific encoding used in Adobe-style CMYK JPEGs.
|
|
||||
// ICC profiles expect colorimetric CMYK values, so we must first convert YccK to CMYK using a hardcoded inverse transform.
|
|
||||
// This transform assumes Rec.601 YCbCr coefficients and an inverted K channel.
|
|
||||
//
|
|
||||
// The YccK => Cmyk conversion is independent of any embedded ICC profile.
|
|
||||
// Since the same RGB working space is used during conversion to and from XYZ,
|
|
||||
// colorimetric accuracy is preserved.
|
|
||||
converter.Convert<YccK, Cmyk>(MemoryMarshal.Cast<Cmyk, YccK>(source), source); |
|
||||
|
|
||||
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length]; |
|
||||
|
|
||||
ColorConversionOptions options = new() |
|
||||
{ |
|
||||
SourceIccProfile = profile, |
|
||||
TargetIccProfile = CompactSrgbV4Profile.Profile, |
|
||||
}; |
|
||||
converter = new ColorProfileConverter(options); |
|
||||
converter.Convert<Cmyk, Rgb>(source, destination); |
|
||||
|
|
||||
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,131 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class TiffYccKVector128 : JpegColorConverterVector128 |
|
||||
{ |
|
||||
public TiffYccKVector128(int precision) |
|
||||
: base(JpegColorSpace.TiffYccK, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector128<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector128<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector128<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector128<float> c3Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
Vector128<float> scale = Vector128.Create(1F / this.MaximumValue); |
|
||||
Vector128<float> chromaOffset = Vector128.Create(this.HalfValue) * scale; |
|
||||
Vector128<float> rCrMult = Vector128.Create(YCbCrScalar.RCrMult); |
|
||||
Vector128<float> gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); |
|
||||
Vector128<float> gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); |
|
||||
Vector128<float> bCbMult = Vector128.Create(YCbCrScalar.BCbMult); |
|
||||
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector128<float> c0 = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector128<float> c1 = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector128<float> c2 = ref Unsafe.Add(ref c2Base, i); |
|
||||
ref Vector128<float> c3 = ref Unsafe.Add(ref c3Base, i); |
|
||||
|
|
||||
Vector128<float> y = c0 * scale; |
|
||||
Vector128<float> cb = (c1 * scale) - chromaOffset; |
|
||||
Vector128<float> cr = (c2 * scale) - chromaOffset; |
|
||||
Vector128<float> scaledK = Vector128<float>.One - (c3 * scale); |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK; |
|
||||
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK; |
|
||||
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK; |
|
||||
|
|
||||
c0 = r; |
|
||||
c1 = g; |
|
||||
c2 = b; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector128<float> srcR = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector128<float> srcG = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector128<float> srcB = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
ref Vector128<float> destY = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector128<float> destCb = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector128<float> destCr = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector128<float> destK = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
Vector128<float> maxSourceValue = Vector128.Create(1 / 255F); |
|
||||
Vector128<float> maxSampleValue = Vector128.Create(this.MaximumValue); |
|
||||
Vector128<float> chromaOffset = Vector128.Create(this.HalfValue); |
|
||||
|
|
||||
Vector128<float> f0299 = Vector128.Create(0.299f); |
|
||||
Vector128<float> f0587 = Vector128.Create(0.587f); |
|
||||
Vector128<float> f0114 = Vector128.Create(0.114f); |
|
||||
Vector128<float> fn0168736 = Vector128.Create(-0.168736f); |
|
||||
Vector128<float> fn0331264 = Vector128.Create(-0.331264f); |
|
||||
Vector128<float> fn0418688 = Vector128.Create(-0.418688f); |
|
||||
Vector128<float> fn0081312F = Vector128.Create(-0.081312F); |
|
||||
Vector128<float> f05 = Vector128.Create(0.5f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector128<float> r = Unsafe.Add(ref srcR, i) * maxSourceValue; |
|
||||
Vector128<float> g = Unsafe.Add(ref srcG, i) * maxSourceValue; |
|
||||
Vector128<float> b = Unsafe.Add(ref srcB, i) * maxSourceValue; |
|
||||
Vector128<float> ktmp = Vector128<float>.One - Vector128.Max(r, Vector128.Min(g, b)); |
|
||||
|
|
||||
Vector128<float> kMask = ~Vector128.Equals(ktmp, Vector128<float>.One); |
|
||||
Vector128<float> divisor = Vector128<float>.One / (Vector128<float>.One - ktmp); |
|
||||
|
|
||||
r = (r * divisor) & kMask; |
|
||||
g = (g * divisor) & kMask; |
|
||||
b = (b * divisor) & kMask; |
|
||||
|
|
||||
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
|
|
||||
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
|
|
||||
Vector128<float> y = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
Vector128<float> cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b)); |
|
||||
Vector128<float> cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); |
|
||||
|
|
||||
Unsafe.Add(ref destY, i) = y * maxSampleValue; |
|
||||
Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); |
|
||||
Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); |
|
||||
Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,131 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class TiffYccKVector256 : JpegColorConverterVector256 |
|
||||
{ |
|
||||
public TiffYccKVector256(int precision) |
|
||||
: base(JpegColorSpace.TiffYccK, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector256<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector256<float> c3Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
Vector256<float> scale = Vector256.Create(1F / this.MaximumValue); |
|
||||
Vector256<float> chromaOffset = Vector256.Create(this.HalfValue) * scale; |
|
||||
Vector256<float> rCrMult = Vector256.Create(YCbCrScalar.RCrMult); |
|
||||
Vector256<float> gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); |
|
||||
Vector256<float> gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); |
|
||||
Vector256<float> bCbMult = Vector256.Create(YCbCrScalar.BCbMult); |
|
||||
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i); |
|
||||
ref Vector256<float> c3 = ref Unsafe.Add(ref c3Base, i); |
|
||||
|
|
||||
Vector256<float> y = c0 * scale; |
|
||||
Vector256<float> cb = (c1 * scale) - chromaOffset; |
|
||||
Vector256<float> cr = (c2 * scale) - chromaOffset; |
|
||||
Vector256<float> scaledK = Vector256<float>.One - (c3 * scale); |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK; |
|
||||
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK; |
|
||||
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK; |
|
||||
|
|
||||
c0 = r; |
|
||||
c1 = g; |
|
||||
c2 = b; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector256<float> srcR = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector256<float> srcG = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector256<float> srcB = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
ref Vector256<float> destY = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> destCb = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> destCr = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector256<float> destK = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
Vector256<float> maxSourceValue = Vector256.Create(255F); |
|
||||
Vector256<float> maxSampleValue = Vector256.Create(this.MaximumValue); |
|
||||
Vector256<float> chromaOffset = Vector256.Create(this.HalfValue); |
|
||||
|
|
||||
Vector256<float> f0299 = Vector256.Create(0.299f); |
|
||||
Vector256<float> f0587 = Vector256.Create(0.587f); |
|
||||
Vector256<float> f0114 = Vector256.Create(0.114f); |
|
||||
Vector256<float> fn0168736 = Vector256.Create(-0.168736f); |
|
||||
Vector256<float> fn0331264 = Vector256.Create(-0.331264f); |
|
||||
Vector256<float> fn0418688 = Vector256.Create(-0.418688f); |
|
||||
Vector256<float> fn0081312F = Vector256.Create(-0.081312F); |
|
||||
Vector256<float> f05 = Vector256.Create(0.5f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector256<float> r = Unsafe.Add(ref srcR, i) / maxSourceValue; |
|
||||
Vector256<float> g = Unsafe.Add(ref srcG, i) / maxSourceValue; |
|
||||
Vector256<float> b = Unsafe.Add(ref srcB, i) / maxSourceValue; |
|
||||
Vector256<float> ktmp = Vector256<float>.One - Vector256.Max(r, Vector256.Min(g, b)); |
|
||||
|
|
||||
Vector256<float> kMask = ~Vector256.Equals(ktmp, Vector256<float>.One); |
|
||||
Vector256<float> divisor = Vector256<float>.One / (Vector256<float>.One - ktmp); |
|
||||
|
|
||||
r = (r * divisor) & kMask; |
|
||||
g = (g * divisor) & kMask; |
|
||||
b = (b * divisor) & kMask; |
|
||||
|
|
||||
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
|
|
||||
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
|
|
||||
Vector256<float> y = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
Vector256<float> cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b)); |
|
||||
Vector256<float> cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); |
|
||||
|
|
||||
Unsafe.Add(ref destY, i) = y * maxSampleValue; |
|
||||
Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); |
|
||||
Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); |
|
||||
Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,142 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class TiffYccKVector512 : JpegColorConverterVector512 |
|
||||
{ |
|
||||
public TiffYccKVector512(int precision) |
|
||||
: base(JpegColorSpace.TiffYccK, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector512<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector512<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector512<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector512<float> c3Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
Vector512<float> scale = Vector512.Create(1F / this.MaximumValue); |
|
||||
Vector512<float> chromaOffset = Vector512.Create(this.HalfValue) * scale; |
|
||||
Vector512<float> rCrMult = Vector512.Create(YCbCrScalar.RCrMult); |
|
||||
Vector512<float> gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); |
|
||||
Vector512<float> gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); |
|
||||
Vector512<float> bCbMult = Vector512.Create(YCbCrScalar.BCbMult); |
|
||||
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector512<float> c0 = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector512<float> c1 = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector512<float> c2 = ref Unsafe.Add(ref c2Base, i); |
|
||||
ref Vector512<float> c3 = ref Unsafe.Add(ref c3Base, i); |
|
||||
|
|
||||
Vector512<float> y = c0 * scale; |
|
||||
Vector512<float> cb = (c1 * scale) - chromaOffset; |
|
||||
Vector512<float> cr = (c2 * scale) - chromaOffset; |
|
||||
Vector512<float> scaledK = Vector512<float>.One - (c3 * scale); |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK; |
|
||||
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK; |
|
||||
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK; |
|
||||
|
|
||||
c0 = r; |
|
||||
c1 = g; |
|
||||
c2 = b; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgbVectorized(in values, this.MaximumValue, this.HalfValue, rLane, gLane, bLane); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) |
|
||||
=> TiffYccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> TiffYccKScalar.ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, float halfValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector512<float> srcR = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector512<float> srcG = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector512<float> srcB = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
ref Vector512<float> destY = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector512<float> destCb = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector512<float> destCr = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector512<float> destK = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
Vector512<float> maxSourceValue = Vector512.Create(255F); |
|
||||
Vector512<float> maxSampleValue = Vector512.Create(maxValue); |
|
||||
Vector512<float> chromaOffset = Vector512.Create(halfValue); |
|
||||
|
|
||||
Vector512<float> f0299 = Vector512.Create(0.299f); |
|
||||
Vector512<float> f0587 = Vector512.Create(0.587f); |
|
||||
Vector512<float> f0114 = Vector512.Create(0.114f); |
|
||||
Vector512<float> fn0168736 = Vector512.Create(-0.168736f); |
|
||||
Vector512<float> fn0331264 = Vector512.Create(-0.331264f); |
|
||||
Vector512<float> fn0418688 = Vector512.Create(-0.418688f); |
|
||||
Vector512<float> fn0081312F = Vector512.Create(-0.081312F); |
|
||||
Vector512<float> f05 = Vector512.Create(0.5f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector512<float> r = Unsafe.Add(ref srcR, i) / maxSourceValue; |
|
||||
Vector512<float> g = Unsafe.Add(ref srcG, i) / maxSourceValue; |
|
||||
Vector512<float> b = Unsafe.Add(ref srcB, i) / maxSourceValue; |
|
||||
Vector512<float> ktmp = Vector512<float>.One - Vector512.Max(r, Vector512.Min(g, b)); |
|
||||
|
|
||||
Vector512<float> kMask = ~Vector512.Equals(ktmp, Vector512<float>.One); |
|
||||
Vector512<float> divisor = Vector512<float>.One / (Vector512<float>.One - ktmp); |
|
||||
|
|
||||
r = (r * divisor) & kMask; |
|
||||
g = (g * divisor) & kMask; |
|
||||
b = (b * divisor) & kMask; |
|
||||
|
|
||||
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
|
|
||||
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
|
|
||||
Vector512<float> y = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
Vector512<float> cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b)); |
|
||||
Vector512<float> cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); |
|
||||
|
|
||||
Unsafe.Add(ref destY, i) = y * maxSampleValue; |
|
||||
Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); |
|
||||
Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); |
|
||||
Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,170 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the JPEG YCbCr conversion formula for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct YCbCrOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The BT.601 red contribution from centered Cr.
|
||||
|
/// </summary>
|
||||
|
public const float RCrMult = 1.402F; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The BT.601 green contribution from centered Cb.
|
||||
|
/// </summary>
|
||||
|
public const float GCbMult = (float)(0.114 * 1.772 / 0.587); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The BT.601 green contribution from centered Cr.
|
||||
|
/// </summary>
|
||||
|
public const float GCrMult = (float)(0.299 * 1.402 / 0.587); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The BT.601 blue contribution from centered Cb.
|
||||
|
/// </summary>
|
||||
|
public const float BCbMult = 1.772F; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.YCbCr; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 3; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) |
||||
|
{ |
||||
|
float y = c0; |
||||
|
float cb = c1 - halfValue; |
||||
|
float cr = c2 - halfValue; |
||||
|
|
||||
|
// c0/c1/c2 initially mean Y/Cb/Cr. Chroma is centered around zero before applying
|
||||
|
// the BT.601 matrix, then integer-domain RGB is rounded away from zero and normalized
|
||||
|
// to [nominally] 0..1. Values intentionally remain unclamped because quantizing RGB into the
|
||||
|
// destination pixel format owns saturation; retaining overshoot avoids discarding color information.
|
||||
|
c0 = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; |
||||
|
c1 = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; |
||||
|
c2 = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector128<float> c0, ref Vector128<float> c1, ref Vector128<float> c2, Vector128<float> c3, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale) |
||||
|
{ |
||||
|
Vector128<float> y = c0; |
||||
|
Vector128<float> cb = c1 - halfValue; |
||||
|
Vector128<float> cr = c2 - halfValue; |
||||
|
|
||||
|
// Lanes are four independent Y/Cb/Cr samples. MultiplyAddEstimate maps to FMA where available:
|
||||
|
// R uses Cr, B uses Cb, and G subtracts both chroma contributions. Rounding occurs in the sample
|
||||
|
// domain before the common normalization scale so all precisions use integer JPEG sample semantics.
|
||||
|
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(RCrMult), y); |
||||
|
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-GCbMult), y)); |
||||
|
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(BCbMult), y); |
||||
|
|
||||
|
c0 = Vector128_.RoundToNearestInteger(r) * scale; |
||||
|
c1 = Vector128_.RoundToNearestInteger(g) * scale; |
||||
|
c2 = Vector128_.RoundToNearestInteger(b) * scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector256<float> c0, ref Vector256<float> c1, ref Vector256<float> c2, Vector256<float> c3, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale) |
||||
|
{ |
||||
|
Vector256<float> y = c0; |
||||
|
Vector256<float> cb = c1 - halfValue; |
||||
|
Vector256<float> cr = c2 - halfValue; |
||||
|
|
||||
|
// These eight lanes have the same layout and BT.601 arithmetic as the Vector128 overload.
|
||||
|
// Keeping an explicit overload allows the JIT to emit native YMM operations without a width
|
||||
|
// switch or decomposing the vector into smaller values.
|
||||
|
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(RCrMult), y); |
||||
|
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-GCbMult), y)); |
||||
|
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(BCbMult), y); |
||||
|
|
||||
|
c0 = Vector256_.RoundToNearestInteger(r) * scale; |
||||
|
c1 = Vector256_.RoundToNearestInteger(g) * scale; |
||||
|
c2 = Vector256_.RoundToNearestInteger(b) * scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector512<float> c0, ref Vector512<float> c1, ref Vector512<float> c2, Vector512<float> c3, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale) |
||||
|
{ |
||||
|
Vector512<float> y = c0; |
||||
|
Vector512<float> cb = c1 - halfValue; |
||||
|
Vector512<float> cr = c2 - halfValue; |
||||
|
|
||||
|
// Sixteen independent samples occupy the ZMM lanes. The explicit constants are broadcasts;
|
||||
|
// assembly inspection verifies the JIT hoists them from the loop and retains fused operations.
|
||||
|
// The formula and rounding order remain identical to the narrower overloads.
|
||||
|
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(RCrMult), y); |
||||
|
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-GCbMult), y)); |
||||
|
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(BCbMult), y); |
||||
|
|
||||
|
c0 = Vector512_.RoundToNearestInteger(r) * scale; |
||||
|
c1 = Vector512_.RoundToNearestInteger(g) * scale; |
||||
|
c2 = Vector512_.RoundToNearestInteger(b) * scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) |
||||
|
{ |
||||
|
// The RGB inputs are unnormalized 0..255 encoder lanes. The BT.601 luma weights form Y,
|
||||
|
// while the signed chroma projections are biased by halfValue into the JPEG sample domain.
|
||||
|
// YCbCr has no fourth component, so c3 is a compile-time-unused placeholder for the shared loop.
|
||||
|
c0 = (0.299F * r) + (0.587F * g) + (0.114F * b); |
||||
|
c1 = halfValue - (0.168736F * r) - (0.331264F * g) + (0.5F * b); |
||||
|
c2 = halfValue + (0.5F * r) - (0.418688F * g) - (0.081312F * b); |
||||
|
c3 = 0; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3) |
||||
|
{ |
||||
|
// Each vector holds four consecutive values from one RGB plane. The nested multiply-add sequence
|
||||
|
// produces four Y lanes, four Cb lanes, and four Cr lanes without transposition. The association
|
||||
|
// exposes two FMA opportunities per output while preserving the scalar formula's term grouping.
|
||||
|
c0 = Vector128_.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)); |
||||
|
c1 = halfValue + Vector128_.MultiplyAddEstimate(Vector128.Create(-0.168736F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b)); |
||||
|
c2 = halfValue + Vector128_.MultiplyAddEstimate(Vector128.Create(0.5F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b)); |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3) |
||||
|
{ |
||||
|
// Eight planar RGB samples use the identical association as Vector128, allowing direct YMM FMA
|
||||
|
// generation while preserving the component-per-vector output layout.
|
||||
|
c0 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)); |
||||
|
c1 = halfValue + Vector256_.MultiplyAddEstimate(Vector256.Create(-0.168736F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b)); |
||||
|
c2 = halfValue + Vector256_.MultiplyAddEstimate(Vector256.Create(0.5F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b)); |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3) |
||||
|
{ |
||||
|
// Sixteen planar RGB samples use the same nested form. Constants are lane broadcasts and c3 is
|
||||
|
// deliberately zero because the shared traversal removes the unused fourth store for this operator.
|
||||
|
c0 = Vector512_.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)); |
||||
|
c1 = halfValue + Vector512_.MultiplyAddEstimate(Vector512.Create(-0.168736F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b)); |
||||
|
c2 = halfValue + Vector512_.MultiplyAddEstimate(Vector512.Create(0.5F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)); |
||||
|
c3 = default; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,121 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Buffers; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.ColorProfiles; |
|
||||
using SixLabors.ImageSharp.ColorProfiles.Icc; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class YCbCrScalar : JpegColorConverterScalar |
|
||||
{ |
|
||||
// derived from ITU-T Rec. T.871
|
|
||||
internal const float RCrMult = 1.402f; |
|
||||
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); |
|
||||
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); |
|
||||
internal const float BCbMult = 1.772f; |
|
||||
|
|
||||
public YCbCrScalar(int precision) |
|
||||
: base(JpegColorSpace.YCbCr, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
=> ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgb(values, this.HalfValue, rLane, gLane, bLane); |
|
||||
|
|
||||
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) |
|
||||
{ |
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
|
|
||||
float scale = 1 / maxValue; |
|
||||
|
|
||||
for (int i = 0; i < c0.Length; i++) |
|
||||
{ |
|
||||
float y = c0[i]; |
|
||||
float cb = c1[i] - halfValue; |
|
||||
float cr = c2[i] - halfValue; |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; |
|
||||
c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; |
|
||||
c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3); |
|
||||
Span<float> packed = memoryOwner.Memory.Span; |
|
||||
|
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
|
|
||||
// Although YCbCr is a defined ICC color space, in practice ICC profiles
|
|
||||
// do not implement transforms from it.
|
|
||||
// Therefore, we first convert JPEG YCbCr to RGB manually, then perform
|
|
||||
// color-managed conversion to the target profile.
|
|
||||
//
|
|
||||
// The YCbCr => RGB conversion is based on BT.601 and is independent of any embedded ICC profile.
|
|
||||
// Since the same RGB working space is used during conversion to and from XYZ,
|
|
||||
// colorimetric accuracy is preserved.
|
|
||||
ColorProfileConverter converter = new(); |
|
||||
|
|
||||
PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maxValue); |
|
||||
|
|
||||
Span<YCbCr> source = MemoryMarshal.Cast<float, YCbCr>(packed); |
|
||||
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed); |
|
||||
|
|
||||
converter.Convert<YCbCr, Rgb>(source, destination); |
|
||||
|
|
||||
ColorConversionOptions options = new() |
|
||||
{ |
|
||||
SourceIccProfile = profile, |
|
||||
TargetIccProfile = CompactSrgbV4Profile.Profile, |
|
||||
}; |
|
||||
converter = new ColorProfileConverter(options); |
|
||||
converter.Convert<Rgb, Rgb>(destination, destination); |
|
||||
|
|
||||
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2); |
|
||||
} |
|
||||
|
|
||||
public static void ConvertFromRgb(in ComponentValues values, float halfValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
Span<float> y = values.Component0; |
|
||||
Span<float> cb = values.Component1; |
|
||||
Span<float> cr = values.Component2; |
|
||||
|
|
||||
for (int i = 0; i < y.Length; i++) |
|
||||
{ |
|
||||
float r = rLane[i]; |
|
||||
float g = gLane[i]; |
|
||||
float b = bLane[i]; |
|
||||
|
|
||||
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
|
|
||||
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
|
|
||||
y[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); |
|
||||
cb[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); |
|
||||
cr[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,121 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class YCbCrVector128 : JpegColorConverterVector128 |
|
||||
{ |
|
||||
public YCbCrVector128(int precision) |
|
||||
: base(JpegColorSpace.YCbCr, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector128<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector128<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector128<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
Vector128<float> chromaOffset = Vector128.Create(-this.HalfValue); |
|
||||
Vector128<float> scale = Vector128.Create(1 / this.MaximumValue); |
|
||||
Vector128<float> rCrMult = Vector128.Create(YCbCrScalar.RCrMult); |
|
||||
Vector128<float> gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); |
|
||||
Vector128<float> gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); |
|
||||
Vector128<float> bCbMult = Vector128.Create(YCbCrScalar.BCbMult); |
|
||||
|
|
||||
// Walking 8 elements at one step:
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
// y = yVals[i];
|
|
||||
// cb = cbVals[i] - 128F;
|
|
||||
// cr = crVals[i] - 128F;
|
|
||||
ref Vector128<float> c0 = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector128<float> c1 = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector128<float> c2 = ref Unsafe.Add(ref c2Base, i); |
|
||||
|
|
||||
Vector128<float> y = c0; |
|
||||
Vector128<float> cb = c1 + chromaOffset; |
|
||||
Vector128<float> cr = c2 + chromaOffset; |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, rCrMult, y); |
|
||||
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y)); |
|
||||
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y); |
|
||||
|
|
||||
r = Vector128_.RoundToNearestInteger(r) * scale; |
|
||||
g = Vector128_.RoundToNearestInteger(g) * scale; |
|
||||
b = Vector128_.RoundToNearestInteger(b) * scale; |
|
||||
|
|
||||
c0 = r; |
|
||||
c1 = g; |
|
||||
c2 = b; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector128<float> destY = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector128<float> destCb = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector128<float> destCr = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
ref Vector128<float> srcR = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector128<float> srcG = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector128<float> srcB = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
Vector128<float> chromaOffset = Vector128.Create(this.HalfValue); |
|
||||
Vector128<float> f0299 = Vector128.Create(0.299f); |
|
||||
Vector128<float> f0587 = Vector128.Create(0.587f); |
|
||||
Vector128<float> f0114 = Vector128.Create(0.114f); |
|
||||
Vector128<float> fn0168736 = Vector128.Create(-0.168736f); |
|
||||
Vector128<float> fn0331264 = Vector128.Create(-0.331264f); |
|
||||
Vector128<float> fn0418688 = Vector128.Create(-0.418688f); |
|
||||
Vector128<float> fn0081312F = Vector128.Create(-0.081312F); |
|
||||
Vector128<float> f05 = Vector128.Create(0.5f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector128<float> r = Unsafe.Add(ref srcR, i); |
|
||||
Vector128<float> g = Unsafe.Add(ref srcG, i); |
|
||||
Vector128<float> b = Unsafe.Add(ref srcB, i); |
|
||||
|
|
||||
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
|
|
||||
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
|
|
||||
Vector128<float> y = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
Vector128<float> cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b)); |
|
||||
Vector128<float> cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); |
|
||||
|
|
||||
Unsafe.Add(ref destY, i) = y; |
|
||||
Unsafe.Add(ref destCb, i) = cb; |
|
||||
Unsafe.Add(ref destCr, i) = cr; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,121 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class YCbCrVector256 : JpegColorConverterVector256 |
|
||||
{ |
|
||||
public YCbCrVector256(int precision) |
|
||||
: base(JpegColorSpace.YCbCr, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector256<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
Vector256<float> chromaOffset = Vector256.Create(-this.HalfValue); |
|
||||
Vector256<float> scale = Vector256.Create(1 / this.MaximumValue); |
|
||||
Vector256<float> rCrMult = Vector256.Create(YCbCrScalar.RCrMult); |
|
||||
Vector256<float> gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); |
|
||||
Vector256<float> gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); |
|
||||
Vector256<float> bCbMult = Vector256.Create(YCbCrScalar.BCbMult); |
|
||||
|
|
||||
// Walking 8 elements at one step:
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
// y = yVals[i];
|
|
||||
// cb = cbVals[i] - 128F;
|
|
||||
// cr = crVals[i] - 128F;
|
|
||||
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i); |
|
||||
|
|
||||
Vector256<float> y = c0; |
|
||||
Vector256<float> cb = c1 + chromaOffset; |
|
||||
Vector256<float> cr = c2 + chromaOffset; |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, rCrMult, y); |
|
||||
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y)); |
|
||||
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y); |
|
||||
|
|
||||
r = Vector256_.RoundToNearestInteger(r) * scale; |
|
||||
g = Vector256_.RoundToNearestInteger(g) * scale; |
|
||||
b = Vector256_.RoundToNearestInteger(b) * scale; |
|
||||
|
|
||||
c0 = r; |
|
||||
c1 = g; |
|
||||
c2 = b; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector256<float> destY = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> destCb = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> destCr = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
ref Vector256<float> srcR = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector256<float> srcG = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector256<float> srcB = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
Vector256<float> chromaOffset = Vector256.Create(this.HalfValue); |
|
||||
Vector256<float> f0299 = Vector256.Create(0.299f); |
|
||||
Vector256<float> f0587 = Vector256.Create(0.587f); |
|
||||
Vector256<float> f0114 = Vector256.Create(0.114f); |
|
||||
Vector256<float> fn0168736 = Vector256.Create(-0.168736f); |
|
||||
Vector256<float> fn0331264 = Vector256.Create(-0.331264f); |
|
||||
Vector256<float> fn0418688 = Vector256.Create(-0.418688f); |
|
||||
Vector256<float> fn0081312F = Vector256.Create(-0.081312F); |
|
||||
Vector256<float> f05 = Vector256.Create(0.5f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector256<float> r = Unsafe.Add(ref srcR, i); |
|
||||
Vector256<float> g = Unsafe.Add(ref srcG, i); |
|
||||
Vector256<float> b = Unsafe.Add(ref srcB, i); |
|
||||
|
|
||||
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
|
|
||||
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
|
|
||||
Vector256<float> y = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
Vector256<float> cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b)); |
|
||||
Vector256<float> cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); |
|
||||
|
|
||||
Unsafe.Add(ref destY, i) = y; |
|
||||
Unsafe.Add(ref destCb, i) = cb; |
|
||||
Unsafe.Add(ref destCr, i) = cr; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,128 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class YCbCrVector512 : JpegColorConverterVector512 |
|
||||
{ |
|
||||
public YCbCrVector512(int precision) |
|
||||
: base(JpegColorSpace.YCbCr, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector512<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector512<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector512<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
Vector512<float> chromaOffset = Vector512.Create(-this.HalfValue); |
|
||||
Vector512<float> scale = Vector512.Create(1 / this.MaximumValue); |
|
||||
Vector512<float> rCrMult = Vector512.Create(YCbCrScalar.RCrMult); |
|
||||
Vector512<float> gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); |
|
||||
Vector512<float> gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); |
|
||||
Vector512<float> bCbMult = Vector512.Create(YCbCrScalar.BCbMult); |
|
||||
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
// y = yVals[i];
|
|
||||
// cb = cbVals[i] - 128F;
|
|
||||
// cr = crVals[i] - 128F;
|
|
||||
ref Vector512<float> c0 = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector512<float> c1 = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector512<float> c2 = ref Unsafe.Add(ref c2Base, i); |
|
||||
|
|
||||
Vector512<float> y = c0; |
|
||||
Vector512<float> cb = c1 + chromaOffset; |
|
||||
Vector512<float> cr = c2 + chromaOffset; |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, rCrMult, y); |
|
||||
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y)); |
|
||||
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y); |
|
||||
|
|
||||
r = Vector512_.RoundToNearestInteger(r) * scale; |
|
||||
g = Vector512_.RoundToNearestInteger(g) * scale; |
|
||||
b = Vector512_.RoundToNearestInteger(b) * scale; |
|
||||
|
|
||||
c0 = r; |
|
||||
c1 = g; |
|
||||
c2 = b; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
ref Vector512<float> destY = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector512<float> destCb = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector512<float> destCr = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
ref Vector512<float> srcR = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(rLane)); |
|
||||
ref Vector512<float> srcG = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(gLane)); |
|
||||
ref Vector512<float> srcB = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(bLane)); |
|
||||
|
|
||||
Vector512<float> chromaOffset = Vector512.Create(this.HalfValue); |
|
||||
Vector512<float> f0299 = Vector512.Create(0.299f); |
|
||||
Vector512<float> f0587 = Vector512.Create(0.587f); |
|
||||
Vector512<float> f0114 = Vector512.Create(0.114f); |
|
||||
Vector512<float> fn0168736 = Vector512.Create(-0.168736f); |
|
||||
Vector512<float> fn0331264 = Vector512.Create(-0.331264f); |
|
||||
Vector512<float> fn0418688 = Vector512.Create(-0.418688f); |
|
||||
Vector512<float> fn0081312F = Vector512.Create(-0.081312F); |
|
||||
Vector512<float> f05 = Vector512.Create(0.5f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector512<float> r = Unsafe.Add(ref srcR, i); |
|
||||
Vector512<float> g = Unsafe.Add(ref srcG, i); |
|
||||
Vector512<float> b = Unsafe.Add(ref srcB, i); |
|
||||
|
|
||||
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
|
|
||||
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
|
|
||||
Vector512<float> y = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
Vector512<float> cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b)); |
|
||||
Vector512<float> cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); |
|
||||
|
|
||||
Unsafe.Add(ref destY, i) = y; |
|
||||
Unsafe.Add(ref destCb, i) = cb; |
|
||||
Unsafe.Add(ref destCr, i) = cr; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) |
|
||||
=> YCbCrScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> YCbCrScalar.ConvertFromRgb(values, this.HalfValue, rLane, gLane, bLane); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,134 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements inverted JPEG YccK conversion for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct YccKOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.Ycck; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 4; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) |
||||
|
{ |
||||
|
float y = c0; |
||||
|
float cb = c1 - halfValue; |
||||
|
float cr = c2 - halfValue; |
||||
|
float scaledK = c3 * scale * scale; |
||||
|
|
||||
|
// YccK first reconstructs inverted RGB in the integer sample domain. Rounding must occur before
|
||||
|
// subtracting from max and applying K because changing that order changes encoded JPEG semantics.
|
||||
|
c0 = (maximumValue - MathF.Round(y + (YCbCrOperator.RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; |
||||
|
c1 = (maximumValue - MathF.Round(y - (YCbCrOperator.GCbMult * cb) - (YCbCrOperator.GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; |
||||
|
c2 = (maximumValue - MathF.Round(y + (YCbCrOperator.BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector128<float> c0, ref Vector128<float> c1, ref Vector128<float> c2, Vector128<float> c3, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale) |
||||
|
{ |
||||
|
Vector128<float> y = c0; |
||||
|
Vector128<float> cb = c1 - halfValue; |
||||
|
Vector128<float> cr = c2 - halfValue; |
||||
|
Vector128<float> scaledK = c3 * scale * scale; |
||||
|
|
||||
|
// Four lanes reconstruct YCbCr concurrently; each rounded result is inverted and modulated by its K lane.
|
||||
|
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y); |
||||
|
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y)); |
||||
|
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y); |
||||
|
c0 = (maximumValue - Vector128_.RoundToNearestInteger(r)) * scaledK; |
||||
|
c1 = (maximumValue - Vector128_.RoundToNearestInteger(g)) * scaledK; |
||||
|
c2 = (maximumValue - Vector128_.RoundToNearestInteger(b)) * scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector256<float> c0, ref Vector256<float> c1, ref Vector256<float> c2, Vector256<float> c3, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale) |
||||
|
{ |
||||
|
Vector256<float> y = c0; |
||||
|
Vector256<float> cb = c1 - halfValue; |
||||
|
Vector256<float> cr = c2 - halfValue; |
||||
|
Vector256<float> scaledK = c3 * scale * scale; |
||||
|
|
||||
|
// Eight lanes retain planar alignment from Y/Cb/Cr/K through normalized RGB.
|
||||
|
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y); |
||||
|
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y)); |
||||
|
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y); |
||||
|
c0 = (maximumValue - Vector256_.RoundToNearestInteger(r)) * scaledK; |
||||
|
c1 = (maximumValue - Vector256_.RoundToNearestInteger(g)) * scaledK; |
||||
|
c2 = (maximumValue - Vector256_.RoundToNearestInteger(b)) * scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector512<float> c0, ref Vector512<float> c1, ref Vector512<float> c2, Vector512<float> c3, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale) |
||||
|
{ |
||||
|
Vector512<float> y = c0; |
||||
|
Vector512<float> cb = c1 - halfValue; |
||||
|
Vector512<float> cr = c2 - halfValue; |
||||
|
Vector512<float> scaledK = c3 * scale * scale; |
||||
|
|
||||
|
// Sixteen lanes use the same matrix, rounding, inversion, and K modulation order as scalar code.
|
||||
|
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y); |
||||
|
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y)); |
||||
|
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y); |
||||
|
c0 = (maximumValue - Vector512_.RoundToNearestInteger(r)) * scaledK; |
||||
|
c1 = (maximumValue - Vector512_.RoundToNearestInteger(g)) * scaledK; |
||||
|
c2 = (maximumValue - Vector512_.RoundToNearestInteger(b)) * scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) |
||||
|
{ |
||||
|
// CMYK extraction supplies inverted chromatic samples and K. Reflecting the first three results
|
||||
|
// reconstructs the chromatic RGB that YCbCr encodes, while K passes through untouched.
|
||||
|
CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out float c, out float m, out float y, out c3); |
||||
|
|
||||
|
YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3) |
||||
|
{ |
||||
|
// Static constrained calls inline both stages, keeping four pixels in registers without materializing CMYK planes.
|
||||
|
CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector128<float> c, out Vector128<float> m, out Vector128<float> y, out c3); |
||||
|
|
||||
|
YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3) |
||||
|
{ |
||||
|
// Eight pixels flow through CMYK extraction and YCbCr projection entirely in YMM registers.
|
||||
|
CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector256<float> c, out Vector256<float> m, out Vector256<float> y, out c3); |
||||
|
|
||||
|
YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3) |
||||
|
{ |
||||
|
// Sixteen pixels flow through both mathematical stages in registers without materializing intermediate planes.
|
||||
|
CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector512<float> c, out Vector512<float> m, out Vector512<float> y, out c3); |
||||
|
|
||||
|
YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,125 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Buffers; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.ColorProfiles; |
|
||||
using SixLabors.ImageSharp.ColorProfiles.Icc; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class YccKScalar : JpegColorConverterScalar |
|
||||
{ |
|
||||
// Derived from ITU-T Rec. T.871
|
|
||||
internal const float RCrMult = 1.402f; |
|
||||
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); |
|
||||
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); |
|
||||
internal const float BCbMult = 1.772f; |
|
||||
|
|
||||
public YccKScalar(int precision) |
|
||||
: base(JpegColorSpace.Ycck, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
=> ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) |
|
||||
{ |
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
Span<float> c3 = values.Component3; |
|
||||
|
|
||||
float scale = 1 / (maxValue * maxValue); |
|
||||
|
|
||||
for (int i = 0; i < values.Component0.Length; i++) |
|
||||
{ |
|
||||
float y = c0[i]; |
|
||||
float cb = c1[i] - halfValue; |
|
||||
float cr = c2[i] - halfValue; |
|
||||
float scaledK = c3[i] * scale; |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
c0[i] = (maxValue - MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; |
|
||||
c1[i] = (maxValue - MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; |
|
||||
c2[i] = (maxValue - MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
// rgb -> cmyk
|
|
||||
CmykScalar.ConvertFromRgb(in values, maxValue, rLane, gLane, bLane); |
|
||||
|
|
||||
// cmyk -> ycck
|
|
||||
Span<float> c = values.Component0; |
|
||||
Span<float> m = values.Component1; |
|
||||
Span<float> y = values.Component2; |
|
||||
|
|
||||
for (int i = 0; i < y.Length; i++) |
|
||||
{ |
|
||||
float r = maxValue - c[i]; |
|
||||
float g = maxValue - m[i]; |
|
||||
float b = maxValue - y[i]; |
|
||||
|
|
||||
// k value is passed untouched from rgb -> cmyk conversion
|
|
||||
c[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); |
|
||||
m[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); |
|
||||
y[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4); |
|
||||
Span<float> packed = memoryOwner.Memory.Span; |
|
||||
|
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
Span<float> c3 = values.Component3; |
|
||||
|
|
||||
PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); |
|
||||
|
|
||||
ColorProfileConverter converter = new(); |
|
||||
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed); |
|
||||
|
|
||||
// YccK is not a defined ICC color space — it's a JPEG-specific encoding used in Adobe-style CMYK JPEGs.
|
|
||||
// ICC profiles expect colorimetric CMYK values, so we must first convert YccK to CMYK using a hardcoded inverse transform.
|
|
||||
// This transform assumes Rec.601 YCbCr coefficients and an inverted K channel.
|
|
||||
//
|
|
||||
// The YccK => Cmyk conversion is independent of any embedded ICC profile.
|
|
||||
// Since the same RGB working space is used during conversion to and from XYZ,
|
|
||||
// colorimetric accuracy is preserved.
|
|
||||
converter.Convert<YccK, Cmyk>(MemoryMarshal.Cast<Cmyk, YccK>(source), source); |
|
||||
|
|
||||
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length]; |
|
||||
|
|
||||
ColorConversionOptions options = new() |
|
||||
{ |
|
||||
SourceIccProfile = profile, |
|
||||
TargetIccProfile = CompactSrgbV4Profile.Profile, |
|
||||
}; |
|
||||
converter = new ColorProfileConverter(options); |
|
||||
converter.Convert<Cmyk, Rgb>(source, destination); |
|
||||
|
|
||||
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,135 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class YccKVector128 : JpegColorConverterVector128 |
|
||||
{ |
|
||||
public YccKVector128(int precision) |
|
||||
: base(JpegColorSpace.Ycck, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector128<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector128<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector128<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector128<float> kBase = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector128<float> chromaOffset = Vector128.Create(-this.HalfValue); |
|
||||
Vector128<float> scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue)); |
|
||||
Vector128<float> max = Vector128.Create(this.MaximumValue); |
|
||||
Vector128<float> rCrMult = Vector128.Create(YCbCrScalar.RCrMult); |
|
||||
Vector128<float> gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); |
|
||||
Vector128<float> gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); |
|
||||
Vector128<float> bCbMult = Vector128.Create(YCbCrScalar.BCbMult); |
|
||||
|
|
||||
// Walking 8 elements at one step:
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
// y = yVals[i];
|
|
||||
// cb = cbVals[i] - 128F;
|
|
||||
// cr = crVals[i] - 128F;
|
|
||||
// k = kVals[i] / 256F;
|
|
||||
ref Vector128<float> c0 = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector128<float> c1 = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector128<float> c2 = ref Unsafe.Add(ref c2Base, i); |
|
||||
Vector128<float> y = c0; |
|
||||
Vector128<float> cb = c1 + chromaOffset; |
|
||||
Vector128<float> cr = c2 + chromaOffset; |
|
||||
Vector128<float> scaledK = Unsafe.Add(ref kBase, i) * scale; |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, rCrMult, y); |
|
||||
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y)); |
|
||||
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y); |
|
||||
|
|
||||
r = max - Vector128_.RoundToNearestInteger(r); |
|
||||
g = max - Vector128_.RoundToNearestInteger(g); |
|
||||
b = max - Vector128_.RoundToNearestInteger(b); |
|
||||
|
|
||||
r *= scaledK; |
|
||||
g *= scaledK; |
|
||||
b *= scaledK; |
|
||||
|
|
||||
c0 = r; |
|
||||
c1 = g; |
|
||||
c2 = b; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
// rgb -> cmyk
|
|
||||
CmykVector128.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
// cmyk -> ycck
|
|
||||
ref Vector128<float> destY = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector128<float> destCb = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector128<float> destCr = |
|
||||
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
ref Vector128<float> srcR = ref destY; |
|
||||
ref Vector128<float> srcG = ref destCb; |
|
||||
ref Vector128<float> srcB = ref destCr; |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector128<float> maxSampleValue = Vector128.Create(this.MaximumValue); |
|
||||
|
|
||||
Vector128<float> chromaOffset = Vector128.Create(this.HalfValue); |
|
||||
|
|
||||
Vector128<float> f0299 = Vector128.Create(0.299f); |
|
||||
Vector128<float> f0587 = Vector128.Create(0.587f); |
|
||||
Vector128<float> f0114 = Vector128.Create(0.114f); |
|
||||
Vector128<float> fn0168736 = Vector128.Create(-0.168736f); |
|
||||
Vector128<float> fn0331264 = Vector128.Create(-0.331264f); |
|
||||
Vector128<float> fn0418688 = Vector128.Create(-0.418688f); |
|
||||
Vector128<float> fn0081312F = Vector128.Create(-0.081312F); |
|
||||
Vector128<float> f05 = Vector128.Create(0.5f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector128Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector128<float> r = maxSampleValue - Unsafe.Add(ref srcR, i); |
|
||||
Vector128<float> g = maxSampleValue - Unsafe.Add(ref srcG, i); |
|
||||
Vector128<float> b = maxSampleValue - Unsafe.Add(ref srcB, i); |
|
||||
|
|
||||
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
|
|
||||
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
|
|
||||
Vector128<float> y = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
Vector128<float> cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b)); |
|
||||
Vector128<float> cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); |
|
||||
|
|
||||
Unsafe.Add(ref destY, i) = y; |
|
||||
Unsafe.Add(ref destCb, i) = cb; |
|
||||
Unsafe.Add(ref destCr, i) = cr; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,135 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class YccKVector256 : JpegColorConverterVector256 |
|
||||
{ |
|
||||
public YccKVector256(int precision) |
|
||||
: base(JpegColorSpace.Ycck, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector256<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector256<float> kBase = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector256<float> chromaOffset = Vector256.Create(-this.HalfValue); |
|
||||
Vector256<float> scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); |
|
||||
Vector256<float> max = Vector256.Create(this.MaximumValue); |
|
||||
Vector256<float> rCrMult = Vector256.Create(YCbCrScalar.RCrMult); |
|
||||
Vector256<float> gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); |
|
||||
Vector256<float> gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); |
|
||||
Vector256<float> bCbMult = Vector256.Create(YCbCrScalar.BCbMult); |
|
||||
|
|
||||
// Walking 8 elements at one step:
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
// y = yVals[i];
|
|
||||
// cb = cbVals[i] - 128F;
|
|
||||
// cr = crVals[i] - 128F;
|
|
||||
// k = kVals[i] / 256F;
|
|
||||
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i); |
|
||||
Vector256<float> y = c0; |
|
||||
Vector256<float> cb = c1 + chromaOffset; |
|
||||
Vector256<float> cr = c2 + chromaOffset; |
|
||||
Vector256<float> scaledK = Unsafe.Add(ref kBase, i) * scale; |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, rCrMult, y); |
|
||||
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y)); |
|
||||
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y); |
|
||||
|
|
||||
r = max - Vector256_.RoundToNearestInteger(r); |
|
||||
g = max - Vector256_.RoundToNearestInteger(g); |
|
||||
b = max - Vector256_.RoundToNearestInteger(b); |
|
||||
|
|
||||
r *= scaledK; |
|
||||
g *= scaledK; |
|
||||
b *= scaledK; |
|
||||
|
|
||||
c0 = r; |
|
||||
c1 = g; |
|
||||
c2 = b; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
// rgb -> cmyk
|
|
||||
CmykVector256.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
// cmyk -> ycck
|
|
||||
ref Vector256<float> destY = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> destCb = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> destCr = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
ref Vector256<float> srcR = ref destY; |
|
||||
ref Vector256<float> srcG = ref destCb; |
|
||||
ref Vector256<float> srcB = ref destCr; |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector256<float> maxSampleValue = Vector256.Create(this.MaximumValue); |
|
||||
|
|
||||
Vector256<float> chromaOffset = Vector256.Create(this.HalfValue); |
|
||||
|
|
||||
Vector256<float> f0299 = Vector256.Create(0.299f); |
|
||||
Vector256<float> f0587 = Vector256.Create(0.587f); |
|
||||
Vector256<float> f0114 = Vector256.Create(0.114f); |
|
||||
Vector256<float> fn0168736 = Vector256.Create(-0.168736f); |
|
||||
Vector256<float> fn0331264 = Vector256.Create(-0.331264f); |
|
||||
Vector256<float> fn0418688 = Vector256.Create(-0.418688f); |
|
||||
Vector256<float> fn0081312F = Vector256.Create(-0.081312F); |
|
||||
Vector256<float> f05 = Vector256.Create(0.5f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector256Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector256<float> r = maxSampleValue - Unsafe.Add(ref srcR, i); |
|
||||
Vector256<float> g = maxSampleValue - Unsafe.Add(ref srcG, i); |
|
||||
Vector256<float> b = maxSampleValue - Unsafe.Add(ref srcB, i); |
|
||||
|
|
||||
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
|
|
||||
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
|
|
||||
Vector256<float> y = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
Vector256<float> cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b)); |
|
||||
Vector256<float> cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); |
|
||||
|
|
||||
Unsafe.Add(ref destY, i) = y; |
|
||||
Unsafe.Add(ref destCb, i) = cb; |
|
||||
Unsafe.Add(ref destCr, i) = cr; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,143 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
internal sealed class YccKVector512 : JpegColorConverterVector512 |
|
||||
{ |
|
||||
public YccKVector512(int precision) |
|
||||
: base(JpegColorSpace.Ycck, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector512<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector512<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector512<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector512<float> kBase = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector512<float> chromaOffset = Vector512.Create(-this.HalfValue); |
|
||||
Vector512<float> scale = Vector512.Create(1 / (this.MaximumValue * this.MaximumValue)); |
|
||||
Vector512<float> max = Vector512.Create(this.MaximumValue); |
|
||||
Vector512<float> rCrMult = Vector512.Create(YCbCrScalar.RCrMult); |
|
||||
Vector512<float> gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); |
|
||||
Vector512<float> gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); |
|
||||
Vector512<float> bCbMult = Vector512.Create(YCbCrScalar.BCbMult); |
|
||||
|
|
||||
// Walking 8 elements at one step:
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
// y = yVals[i];
|
|
||||
// cb = cbVals[i] - 128F;
|
|
||||
// cr = crVals[i] - 128F;
|
|
||||
// k = kVals[i] / 256F;
|
|
||||
ref Vector512<float> c0 = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector512<float> c1 = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector512<float> c2 = ref Unsafe.Add(ref c2Base, i); |
|
||||
Vector512<float> y = c0; |
|
||||
Vector512<float> cb = c1 + chromaOffset; |
|
||||
Vector512<float> cr = c2 + chromaOffset; |
|
||||
Vector512<float> scaledK = Unsafe.Add(ref kBase, i) * scale; |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, rCrMult, y); |
|
||||
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y)); |
|
||||
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y); |
|
||||
|
|
||||
r = max - Vector512_.RoundToNearestInteger(r); |
|
||||
g = max - Vector512_.RoundToNearestInteger(g); |
|
||||
b = max - Vector512_.RoundToNearestInteger(b); |
|
||||
|
|
||||
r *= scaledK; |
|
||||
g *= scaledK; |
|
||||
b *= scaledK; |
|
||||
|
|
||||
c0 = r; |
|
||||
c1 = g; |
|
||||
c2 = b; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
|
||||
=> YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) |
|
||||
=> YccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
// rgb -> cmyk
|
|
||||
CmykVector512.ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); |
|
||||
|
|
||||
// cmyk -> ycck
|
|
||||
ref Vector512<float> destY = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector512<float> destCb = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector512<float> destCr = |
|
||||
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
ref Vector512<float> srcR = ref destY; |
|
||||
ref Vector512<float> srcG = ref destCb; |
|
||||
ref Vector512<float> srcB = ref destCr; |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
Vector512<float> maxSampleValue = Vector512.Create(this.MaximumValue); |
|
||||
|
|
||||
Vector512<float> chromaOffset = Vector512.Create(this.HalfValue); |
|
||||
|
|
||||
Vector512<float> f0299 = Vector512.Create(0.299f); |
|
||||
Vector512<float> f0587 = Vector512.Create(0.587f); |
|
||||
Vector512<float> f0114 = Vector512.Create(0.114f); |
|
||||
Vector512<float> fn0168736 = Vector512.Create(-0.168736f); |
|
||||
Vector512<float> fn0331264 = Vector512.Create(-0.331264f); |
|
||||
Vector512<float> fn0418688 = Vector512.Create(-0.418688f); |
|
||||
Vector512<float> fn0081312F = Vector512.Create(-0.081312F); |
|
||||
Vector512<float> f05 = Vector512.Create(0.5f); |
|
||||
|
|
||||
nuint n = values.Component0.Vector512Count<float>(); |
|
||||
for (nuint i = 0; i < n; i++) |
|
||||
{ |
|
||||
Vector512<float> r = maxSampleValue - Unsafe.Add(ref srcR, i); |
|
||||
Vector512<float> g = maxSampleValue - Unsafe.Add(ref srcG, i); |
|
||||
Vector512<float> b = maxSampleValue - Unsafe.Add(ref srcB, i); |
|
||||
|
|
||||
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
|
|
||||
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
|
|
||||
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
|
|
||||
Vector512<float> y = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); |
|
||||
Vector512<float> cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b)); |
|
||||
Vector512<float> cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); |
|
||||
|
|
||||
Unsafe.Add(ref destY, i) = y; |
|
||||
Unsafe.Add(ref destCb, i) = cb; |
|
||||
Unsafe.Add(ref destCr, i) = cr; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
=> YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,140 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
#nullable disable |
||||
|
|
||||
|
using System.Buffers; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using SixLabors.ImageSharp.ColorProfiles; |
||||
|
using SixLabors.ImageSharp.ColorProfiles.Icc; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Converts planar jpeg component values in <paramref name="values"/> to RGB color space in-place using the given ICC profile.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration instance to use for the conversion.</param>
|
||||
|
/// <param name="values">The input/output as a stack-only <see cref="ComponentValues"/> struct.</param>
|
||||
|
/// <param name="profile">The ICC profile to use for the conversion.</param>
|
||||
|
public void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
||||
|
{ |
||||
|
Span<float> c0 = values.Component0; |
||||
|
Span<float> c1 = values.Component1; |
||||
|
Span<float> c2 = values.Component2; |
||||
|
int length = c0.Length; |
||||
|
|
||||
|
// Four-component JPEG models need room for an interleaved CMYK or YccK source. Every conversion
|
||||
|
// finishes with three packed RGB floats, which safely occupy the start of the same temporary buffer.
|
||||
|
bool hasFourthComponent = this.ColorSpace is JpegColorSpace.Ycck |
||||
|
or JpegColorSpace.Cmyk |
||||
|
or JpegColorSpace.TiffYccK |
||||
|
or JpegColorSpace.TiffCmyk; |
||||
|
int packedComponentCount = hasFourthComponent ? 4 : 3; |
||||
|
|
||||
|
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(length * packedComponentCount); |
||||
|
Span<float> packed = memoryOwner.Memory.Span; |
||||
|
|
||||
|
if (this.ColorSpace == JpegColorSpace.Grayscale) |
||||
|
{ |
||||
|
// The single luminance plane is the ICC source, so it is normalized in place. The temporary
|
||||
|
// buffer is still RGB-sized because the profile conversion expands each Y sample to three lanes.
|
||||
|
TensorPrimitives_.Multiply(c0, 1F / this.MaximumValue, c0); |
||||
|
|
||||
|
Span<Y> source = MemoryMarshal.Cast<float, Y>(c0); |
||||
|
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed); |
||||
|
ColorConversionOptions options = new() |
||||
|
{ |
||||
|
SourceIccProfile = profile, |
||||
|
TargetIccProfile = CompactSrgbV4Profile.Profile, |
||||
|
}; |
||||
|
|
||||
|
ColorProfileConverter converter = new(options); |
||||
|
converter.Convert<Y, Rgb>(source, destination); |
||||
|
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..length], c0, c1, c2); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// RGB and YCbCr become packed RGB before the profile transform. CMYK models remain packed CMYK,
|
||||
|
// because their source profile describes that four-component space rather than an intermediate RGB space.
|
||||
|
bool profileSourceIsCmyk = false; |
||||
|
|
||||
|
switch (this.ColorSpace) |
||||
|
{ |
||||
|
case JpegColorSpace.RGB: |
||||
|
// JPEG RGB planes use the integer sample domain; ICC RGB values use normalized interleaved lanes.
|
||||
|
PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / this.MaximumValue); |
||||
|
break; |
||||
|
|
||||
|
case JpegColorSpace.YCbCr: |
||||
|
// ICC profiles rarely expose YCbCr transforms. BT.601 therefore produces RGB in the profile's
|
||||
|
// source space first, and that packed RGB becomes the input to the profile transform below.
|
||||
|
PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / this.MaximumValue); |
||||
|
|
||||
|
ColorProfileConverter yCbCrConverter = new(); |
||||
|
Span<YCbCr> yCbCr = MemoryMarshal.Cast<float, YCbCr>(packed); |
||||
|
Span<Rgb> yCbCrDestination = MemoryMarshal.Cast<float, Rgb>(packed); |
||||
|
yCbCrConverter.Convert<YCbCr, Rgb>(yCbCr, yCbCrDestination); |
||||
|
break; |
||||
|
|
||||
|
case JpegColorSpace.Cmyk: |
||||
|
// Adobe-style JPEG CMYK stores inverted samples, while ICC consumes conventional normalized CMYK.
|
||||
|
PackedInvertNormalizeInterleave4(c0, c1, c2, values.Component3, packed, this.MaximumValue); |
||||
|
profileSourceIsCmyk = true; |
||||
|
break; |
||||
|
|
||||
|
case JpegColorSpace.TiffCmyk: |
||||
|
// TIFF JPEG CMYK is already non-inverted, so only normalization and interleaving are required.
|
||||
|
PackedNormalizeInterleave4(c0, c1, c2, values.Component3, packed, this.MaximumValue); |
||||
|
profileSourceIsCmyk = true; |
||||
|
break; |
||||
|
|
||||
|
case JpegColorSpace.Ycck: |
||||
|
// Adobe-style JPEG YccK is inverted before its format-defined YccK-to-CMYK transform.
|
||||
|
PackedInvertNormalizeInterleave4(c0, c1, c2, values.Component3, packed, this.MaximumValue); |
||||
|
|
||||
|
ColorProfileConverter yccKConverter = new(); |
||||
|
Span<Cmyk> yccKCmyk = MemoryMarshal.Cast<float, Cmyk>(packed); |
||||
|
yccKConverter.Convert<YccK, Cmyk>(MemoryMarshal.Cast<Cmyk, YccK>(yccKCmyk), yccKCmyk); |
||||
|
profileSourceIsCmyk = true; |
||||
|
break; |
||||
|
|
||||
|
case JpegColorSpace.TiffYccK: |
||||
|
// TIFF JPEG YccK is non-inverted, but otherwise uses the same YccK-to-CMYK transform.
|
||||
|
PackedNormalizeInterleave4(c0, c1, c2, values.Component3, packed, this.MaximumValue); |
||||
|
|
||||
|
ColorProfileConverter tiffYccKConverter = new(); |
||||
|
Span<Cmyk> tiffYccKCmyk = MemoryMarshal.Cast<float, Cmyk>(packed); |
||||
|
tiffYccKConverter.Convert<YccK, Cmyk>(MemoryMarshal.Cast<Cmyk, YccK>(tiffYccKCmyk), tiffYccKCmyk); |
||||
|
profileSourceIsCmyk = true; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
ColorConversionOptions profileOptions = new() |
||||
|
{ |
||||
|
SourceIccProfile = profile, |
||||
|
TargetIccProfile = CompactSrgbV4Profile.Profile, |
||||
|
}; |
||||
|
|
||||
|
ColorProfileConverter profileConverter = new(profileOptions); |
||||
|
Span<Rgb> rgb = MemoryMarshal.Cast<float, Rgb>(packed)[..length]; |
||||
|
|
||||
|
if (profileSourceIsCmyk) |
||||
|
{ |
||||
|
// The destination aliases the first three floats of each four-float source item. The converter
|
||||
|
// supports this established in-place contraction, and the source span retains its original length.
|
||||
|
profileConverter.Convert<Cmyk, Rgb>(MemoryMarshal.Cast<float, Cmyk>(packed), rgb); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
profileConverter.Convert<Rgb, Rgb>(rgb, rgb); |
||||
|
} |
||||
|
|
||||
|
// Only the packed RGB prefix is meaningful after four-component conversion; scatter it back to the
|
||||
|
// decoder's three planar output buffers while leaving the fourth source component untouched.
|
||||
|
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..length], c0, c1, c2); |
||||
|
} |
||||
|
} |
||||
@ -1,23 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
|
|
||||
/// based on scalar instructions.
|
|
||||
/// </summary>
|
|
||||
internal abstract class JpegColorConverterScalar : JpegColorConverterBase |
|
||||
{ |
|
||||
protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision) |
|
||||
: base(colorSpace, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public sealed override bool IsAvailable => true; |
|
||||
|
|
||||
public sealed override int ElementsPerBatch => 1; |
|
||||
} |
|
||||
} |
|
||||
@ -1,129 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Numerics; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
|
|
||||
/// based on <see cref="Vector"/> API.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// Converters of this family can work with data of any size.
|
|
||||
/// Even though real life data is guaranteed to be of size
|
|
||||
/// divisible by 8 newer SIMD instructions like AVX512 won't work with
|
|
||||
/// such data out of the box. These converters have fallback code
|
|
||||
/// for 'remainder' data.
|
|
||||
/// </remarks>
|
|
||||
internal abstract class JpegColorConverterVector : JpegColorConverterBase |
|
||||
{ |
|
||||
protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) |
|
||||
: base(colorSpace, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets a value indicating whether this converter is supported on current hardware.
|
|
||||
/// </summary>
|
|
||||
public static bool IsSupported => Vector.IsHardwareAccelerated && Vector<float>.Count % 4 == 0; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public sealed override bool IsAvailable => IsSupported; |
|
||||
|
|
||||
public override int ElementsPerBatch => Vector<float>.Count; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public sealed override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); |
|
||||
|
|
||||
int length = values.Component0.Length; |
|
||||
int remainder = (int)((uint)length % (uint)Vector<float>.Count); |
|
||||
|
|
||||
int simdCount = length - remainder; |
|
||||
if (simdCount > 0) |
|
||||
{ |
|
||||
this.ConvertToRgbInPlaceVectorized(values.Slice(0, simdCount)); |
|
||||
} |
|
||||
|
|
||||
// Jpeg images width is always divisible by 8 without a remainder
|
|
||||
// so it's safe to say SSE/AVX1/AVX2 implementations would never have
|
|
||||
// 'remainder' pixels
|
|
||||
// But some exotic simd implementations e.g. AVX-512 can have
|
|
||||
// remainder pixels
|
|
||||
if (remainder > 0) |
|
||||
{ |
|
||||
this.ConvertToRgbInPlaceScalarRemainder(values.Slice(simdCount, remainder)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public sealed override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); |
|
||||
|
|
||||
int length = values.Component0.Length; |
|
||||
int remainder = (int)((uint)length % (uint)Vector<float>.Count); |
|
||||
|
|
||||
int simdCount = length - remainder; |
|
||||
if (simdCount > 0) |
|
||||
{ |
|
||||
this.ConvertFromRgbVectorized( |
|
||||
values.Slice(0, simdCount), |
|
||||
rLane[..simdCount], |
|
||||
gLane[..simdCount], |
|
||||
bLane[..simdCount]); |
|
||||
} |
|
||||
|
|
||||
// Jpeg images width is always divisible by 8 without a remainder
|
|
||||
// so it's safe to say SSE/AVX1/AVX2 implementations would never have
|
|
||||
// 'remainder' pixels
|
|
||||
// But some exotic simd implementations e.g. AVX-512 can have
|
|
||||
// remainder pixels
|
|
||||
if (remainder > 0) |
|
||||
{ |
|
||||
this.ConvertFromRgbScalarRemainder( |
|
||||
values.Slice(simdCount, remainder), |
|
||||
rLane.Slice(simdCount, remainder), |
|
||||
gLane.Slice(simdCount, remainder), |
|
||||
bLane.Slice(simdCount, remainder)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts planar jpeg component values in <paramref name="values"/>
|
|
||||
/// to RGB color space in place using <see cref="Vector"/> API.
|
|
||||
/// </summary>
|
|
||||
/// <param name="values">The input/output as a stack-only <see cref="ComponentValues"/> struct</param>
|
|
||||
protected abstract void ConvertToRgbInPlaceVectorized(in ComponentValues values); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts remainder of the planar jpeg component values after
|
|
||||
/// conversion in <see cref="ConvertToRgbInPlaceVectorized(in ComponentValues)"/>.
|
|
||||
/// </summary>
|
|
||||
/// <param name="values">The input/output as a stack-only <see cref="ComponentValues"/> struct</param>
|
|
||||
protected abstract void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts RGB lanes to jpeg component values using <see cref="Vector"/> API.
|
|
||||
/// </summary>
|
|
||||
/// <param name="values">Jpeg component values.</param>
|
|
||||
/// <param name="rLane">Red colors lane.</param>
|
|
||||
/// <param name="gLane">Green colors lane.</param>
|
|
||||
/// <param name="bLane">Blue colors lane.</param>
|
|
||||
protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts remainder of RGB lanes to jpeg component values after
|
|
||||
/// conversion in <see cref="ConvertFromRgbVectorized(in ComponentValues, Span{float}, Span{float}, Span{float})"/>.
|
|
||||
/// </summary>
|
|
||||
/// <param name="values">Jpeg component values.</param>
|
|
||||
/// <param name="rLane">Red colors lane.</param>
|
|
||||
/// <param name="gLane">Green colors lane.</param>
|
|
||||
/// <param name="bLane">Blue colors lane.</param>
|
|
||||
protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane); |
|
||||
} |
|
||||
} |
|
||||
@ -1,34 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.Intrinsics; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
|
|
||||
/// based on <see cref="Vector128{T}"/> instructions.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// Converters of this family would expect input buffers lengths to be
|
|
||||
/// divisible by 8 without a remainder.
|
|
||||
/// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks.
|
|
||||
/// DO NOT pass test data of invalid size to these converters as they
|
|
||||
/// potentially won't do a bound check and return a false positive result.
|
|
||||
/// </remarks>
|
|
||||
internal abstract class JpegColorConverterVector128 : JpegColorConverterBase |
|
||||
{ |
|
||||
protected JpegColorConverterVector128(JpegColorSpace colorSpace, int precision) |
|
||||
: base(colorSpace, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public static bool IsSupported => Vector128.IsHardwareAccelerated; |
|
||||
|
|
||||
public sealed override bool IsAvailable => IsSupported; |
|
||||
|
|
||||
public sealed override int ElementsPerBatch => Vector128<float>.Count; |
|
||||
} |
|
||||
} |
|
||||
@ -1,34 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.Intrinsics; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
|
|
||||
/// based on <see cref="Vector256{T}"/> instructions.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// Converters of this family would expect input buffers lengths to be
|
|
||||
/// divisible by 8 without a remainder.
|
|
||||
/// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks.
|
|
||||
/// DO NOT pass test data of invalid size to these converters as they
|
|
||||
/// potentially won't do a bound check and return a false positive result.
|
|
||||
/// </remarks>
|
|
||||
internal abstract class JpegColorConverterVector256 : JpegColorConverterBase |
|
||||
{ |
|
||||
protected JpegColorConverterVector256(JpegColorSpace colorSpace, int precision) |
|
||||
: base(colorSpace, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public static bool IsSupported => Vector256.IsHardwareAccelerated; |
|
||||
|
|
||||
public sealed override bool IsAvailable => IsSupported; |
|
||||
|
|
||||
public sealed override int ElementsPerBatch => Vector256<float>.Count; |
|
||||
} |
|
||||
} |
|
||||
@ -1,111 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Numerics; |
|
||||
using System.Runtime.Intrinsics; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
|
|
||||
internal abstract partial class JpegColorConverterBase |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
|
|
||||
/// based on <see cref="Vector512{T}"/> instructions.
|
|
||||
/// </summary>
|
|
||||
internal abstract class JpegColorConverterVector512 : JpegColorConverterBase |
|
||||
{ |
|
||||
protected JpegColorConverterVector512(JpegColorSpace colorSpace, int precision) |
|
||||
: base(colorSpace, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public static bool IsSupported => Vector512.IsHardwareAccelerated; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override bool IsAvailable => IsSupported; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public override int ElementsPerBatch => Vector512<float>.Count; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public sealed override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
|
||||
{ |
|
||||
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); |
|
||||
|
|
||||
int length = values.Component0.Length; |
|
||||
int remainder = (int)((uint)length % (uint)Vector512<float>.Count); |
|
||||
|
|
||||
int simdCount = length - remainder; |
|
||||
if (simdCount > 0) |
|
||||
{ |
|
||||
this.ConvertFromRgbVectorized( |
|
||||
values.Slice(0, simdCount), |
|
||||
rLane[..simdCount], |
|
||||
gLane[..simdCount], |
|
||||
bLane[..simdCount]); |
|
||||
} |
|
||||
|
|
||||
if (remainder > 0) |
|
||||
{ |
|
||||
this.ConvertFromRgbScalarRemainder( |
|
||||
values.Slice(simdCount, remainder), |
|
||||
rLane.Slice(simdCount, remainder), |
|
||||
gLane.Slice(simdCount, remainder), |
|
||||
bLane.Slice(simdCount, remainder)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public sealed override void ConvertToRgbInPlace(in ComponentValues values) |
|
||||
{ |
|
||||
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); |
|
||||
|
|
||||
int length = values.Component0.Length; |
|
||||
int remainder = (int)((uint)length % (uint)Vector512<float>.Count); |
|
||||
|
|
||||
int simdCount = length - remainder; |
|
||||
if (simdCount > 0) |
|
||||
{ |
|
||||
this.ConvertToRgbInPlaceVectorized(values.Slice(0, simdCount)); |
|
||||
} |
|
||||
|
|
||||
if (remainder > 0) |
|
||||
{ |
|
||||
this.ConvertToRgbInPlaceScalarRemainder(values.Slice(simdCount, remainder)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts planar jpeg component values in <paramref name="values"/>
|
|
||||
/// to RGB color space in place using <see cref="Vector"/> API.
|
|
||||
/// </summary>
|
|
||||
/// <param name="values">The input/output as a stack-only <see cref="ComponentValues"/> struct</param>
|
|
||||
protected abstract void ConvertToRgbInPlaceVectorized(in ComponentValues values); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts remainder of the planar jpeg component values after
|
|
||||
/// conversion in <see cref="ConvertToRgbInPlaceVectorized(in ComponentValues)"/>.
|
|
||||
/// </summary>
|
|
||||
/// <param name="values">The input/output as a stack-only <see cref="ComponentValues"/> struct</param>
|
|
||||
protected abstract void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts RGB lanes to jpeg component values using <see cref="Vector"/> API.
|
|
||||
/// </summary>
|
|
||||
/// <param name="values">Jpeg component values.</param>
|
|
||||
/// <param name="rLane">Red colors lane.</param>
|
|
||||
/// <param name="gLane">Green colors lane.</param>
|
|
||||
/// <param name="bLane">Blue colors lane.</param>
|
|
||||
protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts remainder of RGB lanes to jpeg component values after
|
|
||||
/// conversion in <see cref="ConvertFromRgbVectorized(in ComponentValues, Span{float}, Span{float}, Span{float})"/>.
|
|
||||
/// </summary>
|
|
||||
/// <param name="values">Jpeg component values.</param>
|
|
||||
/// <param name="rLane">Red colors lane.</param>
|
|
||||
/// <param name="gLane">Green colors lane.</param>
|
|
||||
/// <param name="bLane">Blue colors lane.</param>
|
|
||||
protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,424 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using System.Runtime.Intrinsics.Arm; |
||||
|
using System.Runtime.Intrinsics.X86; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Png.Filters; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the scalar and SIMD mappings used by the shared PNG filter traversal.
|
||||
|
/// </summary>
|
||||
|
internal interface IPngFilterOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the filter type written to the leading result byte.
|
||||
|
/// </summary>
|
||||
|
public static abstract FilterType Type { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the predictor reads the left component.
|
||||
|
/// </summary>
|
||||
|
public static abstract bool UsesLeft { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the predictor reads the above component.
|
||||
|
/// </summary>
|
||||
|
public static abstract bool UsesAbove { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the predictor reads the upper-left component.
|
||||
|
/// </summary>
|
||||
|
public static abstract bool UsesUpperLeft { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Filters one byte from its PNG neighborhood.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scan">The component being filtered.</param>
|
||||
|
/// <param name="left">The corresponding component in the preceding pixel.</param>
|
||||
|
/// <param name="above">The corresponding component in the preceding scanline.</param>
|
||||
|
/// <param name="upperLeft">The preceding component in the preceding scanline.</param>
|
||||
|
/// <returns>The filtered residual.</returns>
|
||||
|
public static abstract byte Invoke(byte scan, byte left, byte above, byte upperLeft); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Filters sixteen byte lanes from their PNG neighborhoods.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scan">The components being filtered.</param>
|
||||
|
/// <param name="left">The corresponding components in the preceding pixels.</param>
|
||||
|
/// <param name="above">The corresponding components in the preceding scanline.</param>
|
||||
|
/// <param name="upperLeft">The preceding components in the preceding scanline.</param>
|
||||
|
/// <returns>The filtered residuals.</returns>
|
||||
|
public static abstract Vector128<byte> Invoke(Vector128<byte> scan, Vector128<byte> left, Vector128<byte> above, Vector128<byte> upperLeft); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Filters thirty-two byte lanes from their PNG neighborhoods.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scan">The components being filtered.</param>
|
||||
|
/// <param name="left">The corresponding components in the preceding pixels.</param>
|
||||
|
/// <param name="above">The corresponding components in the preceding scanline.</param>
|
||||
|
/// <param name="upperLeft">The preceding components in the preceding scanline.</param>
|
||||
|
/// <returns>The filtered residuals.</returns>
|
||||
|
public static abstract Vector256<byte> Invoke(Vector256<byte> scan, Vector256<byte> left, Vector256<byte> above, Vector256<byte> upperLeft); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Filters sixty-four byte lanes from their PNG neighborhoods.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scan">The components being filtered.</param>
|
||||
|
/// <param name="left">The corresponding components in the preceding pixels.</param>
|
||||
|
/// <param name="above">The corresponding components in the preceding scanline.</param>
|
||||
|
/// <param name="upperLeft">The preceding components in the preceding scanline.</param>
|
||||
|
/// <returns>The filtered residuals.</returns>
|
||||
|
public static abstract Vector512<byte> Invoke(Vector512<byte> scan, Vector512<byte> left, Vector512<byte> above, Vector512<byte> upperLeft); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maps each component to its difference from the corresponding component in the preceding pixel.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct SubFilterOperator : IPngFilterOperator |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public static FilterType Type => FilterType.Sub; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesLeft => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesAbove => false; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesUpperLeft => false; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) => (byte)(scan - left); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector128<byte> Invoke(Vector128<byte> scan, Vector128<byte> left, Vector128<byte> above, Vector128<byte> upperLeft) |
||||
|
=> scan - left; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector256<byte> Invoke(Vector256<byte> scan, Vector256<byte> left, Vector256<byte> above, Vector256<byte> upperLeft) |
||||
|
=> scan - left; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector512<byte> Invoke(Vector512<byte> scan, Vector512<byte> left, Vector512<byte> above, Vector512<byte> upperLeft) |
||||
|
=> scan - left; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maps each component to its difference from the component directly above it.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct UpFilterOperator : IPngFilterOperator |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public static FilterType Type => FilterType.Up; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesLeft => false; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesAbove => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesUpperLeft => false; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) => (byte)(scan - above); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector128<byte> Invoke(Vector128<byte> scan, Vector128<byte> left, Vector128<byte> above, Vector128<byte> upperLeft) |
||||
|
=> scan - above; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector256<byte> Invoke(Vector256<byte> scan, Vector256<byte> left, Vector256<byte> above, Vector256<byte> upperLeft) |
||||
|
=> scan - above; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector512<byte> Invoke(Vector512<byte> scan, Vector512<byte> left, Vector512<byte> above, Vector512<byte> upperLeft) |
||||
|
=> scan - above; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maps each component to its difference from the truncated average of its left and above neighbors.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct AverageFilterOperator : IPngFilterOperator |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public static FilterType Type => FilterType.Average; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesLeft => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesAbove => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesUpperLeft => false; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) |
||||
|
=> (byte)(scan - ((left + above) >> 1)); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector128<byte> Invoke(Vector128<byte> scan, Vector128<byte> left, Vector128<byte> above, Vector128<byte> upperLeft) |
||||
|
{ |
||||
|
Vector128<byte> average; |
||||
|
|
||||
|
if (Sse2.IsSupported) |
||||
|
{ |
||||
|
// PAVG rounds upward. Complementing both inputs and the result converts
|
||||
|
// that rounding into the floor((left + above) / 2) required by PNG.
|
||||
|
average = ~Sse2.Average(~left, ~above); |
||||
|
} |
||||
|
else if (AdvSimd.IsSupported) |
||||
|
{ |
||||
|
// ARM's halving add truncates directly and therefore needs no correction.
|
||||
|
average = AdvSimd.FusedAddHalving(left, above); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Portable 128-bit backends use the carry-free average identity. Shared
|
||||
|
// bits supply the integer part while differing bits supply half the remainder.
|
||||
|
average = (left & above) + Vector128.ShiftRightLogical(left ^ above, 1); |
||||
|
} |
||||
|
|
||||
|
return scan - average; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector256<byte> Invoke(Vector256<byte> scan, Vector256<byte> left, Vector256<byte> above, Vector256<byte> upperLeft) |
||||
|
|
||||
|
// VPAVGB rounds (left + above) / 2 upward. Complementing both inputs and
|
||||
|
// the result changes that to the truncated average required by PNG.
|
||||
|
=> scan - ~Avx2.Average(~left, ~above); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector512<byte> Invoke(Vector512<byte> scan, Vector512<byte> left, Vector512<byte> above, Vector512<byte> upperLeft) |
||||
|
|
||||
|
// AVX-512BW retains VPAVGB's upward rounding, so use the same complement
|
||||
|
// identity as AVX2 to obtain floor((left + above) / 2) in every byte lane.
|
||||
|
=> scan - ~Avx512BW.Average(~left, ~above); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maps each component to its difference from the nearest Paeth neighbor.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct PaethFilterOperator : IPngFilterOperator |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public static FilterType Type => FilterType.Paeth; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesLeft => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesAbove => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesUpperLeft => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) |
||||
|
{ |
||||
|
int p = left + above - upperLeft; |
||||
|
int distanceLeft = Numerics.Abs(p - left); |
||||
|
int distanceAbove = Numerics.Abs(p - above); |
||||
|
int distanceUpperLeft = Numerics.Abs(p - upperLeft); |
||||
|
|
||||
|
// PNG resolves equal distances in left, above, upper-left order.
|
||||
|
byte predictor = distanceLeft <= distanceAbove && distanceLeft <= distanceUpperLeft ? left : distanceAbove <= distanceUpperLeft ? above : upperLeft; |
||||
|
|
||||
|
return (byte)(scan - predictor); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector128<byte> Invoke(Vector128<byte> scan, Vector128<byte> left, Vector128<byte> above, Vector128<byte> upperLeft) |
||||
|
{ |
||||
|
Vector128<byte> predictor = Predict(left, above, upperLeft); |
||||
|
return scan - predictor; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector256<byte> Invoke(Vector256<byte> scan, Vector256<byte> left, Vector256<byte> above, Vector256<byte> upperLeft) |
||||
|
{ |
||||
|
Vector256<byte> predictor = Predict(left, above, upperLeft); |
||||
|
return scan - predictor; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector512<byte> Invoke(Vector512<byte> scan, Vector512<byte> left, Vector512<byte> above, Vector512<byte> upperLeft) |
||||
|
{ |
||||
|
Vector512<byte> predictor = Predict(left, above, upperLeft); |
||||
|
return scan - predictor; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth neighbor for sixteen independent byte lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The reconstructed component immediately before each current component.</param>
|
||||
|
/// <param name="above">The reconstructed component immediately above each current component.</param>
|
||||
|
/// <param name="upperLeft">The reconstructed component diagonally above and before each current component.</param>
|
||||
|
/// <returns>The selected Paeth predictor for each byte lane.</returns>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector128<byte> Predict(Vector128<byte> left, Vector128<byte> above, Vector128<byte> upperLeft) |
||||
|
{ |
||||
|
// For p = left + above - upperLeft, the Paeth distances simplify to:
|
||||
|
// distanceLeft = |above - upperLeft|
|
||||
|
// distanceAbove = |left - upperLeft|
|
||||
|
// Computing both unsigned subtraction directions and OR-ing them obtains
|
||||
|
// each absolute difference without widening the byte lanes.
|
||||
|
Vector128<byte> aboveMinusUpper = Vector128_.SubtractSaturate(above, upperLeft); |
||||
|
Vector128<byte> leftMinusUpper = Vector128_.SubtractSaturate(left, upperLeft); |
||||
|
Vector128<byte> distanceLeft = Vector128_.SubtractSaturate(upperLeft, above) | aboveMinusUpper; |
||||
|
Vector128<byte> distanceAbove = Vector128_.SubtractSaturate(upperLeft, left) | leftMinusUpper; |
||||
|
|
||||
|
return SelectPredictor(left, above, upperLeft, aboveMinusUpper, leftMinusUpper, distanceLeft, distanceAbove); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth neighbor for thirty-two independent byte lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The reconstructed component immediately before each current component.</param>
|
||||
|
/// <param name="above">The reconstructed component immediately above each current component.</param>
|
||||
|
/// <param name="upperLeft">The reconstructed component diagonally above and before each current component.</param>
|
||||
|
/// <returns>The selected Paeth predictor for each byte lane.</returns>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector256<byte> Predict(Vector256<byte> left, Vector256<byte> above, Vector256<byte> upperLeft) |
||||
|
{ |
||||
|
// Apply the same Paeth identities as the 128-bit path to thirty-two lanes.
|
||||
|
// Saturating subtraction in both directions forms the absolute differences
|
||||
|
// without widening, preserving one predictor result per source byte.
|
||||
|
Vector256<byte> aboveMinusUpper = Vector256_.SubtractSaturate(above, upperLeft); |
||||
|
Vector256<byte> leftMinusUpper = Vector256_.SubtractSaturate(left, upperLeft); |
||||
|
Vector256<byte> distanceLeft = Vector256_.SubtractSaturate(upperLeft, above) | aboveMinusUpper; |
||||
|
Vector256<byte> distanceAbove = Vector256_.SubtractSaturate(upperLeft, left) | leftMinusUpper; |
||||
|
|
||||
|
return SelectPredictor(left, above, upperLeft, aboveMinusUpper, leftMinusUpper, distanceLeft, distanceAbove); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth neighbor for sixty-four independent byte lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The reconstructed component immediately before each current component.</param>
|
||||
|
/// <param name="above">The reconstructed component immediately above each current component.</param>
|
||||
|
/// <param name="upperLeft">The reconstructed component diagonally above and before each current component.</param>
|
||||
|
/// <returns>The selected Paeth predictor for each byte lane.</returns>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector512<byte> Predict(Vector512<byte> left, Vector512<byte> above, Vector512<byte> upperLeft) |
||||
|
{ |
||||
|
// Apply the same byte-lane Paeth identities to sixty-four AVX-512BW lanes.
|
||||
|
// No cross-lane operation is required because every component has its own
|
||||
|
// left, above, and upper-left inputs at the matching vector index.
|
||||
|
Vector512<byte> aboveMinusUpper = Vector512_.SubtractSaturate(above, upperLeft); |
||||
|
Vector512<byte> leftMinusUpper = Vector512_.SubtractSaturate(left, upperLeft); |
||||
|
Vector512<byte> distanceLeft = Vector512_.SubtractSaturate(upperLeft, above) | aboveMinusUpper; |
||||
|
Vector512<byte> distanceAbove = Vector512_.SubtractSaturate(upperLeft, left) | leftMinusUpper; |
||||
|
|
||||
|
return SelectPredictor(left, above, upperLeft, aboveMinusUpper, leftMinusUpper, distanceLeft, distanceAbove); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies Paeth distance and tie-breaking rules to sixteen lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The left-neighbor candidates.</param>
|
||||
|
/// <param name="above">The above-neighbor candidates.</param>
|
||||
|
/// <param name="upperLeft">The upper-left-neighbor candidates.</param>
|
||||
|
/// <param name="aboveMinusUpper">The saturated differences from above to upper-left.</param>
|
||||
|
/// <param name="leftMinusUpper">The saturated differences from left to upper-left.</param>
|
||||
|
/// <param name="distanceLeft">The Paeth distances for the left candidates.</param>
|
||||
|
/// <param name="distanceAbove">The Paeth distances for the above candidates.</param>
|
||||
|
/// <returns>The selected Paeth predictor for each byte lane.</returns>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector128<byte> SelectPredictor(Vector128<byte> left, Vector128<byte> above, Vector128<byte> upperLeft, Vector128<byte> aboveMinusUpper, Vector128<byte> leftMinusUpper, Vector128<byte> distanceLeft, Vector128<byte> distanceAbove) |
||||
|
{ |
||||
|
Vector128<byte> sameDirection = Vector128.Equals(Vector128.Equals(aboveMinusUpper, Vector128<byte>.Zero), Vector128.Equals(leftMinusUpper, Vector128<byte>.Zero)); |
||||
|
|
||||
|
// If left and above lie on the same side of upper-left, distanceUpper is
|
||||
|
// their summed distance and cannot beat either neighbor; the all-bits mask
|
||||
|
// excludes upper-left. On opposite sides, that distance is the absolute
|
||||
|
// difference between distanceLeft and distanceAbove.
|
||||
|
Vector128<byte> distanceUpper = sameDirection | Vector128_.SubtractSaturate(distanceAbove, distanceLeft) | Vector128_.SubtractSaturate(distanceLeft, distanceAbove); |
||||
|
|
||||
|
// Equality selects above before upper-left, implementing PNG's second tie rule.
|
||||
|
Vector128<byte> minimumAboveUpper = Vector128.Min(distanceUpper, distanceAbove); |
||||
|
Vector128<byte> aboveOrUpper = Vector128.ConditionalSelect(Vector128.Equals(minimumAboveUpper, distanceAbove), above, upperLeft); |
||||
|
|
||||
|
// Applying the left comparison last preserves PNG's left-first tie rule.
|
||||
|
return Vector128.ConditionalSelect(Vector128.Equals(Vector128.Min(minimumAboveUpper, distanceLeft), distanceLeft), left, aboveOrUpper); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies Paeth distance and tie-breaking rules to thirty-two lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The left-neighbor candidates.</param>
|
||||
|
/// <param name="above">The above-neighbor candidates.</param>
|
||||
|
/// <param name="upperLeft">The upper-left-neighbor candidates.</param>
|
||||
|
/// <param name="aboveMinusUpper">The saturated differences from above to upper-left.</param>
|
||||
|
/// <param name="leftMinusUpper">The saturated differences from left to upper-left.</param>
|
||||
|
/// <param name="distanceLeft">The Paeth distances for the left candidates.</param>
|
||||
|
/// <param name="distanceAbove">The Paeth distances for the above candidates.</param>
|
||||
|
/// <returns>The selected Paeth predictor for each byte lane.</returns>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector256<byte> SelectPredictor(Vector256<byte> left, Vector256<byte> above, Vector256<byte> upperLeft, Vector256<byte> aboveMinusUpper, Vector256<byte> leftMinusUpper, Vector256<byte> distanceLeft, Vector256<byte> distanceAbove) |
||||
|
{ |
||||
|
Vector256<byte> sameDirection = Vector256.Equals(Vector256.Equals(aboveMinusUpper, Vector256<byte>.Zero), Vector256.Equals(leftMinusUpper, Vector256<byte>.Zero)); |
||||
|
|
||||
|
// Exclude upper-left when its distance is the non-minimal sum; otherwise
|
||||
|
// compute its distance as the absolute difference of the two known distances.
|
||||
|
Vector256<byte> distanceUpper = sameDirection | Vector256_.SubtractSaturate(distanceAbove, distanceLeft) | Vector256_.SubtractSaturate(distanceLeft, distanceAbove); |
||||
|
|
||||
|
// Select above on equality, then select left on equality to preserve PNG's
|
||||
|
// required left, above, upper-left tie order in every byte lane.
|
||||
|
Vector256<byte> minimumAboveUpper = Vector256.Min(distanceUpper, distanceAbove); |
||||
|
Vector256<byte> aboveOrUpper = Vector256.ConditionalSelect(Vector256.Equals(minimumAboveUpper, distanceAbove), above, upperLeft); |
||||
|
|
||||
|
return Vector256.ConditionalSelect(Vector256.Equals(Vector256.Min(minimumAboveUpper, distanceLeft), distanceLeft), left, aboveOrUpper); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies Paeth distance and tie-breaking rules to sixty-four lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The left-neighbor candidates.</param>
|
||||
|
/// <param name="above">The above-neighbor candidates.</param>
|
||||
|
/// <param name="upperLeft">The upper-left-neighbor candidates.</param>
|
||||
|
/// <param name="aboveMinusUpper">The saturated differences from above to upper-left.</param>
|
||||
|
/// <param name="leftMinusUpper">The saturated differences from left to upper-left.</param>
|
||||
|
/// <param name="distanceLeft">The Paeth distances for the left candidates.</param>
|
||||
|
/// <param name="distanceAbove">The Paeth distances for the above candidates.</param>
|
||||
|
/// <returns>The selected Paeth predictor for each byte lane.</returns>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector512<byte> SelectPredictor(Vector512<byte> left, Vector512<byte> above, Vector512<byte> upperLeft, Vector512<byte> aboveMinusUpper, Vector512<byte> leftMinusUpper, Vector512<byte> distanceLeft, Vector512<byte> distanceAbove) |
||||
|
{ |
||||
|
Vector512<byte> sameDirection = Vector512.Equals(Vector512.Equals(aboveMinusUpper, Vector512<byte>.Zero), Vector512.Equals(leftMinusUpper, Vector512<byte>.Zero)); |
||||
|
|
||||
|
// Exclude upper-left when its distance is the non-minimal sum; otherwise
|
||||
|
// compute its distance as the absolute difference of the two known distances.
|
||||
|
Vector512<byte> distanceUpper = sameDirection | Vector512_.SubtractSaturate(distanceAbove, distanceLeft) | Vector512_.SubtractSaturate(distanceLeft, distanceAbove); |
||||
|
|
||||
|
// Select above on equality, then select left on equality to preserve PNG's
|
||||
|
// required left, above, upper-left tie order in every byte lane.
|
||||
|
Vector512<byte> minimumAboveUpper = Vector512.Min(distanceUpper, distanceAbove); |
||||
|
Vector512<byte> aboveOrUpper = Vector512.ConditionalSelect(Vector512.Equals(minimumAboveUpper, distanceAbove), above, upperLeft); |
||||
|
|
||||
|
return Vector512.ConditionalSelect(Vector512.Equals(Vector512.Min(minimumAboveUpper, distanceLeft), distanceLeft), left, aboveOrUpper); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,187 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using System.Runtime.Intrinsics.X86; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Png.Filters; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies PNG filter operators while accumulating the absolute signed residuals used for filter selection.
|
||||
|
/// </summary>
|
||||
|
internal static class PngFilterEncoder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Maps a scanline through a filter operator, writes the residuals, and reduces their total variance.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TOperator">The PNG predictor selected for this closed traversal.</typeparam>
|
||||
|
/// <param name="scanline">The scanline to encode.</param>
|
||||
|
/// <param name="previousScanline">The preceding scanline.</param>
|
||||
|
/// <param name="result">The destination including its leading filter byte.</param>
|
||||
|
/// <param name="bytesPerPixel">The distance to the corresponding component in the preceding pixel.</param>
|
||||
|
/// <param name="sum">The sum of the absolute signed residuals.</param>
|
||||
|
// Inlining closes every static interface call over TOperator. The JIT can then remove
|
||||
|
// source loads ignored by simpler predictors and specialize the active register widths.
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static void Encode<TOperator>(ReadOnlySpan<byte> scanline, ReadOnlySpan<byte> previousScanline, Span<byte> result, uint bytesPerPixel, out int sum) |
||||
|
where TOperator : struct, IPngFilterOperator |
||||
|
{ |
||||
|
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); |
||||
|
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); |
||||
|
|
||||
|
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); |
||||
|
ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); |
||||
|
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); |
||||
|
|
||||
|
resultBaseRef = (byte)TOperator.Type; |
||||
|
sum = 0; |
||||
|
|
||||
|
nuint x = 0; |
||||
|
|
||||
|
// Components in the first pixel have no left or upper-left neighbor. Supplying
|
||||
|
// zeroes expresses the PNG boundary rule directly through the same predictor.
|
||||
|
for (; x < bytesPerPixel; x++) |
||||
|
{ |
||||
|
byte above = TOperator.UsesAbove ? Unsafe.Add(ref previousBaseRef, x) : (byte)0; |
||||
|
|
||||
|
byte filtered = TOperator.Invoke(Unsafe.Add(ref scanBaseRef, x), 0, above, 0); |
||||
|
|
||||
|
Unsafe.Add(ref resultBaseRef, x + 1) = filtered; |
||||
|
sum += Numerics.Abs(unchecked((sbyte)filtered)); |
||||
|
} |
||||
|
|
||||
|
Vector128<uint> sum128 = Vector128<uint>.Zero; |
||||
|
|
||||
|
// A single 512-bit register does not amortize folding its SAD accumulator.
|
||||
|
// Leave short rows to the narrower paths, which have lower fixed reduction cost.
|
||||
|
if (Avx512BW.IsSupported && scanline.Length - (int)x >= Vector512<byte>.Count * 2) |
||||
|
{ |
||||
|
Vector512<uint> sum512 = Vector512<uint>.Zero; |
||||
|
int oneRegisterFromEnd = scanline.Length - Vector512<byte>.Count; |
||||
|
|
||||
|
for (nuint xLeft = x - bytesPerPixel; (int)x <= oneRegisterFromEnd; xLeft += (uint)Vector512<byte>.Count) |
||||
|
{ |
||||
|
// Each byte lane represents one independently filtered component. The
|
||||
|
// four input vectors retain the scan/left/above/upper-left PNG layout.
|
||||
|
// Operator usage flags are constants after generic specialization, so
|
||||
|
// unused predictors do not retain even fault-preserving probe loads.
|
||||
|
Vector512<byte> left = TOperator.UsesLeft ? Unsafe.As<byte, Vector512<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)) : default; |
||||
|
Vector512<byte> above = TOperator.UsesAbove ? Unsafe.As<byte, Vector512<byte>>(ref Unsafe.Add(ref previousBaseRef, x)) : default; |
||||
|
Vector512<byte> upperLeft = TOperator.UsesUpperLeft ? Unsafe.As<byte, Vector512<byte>>(ref Unsafe.Add(ref previousBaseRef, xLeft)) : default; |
||||
|
|
||||
|
Vector512<byte> filtered = TOperator.Invoke(Unsafe.As<byte, Vector512<byte>>(ref Unsafe.Add(ref scanBaseRef, x)), left, above, upperLeft); |
||||
|
|
||||
|
Unsafe.As<byte, Vector512<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; |
||||
|
x += (uint)Vector512<byte>.Count; |
||||
|
|
||||
|
// Vector512.Abs lowers to VPABSB under the surrounding AVX-512BW guard.
|
||||
|
// Reinterpreting the signed result preserves -128's 0x80 bit pattern as
|
||||
|
// the unsigned magnitude 128 consumed by VPSADBW.
|
||||
|
Vector512<byte> absolute = Vector512.Abs(filtered.AsSByte()).AsByte(); |
||||
|
sum512 += Avx512BW.SumAbsoluteDifferences(absolute, Vector512<byte>.Zero).AsUInt32(); |
||||
|
} |
||||
|
|
||||
|
// Fold only widths that processed data. Short rows therefore avoid both
|
||||
|
// wide accumulator initialization and an otherwise empty reduction.
|
||||
|
Vector256<uint> folded512 = sum512.GetLower() + sum512.GetUpper(); |
||||
|
sum128 += folded512.GetLower() + folded512.GetUpper(); |
||||
|
} |
||||
|
|
||||
|
if (Avx2.IsSupported) |
||||
|
{ |
||||
|
Vector256<uint> sum256 = Vector256<uint>.Zero; |
||||
|
int oneRegisterFromEnd = scanline.Length - Vector256<byte>.Count; |
||||
|
|
||||
|
for (nuint xLeft = x - bytesPerPixel; (int)x <= oneRegisterFromEnd; xLeft += (uint)Vector256<byte>.Count) |
||||
|
{ |
||||
|
// Thirty-two byte lanes preserve the same scan/left/above/upper-left
|
||||
|
// correspondence as the 512-bit path. Closed operator flags remove
|
||||
|
// unused loads when the predictor does not consume that neighbor.
|
||||
|
Vector256<byte> left = TOperator.UsesLeft ? Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)) : default; |
||||
|
Vector256<byte> above = TOperator.UsesAbove ? Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, x)) : default; |
||||
|
Vector256<byte> upperLeft = TOperator.UsesUpperLeft ? Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, xLeft)) : default; |
||||
|
|
||||
|
Vector256<byte> filtered = TOperator.Invoke(Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x)), left, above, upperLeft); |
||||
|
|
||||
|
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; |
||||
|
x += (uint)Vector256<byte>.Count; |
||||
|
|
||||
|
// Vector256.Abs lowers to VPABSB under the surrounding AVX2 guard.
|
||||
|
// Reinterpreting the signed result preserves -128's 0x80 bit pattern as
|
||||
|
// the unsigned magnitude 128 consumed by VPSADBW.
|
||||
|
Vector256<byte> absolute = Vector256.Abs(filtered.AsSByte()).AsByte(); |
||||
|
sum256 += Avx2.SumAbsoluteDifferences(absolute, Vector256<byte>.Zero).AsUInt32(); |
||||
|
} |
||||
|
|
||||
|
// Fold both 128-bit halves into the shared four-lane accumulator.
|
||||
|
sum128 += sum256.GetLower() + sum256.GetUpper(); |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneRegisterFromEnd = scanline.Length - Vector128<byte>.Count; |
||||
|
|
||||
|
for (nuint xLeft = x - bytesPerPixel; (int)x <= oneRegisterFromEnd; xLeft += (uint)Vector128<byte>.Count) |
||||
|
{ |
||||
|
// The final vector width handles sixteen more components with the
|
||||
|
// same lane-wise neighborhood layout before the scalar remainder.
|
||||
|
Vector128<byte> left = TOperator.UsesLeft ? Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)) : default; |
||||
|
Vector128<byte> above = TOperator.UsesAbove ? Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref previousBaseRef, x)) : default; |
||||
|
Vector128<byte> upperLeft = TOperator.UsesUpperLeft ? Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref previousBaseRef, xLeft)) : default; |
||||
|
|
||||
|
Vector128<byte> filtered = TOperator.Invoke(Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, x)), left, above, upperLeft); |
||||
|
|
||||
|
Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; |
||||
|
x += (uint)Vector128<byte>.Count; |
||||
|
|
||||
|
// AccumulateAbsolute selects the available x86 or portable widening
|
||||
|
// reduction while preserving the same unsigned 32-bit partial sums.
|
||||
|
sum128 = AccumulateAbsolute(sum128, filtered); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Reduce the four partial lanes before adding individually scored tail bytes.
|
||||
|
sum += unchecked((int)Vector128.Sum(sum128)); |
||||
|
|
||||
|
for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; xLeft++, x++) |
||||
|
{ |
||||
|
byte left = TOperator.UsesLeft ? Unsafe.Add(ref scanBaseRef, xLeft) : (byte)0; |
||||
|
byte above = TOperator.UsesAbove ? Unsafe.Add(ref previousBaseRef, x) : (byte)0; |
||||
|
byte upperLeft = TOperator.UsesUpperLeft ? Unsafe.Add(ref previousBaseRef, xLeft) : (byte)0; |
||||
|
|
||||
|
byte filtered = TOperator.Invoke(Unsafe.Add(ref scanBaseRef, x), left, above, upperLeft); |
||||
|
|
||||
|
Unsafe.Add(ref resultBaseRef, x + 1) = filtered; |
||||
|
sum += Numerics.Abs(unchecked((sbyte)filtered)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Accumulates the absolute signed values in a 128-bit residual vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="accumulator">The four-lane unsigned accumulator.</param>
|
||||
|
/// <param name="residuals">The sixteen filtered byte residuals.</param>
|
||||
|
/// <returns>The updated accumulator.</returns>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector128<uint> AccumulateAbsolute(Vector128<uint> accumulator, Vector128<byte> residuals) |
||||
|
{ |
||||
|
// The generic absolute-value intrinsic selects PABSB where available and
|
||||
|
// preserves -128's 0x80 bit pattern as the unsigned magnitude 128.
|
||||
|
Vector128<byte> absolute = Vector128.Abs(residuals.AsSByte()).AsByte(); |
||||
|
|
||||
|
if (Sse2.IsSupported) |
||||
|
{ |
||||
|
return accumulator + Sse2.SumAbsoluteDifferences(absolute, Vector128<byte>.Zero).AsUInt32(); |
||||
|
} |
||||
|
|
||||
|
(Vector128<ushort> lower16, Vector128<ushort> upper16) = Vector128.Widen(absolute); |
||||
|
(Vector128<uint> lower0, Vector128<uint> lower1) = Vector128.Widen(lower16); |
||||
|
(Vector128<uint> upper0, Vector128<uint> upper1) = Vector128.Widen(upper16); |
||||
|
|
||||
|
// Four widening additions keep every byte contribution in a 32-bit lane,
|
||||
|
// matching the x86 accumulator's overflow behavior without scalar reduction.
|
||||
|
return accumulator + lower0 + lower1 + upper0 + upper1; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,253 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts integer ICC lookup-table entries to their normalized single-precision representation.
|
||||
|
/// </summary>
|
||||
|
internal static class IccLutNormalizer |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the scalar and SIMD conversion for an integer lookup-table element type.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The integer element type.</typeparam>
|
||||
|
private interface INormalizeOperator<T> |
||||
|
where T : unmanaged |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the divisor that maps the integer range to <c>[0, 1]</c>.
|
||||
|
/// </summary>
|
||||
|
public static abstract float Divisor { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts one scalar value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The integer value.</param>
|
||||
|
/// <returns>The normalized value.</returns>
|
||||
|
public static abstract float Invoke(T source); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts one 128-bit input vector and stores the expanded single-precision results.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed integer values.</param>
|
||||
|
/// <param name="divisor">The normalization divisor.</param>
|
||||
|
/// <param name="destination">The first destination element.</param>
|
||||
|
public static abstract void Invoke(Vector128<T> source, Vector128<float> divisor, ref float destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts one 256-bit input vector and stores the expanded single-precision results.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed integer values.</param>
|
||||
|
/// <param name="divisor">The normalization divisor.</param>
|
||||
|
/// <param name="destination">The first destination element.</param>
|
||||
|
public static abstract void Invoke(Vector256<T> source, Vector256<float> divisor, ref float destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts one 512-bit input vector and stores the expanded single-precision results.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed integer values.</param>
|
||||
|
/// <param name="divisor">The normalization divisor.</param>
|
||||
|
/// <param name="destination">The first destination element.</param>
|
||||
|
public static abstract void Invoke(Vector512<T> source, Vector512<float> divisor, ref float destination); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts byte lookup-table entries to normalized single-precision values.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The integer lookup-table entries.</param>
|
||||
|
/// <param name="destination">The normalized destination values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Normalize(ReadOnlySpan<byte> source, Span<float> destination) |
||||
|
=> Normalize<byte, ByteNormalizeOperator>(source, destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts unsigned-short lookup-table entries to normalized single-precision values.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The integer lookup-table entries.</param>
|
||||
|
/// <param name="destination">The normalized destination values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Normalize(ReadOnlySpan<ushort> source, Span<float> destination) |
||||
|
=> Normalize<ushort, UInt16NormalizeOperator>(source, destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts an integer lookup table using the widest available portable SIMD width, followed by narrower
|
||||
|
/// widths and a scalar remainder.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The integer element type.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The conversion implementation.</typeparam>
|
||||
|
/// <param name="source">The integer lookup-table entries.</param>
|
||||
|
/// <param name="destination">The normalized destination values.</param>
|
||||
|
private static void Normalize<T, TOperator>(ReadOnlySpan<T> source, Span<float> destination) |
||||
|
where T : unmanaged |
||||
|
where TOperator : struct, INormalizeOperator<T> |
||||
|
{ |
||||
|
ref T sourceRef = ref MemoryMarshal.GetReference(source); |
||||
|
ref float destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
nuint length = (uint)source.Length; |
||||
|
nuint index = 0; |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector512<float> divisor = Vector512.Create(TOperator.Divisor); |
||||
|
nuint count = (uint)Vector512<T>.Count; |
||||
|
|
||||
|
while (length - index >= count) |
||||
|
{ |
||||
|
ref float destinationStart = ref Unsafe.Add(ref destinationRef, index); |
||||
|
TOperator.Invoke(Vector512.LoadUnsafe(ref sourceRef, index), divisor, ref destinationStart); |
||||
|
index += count; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector256<float> divisor = Vector256.Create(TOperator.Divisor); |
||||
|
nuint count = (uint)Vector256<T>.Count; |
||||
|
|
||||
|
while (length - index >= count) |
||||
|
{ |
||||
|
ref float destinationStart = ref Unsafe.Add(ref destinationRef, index); |
||||
|
TOperator.Invoke(Vector256.LoadUnsafe(ref sourceRef, index), divisor, ref destinationStart); |
||||
|
index += count; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector128<float> divisor = Vector128.Create(TOperator.Divisor); |
||||
|
nuint count = (uint)Vector128<T>.Count; |
||||
|
|
||||
|
while (length - index >= count) |
||||
|
{ |
||||
|
ref float destinationStart = ref Unsafe.Add(ref destinationRef, index); |
||||
|
TOperator.Invoke(Vector128.LoadUnsafe(ref sourceRef, index), divisor, ref destinationStart); |
||||
|
index += count; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Preserve the scalar division expression for the final partial vector. Multiplication by a reciprocal
|
||||
|
// is not bit-equivalent for every input and would change the values stored in the ICC profile model.
|
||||
|
while (index < length) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, index) = TOperator.Invoke(Unsafe.Add(ref sourceRef, index)); |
||||
|
index++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts packed byte entries to normalized single-precision values.
|
||||
|
/// </summary>
|
||||
|
private readonly struct ByteNormalizeOperator : INormalizeOperator<byte> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static float Divisor => byte.MaxValue; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static float Invoke(byte source) |
||||
|
=> source / (float)byte.MaxValue; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Invoke(Vector128<byte> source, Vector128<float> divisor, ref float destination) |
||||
|
{ |
||||
|
// [b0..b15] becomes four ordered groups of four UInt32 values. Every widened value is at most
|
||||
|
// 255, so reinterpreting UInt32 as Int32 before conversion preserves its numeric value.
|
||||
|
(Vector128<ushort> lower16, Vector128<ushort> upper16) = Vector128.Widen(source); |
||||
|
(Vector128<uint> values0, Vector128<uint> values1) = Vector128.Widen(lower16); |
||||
|
(Vector128<uint> values2, Vector128<uint> values3) = Vector128.Widen(upper16); |
||||
|
|
||||
|
(Vector128.ConvertToSingle(values0.AsInt32()) / divisor).StoreUnsafe(ref destination); |
||||
|
(Vector128.ConvertToSingle(values1.AsInt32()) / divisor).StoreUnsafe(ref destination, 4); |
||||
|
(Vector128.ConvertToSingle(values2.AsInt32()) / divisor).StoreUnsafe(ref destination, 8); |
||||
|
(Vector128.ConvertToSingle(values3.AsInt32()) / divisor).StoreUnsafe(ref destination, 12); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Invoke(Vector256<byte> source, Vector256<float> divisor, ref float destination) |
||||
|
{ |
||||
|
// [b0..b31] becomes four ordered groups of eight UInt32 values, matching four contiguous
|
||||
|
// Vector256<float> stores without shuffling the converted results.
|
||||
|
(Vector256<ushort> lower16, Vector256<ushort> upper16) = Vector256.Widen(source); |
||||
|
(Vector256<uint> values0, Vector256<uint> values1) = Vector256.Widen(lower16); |
||||
|
(Vector256<uint> values2, Vector256<uint> values3) = Vector256.Widen(upper16); |
||||
|
|
||||
|
(Vector256.ConvertToSingle(values0.AsInt32()) / divisor).StoreUnsafe(ref destination); |
||||
|
(Vector256.ConvertToSingle(values1.AsInt32()) / divisor).StoreUnsafe(ref destination, 8); |
||||
|
(Vector256.ConvertToSingle(values2.AsInt32()) / divisor).StoreUnsafe(ref destination, 16); |
||||
|
(Vector256.ConvertToSingle(values3.AsInt32()) / divisor).StoreUnsafe(ref destination, 24); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Invoke(Vector512<byte> source, Vector512<float> divisor, ref float destination) |
||||
|
{ |
||||
|
// [b0..b63] becomes four ordered groups of sixteen UInt32 values, matching four contiguous
|
||||
|
// Vector512<float> stores. The portable widening APIs map to zero-extension instructions.
|
||||
|
(Vector512<ushort> lower16, Vector512<ushort> upper16) = Vector512.Widen(source); |
||||
|
(Vector512<uint> values0, Vector512<uint> values1) = Vector512.Widen(lower16); |
||||
|
(Vector512<uint> values2, Vector512<uint> values3) = Vector512.Widen(upper16); |
||||
|
|
||||
|
(Vector512.ConvertToSingle(values0.AsInt32()) / divisor).StoreUnsafe(ref destination); |
||||
|
(Vector512.ConvertToSingle(values1.AsInt32()) / divisor).StoreUnsafe(ref destination, 16); |
||||
|
(Vector512.ConvertToSingle(values2.AsInt32()) / divisor).StoreUnsafe(ref destination, 32); |
||||
|
(Vector512.ConvertToSingle(values3.AsInt32()) / divisor).StoreUnsafe(ref destination, 48); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts packed unsigned-short entries to normalized single-precision values.
|
||||
|
/// </summary>
|
||||
|
private readonly struct UInt16NormalizeOperator : INormalizeOperator<ushort> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static float Divisor => ushort.MaxValue; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static float Invoke(ushort source) |
||||
|
=> source / (float)ushort.MaxValue; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Invoke(Vector128<ushort> source, Vector128<float> divisor, ref float destination) |
||||
|
{ |
||||
|
// [u0..u7] becomes two ordered groups of four UInt32 values. Every value is at most 65535,
|
||||
|
// so signed conversion after reinterpretation is numerically identical to unsigned conversion.
|
||||
|
(Vector128<uint> lower, Vector128<uint> upper) = Vector128.Widen(source); |
||||
|
|
||||
|
(Vector128.ConvertToSingle(lower.AsInt32()) / divisor).StoreUnsafe(ref destination); |
||||
|
(Vector128.ConvertToSingle(upper.AsInt32()) / divisor).StoreUnsafe(ref destination, 4); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Invoke(Vector256<ushort> source, Vector256<float> divisor, ref float destination) |
||||
|
{ |
||||
|
// [u0..u15] becomes two ordered groups of eight UInt32 values, matching two contiguous
|
||||
|
// Vector256<float> stores without a result shuffle.
|
||||
|
(Vector256<uint> lower, Vector256<uint> upper) = Vector256.Widen(source); |
||||
|
|
||||
|
(Vector256.ConvertToSingle(lower.AsInt32()) / divisor).StoreUnsafe(ref destination); |
||||
|
(Vector256.ConvertToSingle(upper.AsInt32()) / divisor).StoreUnsafe(ref destination, 8); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Invoke(Vector512<ushort> source, Vector512<float> divisor, ref float destination) |
||||
|
{ |
||||
|
// [u0..u31] becomes two ordered groups of sixteen UInt32 values, matching two contiguous
|
||||
|
// Vector512<float> stores. The portable widening APIs map to zero-extension instructions.
|
||||
|
(Vector512<uint> lower, Vector512<uint> upper) = Vector512.Widen(source); |
||||
|
|
||||
|
(Vector512.ConvertToSingle(lower.AsInt32()) / divisor).StoreUnsafe(ref destination); |
||||
|
(Vector512.ConvertToSingle(upper.AsInt32()) / divisor).StoreUnsafe(ref destination, 16); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -1,38 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Numerics; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Provides the vector representation used to blend pixels that store associated alpha.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The associated-alpha pixel format.</typeparam>
|
|
||||
internal abstract class AssociatedAlphaPixelBlender<TPixel> : PixelBlender<TPixel> |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
private static readonly PixelOperations<TPixel> Operations = PixelOperations<TPixel>.Instance; |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
protected override void ToBlendVector4<TPixelSource>( |
|
||||
Configuration configuration, |
|
||||
ReadOnlySpan<TPixelSource> source, |
|
||||
Span<Vector4> destination) |
|
||||
{ |
|
||||
// Selecting the source representation once per row avoids a format check for every blended pixel.
|
|
||||
PixelOperations<TPixelSource>.Instance.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
protected override Vector4 ToBlendVector4(TPixel source) => source.ToAssociatedScaledVector4(); |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
protected override void FromBlendVector4( |
|
||||
Configuration configuration, |
|
||||
Span<Vector4> source, |
|
||||
Span<TPixel> destination) |
|
||||
{ |
|
||||
Operations.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); |
|
||||
} |
|
||||
} |
|
||||
File diff suppressed because it is too large
@ -0,0 +1,45 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines a Porter-Duff equation that can be applied by the shared pixel-blending traversal.
|
||||
|
/// </summary>
|
||||
|
internal interface IPixelBlenderOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether pixel conversion uses associated-alpha vectors.
|
||||
|
/// </summary>
|
||||
|
public static abstract bool IsAssociatedAlpha { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends one pixel represented by four RGBA lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="background">The background RGBA lanes.</param>
|
||||
|
/// <param name="source">The source RGBA lanes.</param>
|
||||
|
/// <param name="amount">The source opacity in the range 0 through 1.</param>
|
||||
|
/// <returns>The blended RGBA lanes.</returns>
|
||||
|
public static abstract Vector4 Invoke(Vector4 background, Vector4 source, float amount); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends two pixels represented by two consecutive groups of four RGBA lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="background">The background RGBA lanes.</param>
|
||||
|
/// <param name="source">The source RGBA lanes.</param>
|
||||
|
/// <param name="amount">The source opacity repeated across each pixel's four lanes.</param>
|
||||
|
/// <returns>The blended RGBA lanes.</returns>
|
||||
|
public static abstract Vector256<float> Invoke(Vector256<float> background, Vector256<float> source, Vector256<float> amount); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends four pixels represented by four consecutive groups of four RGBA lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="background">The background RGBA lanes.</param>
|
||||
|
/// <param name="source">The source RGBA lanes.</param>
|
||||
|
/// <param name="amount">The source opacity repeated across each pixel's four lanes.</param>
|
||||
|
/// <returns>The blended RGBA lanes.</returns>
|
||||
|
public static abstract Vector512<float> Invoke(Vector512<float> background, Vector512<float> source, Vector512<float> amount); |
||||
|
} |
||||
@ -0,0 +1,586 @@ |
|||||
|
// 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; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a statically selected Porter-Duff equation across pixel-vector rows.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The destination pixel format.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The Porter-Duff equation.</typeparam>
|
||||
|
internal sealed class PixelBlender<TPixel, TOperator> : PixelBlender<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
where TOperator : struct, IPixelBlenderOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PixelBlender{TPixel, TOperator}"/> class.
|
||||
|
/// </summary>
|
||||
|
private PixelBlender() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the shared blender instance for this exact pixel and equation combination.
|
||||
|
/// </summary>
|
||||
|
// Keeping the singleton on the already-required closed blender type preserves per-mode lazy initialization
|
||||
|
// without introducing another NativeAOT holder type or eagerly allocating every blender for a pixel format.
|
||||
|
public static PixelBlender<TPixel> Instance { get; } = new PixelBlender<TPixel, TOperator>(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override TPixel Blend(TPixel background, TPixel source, float amount) |
||||
|
{ |
||||
|
// The operator type uniquely determines the alpha representation. NativeAOT therefore needs only the
|
||||
|
// pixel/operator closure, while the JIT can remove this static choice from the generated hot path.
|
||||
|
Vector4 backgroundVector = TOperator.IsAssociatedAlpha |
||||
|
? background.ToAssociatedScaledVector4() |
||||
|
: background.ToUnassociatedScaledVector4(); |
||||
|
Vector4 sourceVector = TOperator.IsAssociatedAlpha |
||||
|
? source.ToAssociatedScaledVector4() |
||||
|
: source.ToUnassociatedScaledVector4(); |
||||
|
|
||||
|
Vector4 result = TOperator.Invoke(backgroundVector, sourceVector, Numerics.Clamp(amount, 0, 1F)); |
||||
|
|
||||
|
return TOperator.IsAssociatedAlpha |
||||
|
? TPixel.FromAssociatedScaledVector4(result) |
||||
|
: TPixel.FromUnassociatedScaledVector4(result); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void ToBlendVector4<TPixelSource>( |
||||
|
Configuration configuration, |
||||
|
ReadOnlySpan<TPixelSource> source, |
||||
|
Span<Vector4> destination) |
||||
|
{ |
||||
|
PixelConversionModifiers modifiers = TOperator.IsAssociatedAlpha |
||||
|
? PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply |
||||
|
: PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply; |
||||
|
|
||||
|
PixelOperations<TPixelSource>.Instance.ToVector4(configuration, source, destination, modifiers); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override Vector4 ToBlendVector4(TPixel source) |
||||
|
=> TOperator.IsAssociatedAlpha |
||||
|
? source.ToAssociatedScaledVector4() |
||||
|
: source.ToUnassociatedScaledVector4(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void FromBlendVector4( |
||||
|
Configuration configuration, |
||||
|
Span<Vector4> source, |
||||
|
Span<TPixel> destination) |
||||
|
{ |
||||
|
PixelConversionModifiers modifiers = TOperator.IsAssociatedAlpha |
||||
|
? PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply |
||||
|
: PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply; |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, source, destination, modifiers); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount) |
||||
|
{ |
||||
|
// Public entry points validate the row lengths, so all three references can advance in lockstep.
|
||||
|
int scalarStart = 0; |
||||
|
amount = Numerics.Clamp(amount, 0, 1F); |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated && destination.Length >= 4) |
||||
|
{ |
||||
|
// A 512-bit register holds four complete RGBA pixels in [R,G,B,A] groups.
|
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> amountVector = Vector512.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) |
||||
|
{ |
||||
|
// A 256-bit register holds two complete RGBA pixels in [R,G,B,A] groups.
|
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> amountVector = Vector256.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
// Vector4 is both the scalar pixel representation and the portable SIMD fallback.
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), Unsafe.Add(ref sourceRef, (uint)i), amount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount) |
||||
|
{ |
||||
|
// Public entry points validate the row lengths, so the destination and background advance together.
|
||||
|
int scalarStart = 0; |
||||
|
amount = Numerics.Clamp(amount, 0, 1F); |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated && destination.Length >= 4) |
||||
|
{ |
||||
|
// Repeat one [R,G,B,A] source group four times to match the four background pixels.
|
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> sourceVector = CreateVector512(source); |
||||
|
Vector512<float> amountVector = Vector512.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) |
||||
|
{ |
||||
|
// Repeat one [R,G,B,A] source group twice to match the two background pixels.
|
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> sourceVector = CreateVector256(source); |
||||
|
Vector256<float> amountVector = Vector256.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
// The remaining pixel count is at most three after AVX-512 or one after AVX2.
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), source, amount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount) |
||||
|
{ |
||||
|
// Each amount belongs to one pixel and must be repeated across that pixel's four RGBA lanes.
|
||||
|
int scalarStart = 0; |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); |
||||
|
ref float amountRef = ref MemoryMarshal.GetReference(amount); |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
// Four scalar amounts become four contiguous [a,a,a,a] groups before clamping.
|
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); |
||||
|
Vector512<float> amountVector = CreateClampedVector512(ref amountBase); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
// Two scalar amounts become two contiguous [a,a,a,a] groups before clamping.
|
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); |
||||
|
Vector256<float> amountVector = CreateClampedVector256(ref amountBase); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), Unsafe.Add(ref sourceRef, (uint)i), Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount) |
||||
|
{ |
||||
|
// The source is invariant, while each background pixel has its own independently clamped amount.
|
||||
|
int scalarStart = 0; |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref float amountRef = ref MemoryMarshal.GetReference(amount); |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> sourceVector = CreateVector512(source); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); |
||||
|
Vector512<float> amountVector = CreateClampedVector512(ref amountBase); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> sourceVector = CreateVector256(source); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); |
||||
|
Vector256<float> amountVector = CreateClampedVector256(ref amountBase); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), source, Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount, ReadOnlySpan<float> coverage) |
||||
|
{ |
||||
|
// Coverage mixes the composed result back toward the original background, so it is fused into this pass.
|
||||
|
int scalarStart = 0; |
||||
|
amount = Numerics.Clamp(amount, 0, 1F); |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); |
||||
|
ref float coverageRef = ref MemoryMarshal.GetReference(coverage); |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> amountVector = Vector512.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector512<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); |
||||
|
Vector512<float> coverageVector = CreateClampedVector512(ref coverageBase); |
||||
|
Vector512<float> blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> amountVector = Vector256.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector256<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); |
||||
|
Vector256<float> coverageVector = CreateClampedVector256(ref coverageBase); |
||||
|
Vector256<float> blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); |
||||
|
Vector4 blended = TOperator.Invoke(backgroundPixel, Unsafe.Add(ref sourceRef, (uint)i), amount); |
||||
|
|
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount, ReadOnlySpan<float> coverage) |
||||
|
{ |
||||
|
// The constant source is expanded once per selected width and reused for the complete row.
|
||||
|
int scalarStart = 0; |
||||
|
amount = Numerics.Clamp(amount, 0, 1F); |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref float coverageRef = ref MemoryMarshal.GetReference(coverage); |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> sourceVector = CreateVector512(source); |
||||
|
Vector512<float> amountVector = Vector512.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector512<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); |
||||
|
Vector512<float> coverageVector = CreateClampedVector512(ref coverageBase); |
||||
|
Vector512<float> blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> sourceVector = CreateVector256(source); |
||||
|
Vector256<float> amountVector = Vector256.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector256<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); |
||||
|
Vector256<float> coverageVector = CreateClampedVector256(ref coverageBase); |
||||
|
Vector256<float> blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); |
||||
|
Vector4 blended = TOperator.Invoke(backgroundPixel, source, amount); |
||||
|
|
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount, ReadOnlySpan<float> coverage) |
||||
|
{ |
||||
|
// Amount controls composition while coverage controls the final mix with the untouched background.
|
||||
|
int scalarStart = 0; |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); |
||||
|
ref float amountRef = ref MemoryMarshal.GetReference(amount); |
||||
|
ref float coverageRef = ref MemoryMarshal.GetReference(coverage); |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector512<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); |
||||
|
Vector512<float> amountVector = CreateClampedVector512(ref amountBase); |
||||
|
Vector512<float> coverageVector = CreateClampedVector512(ref coverageBase); |
||||
|
Vector512<float> blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector256<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); |
||||
|
Vector256<float> amountVector = CreateClampedVector256(ref amountBase); |
||||
|
Vector256<float> coverageVector = CreateClampedVector256(ref coverageBase); |
||||
|
Vector256<float> blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); |
||||
|
Vector4 blended = TOperator.Invoke(backgroundPixel, Unsafe.Add(ref sourceRef, (uint)i), Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); |
||||
|
|
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount, ReadOnlySpan<float> coverage) |
||||
|
{ |
||||
|
// The invariant source is expanded once; only amount and coverage are gathered for each vector batch.
|
||||
|
int scalarStart = 0; |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref float amountRef = ref MemoryMarshal.GetReference(amount); |
||||
|
ref float coverageRef = ref MemoryMarshal.GetReference(coverage); |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> sourceVector = CreateVector512(source); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector512<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); |
||||
|
Vector512<float> amountVector = CreateClampedVector512(ref amountBase); |
||||
|
Vector512<float> coverageVector = CreateClampedVector512(ref coverageBase); |
||||
|
Vector512<float> blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> sourceVector = CreateVector256(source); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector256<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); |
||||
|
Vector256<float> amountVector = CreateClampedVector256(ref amountBase); |
||||
|
Vector256<float> coverageVector = CreateClampedVector256(ref coverageBase); |
||||
|
Vector256<float> blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); |
||||
|
Vector4 blended = TOperator.Invoke(backgroundPixel, source, Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); |
||||
|
|
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Repeats one RGBA pixel twice to fill a 256-bit register.
|
||||
|
/// </summary>
|
||||
|
/// <param name="pixel">The pixel represented by ordered RGBA lanes.</param>
|
||||
|
/// <returns>Two consecutive copies of the pixel.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<float> CreateVector256(Vector4 pixel) |
||||
|
=> Vector256.Create(pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Repeats one RGBA pixel four times to fill a 512-bit register.
|
||||
|
/// </summary>
|
||||
|
/// <param name="pixel">The pixel represented by ordered RGBA lanes.</param>
|
||||
|
/// <returns>Four consecutive copies of the pixel.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<float> CreateVector512(Vector4 pixel) |
||||
|
=> Vector512.Create(pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Expands and clamps two per-pixel scalar values for two packed RGBA pixels.
|
||||
|
/// </summary>
|
||||
|
/// <param name="values">The first of two contiguous scalar values.</param>
|
||||
|
/// <returns>Two consecutive groups containing one clamped value in each group's four lanes.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<float> CreateClampedVector256(ref float values) |
||||
|
{ |
||||
|
Vector256<float> result = Vector256.Create(Vector128.Create(values), Vector128.Create(Unsafe.Add(ref values, 1))); |
||||
|
|
||||
|
// Amount and coverage share the same public 0..1 contract and therefore the same packed clamp.
|
||||
|
return Vector256.Min(Vector256.Max(Vector256<float>.Zero, result), Vector256.Create(1F)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Expands and clamps four per-pixel scalar values for four packed RGBA pixels.
|
||||
|
/// </summary>
|
||||
|
/// <param name="values">The first of four contiguous scalar values.</param>
|
||||
|
/// <returns>Four consecutive groups containing one clamped value in each group's four lanes.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<float> CreateClampedVector512(ref float values) |
||||
|
{ |
||||
|
Vector512<float> result = Vector512.Create(values, values, values, values, Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 3), Unsafe.Add(ref values, 3), Unsafe.Add(ref values, 3), Unsafe.Add(ref values, 3)); |
||||
|
|
||||
|
// Amount and coverage share the same public 0..1 contract and therefore the same packed clamp.
|
||||
|
return Vector512.Min(Vector512.Max(Vector512<float>.Zero, result), Vector512.Create(1F)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,147 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.PixelFormats.Utils; |
||||
|
|
||||
|
internal static partial class Vector4Converters |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines a stateful component transform for each register width used by the shared traversal.
|
||||
|
/// </summary>
|
||||
|
private interface IStatefulVector4Operator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Transforms one pixel represented by four components.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source components.</param>
|
||||
|
/// <returns>The transformed components.</returns>
|
||||
|
public Vector4 Invoke(Vector4 source); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transforms one pixel represented by the four single-precision lanes in a 128-bit register.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source components.</param>
|
||||
|
/// <returns>The transformed components.</returns>
|
||||
|
public Vector128<float> Invoke(Vector128<float> source); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transforms two pixels represented by the eight single-precision lanes in a 256-bit register.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source components.</param>
|
||||
|
/// <returns>The transformed components.</returns>
|
||||
|
public Vector256<float> Invoke(Vector256<float> source); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transforms four pixels represented by the sixteen single-precision lanes in a 512-bit register.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source components.</param>
|
||||
|
/// <returns>The transformed components.</returns>
|
||||
|
public Vector512<float> Invoke(Vector512<float> source); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Carries the component state for a multiply-then-add transform.
|
||||
|
/// </summary>
|
||||
|
private readonly struct MultiplyThenAddOperator : IStatefulVector4Operator |
||||
|
{ |
||||
|
private readonly Vector512<float> multiplier; |
||||
|
private readonly Vector512<float> offset; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="MultiplyThenAddOperator"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="multiplier">The component-wise multiplier.</param>
|
||||
|
/// <param name="offset">The component-wise offset applied after multiplication.</param>
|
||||
|
public MultiplyThenAddOperator(Vector4 multiplier, Vector4 offset) |
||||
|
{ |
||||
|
Vector128<float> multiplier128 = multiplier.AsVector128(); |
||||
|
Vector128<float> offset128 = offset.AsVector128(); |
||||
|
Vector256<float> multiplier256 = Vector256.Create(multiplier128, multiplier128); |
||||
|
Vector256<float> offset256 = Vector256.Create(offset128, offset128); |
||||
|
|
||||
|
// Expanding the invariant state once prevents the width-specific Invoke methods
|
||||
|
// from rebuilding identical lane groups for every vector processed by the loop.
|
||||
|
this.multiplier = Vector512.Create(multiplier256, multiplier256); |
||||
|
this.offset = Vector512.Create(offset256, offset256); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public Vector4 Invoke(Vector4 source) |
||||
|
{ |
||||
|
Vector128<float> result = (source.AsVector128() * this.multiplier.GetLower().GetLower()) + this.offset.GetLower().GetLower(); |
||||
|
|
||||
|
return result.AsVector4(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public Vector128<float> Invoke(Vector128<float> source) |
||||
|
=> (source * this.multiplier.GetLower().GetLower()) + this.offset.GetLower().GetLower(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public Vector256<float> Invoke(Vector256<float> source) |
||||
|
=> (source * this.multiplier.GetLower()) + this.offset.GetLower(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public Vector512<float> Invoke(Vector512<float> source) |
||||
|
=> (source * this.multiplier) + this.offset; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Carries the component state for an add-then-divide transform.
|
||||
|
/// </summary>
|
||||
|
private readonly struct AddThenDivideOperator : IStatefulVector4Operator |
||||
|
{ |
||||
|
private readonly Vector512<float> offset; |
||||
|
private readonly Vector512<float> divisor; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="AddThenDivideOperator"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="offset">The component-wise offset applied before division.</param>
|
||||
|
/// <param name="divisor">The component-wise divisor.</param>
|
||||
|
public AddThenDivideOperator(Vector4 offset, Vector4 divisor) |
||||
|
{ |
||||
|
Vector128<float> offset128 = offset.AsVector128(); |
||||
|
Vector128<float> divisor128 = divisor.AsVector128(); |
||||
|
Vector256<float> offset256 = Vector256.Create(offset128, offset128); |
||||
|
Vector256<float> divisor256 = Vector256.Create(divisor128, divisor128); |
||||
|
|
||||
|
// All register widths consume prefixes of this repeated four-pixel state,
|
||||
|
// so one construction serves the wide loop and every narrower remainder.
|
||||
|
this.offset = Vector512.Create(offset256, offset256); |
||||
|
this.divisor = Vector512.Create(divisor256, divisor256); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public Vector4 Invoke(Vector4 source) |
||||
|
{ |
||||
|
Vector128<float> result = (source.AsVector128() + this.offset.GetLower().GetLower()) / this.divisor.GetLower().GetLower(); |
||||
|
|
||||
|
return result.AsVector4(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public Vector128<float> Invoke(Vector128<float> source) |
||||
|
=> (source + this.offset.GetLower().GetLower()) / this.divisor.GetLower().GetLower(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public Vector256<float> Invoke(Vector256<float> source) |
||||
|
=> (source + this.offset.GetLower()) / this.divisor.GetLower(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public Vector512<float> Invoke(Vector512<float> source) |
||||
|
=> (source + this.offset) / this.divisor; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,178 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using BenchmarkDotNet.Columns; |
||||
|
using BenchmarkDotNet.Configs; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compares the previous scalar JPEG packing loops with the SIMD register-transpose implementation.
|
||||
|
/// </summary>
|
||||
|
[Config(typeof(Config.Short))] |
||||
|
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] |
||||
|
[CategoriesColumn] |
||||
|
public class JpegColorPacking |
||||
|
{ |
||||
|
private const float MaximumValue = 255F; |
||||
|
private const float Scale = 1F / MaximumValue; |
||||
|
|
||||
|
private float[] x = null!; |
||||
|
private float[] y = null!; |
||||
|
private float[] z = null!; |
||||
|
private float[] w = null!; |
||||
|
private Vector3[] packed3 = null!; |
||||
|
private float[] destination3 = null!; |
||||
|
private float[] destination4 = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the number of pixels transformed by each benchmark invocation.
|
||||
|
/// </summary>
|
||||
|
[Params(128, 1024, 4096)] |
||||
|
public int Length { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates deterministic source and destination buffers outside the measured operations.
|
||||
|
/// </summary>
|
||||
|
[GlobalSetup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
this.x = CreateSamples(this.Length, 1); |
||||
|
this.y = CreateSamples(this.Length, 2); |
||||
|
this.z = CreateSamples(this.Length, 3); |
||||
|
this.w = CreateSamples(this.Length, 4); |
||||
|
this.packed3 = new Vector3[this.Length]; |
||||
|
this.destination3 = new float[this.Length * 3]; |
||||
|
this.destination4 = new float[this.Length * 4]; |
||||
|
|
||||
|
for (int i = 0; i < this.packed3.Length; i++) |
||||
|
{ |
||||
|
this.packed3[i] = new Vector3(this.x[i], this.y[i], this.z[i]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures the previous scalar three-plane normalization and interleave loop.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The last destination value, keeping the writes observable.</returns>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("Pack3")] |
||||
|
public float PackedNormalizeInterleave3Scalar() |
||||
|
{ |
||||
|
JpegColorPackingScalar.PackedNormalizeInterleave3(this.x, this.y, this.z, this.destination3, Scale); |
||||
|
|
||||
|
return this.destination3[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures the SIMD three-plane normalization and interleave implementation.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The last destination value, keeping the writes observable.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("Pack3")] |
||||
|
public float PackedNormalizeInterleave3Simd() |
||||
|
{ |
||||
|
JpegColorConverterBase.PackedNormalizeInterleave3(this.x, this.y, this.z, this.destination3, Scale); |
||||
|
|
||||
|
return this.destination3[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures the previous scalar packed-three-channel deinterleave loop.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A checksum containing the last value written to every destination plane.</returns>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("Unpack3")] |
||||
|
public float UnpackDeinterleave3Scalar() |
||||
|
{ |
||||
|
JpegColorPackingScalar.UnpackDeinterleave3(this.packed3, this.x, this.y, this.z); |
||||
|
|
||||
|
return this.x[^1] + this.y[^1] + this.z[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures the SIMD packed-three-channel deinterleave implementation.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A checksum containing the last value written to every destination plane.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("Unpack3")] |
||||
|
public float UnpackDeinterleave3Simd() |
||||
|
{ |
||||
|
JpegColorConverterBase.UnpackDeinterleave3(this.packed3, this.x, this.y, this.z); |
||||
|
|
||||
|
return this.x[^1] + this.y[^1] + this.z[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures the previous scalar four-plane normalization and interleave loop.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The last destination value, keeping the writes observable.</returns>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("Pack4")] |
||||
|
public float PackedNormalizeInterleave4Scalar() |
||||
|
{ |
||||
|
JpegColorPackingScalar.PackedNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); |
||||
|
|
||||
|
return this.destination4[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures the SIMD four-plane normalization and interleave implementation.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The last destination value, keeping the writes observable.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("Pack4")] |
||||
|
public float PackedNormalizeInterleave4Simd() |
||||
|
{ |
||||
|
JpegColorConverterBase.PackedNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); |
||||
|
|
||||
|
return this.destination4[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures the previous scalar inverted four-plane normalization and interleave loop.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The last destination value, keeping the writes observable.</returns>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("InvertPack4")] |
||||
|
public float PackedInvertNormalizeInterleave4Scalar() |
||||
|
{ |
||||
|
JpegColorPackingScalar.PackedInvertNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); |
||||
|
|
||||
|
return this.destination4[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures the SIMD inverted four-plane normalization and interleave implementation.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The last destination value, keeping the writes observable.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("InvertPack4")] |
||||
|
public float PackedInvertNormalizeInterleave4Simd() |
||||
|
{ |
||||
|
JpegColorConverterBase.PackedInvertNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); |
||||
|
|
||||
|
return this.destination4[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates deterministic, non-integral samples for one component plane.
|
||||
|
/// </summary>
|
||||
|
/// <param name="length">The number of samples to create.</param>
|
||||
|
/// <param name="component">The one-based component number used to distinguish the plane.</param>
|
||||
|
/// <returns>The generated samples.</returns>
|
||||
|
private static float[] CreateSamples(int length, int component) |
||||
|
{ |
||||
|
float[] samples = new float[length]; |
||||
|
|
||||
|
for (int i = 0; i < samples.Length; i++) |
||||
|
{ |
||||
|
samples[i] = (((i * 37) + (component * 53)) % 251) + (component * 0.125F); |
||||
|
} |
||||
|
|
||||
|
return samples; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,117 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Preserves the scalar JPEG packing loops that preceded the SIMD investigation.
|
||||
|
/// </summary>
|
||||
|
internal static class JpegColorPackingScalar |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Normalizes and interleaves three planar component lanes using the previous scalar implementation.
|
||||
|
/// </summary>
|
||||
|
/// <param name="xLane">The planar X components.</param>
|
||||
|
/// <param name="yLane">The planar Y components.</param>
|
||||
|
/// <param name="zLane">The planar Z components.</param>
|
||||
|
/// <param name="packed">The destination ordered as consecutive XYZ triples.</param>
|
||||
|
/// <param name="scale">The normalization factor applied to every component.</param>
|
||||
|
public static void PackedNormalizeInterleave3(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, Span<float> packed, float scale) |
||||
|
{ |
||||
|
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
||||
|
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
||||
|
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
||||
|
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
||||
|
|
||||
|
for (nuint i = 0; i < (nuint)xLane.Length; i++) |
||||
|
{ |
||||
|
nuint packedOffset = i * 3; |
||||
|
Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, i) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, i) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, i) * scale; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Deinterleaves packed XYZ values using the previous scalar implementation.
|
||||
|
/// </summary>
|
||||
|
/// <param name="packed">The source ordered as consecutive XYZ triples.</param>
|
||||
|
/// <param name="xLane">The destination X components.</param>
|
||||
|
/// <param name="yLane">The destination Y components.</param>
|
||||
|
/// <param name="zLane">The destination Z components.</param>
|
||||
|
public static void UnpackDeinterleave3(ReadOnlySpan<Vector3> packed, Span<float> xLane, Span<float> yLane, Span<float> zLane) |
||||
|
{ |
||||
|
ref float packedRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Vector3, float>(packed)); |
||||
|
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
||||
|
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
||||
|
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
||||
|
|
||||
|
for (nuint i = 0; i < (nuint)packed.Length; i++) |
||||
|
{ |
||||
|
nuint packedOffset = i * 3; |
||||
|
Unsafe.Add(ref xLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset); |
||||
|
Unsafe.Add(ref yLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 1); |
||||
|
Unsafe.Add(ref zLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 2); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Normalizes and interleaves four planar component lanes using the previous scalar implementation.
|
||||
|
/// </summary>
|
||||
|
/// <param name="xLane">The planar X components.</param>
|
||||
|
/// <param name="yLane">The planar Y components.</param>
|
||||
|
/// <param name="zLane">The planar Z components.</param>
|
||||
|
/// <param name="wLane">The planar W components.</param>
|
||||
|
/// <param name="packed">The destination ordered as consecutive XYZW groups.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value used to normalize each component.</param>
|
||||
|
public static void PackedNormalizeInterleave4(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, ReadOnlySpan<float> wLane, Span<float> packed, float maximumValue) |
||||
|
{ |
||||
|
float scale = 1F / maximumValue; |
||||
|
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
||||
|
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
||||
|
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
||||
|
ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); |
||||
|
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
||||
|
|
||||
|
for (nuint i = 0; i < (nuint)xLane.Length; i++) |
||||
|
{ |
||||
|
nuint packedOffset = i * 4; |
||||
|
Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, i) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, i) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, i) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 3) = Unsafe.Add(ref wLaneRef, i) * scale; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Inverts, normalizes, and interleaves four planar lanes using the previous scalar implementation.
|
||||
|
/// </summary>
|
||||
|
/// <param name="xLane">The inverted planar X components.</param>
|
||||
|
/// <param name="yLane">The inverted planar Y components.</param>
|
||||
|
/// <param name="zLane">The inverted planar Z components.</param>
|
||||
|
/// <param name="wLane">The inverted planar W components.</param>
|
||||
|
/// <param name="packed">The destination ordered as consecutive conventional XYZW groups.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value used for inversion and normalization.</param>
|
||||
|
public static void PackedInvertNormalizeInterleave4(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, ReadOnlySpan<float> wLane, Span<float> packed, float maximumValue) |
||||
|
{ |
||||
|
float scale = 1F / maximumValue; |
||||
|
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
||||
|
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
||||
|
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
||||
|
ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); |
||||
|
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
||||
|
|
||||
|
for (nuint i = 0; i < (nuint)xLane.Length; i++) |
||||
|
{ |
||||
|
nuint packedOffset = i * 4; |
||||
|
Unsafe.Add(ref packedRef, packedOffset) = (maximumValue - Unsafe.Add(ref xLaneRef, i)) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 1) = (maximumValue - Unsafe.Add(ref yLaneRef, i)) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 2) = (maximumValue - Unsafe.Add(ref zLaneRef, i)) * scale; |
||||
|
Unsafe.Add(ref packedRef, packedOffset + 3) = (maximumValue - Unsafe.Add(ref wLaneRef, i)) * scale; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue