mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Introduce Rgba32P, Bgra32P, Argb32P, Abgr32P, NormalizedByte4P, and HalfVector4P. Add representation-aware scalar and bulk conversions, optimized pixel operations, and associated-alpha blending across all Porter-Duff modes. Include comprehensive conversion, layout, blending, and performance coverage.js/premultiplied-pixel-formats
57 changed files with 95465 additions and 219 deletions
@ -0,0 +1,258 @@ |
|||
// 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.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Provides bulk operations for pixel formats that store associated alpha.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The associated-alpha pixel format.</typeparam>
|
|||
internal class AssociatedAlphaPixelOperations<TPixel> : PixelOperations<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
/// <inheritdoc />
|
|||
public override PixelBlender<TPixel> GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) |
|||
=> AssociatedAlphaPixelBlenders<TPixel>.GetPixelBlender(colorMode, alphaMode); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override Vector4 ToUnassociatedScaledVector4(TPixel source) |
|||
{ |
|||
Vector4 vector = source.ToScaledVector4(); |
|||
Numerics.UnPremultiply(ref vector); |
|||
return vector; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override TPixel FromUnassociatedScaledVector4(Vector4 source) => TPixel.FromScaledVector4(Associate(source)); |
|||
|
|||
/// <summary>
|
|||
/// Converts an associated scaled vector to a destination pixel after associating RGB with the alpha value the destination stores.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated scaled vector.</param>
|
|||
/// <returns>The destination pixel.</returns>
|
|||
public virtual TPixel FromAssociatedScaledVector4(Vector4 source) => TPixel.FromScaledVector4(Reassociate(source)); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void ToUnassociatedScaledVector4( |
|||
Configuration configuration, |
|||
ReadOnlySpan<TPixel> source, |
|||
Span<Vector4> destination) |
|||
{ |
|||
this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
Numerics.UnPremultiply(destination[..source.Length]); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void ToAssociatedScaledVector4( |
|||
Configuration configuration, |
|||
ReadOnlySpan<TPixel> source, |
|||
Span<Vector4> destination) |
|||
=> this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void FromUnassociatedScaledVector4( |
|||
Configuration configuration, |
|||
Span<Vector4> source, |
|||
Span<TPixel> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
source[i] = Associate(source[i]); |
|||
} |
|||
|
|||
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts associated scaled vectors to destination pixels after associating RGB with the alpha values the destination stores.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="source">The associated scaled vectors.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
public virtual void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<TPixel> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
source[i] = Reassociate(source[i]); |
|||
} |
|||
|
|||
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void From<TSourcePixel>( |
|||
Configuration configuration, |
|||
ReadOnlySpan<TSourcePixel> source, |
|||
Span<TPixel> destination) |
|||
{ |
|||
const int sliceLength = 1024; |
|||
int numberOfSlices = source.Length / sliceLength; |
|||
|
|||
using IMemoryOwner<Vector4> tempVectors = configuration.MemoryAllocator.Allocate<Vector4>(sliceLength); |
|||
Span<Vector4> vectorSpan = tempVectors.GetSpan(); |
|||
|
|||
// Convert through unassociated vectors so the destination operation can quantize alpha to its own storage before associating RGB.
|
|||
for (int i = 0; i < numberOfSlices; i++) |
|||
{ |
|||
int start = i * sliceLength; |
|||
ReadOnlySpan<TSourcePixel> sourceSlice = source.Slice(start, sliceLength); |
|||
Span<TPixel> destinationSlice = destination.Slice(start, sliceLength); |
|||
PixelOperations<TSourcePixel>.Instance.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan); |
|||
this.FromUnassociatedScaledVector4(configuration, vectorSpan, destinationSlice); |
|||
} |
|||
|
|||
int endOfCompleteSlices = numberOfSlices * sliceLength; |
|||
int remainder = source.Length - endOfCompleteSlices; |
|||
|
|||
if (remainder > 0) |
|||
{ |
|||
ReadOnlySpan<TSourcePixel> sourceSlice = source[endOfCompleteSlices..]; |
|||
Span<TPixel> destinationSlice = destination[endOfCompleteSlices..]; |
|||
vectorSpan = vectorSpan[..remainder]; |
|||
PixelOperations<TSourcePixel>.Instance.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan); |
|||
this.FromUnassociatedScaledVector4(configuration, vectorSpan, destinationSlice); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void FromVector4Destructive( |
|||
Configuration configuration, |
|||
Span<Vector4> sourceVectors, |
|||
Span<TPixel> destination, |
|||
PixelConversionModifiers modifiers) |
|||
=> base.FromVector4Destructive(configuration, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToVector4( |
|||
Configuration configuration, |
|||
ReadOnlySpan<TPixel> source, |
|||
Span<Vector4> destinationVectors, |
|||
PixelConversionModifiers modifiers) |
|||
=> base.ToVector4(configuration, source, destinationVectors, modifiers.Remove(PixelConversionModifiers.Premultiply)); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToArgb32(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Argb32> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToAbgr32(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Abgr32> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToBgr24(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Bgr24> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToBgra32(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Bgra32> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToL8(Configuration configuration, ReadOnlySpan<TPixel> source, Span<L8> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToL16(Configuration configuration, ReadOnlySpan<TPixel> source, Span<L16> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToLa16(Configuration configuration, ReadOnlySpan<TPixel> source, Span<La16> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToLa32(Configuration configuration, ReadOnlySpan<TPixel> source, Span<La32> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToRgb24(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Rgb24> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToRgba32(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Rgba32> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToRgb48(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Rgb48> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToRgba64(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Rgba64> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToBgra5551(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Bgra5551> destination) |
|||
=> this.ConvertToUnassociated(configuration, source, destination); |
|||
|
|||
/// <summary>
|
|||
/// Converts associated source pixels to an unassociated destination format.
|
|||
/// </summary>
|
|||
/// <typeparam name="TDestinationPixel">The destination pixel format.</typeparam>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="source">The source pixels.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
private void ConvertToUnassociated<TDestinationPixel>( |
|||
Configuration configuration, |
|||
ReadOnlySpan<TPixel> source, |
|||
Span<TDestinationPixel> destination) |
|||
where TDestinationPixel : unmanaged, IPixel<TDestinationPixel> |
|||
{ |
|||
Guard.NotNull(configuration, nameof(configuration)); |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref TDestinationPixel destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Vector4 vector = this.ToUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i)); |
|||
Unsafe.Add(ref destinationBase, i) = TDestinationPixel.FromScaledVector4(vector); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an unassociated scaled vector to the associated representation of the destination pixel type.
|
|||
/// </summary>
|
|||
/// <param name="source">The unassociated scaled vector.</param>
|
|||
/// <returns>The associated scaled vector.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 Associate(Vector4 source) |
|||
{ |
|||
// Round-trip alpha through TPixel so the generic fallback associates RGB with the value the destination actually stores.
|
|||
source.W = TPixel.FromScaledVector4(new Vector4(0, 0, 0, source.W)).ToScaledVector4().W; |
|||
Numerics.Premultiply(ref source); |
|||
return source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reassociates a scaled vector with the alpha value the destination pixel can store.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated scaled vector.</param>
|
|||
/// <returns>The reassociated scaled vector.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 Reassociate(Vector4 source) |
|||
{ |
|||
float alpha = source.W; |
|||
|
|||
if (alpha == 0) |
|||
{ |
|||
return Vector4.Zero; |
|||
} |
|||
|
|||
float storedAlpha = TPixel.FromScaledVector4(new Vector4(0, 0, 0, alpha)).ToScaledVector4().W; |
|||
|
|||
// Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization.
|
|||
source *= storedAlpha / alpha; |
|||
source.W = storedAlpha; |
|||
return source; |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,847 @@ |
|||
<# |
|||
// Copyright (c) Six Labors. |
|||
// Licensed under the Six Labors Split License. |
|||
#> |
|||
<#@ template debug="false" hostspecific="false" language="C#" #> |
|||
<#@ assembly name="System.Core" #> |
|||
<#@ import namespace="System.Linq" #> |
|||
<#@ import namespace="System.Text" #> |
|||
<#@ import namespace="System.Collections.Generic" #> |
|||
<#@ output extension=".cs" #> |
|||
// Copyright (c) Six Labors. |
|||
// Licensed under the Six Labors Split License. |
|||
|
|||
// <auto-generated /> |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using System.Runtime.Intrinsics.X86; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
|||
|
|||
/// <summary> |
|||
/// Provides generated Porter-Duff blenders for associated-alpha pixel formats. |
|||
/// </summary> |
|||
internal static partial class AssociatedAlphaPixelBlenders<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
|
|||
<# |
|||
var composers = new []{ |
|||
"Src", |
|||
"SrcAtop", |
|||
"SrcOver", |
|||
"SrcIn", |
|||
"SrcOut", |
|||
"Dest", |
|||
"DestAtop", |
|||
"DestOver", |
|||
"DestIn", |
|||
"DestOut", |
|||
"Clear", |
|||
"Xor", |
|||
}; |
|||
|
|||
var blenders = new []{ |
|||
"Normal", |
|||
"Multiply", |
|||
"Add", |
|||
"Subtract", |
|||
"Screen", |
|||
"Darken", |
|||
"Lighten", |
|||
"Overlay", |
|||
"HardLight" |
|||
}; |
|||
|
|||
foreach(var composer in composers) { |
|||
foreach(var blender in blenders) { |
|||
|
|||
var blender_composer= $"{blender}{composer}"; |
|||
#> |
|||
/// <summary> |
|||
/// A pixel blender that implements the "<#= blender_composer#>" composition equation. |
|||
/// </summary> |
|||
public sealed class <#= blender_composer#> : AssociatedAlphaPixelBlender<TPixel> |
|||
{ |
|||
/// <summary> |
|||
/// Gets the static instance of this blender. |
|||
/// </summary> |
|||
public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>(); |
|||
|
|||
/// <inheritdoc /> |
|||
public override TPixel Blend(TPixel background, TPixel source, float amount) |
|||
{ |
|||
return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount) |
|||
{ |
|||
amount = Numerics.Clamp(amount, 0, 1); |
|||
|
|||
if (Avx512F.IsSupported && destination.Length >= 4) |
|||
{ |
|||
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float> |
|||
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); |
|||
|
|||
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source)); |
|||
Vector512<float> opacity = Vector512.Create(amount); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
sourceBase = ref Unsafe.Add(ref sourceBase, 1); |
|||
} |
|||
|
|||
int remainder = Numerics.Modulo4(destination.Length); |
|||
if (remainder != 0) |
|||
{ |
|||
for (int i = destination.Length - remainder; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); |
|||
} |
|||
} |
|||
} |
|||
else if (Avx2.IsSupported && destination.Length >= 2) |
|||
{ |
|||
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> |
|||
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); |
|||
|
|||
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source)); |
|||
Vector256<float> opacity = Vector256.Create(amount); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
sourceBase = ref Unsafe.Add(ref sourceBase, 1); |
|||
} |
|||
|
|||
if (Numerics.Modulo2(destination.Length) != 0) |
|||
{ |
|||
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1. |
|||
int i = destination.Length - 1; |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount) |
|||
{ |
|||
amount = Numerics.Clamp(amount, 0, 1); |
|||
|
|||
if (Avx512F.IsSupported && destination.Length >= 4) |
|||
{ |
|||
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float> |
|||
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); |
|||
|
|||
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background)); |
|||
Vector512<float> sourceBase = Vector512.Create( |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W); |
|||
Vector512<float> opacity = Vector512.Create(amount); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
} |
|||
|
|||
int remainder = Numerics.Modulo4(destination.Length); |
|||
if (remainder != 0) |
|||
{ |
|||
for (int i = destination.Length - remainder; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); |
|||
} |
|||
} |
|||
} |
|||
else if (Avx2.IsSupported && destination.Length >= 2) |
|||
{ |
|||
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> |
|||
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); |
|||
|
|||
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background)); |
|||
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); |
|||
Vector256<float> opacity = Vector256.Create(amount); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
} |
|||
|
|||
if (Numerics.Modulo2(destination.Length) != 0) |
|||
{ |
|||
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1. |
|||
int i = destination.Length - 1; |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount) |
|||
{ |
|||
if (Avx512F.IsSupported && destination.Length >= 4) |
|||
{ |
|||
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float> |
|||
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); |
|||
|
|||
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source)); |
|||
ref float amountBase = ref MemoryMarshal.GetReference(amount); |
|||
|
|||
Vector512<float> vOne = Vector512.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
float amount0 = amountBase; |
|||
float amount1 = Unsafe.Add(ref amountBase, 1); |
|||
float amount2 = Unsafe.Add(ref amountBase, 2); |
|||
float amount3 = Unsafe.Add(ref amountBase, 3); |
|||
|
|||
// We need to create a Vector512<float> containing the current four amount values |
|||
// taking up each quarter of the Vector512<float> and then clamp them. |
|||
Vector512<float> opacity = Vector512.Create( |
|||
amount0, amount0, amount0, amount0, |
|||
amount1, amount1, amount1, amount1, |
|||
amount2, amount2, amount2, amount2, |
|||
amount3, amount3, amount3, amount3); |
|||
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne); |
|||
|
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
sourceBase = ref Unsafe.Add(ref sourceBase, 1); |
|||
amountBase = ref Unsafe.Add(ref amountBase, 4); |
|||
} |
|||
|
|||
int remainder = Numerics.Modulo4(destination.Length); |
|||
if (remainder != 0) |
|||
{ |
|||
for (int i = destination.Length - remainder; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
else if (Avx2.IsSupported && destination.Length >= 2) |
|||
{ |
|||
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> |
|||
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); |
|||
|
|||
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source)); |
|||
ref float amountBase = ref MemoryMarshal.GetReference(amount); |
|||
|
|||
Vector256<float> vOne = Vector256.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
// We need to create a Vector256<float> containing the current and next amount values |
|||
// taking up each half of the Vector256<float> and then clamp them. |
|||
Vector256<float> opacity = Vector256.Create( |
|||
Vector128.Create(amountBase), |
|||
Vector128.Create(Unsafe.Add(ref amountBase, 1))); |
|||
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne); |
|||
|
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
sourceBase = ref Unsafe.Add(ref sourceBase, 1); |
|||
amountBase = ref Unsafe.Add(ref amountBase, 2); |
|||
} |
|||
|
|||
if (Numerics.Modulo2(destination.Length) != 0) |
|||
{ |
|||
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1. |
|||
int i = destination.Length - 1; |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount) |
|||
{ |
|||
if (Avx512F.IsSupported && destination.Length >= 4) |
|||
{ |
|||
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float> |
|||
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); |
|||
|
|||
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref float amountBase = ref MemoryMarshal.GetReference(amount); |
|||
|
|||
Vector512<float> sourceBase = Vector512.Create( |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W); |
|||
Vector512<float> vOne = Vector512.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
float amount0 = amountBase; |
|||
float amount1 = Unsafe.Add(ref amountBase, 1); |
|||
float amount2 = Unsafe.Add(ref amountBase, 2); |
|||
float amount3 = Unsafe.Add(ref amountBase, 3); |
|||
|
|||
// We need to create a Vector512<float> containing the current four amount values |
|||
// taking up each quarter of the Vector512<float> and then clamp them. |
|||
Vector512<float> opacity = Vector512.Create( |
|||
amount0, amount0, amount0, amount0, |
|||
amount1, amount1, amount1, amount1, |
|||
amount2, amount2, amount2, amount2, |
|||
amount3, amount3, amount3, amount3); |
|||
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne); |
|||
|
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
amountBase = ref Unsafe.Add(ref amountBase, 4); |
|||
} |
|||
|
|||
int remainder = Numerics.Modulo4(destination.Length); |
|||
if (remainder != 0) |
|||
{ |
|||
for (int i = destination.Length - remainder; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
else if (Avx2.IsSupported && destination.Length >= 2) |
|||
{ |
|||
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> |
|||
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); |
|||
|
|||
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref float amountBase = ref MemoryMarshal.GetReference(amount); |
|||
|
|||
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); |
|||
Vector256<float> vOne = Vector256.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
// We need to create a Vector256<float> containing the current and next amount values |
|||
// taking up each half of the Vector256<float> and then clamp them. |
|||
Vector256<float> opacity = Vector256.Create( |
|||
Vector128.Create(amountBase), |
|||
Vector128.Create(Unsafe.Add(ref amountBase, 1))); |
|||
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne); |
|||
|
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
amountBase = ref Unsafe.Add(ref amountBase, 2); |
|||
} |
|||
|
|||
if (Numerics.Modulo2(destination.Length) != 0) |
|||
{ |
|||
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1. |
|||
int i = destination.Length - 1; |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount, ReadOnlySpan<float> coverage) |
|||
{ |
|||
amount = Numerics.Clamp(amount, 0, 1); |
|||
|
|||
if (Avx512F.IsSupported && destination.Length >= 4) |
|||
{ |
|||
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float> |
|||
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); |
|||
|
|||
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source)); |
|||
ref float coverageBase = ref MemoryMarshal.GetReference(coverage); |
|||
Vector512<float> opacity = Vector512.Create(amount); |
|||
Vector512<float> vOne = Vector512.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
float coverage0 = coverageBase; |
|||
float coverage1 = Unsafe.Add(ref coverageBase, 1); |
|||
float coverage2 = Unsafe.Add(ref coverageBase, 2); |
|||
float coverage3 = Unsafe.Add(ref coverageBase, 3); |
|||
|
|||
// We need to create a Vector512<float> containing the current four coverage values |
|||
// taking up each quarter of the Vector512<float> and then clamp them. |
|||
Vector512<float> coverageVector = Vector512.Create( |
|||
coverage0, coverage0, coverage0, coverage0, |
|||
coverage1, coverage1, coverage1, coverage1, |
|||
coverage2, coverage2, coverage2, coverage2, |
|||
coverage3, coverage3, coverage3, coverage3); |
|||
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne); |
|||
|
|||
Vector512<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
sourceBase = ref Unsafe.Add(ref sourceBase, 1); |
|||
coverageBase = ref Unsafe.Add(ref coverageBase, 4); |
|||
} |
|||
|
|||
int remainder = Numerics.Modulo4(destination.Length); |
|||
if (remainder != 0) |
|||
{ |
|||
for (int i = destination.Length - remainder; i < destination.Length; i++) |
|||
{ |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
else if (Avx2.IsSupported && destination.Length >= 2) |
|||
{ |
|||
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> |
|||
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); |
|||
|
|||
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source)); |
|||
ref float coverageBase = ref MemoryMarshal.GetReference(coverage); |
|||
Vector256<float> opacity = Vector256.Create(amount); |
|||
Vector256<float> vOne = Vector256.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
// We need to create a Vector256<float> containing the current and next coverage values |
|||
// taking up each half of the Vector256<float> and then clamp them. |
|||
Vector256<float> coverageVector = Vector256.Create( |
|||
Vector128.Create(coverageBase), |
|||
Vector128.Create(Unsafe.Add(ref coverageBase, 1))); |
|||
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne); |
|||
|
|||
Vector256<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
sourceBase = ref Unsafe.Add(ref sourceBase, 1); |
|||
coverageBase = ref Unsafe.Add(ref coverageBase, 2); |
|||
} |
|||
|
|||
if (Numerics.Modulo2(destination.Length) != 0) |
|||
{ |
|||
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1. |
|||
int i = destination.Length - 1; |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount, ReadOnlySpan<float> coverage) |
|||
{ |
|||
amount = Numerics.Clamp(amount, 0, 1); |
|||
|
|||
if (Avx512F.IsSupported && destination.Length >= 4) |
|||
{ |
|||
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float> |
|||
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); |
|||
|
|||
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref float coverageBase = ref MemoryMarshal.GetReference(coverage); |
|||
|
|||
Vector512<float> sourceBase = Vector512.Create( |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W); |
|||
Vector512<float> opacity = Vector512.Create(amount); |
|||
Vector512<float> vOne = Vector512.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
float coverage0 = coverageBase; |
|||
float coverage1 = Unsafe.Add(ref coverageBase, 1); |
|||
float coverage2 = Unsafe.Add(ref coverageBase, 2); |
|||
float coverage3 = Unsafe.Add(ref coverageBase, 3); |
|||
|
|||
// We need to create a Vector512<float> containing the current four coverage values |
|||
// taking up each quarter of the Vector512<float> and then clamp them. |
|||
Vector512<float> coverageVector = Vector512.Create( |
|||
coverage0, coverage0, coverage0, coverage0, |
|||
coverage1, coverage1, coverage1, coverage1, |
|||
coverage2, coverage2, coverage2, coverage2, |
|||
coverage3, coverage3, coverage3, coverage3); |
|||
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne); |
|||
|
|||
Vector512<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
coverageBase = ref Unsafe.Add(ref coverageBase, 4); |
|||
} |
|||
|
|||
int remainder = Numerics.Modulo4(destination.Length); |
|||
if (remainder != 0) |
|||
{ |
|||
for (int i = destination.Length - remainder; i < destination.Length; i++) |
|||
{ |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
else if (Avx2.IsSupported && destination.Length >= 2) |
|||
{ |
|||
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> |
|||
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); |
|||
|
|||
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref float coverageBase = ref MemoryMarshal.GetReference(coverage); |
|||
|
|||
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); |
|||
Vector256<float> opacity = Vector256.Create(amount); |
|||
Vector256<float> vOne = Vector256.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
// We need to create a Vector256<float> containing the current and next coverage values |
|||
// taking up each half of the Vector256<float> and then clamp them. |
|||
Vector256<float> coverageVector = Vector256.Create( |
|||
Vector128.Create(coverageBase), |
|||
Vector128.Create(Unsafe.Add(ref coverageBase, 1))); |
|||
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne); |
|||
|
|||
Vector256<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
coverageBase = ref Unsafe.Add(ref coverageBase, 2); |
|||
} |
|||
|
|||
if (Numerics.Modulo2(destination.Length) != 0) |
|||
{ |
|||
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1. |
|||
int i = destination.Length - 1; |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount, ReadOnlySpan<float> coverage) |
|||
{ |
|||
if (Avx512F.IsSupported && destination.Length >= 4) |
|||
{ |
|||
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float> |
|||
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); |
|||
|
|||
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source)); |
|||
ref float amountBase = ref MemoryMarshal.GetReference(amount); |
|||
ref float coverageBase = ref MemoryMarshal.GetReference(coverage); |
|||
|
|||
Vector512<float> vOne = Vector512.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
float amount0 = amountBase; |
|||
float amount1 = Unsafe.Add(ref amountBase, 1); |
|||
float amount2 = Unsafe.Add(ref amountBase, 2); |
|||
float amount3 = Unsafe.Add(ref amountBase, 3); |
|||
|
|||
// We need to create a Vector512<float> containing the current four amount values |
|||
// taking up each quarter of the Vector512<float> and then clamp them. |
|||
Vector512<float> opacity = Vector512.Create( |
|||
amount0, amount0, amount0, amount0, |
|||
amount1, amount1, amount1, amount1, |
|||
amount2, amount2, amount2, amount2, |
|||
amount3, amount3, amount3, amount3); |
|||
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne); |
|||
|
|||
float coverage0 = coverageBase; |
|||
float coverage1 = Unsafe.Add(ref coverageBase, 1); |
|||
float coverage2 = Unsafe.Add(ref coverageBase, 2); |
|||
float coverage3 = Unsafe.Add(ref coverageBase, 3); |
|||
|
|||
// We need to create a Vector512<float> containing the current four coverage values |
|||
// taking up each quarter of the Vector512<float> and then clamp them. |
|||
Vector512<float> coverageVector = Vector512.Create( |
|||
coverage0, coverage0, coverage0, coverage0, |
|||
coverage1, coverage1, coverage1, coverage1, |
|||
coverage2, coverage2, coverage2, coverage2, |
|||
coverage3, coverage3, coverage3, coverage3); |
|||
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne); |
|||
|
|||
Vector512<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
sourceBase = ref Unsafe.Add(ref sourceBase, 1); |
|||
amountBase = ref Unsafe.Add(ref amountBase, 4); |
|||
coverageBase = ref Unsafe.Add(ref coverageBase, 4); |
|||
} |
|||
|
|||
int remainder = Numerics.Modulo4(destination.Length); |
|||
if (remainder != 0) |
|||
{ |
|||
for (int i = destination.Length - remainder; i < destination.Length; i++) |
|||
{ |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
else if (Avx2.IsSupported && destination.Length >= 2) |
|||
{ |
|||
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> |
|||
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); |
|||
|
|||
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source)); |
|||
ref float amountBase = ref MemoryMarshal.GetReference(amount); |
|||
ref float coverageBase = ref MemoryMarshal.GetReference(coverage); |
|||
|
|||
Vector256<float> vOne = Vector256.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
// We need to create a Vector256<float> containing the current and next amount values |
|||
// taking up each half of the Vector256<float> and then clamp them. |
|||
Vector256<float> opacity = Vector256.Create( |
|||
Vector128.Create(amountBase), |
|||
Vector128.Create(Unsafe.Add(ref amountBase, 1))); |
|||
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne); |
|||
|
|||
// We need to create a Vector256<float> containing the current and next coverage values |
|||
// taking up each half of the Vector256<float> and then clamp them. |
|||
Vector256<float> coverageVector = Vector256.Create( |
|||
Vector128.Create(coverageBase), |
|||
Vector128.Create(Unsafe.Add(ref coverageBase, 1))); |
|||
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne); |
|||
|
|||
Vector256<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
sourceBase = ref Unsafe.Add(ref sourceBase, 1); |
|||
amountBase = ref Unsafe.Add(ref amountBase, 2); |
|||
coverageBase = ref Unsafe.Add(ref coverageBase, 2); |
|||
} |
|||
|
|||
if (Numerics.Modulo2(destination.Length) != 0) |
|||
{ |
|||
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1. |
|||
int i = destination.Length - 1; |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount, ReadOnlySpan<float> coverage) |
|||
{ |
|||
if (Avx512F.IsSupported && destination.Length >= 4) |
|||
{ |
|||
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float> |
|||
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); |
|||
|
|||
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref float amountBase = ref MemoryMarshal.GetReference(amount); |
|||
ref float coverageBase = ref MemoryMarshal.GetReference(coverage); |
|||
|
|||
Vector512<float> sourceBase = Vector512.Create( |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W, |
|||
source.X, source.Y, source.Z, source.W); |
|||
Vector512<float> vOne = Vector512.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
float amount0 = amountBase; |
|||
float amount1 = Unsafe.Add(ref amountBase, 1); |
|||
float amount2 = Unsafe.Add(ref amountBase, 2); |
|||
float amount3 = Unsafe.Add(ref amountBase, 3); |
|||
|
|||
// We need to create a Vector512<float> containing the current four amount values |
|||
// taking up each quarter of the Vector512<float> and then clamp them. |
|||
Vector512<float> opacity = Vector512.Create( |
|||
amount0, amount0, amount0, amount0, |
|||
amount1, amount1, amount1, amount1, |
|||
amount2, amount2, amount2, amount2, |
|||
amount3, amount3, amount3, amount3); |
|||
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne); |
|||
|
|||
float coverage0 = coverageBase; |
|||
float coverage1 = Unsafe.Add(ref coverageBase, 1); |
|||
float coverage2 = Unsafe.Add(ref coverageBase, 2); |
|||
float coverage3 = Unsafe.Add(ref coverageBase, 3); |
|||
|
|||
// We need to create a Vector512<float> containing the current four coverage values |
|||
// taking up each quarter of the Vector512<float> and then clamp them. |
|||
Vector512<float> coverageVector = Vector512.Create( |
|||
coverage0, coverage0, coverage0, coverage0, |
|||
coverage1, coverage1, coverage1, coverage1, |
|||
coverage2, coverage2, coverage2, coverage2, |
|||
coverage3, coverage3, coverage3, coverage3); |
|||
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne); |
|||
|
|||
Vector512<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
amountBase = ref Unsafe.Add(ref amountBase, 4); |
|||
coverageBase = ref Unsafe.Add(ref coverageBase, 4); |
|||
} |
|||
|
|||
int remainder = Numerics.Modulo4(destination.Length); |
|||
if (remainder != 0) |
|||
{ |
|||
for (int i = destination.Length - remainder; i < destination.Length; i++) |
|||
{ |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
else if (Avx2.IsSupported && destination.Length >= 2) |
|||
{ |
|||
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> |
|||
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); |
|||
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); |
|||
|
|||
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background)); |
|||
ref float amountBase = ref MemoryMarshal.GetReference(amount); |
|||
ref float coverageBase = ref MemoryMarshal.GetReference(coverage); |
|||
|
|||
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); |
|||
Vector256<float> vOne = Vector256.Create(1F); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) |
|||
{ |
|||
// We need to create a Vector256<float> containing the current and next amount values |
|||
// taking up each half of the Vector256<float> and then clamp them. |
|||
Vector256<float> opacity = Vector256.Create( |
|||
Vector128.Create(amountBase), |
|||
Vector128.Create(Unsafe.Add(ref amountBase, 1))); |
|||
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne); |
|||
|
|||
// We need to create a Vector256<float> containing the current and next coverage values |
|||
// taking up each half of the Vector256<float> and then clamp them. |
|||
Vector256<float> coverageVector = Vector256.Create( |
|||
Vector128.Create(coverageBase), |
|||
Vector128.Create(Unsafe.Add(ref coverageBase, 1))); |
|||
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne); |
|||
|
|||
Vector256<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); |
|||
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); |
|||
destinationBase = ref Unsafe.Add(ref destinationBase, 1); |
|||
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); |
|||
amountBase = ref Unsafe.Add(ref amountBase, 2); |
|||
coverageBase = ref Unsafe.Add(ref coverageBase, 2); |
|||
} |
|||
|
|||
if (Numerics.Modulo2(destination.Length) != 0) |
|||
{ |
|||
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1. |
|||
int i = destination.Length - 1; |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); |
|||
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
<# |
|||
} |
|||
} |
|||
|
|||
#> |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
|||
|
|||
/// <summary>
|
|||
/// Provides pixel blenders for formats that store associated alpha.
|
|||
/// </summary>
|
|||
internal static partial class AssociatedAlphaPixelBlenders<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the blender for the requested color blending and alpha composition modes.
|
|||
/// </summary>
|
|||
/// <param name="colorMode">The color blending mode.</param>
|
|||
/// <param name="alphaMode">The alpha composition mode.</param>
|
|||
/// <returns>The pixel blender.</returns>
|
|||
public static PixelBlender<TPixel> GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) |
|||
{ |
|||
return alphaMode switch |
|||
{ |
|||
PixelAlphaCompositionMode.Src => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplySrc.Instance, |
|||
PixelColorBlendingMode.Add => AddSrc.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractSrc.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenSrc.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenSrc.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenSrc.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlaySrc.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightSrc.Instance, |
|||
_ => NormalSrc.Instance, |
|||
}, |
|||
PixelAlphaCompositionMode.SrcAtop => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplySrcAtop.Instance, |
|||
PixelColorBlendingMode.Add => AddSrcAtop.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractSrcAtop.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenSrcAtop.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenSrcAtop.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenSrcAtop.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlaySrcAtop.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightSrcAtop.Instance, |
|||
_ => NormalSrcAtop.Instance, |
|||
}, |
|||
PixelAlphaCompositionMode.SrcIn => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplySrcIn.Instance, |
|||
PixelColorBlendingMode.Add => AddSrcIn.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractSrcIn.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenSrcIn.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenSrcIn.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenSrcIn.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlaySrcIn.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightSrcIn.Instance, |
|||
_ => NormalSrcIn.Instance, |
|||
}, |
|||
PixelAlphaCompositionMode.SrcOut => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplySrcOut.Instance, |
|||
PixelColorBlendingMode.Add => AddSrcOut.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractSrcOut.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenSrcOut.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenSrcOut.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenSrcOut.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlaySrcOut.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightSrcOut.Instance, |
|||
_ => NormalSrcOut.Instance, |
|||
}, |
|||
PixelAlphaCompositionMode.Dest => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplyDest.Instance, |
|||
PixelColorBlendingMode.Add => AddDest.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractDest.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenDest.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenDest.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenDest.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlayDest.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightDest.Instance, |
|||
_ => NormalDest.Instance, |
|||
}, |
|||
PixelAlphaCompositionMode.DestAtop => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplyDestAtop.Instance, |
|||
PixelColorBlendingMode.Add => AddDestAtop.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractDestAtop.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenDestAtop.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenDestAtop.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenDestAtop.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlayDestAtop.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightDestAtop.Instance, |
|||
_ => NormalDestAtop.Instance, |
|||
}, |
|||
PixelAlphaCompositionMode.DestOver => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplyDestOver.Instance, |
|||
PixelColorBlendingMode.Add => AddDestOver.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractDestOver.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenDestOver.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenDestOver.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenDestOver.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlayDestOver.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightDestOver.Instance, |
|||
_ => NormalDestOver.Instance, |
|||
}, |
|||
PixelAlphaCompositionMode.DestIn => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplyDestIn.Instance, |
|||
PixelColorBlendingMode.Add => AddDestIn.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractDestIn.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenDestIn.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenDestIn.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenDestIn.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlayDestIn.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightDestIn.Instance, |
|||
_ => NormalDestIn.Instance, |
|||
}, |
|||
PixelAlphaCompositionMode.DestOut => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplyDestOut.Instance, |
|||
PixelColorBlendingMode.Add => AddDestOut.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractDestOut.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenDestOut.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenDestOut.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenDestOut.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlayDestOut.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightDestOut.Instance, |
|||
_ => NormalDestOut.Instance, |
|||
}, |
|||
PixelAlphaCompositionMode.Clear => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplyClear.Instance, |
|||
PixelColorBlendingMode.Add => AddClear.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractClear.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenClear.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenClear.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenClear.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlayClear.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightClear.Instance, |
|||
_ => NormalClear.Instance, |
|||
}, |
|||
PixelAlphaCompositionMode.Xor => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplyXor.Instance, |
|||
PixelColorBlendingMode.Add => AddXor.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractXor.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenXor.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenXor.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenXor.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlayXor.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightXor.Instance, |
|||
_ => NormalXor.Instance, |
|||
}, |
|||
_ => colorMode switch |
|||
{ |
|||
PixelColorBlendingMode.Multiply => MultiplySrcOver.Instance, |
|||
PixelColorBlendingMode.Add => AddSrcOver.Instance, |
|||
PixelColorBlendingMode.Subtract => SubtractSrcOver.Instance, |
|||
PixelColorBlendingMode.Screen => ScreenSrcOver.Instance, |
|||
PixelColorBlendingMode.Darken => DarkenSrcOver.Instance, |
|||
PixelColorBlendingMode.Lighten => LightenSrcOver.Instance, |
|||
PixelColorBlendingMode.Overlay => OverlaySrcOver.Instance, |
|||
PixelColorBlendingMode.HardLight => HardLightSrcOver.Instance, |
|||
_ => NormalSrcOver.Instance, |
|||
}, |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// 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> |
|||
{ |
|||
// Associated blenders are created only by AssociatedAlphaPixelOperations, so the cached cast establishes the format-specific output boundary once per closed pixel type.
|
|||
private static readonly AssociatedAlphaPixelOperations<TPixel> Operations = (AssociatedAlphaPixelOperations<TPixel>)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.ToAssociatedScaledVector4(configuration, source, destination); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override Vector4 ToBlendVector4(TPixel source) => source.ToScaledVector4(); |
|||
|
|||
/// <summary>
|
|||
/// Converts an associated blend result to the destination pixel representation.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated blend result.</param>
|
|||
/// <returns>The destination pixel.</returns>
|
|||
public static TPixel FromBlendVector4(Vector4 source) => Operations.FromAssociatedScaledVector4(source); |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void FromBlendVector4( |
|||
Configuration configuration, |
|||
Span<Vector4> source, |
|||
Span<TPixel> destination) |
|||
{ |
|||
Operations.FromAssociatedScaledVector4(configuration, source, destination); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,138 @@ |
|||
<# |
|||
// Copyright (c) Six Labors. |
|||
// Licensed under the Six Labors Split License. |
|||
#> |
|||
<#@ template debug="false" hostspecific="false" language="C#" #> |
|||
<#@ assembly name="System.Core" #> |
|||
<#@ output extension=".cs" #> |
|||
// Copyright (c) Six Labors. |
|||
// Licensed under the Six Labors Split License. |
|||
|
|||
// <auto-generated /> |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
|||
|
|||
internal static partial class AssociatedAlphaPorterDuffFunctions |
|||
{ |
|||
<# |
|||
foreach (string composer in Composers) |
|||
{ |
|||
foreach (string blender in Blenders) |
|||
{ |
|||
string function = blender + composer; |
|||
|
|||
foreach (string vectorType in VectorTypes) |
|||
{ |
|||
string opacityType = vectorType == "Vector4" ? "float" : vectorType; |
|||
string zero = vectorType == "Vector4" ? "Vector4.Zero" : vectorType.Replace("<float>", "<float>.Zero"); |
|||
#> |
|||
/// <summary> |
|||
/// Returns the associated-alpha result of the "<#= function #>" compositing equation. |
|||
/// </summary> |
|||
/// <param name="backdrop">The associated backdrop vector.</param> |
|||
/// <param name="source">The associated source vector.</param> |
|||
/// <param name="opacity">The source opacity in the range 0 through 1.</param> |
|||
/// <returns>The associated composition result.</returns> |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static <#= vectorType #> <#= function #>(<#= vectorType #> backdrop, <#= vectorType #> source, <#= opacityType #> opacity) |
|||
{ |
|||
<# |
|||
if (composer != "Dest" && composer != "Clear") |
|||
{ |
|||
#> |
|||
// Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. |
|||
source *= opacity; |
|||
|
|||
<# |
|||
} |
|||
#> |
|||
return <#= GetComposition(blender, composer, zero) #>; |
|||
} |
|||
|
|||
<# |
|||
} |
|||
} |
|||
} |
|||
#> |
|||
} |
|||
<#+ |
|||
private static readonly string[] Composers = |
|||
{ |
|||
"Src", |
|||
"SrcAtop", |
|||
"SrcOver", |
|||
"SrcIn", |
|||
"SrcOut", |
|||
"Dest", |
|||
"DestAtop", |
|||
"DestOver", |
|||
"DestIn", |
|||
"DestOut", |
|||
"Clear", |
|||
"Xor", |
|||
}; |
|||
|
|||
private static readonly string[] Blenders = |
|||
{ |
|||
"Normal", |
|||
"Multiply", |
|||
"Add", |
|||
"Subtract", |
|||
"Screen", |
|||
"Darken", |
|||
"Lighten", |
|||
"Overlay", |
|||
"HardLight", |
|||
}; |
|||
|
|||
private static readonly string[] VectorTypes = |
|||
{ |
|||
"Vector4", |
|||
"Vector256<float>", |
|||
"Vector512<float>", |
|||
}; |
|||
|
|||
private static string GetComposition(string blender, string composer, string zero) |
|||
{ |
|||
bool normal = blender == "Normal"; |
|||
|
|||
switch (composer) |
|||
{ |
|||
case "Src": |
|||
return "source"; |
|||
case "SrcAtop": |
|||
return normal |
|||
? "AtopNormal(backdrop, source)" |
|||
: $"Atop(backdrop, source, {blender}(backdrop, source))"; |
|||
case "SrcOver": |
|||
return normal |
|||
? "OverNormal(backdrop, source)" |
|||
: $"Over(backdrop, source, {blender}(backdrop, source))"; |
|||
case "SrcIn": |
|||
return "In(backdrop, source)"; |
|||
case "SrcOut": |
|||
return "Out(backdrop, source)"; |
|||
case "Dest": |
|||
return "backdrop"; |
|||
case "DestAtop": |
|||
return normal |
|||
? "AtopNormal(source, backdrop)" |
|||
: $"Atop(source, backdrop, {blender}(source, backdrop))"; |
|||
case "DestOver": |
|||
return normal |
|||
? "OverNormal(source, backdrop)" |
|||
: $"Over(source, backdrop, {blender}(source, backdrop))"; |
|||
case "DestIn": |
|||
return "In(source, backdrop)"; |
|||
case "DestOut": |
|||
return "Out(source, backdrop)"; |
|||
case "Clear": |
|||
return zero; |
|||
default: |
|||
return "Xor(backdrop, source)"; |
|||
} |
|||
} |
|||
#> |
|||
@ -0,0 +1,546 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
using System.Runtime.Intrinsics.X86; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
|||
|
|||
/// <summary>
|
|||
/// Provides Porter-Duff composition functions for associated-alpha vectors.
|
|||
/// </summary>
|
|||
internal static partial class AssociatedAlphaPorterDuffFunctions |
|||
{ |
|||
private const int BlendAlphaControl = 0b_10_00_10_00; |
|||
private const int ShuffleAlphaControl = 0b_11_11_11_11; |
|||
|
|||
/// <summary>
|
|||
/// Calculates the associated overlap term for Multiply blending.
|
|||
/// </summary>
|
|||
/// <param name="backdrop">The associated backdrop vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated overlap term.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Multiply(Vector4 backdrop, Vector4 source) => backdrop * source; |
|||
|
|||
/// <inheritdoc cref="Multiply(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> Multiply(Vector256<float> backdrop, Vector256<float> source) => backdrop * source; |
|||
|
|||
/// <inheritdoc cref="Multiply(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> Multiply(Vector512<float> backdrop, Vector512<float> source) => backdrop * source; |
|||
|
|||
/// <summary>
|
|||
/// Calculates the associated overlap term for Add blending.
|
|||
/// </summary>
|
|||
/// <param name="backdrop">The associated backdrop vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated overlap term.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Add(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
Vector4 backdropAlpha = Numerics.PermuteW(backdrop); |
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
return Vector4.Min(backdropAlpha * sourceAlpha, (backdrop * sourceAlpha) + (source * backdropAlpha)); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Add(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> Add(Vector256<float> backdrop, Vector256<float> source) |
|||
{ |
|||
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
return Vector256.Min(backdropAlpha * sourceAlpha, (backdrop * sourceAlpha) + (source * backdropAlpha)); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Add(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> Add(Vector512<float> backdrop, Vector512<float> source) |
|||
{ |
|||
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
return Vector512.Min(backdropAlpha * sourceAlpha, (backdrop * sourceAlpha) + (source * backdropAlpha)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates the associated overlap term for Subtract blending.
|
|||
/// </summary>
|
|||
/// <param name="backdrop">The associated backdrop vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated overlap term.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Subtract(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
Vector4 backdropAlpha = Numerics.PermuteW(backdrop); |
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
return Vector4.Max(Vector4.Zero, (backdrop * sourceAlpha) - (source * backdropAlpha)); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Subtract(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> Subtract(Vector256<float> backdrop, Vector256<float> source) |
|||
{ |
|||
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
return Vector256.Max(Vector256<float>.Zero, (backdrop * sourceAlpha) - (source * backdropAlpha)); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Subtract(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> Subtract(Vector512<float> backdrop, Vector512<float> source) |
|||
{ |
|||
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
return Vector512.Max(Vector512<float>.Zero, (backdrop * sourceAlpha) - (source * backdropAlpha)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates the associated overlap term for Screen blending.
|
|||
/// </summary>
|
|||
/// <param name="backdrop">The associated backdrop vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated overlap term.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Screen(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
Vector4 backdropAlpha = Numerics.PermuteW(backdrop); |
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
return (backdrop * sourceAlpha) + (source * backdropAlpha) - (backdrop * source); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Screen(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> Screen(Vector256<float> backdrop, Vector256<float> source) |
|||
{ |
|||
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
return (backdrop * sourceAlpha) + (source * backdropAlpha) - (backdrop * source); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Screen(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> Screen(Vector512<float> backdrop, Vector512<float> source) |
|||
{ |
|||
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
return (backdrop * sourceAlpha) + (source * backdropAlpha) - (backdrop * source); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates the associated overlap term for Darken blending.
|
|||
/// </summary>
|
|||
/// <param name="backdrop">The associated backdrop vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated overlap term.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Darken(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
Vector4 backdropAlpha = Numerics.PermuteW(backdrop); |
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
return Vector4.Min(backdrop * sourceAlpha, source * backdropAlpha); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Darken(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> Darken(Vector256<float> backdrop, Vector256<float> source) |
|||
{ |
|||
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
return Vector256.Min(backdrop * sourceAlpha, source * backdropAlpha); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Darken(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> Darken(Vector512<float> backdrop, Vector512<float> source) |
|||
{ |
|||
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
return Vector512.Min(backdrop * sourceAlpha, source * backdropAlpha); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates the associated overlap term for Lighten blending.
|
|||
/// </summary>
|
|||
/// <param name="backdrop">The associated backdrop vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated overlap term.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Lighten(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
Vector4 backdropAlpha = Numerics.PermuteW(backdrop); |
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
return Vector4.Max(backdrop * sourceAlpha, source * backdropAlpha); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Lighten(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> Lighten(Vector256<float> backdrop, Vector256<float> source) |
|||
{ |
|||
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
return Vector256.Max(backdrop * sourceAlpha, source * backdropAlpha); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Lighten(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> Lighten(Vector512<float> backdrop, Vector512<float> source) |
|||
{ |
|||
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
return Vector512.Max(backdrop * sourceAlpha, source * backdropAlpha); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates the associated overlap term for Overlay blending.
|
|||
/// </summary>
|
|||
/// <param name="backdrop">The associated backdrop vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated overlap term.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Overlay(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
Vector4 backdropAlpha = Numerics.PermuteW(backdrop); |
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
|
|||
return new Vector4( |
|||
OverlayValue(backdrop.X, backdropAlpha.X, source.X, sourceAlpha.X), |
|||
OverlayValue(backdrop.Y, backdropAlpha.Y, source.Y, sourceAlpha.Y), |
|||
OverlayValue(backdrop.Z, backdropAlpha.Z, source.Z, sourceAlpha.Z), |
|||
0F); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Overlay(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> Overlay(Vector256<float> backdrop, Vector256<float> source) |
|||
{ |
|||
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
Vector256<float> left = (backdrop + backdrop) * source; |
|||
Vector256<float> right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector256.Create(2F)); |
|||
Vector256<float> useRight = Avx.CompareGreaterThan(backdrop + backdrop, backdropAlpha); |
|||
return Avx.BlendVariable(left, right, useRight); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Overlay(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> Overlay(Vector512<float> backdrop, Vector512<float> source) |
|||
{ |
|||
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
Vector512<float> left = (backdrop + backdrop) * source; |
|||
Vector512<float> right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector512.Create(2F)); |
|||
Vector512<float> useRight = Avx512F.CompareGreaterThan(backdrop + backdrop, backdropAlpha); |
|||
return Vector512.ConditionalSelect(useRight, right, left); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates the associated overlap term for HardLight blending.
|
|||
/// </summary>
|
|||
/// <param name="backdrop">The associated backdrop vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated overlap term.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 HardLight(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
Vector4 backdropAlpha = Numerics.PermuteW(backdrop); |
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
|
|||
return new Vector4( |
|||
OverlayValue(source.X, sourceAlpha.X, backdrop.X, backdropAlpha.X), |
|||
OverlayValue(source.Y, sourceAlpha.Y, backdrop.Y, backdropAlpha.Y), |
|||
OverlayValue(source.Z, sourceAlpha.Z, backdrop.Z, backdropAlpha.Z), |
|||
0F); |
|||
} |
|||
|
|||
/// <inheritdoc cref="HardLight(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> HardLight(Vector256<float> backdrop, Vector256<float> source) |
|||
{ |
|||
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
Vector256<float> left = (backdrop + backdrop) * source; |
|||
Vector256<float> right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector256.Create(2F)); |
|||
Vector256<float> useRight = Avx.CompareGreaterThan(source + source, sourceAlpha); |
|||
return Avx.BlendVariable(left, right, useRight); |
|||
} |
|||
|
|||
/// <inheritdoc cref="HardLight(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> HardLight(Vector512<float> backdrop, Vector512<float> source) |
|||
{ |
|||
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
Vector512<float> left = (backdrop + backdrop) * source; |
|||
Vector512<float> right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector512.Create(2F)); |
|||
Vector512<float> useRight = Avx512F.CompareGreaterThan(source + source, sourceAlpha); |
|||
return Vector512.ConditionalSelect(useRight, right, left); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Composites an associated source over an associated destination without a color-blending function.
|
|||
/// </summary>
|
|||
/// <param name="destination">The associated destination vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated composition result.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 OverNormal(Vector4 destination, Vector4 source) |
|||
{ |
|||
// Associated source-over is Ps + Pb(1 - As); both color and alpha therefore use the same coefficient.
|
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
return source + (destination * (Vector4.One - sourceAlpha)); |
|||
} |
|||
|
|||
/// <inheritdoc cref="OverNormal(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> OverNormal(Vector256<float> destination, Vector256<float> source) |
|||
{ |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
return source + (destination * (Vector256.Create(1F) - sourceAlpha)); |
|||
} |
|||
|
|||
/// <inheritdoc cref="OverNormal(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> OverNormal(Vector512<float> destination, Vector512<float> source) |
|||
{ |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
return source + (destination * (Vector512.Create(1F) - sourceAlpha)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Composites an associated source atop an associated destination without a color-blending function.
|
|||
/// </summary>
|
|||
/// <param name="destination">The associated destination vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated composition result.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 AtopNormal(Vector4 destination, Vector4 source) |
|||
{ |
|||
// Source-atop retains the destination alpha while replacing its covered contribution with the source.
|
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
Vector4 destinationAlpha = Numerics.PermuteW(destination); |
|||
return (source * destinationAlpha) + (destination * (Vector4.One - sourceAlpha)); |
|||
} |
|||
|
|||
/// <inheritdoc cref="AtopNormal(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> AtopNormal(Vector256<float> destination, Vector256<float> source) |
|||
{ |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
Vector256<float> destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); |
|||
return (source * destinationAlpha) + (destination * (Vector256.Create(1F) - sourceAlpha)); |
|||
} |
|||
|
|||
/// <inheritdoc cref="AtopNormal(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> AtopNormal(Vector512<float> destination, Vector512<float> source) |
|||
{ |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
Vector512<float> destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); |
|||
return (source * destinationAlpha) + (destination * (Vector512.Create(1F) - sourceAlpha)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Composites an associated source over an associated destination using an unassociated blended color.
|
|||
/// </summary>
|
|||
/// <param name="destination">The associated destination vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <param name="overlap">The associated overlap term produced by the color-blending function.</param>
|
|||
/// <returns>The associated composition result.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Over(Vector4 destination, Vector4 source, Vector4 overlap) |
|||
{ |
|||
// The three terms cover destination-only, source-only, and overlapping color respectively.
|
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
Vector4 destinationAlpha = Numerics.PermuteW(destination); |
|||
Vector4 result = (destination * (Vector4.One - sourceAlpha)) + (source * (Vector4.One - destinationAlpha)) + overlap; |
|||
Vector4 alpha = source + (destination * (Vector4.One - sourceAlpha)); |
|||
return Numerics.WithW(result, alpha); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Over(Vector4, Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> Over(Vector256<float> destination, Vector256<float> source, Vector256<float> overlap) |
|||
{ |
|||
Vector256<float> one = Vector256.Create(1F); |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
Vector256<float> destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); |
|||
Vector256<float> result = (destination * (one - sourceAlpha)) + (source * (one - destinationAlpha)) + overlap; |
|||
Vector256<float> alpha = source + (destination * (one - sourceAlpha)); |
|||
return Avx.Blend(result, alpha, BlendAlphaControl); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Over(Vector4, Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> Over(Vector512<float> destination, Vector512<float> source, Vector512<float> overlap) |
|||
{ |
|||
Vector512<float> one = Vector512.Create(1F); |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
Vector512<float> destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); |
|||
Vector512<float> result = (destination * (one - sourceAlpha)) + (source * (one - destinationAlpha)) + overlap; |
|||
Vector512<float> alpha = source + (destination * (one - sourceAlpha)); |
|||
return Vector512.ConditionalSelect(AlphaMask512(), alpha, result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Composites an associated source atop an associated destination using an unassociated blended color.
|
|||
/// </summary>
|
|||
/// <param name="destination">The associated destination vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <param name="overlap">The associated overlap term produced by the color-blending function.</param>
|
|||
/// <returns>The associated composition result.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Atop(Vector4 destination, Vector4 source, Vector4 overlap) |
|||
{ |
|||
// Atop discards source-only color and retains the destination alpha unchanged.
|
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
Vector4 destinationAlpha = Numerics.PermuteW(destination); |
|||
Vector4 result = (destination * (Vector4.One - sourceAlpha)) + overlap; |
|||
return Numerics.WithW(result, destinationAlpha); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Atop(Vector4, Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> Atop(Vector256<float> destination, Vector256<float> source, Vector256<float> overlap) |
|||
{ |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
Vector256<float> destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); |
|||
Vector256<float> result = (destination * (Vector256.Create(1F) - sourceAlpha)) + overlap; |
|||
return Avx.Blend(result, destinationAlpha, BlendAlphaControl); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Atop(Vector4, Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> Atop(Vector512<float> destination, Vector512<float> source, Vector512<float> overlap) |
|||
{ |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
Vector512<float> destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); |
|||
Vector512<float> result = (destination * (Vector512.Create(1F) - sourceAlpha)) + overlap; |
|||
return Vector512.ConditionalSelect(AlphaMask512(), destinationAlpha, result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retains the associated source within the destination coverage.
|
|||
/// </summary>
|
|||
/// <param name="destination">The associated destination vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated composition result.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 In(Vector4 destination, Vector4 source) => source * Numerics.PermuteW(destination); |
|||
|
|||
/// <inheritdoc cref="In(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> In(Vector256<float> destination, Vector256<float> source) |
|||
=> source * Avx.Permute(destination, ShuffleAlphaControl); |
|||
|
|||
/// <inheritdoc cref="In(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> In(Vector512<float> destination, Vector512<float> source) |
|||
=> source * Vector512_.ShuffleNative(destination, ShuffleAlphaControl); |
|||
|
|||
/// <summary>
|
|||
/// Retains the associated source outside the destination coverage.
|
|||
/// </summary>
|
|||
/// <param name="destination">The associated destination vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated composition result.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Out(Vector4 destination, Vector4 source) |
|||
=> source * (Vector4.One - Numerics.PermuteW(destination)); |
|||
|
|||
/// <inheritdoc cref="Out(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> Out(Vector256<float> destination, Vector256<float> source) |
|||
=> source * (Vector256.Create(1F) - Avx.Permute(destination, ShuffleAlphaControl)); |
|||
|
|||
/// <inheritdoc cref="Out(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> Out(Vector512<float> destination, Vector512<float> source) |
|||
=> source * (Vector512.Create(1F) - Vector512_.ShuffleNative(destination, ShuffleAlphaControl)); |
|||
|
|||
/// <summary>
|
|||
/// Retains only the non-overlapping parts of two associated vectors.
|
|||
/// </summary>
|
|||
/// <param name="destination">The associated destination vector.</param>
|
|||
/// <param name="source">The associated source vector.</param>
|
|||
/// <returns>The associated composition result.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Xor(Vector4 destination, Vector4 source) |
|||
{ |
|||
Vector4 sourceAlpha = Numerics.PermuteW(source); |
|||
Vector4 destinationAlpha = Numerics.PermuteW(destination); |
|||
return (source * (Vector4.One - destinationAlpha)) + (destination * (Vector4.One - sourceAlpha)); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Xor(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> Xor(Vector256<float> destination, Vector256<float> source) |
|||
{ |
|||
Vector256<float> one = Vector256.Create(1F); |
|||
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); |
|||
Vector256<float> destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); |
|||
return (source * (one - destinationAlpha)) + (destination * (one - sourceAlpha)); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Xor(Vector4, Vector4)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> Xor(Vector512<float> destination, Vector512<float> source) |
|||
{ |
|||
Vector512<float> one = Vector512.Create(1F); |
|||
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); |
|||
Vector512<float> destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); |
|||
return (source * (one - destinationAlpha)) + (destination * (one - sourceAlpha)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies raster coverage to an associated composition result.
|
|||
/// </summary>
|
|||
/// <param name="backdrop">The associated backdrop vector.</param>
|
|||
/// <param name="source">The associated composition result.</param>
|
|||
/// <param name="coverage">The raster coverage in the range 0 through 1.</param>
|
|||
/// <returns>The covered associated result.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 BlendWithCoverage(Vector4 backdrop, Vector4 source, float coverage) |
|||
{ |
|||
// Use the same fused operation as the wider paths so exact midpoints cannot change across vector widths.
|
|||
return Vector128_.MultiplyAdd(backdrop.AsVector128(), (source - backdrop).AsVector128(), Vector128.Create(coverage)).AsVector4(); |
|||
} |
|||
|
|||
/// <inheritdoc cref="BlendWithCoverage(Vector4, Vector4, float)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<float> BlendWithCoverage(Vector256<float> backdrop, Vector256<float> source, Vector256<float> coverage) |
|||
=> Vector256_.MultiplyAdd(backdrop, source - backdrop, coverage); |
|||
|
|||
/// <inheritdoc cref="BlendWithCoverage(Vector4, Vector4, float)" />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<float> BlendWithCoverage(Vector512<float> backdrop, Vector512<float> source, Vector512<float> coverage) |
|||
=> Vector512_.MultiplyAdd(backdrop, source - backdrop, coverage); |
|||
|
|||
/// <summary>
|
|||
/// Calculates one associated Overlay overlap component without recovering either straight component.
|
|||
/// </summary>
|
|||
/// <param name="backdrop">The associated backdrop component.</param>
|
|||
/// <param name="backdropAlpha">The backdrop alpha.</param>
|
|||
/// <param name="source">The associated source component.</param>
|
|||
/// <param name="sourceAlpha">The source alpha.</param>
|
|||
/// <returns>The associated overlap component.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static float OverlayValue(float backdrop, float backdropAlpha, float source, float sourceAlpha) |
|||
{ |
|||
// Comparing 2Pb with Ab is equivalent to comparing the straight backdrop component with one half.
|
|||
return (backdrop + backdrop) <= backdropAlpha |
|||
? (backdrop + backdrop) * source |
|||
: (backdropAlpha * sourceAlpha) - (2F * (backdropAlpha - backdrop) * (sourceAlpha - source)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a SIMD lane mask selecting the alpha component of each packed vector.
|
|||
/// </summary>
|
|||
/// <returns>The alpha-component mask.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<float> AlphaMask512() |
|||
=> Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); |
|||
} |
|||
@ -0,0 +1,241 @@ |
|||
// 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.PixelFormats.Utils; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Packed pixel type containing alpha and associated blue, green, and red components as 8-bit unsigned normalized values.
|
|||
/// Components are stored in alpha, blue, green, and red order from least to most significant byte.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Component, packed, and vector values use associated alpha representation.
|
|||
/// </remarks>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public partial struct Abgr32P : IPixel<Abgr32P>, IPackedVector<uint> |
|||
{ |
|||
private const float ByteScale = 1F / byte.MaxValue; |
|||
|
|||
private static readonly Vector4 Half = new(0.5F); |
|||
private static readonly Vector4 MaxBytes = new(byte.MaxValue); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the alpha component.
|
|||
/// </summary>
|
|||
public byte A; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated blue component.
|
|||
/// </summary>
|
|||
public byte B; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated green component.
|
|||
/// </summary>
|
|||
public byte G; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated red component.
|
|||
/// </summary>
|
|||
public byte R; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
public Abgr32P(byte r, byte g, byte b) |
|||
: this(r, g, b, byte.MaxValue) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
/// <param name="a">The alpha component.</param>
|
|||
public Abgr32P(byte r, byte g, byte b, byte a) |
|||
{ |
|||
this.A = a; |
|||
this.B = b; |
|||
this.G = g; |
|||
this.R = r; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
public Abgr32P(float r, float g, float b) |
|||
: this(new Vector4(r, g, b, 1F)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
/// <param name="a">The alpha component.</param>
|
|||
public Abgr32P(float r, float g, float b, float a) |
|||
: this(new Vector4(r, g, b, a)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from an associated vector.
|
|||
/// </summary>
|
|||
/// <param name="vector">The associated vector.</param>
|
|||
public Abgr32P(Vector3 vector) |
|||
: this(new Vector4(vector, 1F)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from an associated vector.
|
|||
/// </summary>
|
|||
/// <param name="vector">The associated vector.</param>
|
|||
public Abgr32P(Vector4 vector) |
|||
: this() => this = FromScaledVector4(vector); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from a packed associated value.
|
|||
/// </summary>
|
|||
/// <param name="packed">The packed associated value.</param>
|
|||
public Abgr32P(uint packed) |
|||
: this() => this.PackedValue = packed; |
|||
|
|||
/// <inheritdoc />
|
|||
public uint PackedValue |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
readonly get => Unsafe.As<Abgr32P, uint>(ref Unsafe.AsRef(in this)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
set => Unsafe.As<Abgr32P, uint>(ref this) = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Abgr32P"/> values for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are equal.</returns>
|
|||
public static bool operator ==(Abgr32P left, Abgr32P right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Abgr32P"/> values for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are not equal.</returns>
|
|||
public static bool operator !=(Abgr32P left, Abgr32P right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Rgba32 ToRgba32() |
|||
=> Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) * ByteScale; |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToVector4() => this.ToScaledVector4(); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelTypeInfo GetPixelTypeInfo() |
|||
=> PixelTypeInfo.Create<Abgr32P>( |
|||
PixelComponentInfo.Create<Abgr32P>(4, 8, 8, 8, 8), |
|||
PixelColorType.Alpha | PixelColorType.BGR, |
|||
PixelAlphaRepresentation.Associated); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelOperations<Abgr32P> CreatePixelOperations() => new PixelOperations(); |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Abgr32P FromScaledVector4(Vector4 source) => Pack(source); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromVector4(Vector4 source) => FromScaledVector4(source); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Abgr32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly bool Equals(object? obj) => obj is Abgr32P other && this.Equals(other); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly bool Equals(Abgr32P other) => this.PackedValue.Equals(other.PackedValue); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly string ToString() => $"Abgr32P({this.R}, {this.G}, {this.B}, {this.A})"; |
|||
|
|||
/// <summary>
|
|||
/// Converts an unassociated scaled vector to associated representation.
|
|||
/// </summary>
|
|||
/// <param name="source">The unassociated scaled vector.</param>
|
|||
/// <returns>The associated pixel.</returns>
|
|||
private static Abgr32P FromUnassociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Abgr32P Pack(Vector4 vector) |
|||
{ |
|||
vector *= MaxBytes; |
|||
vector += Half; |
|||
vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); |
|||
|
|||
Vector128<byte> result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); |
|||
return new Abgr32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); |
|||
} |
|||
} |
|||
@ -0,0 +1,241 @@ |
|||
// 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.PixelFormats.Utils; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Packed pixel type containing alpha and associated red, green, and blue components as 8-bit unsigned normalized values.
|
|||
/// Components are stored in alpha, red, green, and blue order from least to most significant byte.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Component, packed, and vector values use associated alpha representation.
|
|||
/// </remarks>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public partial struct Argb32P : IPixel<Argb32P>, IPackedVector<uint> |
|||
{ |
|||
private const float ByteScale = 1F / byte.MaxValue; |
|||
|
|||
private static readonly Vector4 Half = new(0.5F); |
|||
private static readonly Vector4 MaxBytes = new(byte.MaxValue); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the alpha component.
|
|||
/// </summary>
|
|||
public byte A; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated red component.
|
|||
/// </summary>
|
|||
public byte R; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated green component.
|
|||
/// </summary>
|
|||
public byte G; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated blue component.
|
|||
/// </summary>
|
|||
public byte B; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Argb32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
public Argb32P(byte r, byte g, byte b) |
|||
: this(r, g, b, byte.MaxValue) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Argb32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
/// <param name="a">The alpha component.</param>
|
|||
public Argb32P(byte r, byte g, byte b, byte a) |
|||
{ |
|||
this.A = a; |
|||
this.R = r; |
|||
this.G = g; |
|||
this.B = b; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Argb32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
public Argb32P(float r, float g, float b) |
|||
: this(new Vector4(r, g, b, 1F)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Argb32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
/// <param name="a">The alpha component.</param>
|
|||
public Argb32P(float r, float g, float b, float a) |
|||
: this(new Vector4(r, g, b, a)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Argb32P"/> struct from an associated vector.
|
|||
/// </summary>
|
|||
/// <param name="vector">The associated vector.</param>
|
|||
public Argb32P(Vector3 vector) |
|||
: this(new Vector4(vector, 1F)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Argb32P"/> struct from an associated vector.
|
|||
/// </summary>
|
|||
/// <param name="vector">The associated vector.</param>
|
|||
public Argb32P(Vector4 vector) |
|||
: this() => this = FromScaledVector4(vector); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Argb32P"/> struct from a packed associated value.
|
|||
/// </summary>
|
|||
/// <param name="packed">The packed associated value.</param>
|
|||
public Argb32P(uint packed) |
|||
: this() => this.PackedValue = packed; |
|||
|
|||
/// <inheritdoc />
|
|||
public uint PackedValue |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
readonly get => Unsafe.As<Argb32P, uint>(ref Unsafe.AsRef(in this)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
set => Unsafe.As<Argb32P, uint>(ref this) = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Argb32P"/> values for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are equal.</returns>
|
|||
public static bool operator ==(Argb32P left, Argb32P right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Argb32P"/> values for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are not equal.</returns>
|
|||
public static bool operator !=(Argb32P left, Argb32P right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Rgba32 ToRgba32() |
|||
=> Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) * ByteScale; |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToVector4() => this.ToScaledVector4(); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelTypeInfo GetPixelTypeInfo() |
|||
=> PixelTypeInfo.Create<Argb32P>( |
|||
PixelComponentInfo.Create<Argb32P>(4, 8, 8, 8, 8), |
|||
PixelColorType.Alpha | PixelColorType.RGB, |
|||
PixelAlphaRepresentation.Associated); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelOperations<Argb32P> CreatePixelOperations() => new PixelOperations(); |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Argb32P FromScaledVector4(Vector4 source) => Pack(source); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromVector4(Vector4 source) => FromScaledVector4(source); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Argb32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly bool Equals(object? obj) => obj is Argb32P other && this.Equals(other); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly bool Equals(Argb32P other) => this.PackedValue.Equals(other.PackedValue); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly string ToString() => $"Argb32P({this.R}, {this.G}, {this.B}, {this.A})"; |
|||
|
|||
/// <summary>
|
|||
/// Converts an unassociated scaled vector to associated representation.
|
|||
/// </summary>
|
|||
/// <param name="source">The unassociated scaled vector.</param>
|
|||
/// <returns>The associated pixel.</returns>
|
|||
private static Argb32P FromUnassociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Argb32P Pack(Vector4 vector) |
|||
{ |
|||
vector *= MaxBytes; |
|||
vector += Half; |
|||
vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); |
|||
|
|||
Vector128<byte> result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); |
|||
return new Argb32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); |
|||
} |
|||
} |
|||
@ -0,0 +1,241 @@ |
|||
// 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.PixelFormats.Utils; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Packed pixel type containing associated blue, green, red, and alpha components as 8-bit unsigned normalized values.
|
|||
/// Components are stored in blue, green, red, and alpha order from least to most significant byte.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Component, packed, and vector values use associated alpha representation.
|
|||
/// </remarks>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public partial struct Bgra32P : IPixel<Bgra32P>, IPackedVector<uint> |
|||
{ |
|||
private const float ByteScale = 1F / byte.MaxValue; |
|||
|
|||
private static readonly Vector4 Half = new(0.5F); |
|||
private static readonly Vector4 MaxBytes = new(byte.MaxValue); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated blue component.
|
|||
/// </summary>
|
|||
public byte B; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated green component.
|
|||
/// </summary>
|
|||
public byte G; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated red component.
|
|||
/// </summary>
|
|||
public byte R; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the alpha component.
|
|||
/// </summary>
|
|||
public byte A; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
public Bgra32P(byte r, byte g, byte b) |
|||
: this(r, g, b, byte.MaxValue) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
/// <param name="a">The alpha component.</param>
|
|||
public Bgra32P(byte r, byte g, byte b, byte a) |
|||
{ |
|||
this.B = b; |
|||
this.G = g; |
|||
this.R = r; |
|||
this.A = a; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
public Bgra32P(float r, float g, float b) |
|||
: this(new Vector4(r, g, b, 1F)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
/// <param name="a">The alpha component.</param>
|
|||
public Bgra32P(float r, float g, float b, float a) |
|||
: this(new Vector4(r, g, b, a)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from an associated vector.
|
|||
/// </summary>
|
|||
/// <param name="vector">The associated vector.</param>
|
|||
public Bgra32P(Vector3 vector) |
|||
: this(new Vector4(vector, 1F)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from an associated vector.
|
|||
/// </summary>
|
|||
/// <param name="vector">The associated vector.</param>
|
|||
public Bgra32P(Vector4 vector) |
|||
: this() => this = FromScaledVector4(vector); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from a packed associated value.
|
|||
/// </summary>
|
|||
/// <param name="packed">The packed associated value.</param>
|
|||
public Bgra32P(uint packed) |
|||
: this() => this.PackedValue = packed; |
|||
|
|||
/// <inheritdoc />
|
|||
public uint PackedValue |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
readonly get => Unsafe.As<Bgra32P, uint>(ref Unsafe.AsRef(in this)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
set => Unsafe.As<Bgra32P, uint>(ref this) = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Bgra32P"/> values for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are equal.</returns>
|
|||
public static bool operator ==(Bgra32P left, Bgra32P right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Bgra32P"/> values for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are not equal.</returns>
|
|||
public static bool operator !=(Bgra32P left, Bgra32P right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Rgba32 ToRgba32() |
|||
=> Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) * ByteScale; |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToVector4() => this.ToScaledVector4(); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelTypeInfo GetPixelTypeInfo() |
|||
=> PixelTypeInfo.Create<Bgra32P>( |
|||
PixelComponentInfo.Create<Bgra32P>(4, 8, 8, 8, 8), |
|||
PixelColorType.BGR | PixelColorType.Alpha, |
|||
PixelAlphaRepresentation.Associated); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelOperations<Bgra32P> CreatePixelOperations() => new PixelOperations(); |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Bgra32P FromScaledVector4(Vector4 source) => Pack(source); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromVector4(Vector4 source) => FromScaledVector4(source); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Bgra32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly bool Equals(object? obj) => obj is Bgra32P other && this.Equals(other); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly bool Equals(Bgra32P other) => this.PackedValue.Equals(other.PackedValue); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly string ToString() => $"Bgra32P({this.R}, {this.G}, {this.B}, {this.A})"; |
|||
|
|||
/// <summary>
|
|||
/// Converts an unassociated scaled vector to associated representation.
|
|||
/// </summary>
|
|||
/// <param name="source">The unassociated scaled vector.</param>
|
|||
/// <returns>The associated pixel.</returns>
|
|||
private static Bgra32P FromUnassociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Bgra32P Pack(Vector4 vector) |
|||
{ |
|||
vector *= MaxBytes; |
|||
vector += Half; |
|||
vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); |
|||
|
|||
Vector128<byte> result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); |
|||
return new Bgra32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); |
|||
} |
|||
} |
|||
@ -0,0 +1,243 @@ |
|||
// 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.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Packed pixel type containing four associated 16-bit floating-point values.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Packed and vector values use associated alpha representation.
|
|||
/// </remarks>
|
|||
public partial struct HalfVector4P : IPixel<HalfVector4P>, IPackedVector<ulong> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HalfVector4P"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The associated x-component.</param>
|
|||
/// <param name="y">The associated y-component.</param>
|
|||
/// <param name="z">The associated z-component.</param>
|
|||
/// <param name="w">The alpha component.</param>
|
|||
public HalfVector4P(float x, float y, float z, float w) |
|||
: this(new Vector4(x, y, z, w)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HalfVector4P"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector containing the associated component values.</param>
|
|||
public HalfVector4P(Vector4 vector) => this.PackedValue = Pack(vector); |
|||
|
|||
/// <inheritdoc />
|
|||
public ulong PackedValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="HalfVector4P"/> values for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are equal.</returns>
|
|||
public static bool operator ==(HalfVector4P left, HalfVector4P right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="HalfVector4P"/> values for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are not equal.</returns>
|
|||
public static bool operator !=(HalfVector4P left, HalfVector4P right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Rgba32 ToRgba32() |
|||
{ |
|||
Vector4 vector = this.ToScaledVector4(); |
|||
Numerics.UnPremultiply(ref vector); |
|||
return Rgba32.FromScaledVector4(vector); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToScaledVector4() |
|||
{ |
|||
Vector4 scaled = this.ToVector4(); |
|||
scaled += Vector4.One; |
|||
scaled /= 2F; |
|||
return scaled; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToVector4() => new( |
|||
HalfTypeHelper.Unpack((ushort)this.PackedValue), |
|||
HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)), |
|||
HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x20)), |
|||
HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x30))); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelTypeInfo GetPixelTypeInfo() |
|||
=> PixelTypeInfo.Create<HalfVector4P>( |
|||
PixelComponentInfo.Create<HalfVector4P>(4, 16, 16, 16, 16), |
|||
PixelColorType.RGB | PixelColorType.Alpha, |
|||
PixelAlphaRepresentation.Associated); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelOperations<HalfVector4P> CreatePixelOperations() => new PixelOperations(); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromScaledVector4(Vector4 source) |
|||
{ |
|||
source *= 2F; |
|||
source -= Vector4.One; |
|||
return FromVector4(source); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static HalfVector4P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly bool Equals(object? obj) => obj is HalfVector4P other && this.Equals(other); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly bool Equals(HalfVector4P other) => this.PackedValue.Equals(other.PackedValue); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly string ToString() |
|||
{ |
|||
Vector4 vector = this.ToVector4(); |
|||
return FormattableString.Invariant($"HalfVector4P({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an unassociated scaled vector to associated representation.
|
|||
/// </summary>
|
|||
/// <param name="source">The unassociated scaled vector.</param>
|
|||
/// <returns>The associated pixel.</returns>
|
|||
private static HalfVector4P FromUnassociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Associate(source)); |
|||
|
|||
/// <summary>
|
|||
/// Converts an unassociated scaled vector to the associated representation of a half-precision destination.
|
|||
/// </summary>
|
|||
/// <param name="source">The unassociated scaled vector.</param>
|
|||
/// <returns>The associated scaled vector.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 Associate(Vector4 source) |
|||
{ |
|||
// Quantize alpha through the destination's native half representation before RGB is associated with it.
|
|||
float nativeAlpha = (source.W * 2F) - 1F; |
|||
source.W = (HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha)) + 1F) / 2F; |
|||
Numerics.Premultiply(ref source); |
|||
return source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts unassociated scaled vectors to the associated representation of a half-precision destination.
|
|||
/// </summary>
|
|||
/// <param name="source">The vectors to convert in place.</param>
|
|||
private static void Associate(Span<Vector4> source) |
|||
{ |
|||
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, i)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reassociates a scaled vector with the alpha value the destination stores.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated scaled vector.</param>
|
|||
/// <returns>The reassociated scaled vector.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 Reassociate(Vector4 source) |
|||
{ |
|||
float alpha = source.W; |
|||
|
|||
if (alpha == 0) |
|||
{ |
|||
return Vector4.Zero; |
|||
} |
|||
|
|||
float nativeAlpha = (alpha * 2F) - 1F; |
|||
float storedAlpha = (HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha)) + 1F) / 2F; |
|||
|
|||
// Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization.
|
|||
source *= storedAlpha / alpha; |
|||
source.W = storedAlpha; |
|||
return source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reassociates scaled vectors with the alpha values the destination stores.
|
|||
/// </summary>
|
|||
/// <param name="source">The vectors to convert in place.</param>
|
|||
private static void Reassociate(Span<Vector4> source) |
|||
{ |
|||
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref sourceBase, i) = Reassociate(Unsafe.Add(ref sourceBase, i)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Packs native half-precision components into a 64-bit value.
|
|||
/// </summary>
|
|||
/// <param name="vector">The native component values.</param>
|
|||
/// <returns>The packed value.</returns>
|
|||
private static ulong Pack(Vector4 vector) |
|||
{ |
|||
ulong x = HalfTypeHelper.Pack(vector.X); |
|||
ulong y = (ulong)HalfTypeHelper.Pack(vector.Y) << 0x10; |
|||
ulong z = (ulong)HalfTypeHelper.Pack(vector.Z) << 0x20; |
|||
ulong w = (ulong)HalfTypeHelper.Pack(vector.W) << 0x30; |
|||
return x | y | z | w; |
|||
} |
|||
} |
|||
@ -0,0 +1,276 @@ |
|||
// 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; |
|||
|
|||
/// <summary>
|
|||
/// Packed pixel type containing four associated 8-bit signed normalized values ranging from -1 to 1.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Packed and vector values use associated alpha representation.
|
|||
/// </remarks>
|
|||
public partial struct NormalizedByte4P : IPixel<NormalizedByte4P>, IPackedVector<uint> |
|||
{ |
|||
private const float MaxPos = 127F; |
|||
private const float ScaledMagnitude = MaxPos * 2F; |
|||
private static readonly Vector4 Half = Vector128.Create(MaxPos).AsVector4(); |
|||
private static readonly Vector4 MinusOne = Vector128.Create(-1F).AsVector4(); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="NormalizedByte4P"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The associated x-component.</param>
|
|||
/// <param name="y">The associated y-component.</param>
|
|||
/// <param name="z">The associated z-component.</param>
|
|||
/// <param name="w">The alpha component.</param>
|
|||
public NormalizedByte4P(float x, float y, float z, float w) |
|||
: this(new Vector4(x, y, z, w)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="NormalizedByte4P"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector containing the associated component values.</param>
|
|||
public NormalizedByte4P(Vector4 vector) => this.PackedValue = Pack(vector); |
|||
|
|||
/// <inheritdoc />
|
|||
public uint PackedValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="NormalizedByte4P"/> values for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are equal.</returns>
|
|||
public static bool operator ==(NormalizedByte4P left, NormalizedByte4P right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="NormalizedByte4P"/> values for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are not equal.</returns>
|
|||
public static bool operator !=(NormalizedByte4P left, NormalizedByte4P right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(ToUnassociatedScaledVector4(this)); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToScaledVector4() |
|||
{ |
|||
Vector4 scaled = this.ToVector4(); |
|||
scaled += Vector4.One; |
|||
scaled /= 2F; |
|||
return scaled; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToVector4() => new( |
|||
(sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos, |
|||
(sbyte)((this.PackedValue >> 8) & 0xFF) / MaxPos, |
|||
(sbyte)((this.PackedValue >> 16) & 0xFF) / MaxPos, |
|||
(sbyte)((this.PackedValue >> 24) & 0xFF) / MaxPos); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelTypeInfo GetPixelTypeInfo() |
|||
=> PixelTypeInfo.Create<NormalizedByte4P>( |
|||
PixelComponentInfo.Create<NormalizedByte4P>(4, 8, 8, 8, 8), |
|||
PixelColorType.RGB | PixelColorType.Alpha, |
|||
PixelAlphaRepresentation.Associated); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelOperations<NormalizedByte4P> CreatePixelOperations() => new PixelOperations(); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromScaledVector4(Vector4 source) |
|||
{ |
|||
source *= 2F; |
|||
source -= Vector4.One; |
|||
return FromVector4(source); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static NormalizedByte4P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly bool Equals(object? obj) => obj is NormalizedByte4P other && this.Equals(other); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly bool Equals(NormalizedByte4P other) => this.PackedValue.Equals(other.PackedValue); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly string ToString() |
|||
{ |
|||
Vector4 vector = this.ToVector4(); |
|||
return FormattableString.Invariant($"NormalizedByte4P({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an unassociated scaled vector to associated representation.
|
|||
/// </summary>
|
|||
/// <param name="source">The unassociated scaled vector.</param>
|
|||
/// <returns>The associated pixel.</returns>
|
|||
private static NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source) |
|||
{ |
|||
return FromScaledVector4(Associate(source)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an unassociated scaled vector to the associated representation of a signed-normalized-byte destination.
|
|||
/// </summary>
|
|||
/// <param name="source">The unassociated scaled vector.</param>
|
|||
/// <returns>The associated scaled vector.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 Associate(Vector4 source) |
|||
{ |
|||
// Reproduce the signed-normalized packer's alpha quantization, then associate RGB with the exact alpha that will be stored.
|
|||
float nativeAlpha = Numerics.Clamp((source.W * 2F) - 1F, -1F, 1F); |
|||
float storedAlpha = MathF.Round(nativeAlpha * MaxPos); |
|||
source.W = (storedAlpha + MaxPos) / ScaledMagnitude; |
|||
Numerics.Premultiply(ref source); |
|||
return source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts the stored associated components to an unassociated scaled vector.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated pixel.</param>
|
|||
/// <returns>The unassociated scaled vector.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 ToUnassociatedScaledVector4(NormalizedByte4P source) |
|||
{ |
|||
// Offset signed storage into exact nonnegative byte magnitudes before division so the quotient retains the destination's byte-rounding midpoint.
|
|||
Vector4 vector = new( |
|||
(sbyte)(source.PackedValue >> 0) + MaxPos, |
|||
(sbyte)(source.PackedValue >> 8) + MaxPos, |
|||
(sbyte)(source.PackedValue >> 16) + MaxPos, |
|||
(sbyte)(source.PackedValue >> 24) + MaxPos); |
|||
|
|||
if (vector.W == 0F) |
|||
{ |
|||
// Numerics.UnPremultiply preserves RGB when alpha is zero. Normalize the stored components because they already are the unassociated value in this case.
|
|||
return vector / ScaledMagnitude; |
|||
} |
|||
|
|||
Numerics.UnPremultiply(ref vector); |
|||
vector.W /= ScaledMagnitude; |
|||
return vector; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts unassociated scaled vectors to the associated representation of a signed-normalized-byte destination.
|
|||
/// </summary>
|
|||
/// <param name="source">The vectors to convert in place.</param>
|
|||
private static void Associate(Span<Vector4> source) |
|||
{ |
|||
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, i)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reassociates a scaled vector with the alpha value the destination stores.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated scaled vector.</param>
|
|||
/// <returns>The reassociated scaled vector.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 Reassociate(Vector4 source) |
|||
{ |
|||
float alpha = source.W; |
|||
|
|||
if (alpha == 0) |
|||
{ |
|||
return Vector4.Zero; |
|||
} |
|||
|
|||
float nativeAlpha = Numerics.Clamp((alpha * 2F) - 1F, -1F, 1F); |
|||
float storedAlpha = (MathF.Round(nativeAlpha * MaxPos) + MaxPos) / ScaledMagnitude; |
|||
|
|||
// Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization.
|
|||
source *= storedAlpha / alpha; |
|||
source.W = storedAlpha; |
|||
return source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reassociates scaled vectors with the alpha values the destination stores.
|
|||
/// </summary>
|
|||
/// <param name="source">The vectors to convert in place.</param>
|
|||
private static void Reassociate(Span<Vector4> source) |
|||
{ |
|||
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref sourceBase, i) = Reassociate(Unsafe.Add(ref sourceBase, i)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Packs native signed normalized components into a 32-bit value.
|
|||
/// </summary>
|
|||
/// <param name="vector">The native component values.</param>
|
|||
/// <returns>The packed value.</returns>
|
|||
private static uint Pack(Vector4 vector) |
|||
{ |
|||
vector = Numerics.Clamp(vector, MinusOne, Vector4.One) * Half; |
|||
|
|||
uint byte4 = ((uint)Convert.ToInt16(MathF.Round(vector.X)) & 0xFF) << 0; |
|||
uint byte3 = ((uint)Convert.ToInt16(MathF.Round(vector.Y)) & 0xFF) << 8; |
|||
uint byte2 = ((uint)Convert.ToInt16(MathF.Round(vector.Z)) & 0xFF) << 16; |
|||
uint byte1 = ((uint)Convert.ToInt16(MathF.Round(vector.W)) & 0xFF) << 24; |
|||
|
|||
return byte4 | byte3 | byte2 | byte1; |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats.Utils; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
/// <content>
|
|||
/// Provides optimized overrides for bulk operations.
|
|||
/// </content>
|
|||
public partial struct Abgr32P |
|||
{ |
|||
/// <summary>
|
|||
/// Provides optimized bulk operations for <see cref="Abgr32P"/>.
|
|||
/// </summary>
|
|||
internal class PixelOperations : AssociatedAlphaPixelOperations<Abgr32P> |
|||
{ |
|||
/// <inheritdoc />
|
|||
internal override Vector4 ToUnassociatedScaledVector4(Abgr32P source) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override Abgr32P FromUnassociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); |
|||
|
|||
/// <inheritdoc />
|
|||
public override Abgr32P FromAssociatedScaledVector4(Vector4 source) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToAbgr32P(source); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<Abgr32P> source, Span<Vector4> destination) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Abgr32P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Vector4Converters.AssociatedRgbaCompatible.Associate(source); |
|||
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Abgr32P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToVector4( |
|||
Configuration configuration, |
|||
ReadOnlySpan<Abgr32P> source, |
|||
Span<Vector4> destinationVectors, |
|||
PixelConversionModifiers modifiers) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void FromVector4Destructive( |
|||
Configuration configuration, |
|||
Span<Vector4> sourceVectors, |
|||
Span<Abgr32P> destination, |
|||
PixelConversionModifiers modifiers) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats.Utils; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
/// <content>
|
|||
/// Provides optimized overrides for bulk operations.
|
|||
/// </content>
|
|||
public partial struct Argb32P |
|||
{ |
|||
/// <summary>
|
|||
/// Provides optimized bulk operations for <see cref="Argb32P"/>.
|
|||
/// </summary>
|
|||
internal class PixelOperations : AssociatedAlphaPixelOperations<Argb32P> |
|||
{ |
|||
/// <inheritdoc />
|
|||
internal override Vector4 ToUnassociatedScaledVector4(Argb32P source) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override Argb32P FromUnassociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); |
|||
|
|||
/// <inheritdoc />
|
|||
public override Argb32P FromAssociatedScaledVector4(Vector4 source) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToArgb32P(source); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<Argb32P> source, Span<Vector4> destination) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Argb32P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Vector4Converters.AssociatedRgbaCompatible.Associate(source); |
|||
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Argb32P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToVector4( |
|||
Configuration configuration, |
|||
ReadOnlySpan<Argb32P> source, |
|||
Span<Vector4> destinationVectors, |
|||
PixelConversionModifiers modifiers) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void FromVector4Destructive( |
|||
Configuration configuration, |
|||
Span<Vector4> sourceVectors, |
|||
Span<Argb32P> destination, |
|||
PixelConversionModifiers modifiers) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats.Utils; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
/// <content>
|
|||
/// Provides optimized overrides for bulk operations.
|
|||
/// </content>
|
|||
public partial struct Bgra32P |
|||
{ |
|||
/// <summary>
|
|||
/// Provides optimized bulk operations for <see cref="Bgra32P"/>.
|
|||
/// </summary>
|
|||
internal class PixelOperations : AssociatedAlphaPixelOperations<Bgra32P> |
|||
{ |
|||
/// <inheritdoc />
|
|||
internal override Vector4 ToUnassociatedScaledVector4(Bgra32P source) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override Bgra32P FromUnassociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); |
|||
|
|||
/// <inheritdoc />
|
|||
public override Bgra32P FromAssociatedScaledVector4(Vector4 source) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToBgra32P(source); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<Bgra32P> source, Span<Vector4> destination) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Bgra32P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Vector4Converters.AssociatedRgbaCompatible.Associate(source); |
|||
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Bgra32P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToVector4( |
|||
Configuration configuration, |
|||
ReadOnlySpan<Bgra32P> source, |
|||
Span<Vector4> destinationVectors, |
|||
PixelConversionModifiers modifiers) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void FromVector4Destructive( |
|||
Configuration configuration, |
|||
Span<Vector4> sourceVectors, |
|||
Span<Bgra32P> destination, |
|||
PixelConversionModifiers modifiers) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
/// <content>
|
|||
/// Provides bulk operations.
|
|||
/// </content>
|
|||
public partial struct HalfVector4P |
|||
{ |
|||
/// <summary>
|
|||
/// Provides bulk operations for <see cref="HalfVector4P"/>.
|
|||
/// </summary>
|
|||
internal class PixelOperations : AssociatedAlphaPixelOperations<HalfVector4P> |
|||
{ |
|||
/// <inheritdoc />
|
|||
internal override Vector4 ToUnassociatedScaledVector4(HalfVector4P source) |
|||
{ |
|||
Vector4 vector = source.ToScaledVector4(); |
|||
Numerics.UnPremultiply(ref vector); |
|||
return vector; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override HalfVector4P FromUnassociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Associate(source)); |
|||
|
|||
/// <inheritdoc />
|
|||
public override HalfVector4P FromAssociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Reassociate(source)); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<HalfVector4P> source, Span<Vector4> destination) |
|||
{ |
|||
this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
Numerics.UnPremultiply(destination[..source.Length]); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<HalfVector4P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Associate(source); |
|||
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<HalfVector4P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Reassociate(source); |
|||
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
// 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.PixelFormats; |
|||
|
|||
/// <content>
|
|||
/// Provides bulk operations.
|
|||
/// </content>
|
|||
public partial struct NormalizedByte4P |
|||
{ |
|||
/// <summary>
|
|||
/// Provides bulk operations for <see cref="NormalizedByte4P"/>.
|
|||
/// </summary>
|
|||
internal class PixelOperations : AssociatedAlphaPixelOperations<NormalizedByte4P> |
|||
{ |
|||
/// <inheritdoc />
|
|||
internal override Vector4 ToUnassociatedScaledVector4(NormalizedByte4P source) |
|||
=> NormalizedByte4P.ToUnassociatedScaledVector4(source); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Associate(source)); |
|||
|
|||
/// <inheritdoc />
|
|||
public override NormalizedByte4P FromAssociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Reassociate(source)); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<NormalizedByte4P> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
ref NormalizedByte4P sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationBase, i) = NormalizedByte4P.ToUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i)); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<NormalizedByte4P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Associate(source); |
|||
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<NormalizedByte4P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Reassociate(source); |
|||
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats.Utils; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
/// <content>
|
|||
/// Provides optimized overrides for bulk operations.
|
|||
/// </content>
|
|||
public partial struct Rgba32P |
|||
{ |
|||
/// <summary>
|
|||
/// Provides optimized bulk operations for <see cref="Rgba32P"/>.
|
|||
/// </summary>
|
|||
internal class PixelOperations : AssociatedAlphaPixelOperations<Rgba32P> |
|||
{ |
|||
/// <inheritdoc />
|
|||
internal override Vector4 ToUnassociatedScaledVector4(Rgba32P source) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override Rgba32P FromUnassociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); |
|||
|
|||
/// <inheritdoc />
|
|||
public override Rgba32P FromAssociatedScaledVector4(Vector4 source) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToRgba32P(source); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<Rgba32P> source, Span<Vector4> destination) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Rgba32P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Vector4Converters.AssociatedRgbaCompatible.Associate(source); |
|||
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Rgba32P> destination) |
|||
{ |
|||
source = source[..destination.Length]; |
|||
Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void ToVector4( |
|||
Configuration configuration, |
|||
ReadOnlySpan<Rgba32P> source, |
|||
Span<Vector4> destinationVectors, |
|||
PixelConversionModifiers modifiers) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void FromVector4Destructive( |
|||
Configuration configuration, |
|||
Span<Vector4> sourceVectors, |
|||
Span<Rgba32P> destination, |
|||
PixelConversionModifiers modifiers) |
|||
=> Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); |
|||
} |
|||
} |
|||
@ -0,0 +1,241 @@ |
|||
// 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.PixelFormats.Utils; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Packed pixel type containing associated red, green, blue, and alpha components as 8-bit unsigned normalized values.
|
|||
/// Components are stored in red, green, blue, and alpha order from least to most significant byte.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Component, packed, and vector values use associated alpha representation.
|
|||
/// </remarks>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public partial struct Rgba32P : IPixel<Rgba32P>, IPackedVector<uint> |
|||
{ |
|||
private const float ByteScale = 1F / byte.MaxValue; |
|||
|
|||
private static readonly Vector4 Half = new(0.5F); |
|||
private static readonly Vector4 MaxBytes = new(byte.MaxValue); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated red component.
|
|||
/// </summary>
|
|||
public byte R; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated green component.
|
|||
/// </summary>
|
|||
public byte G; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the associated blue component.
|
|||
/// </summary>
|
|||
public byte B; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the alpha component.
|
|||
/// </summary>
|
|||
public byte A; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
public Rgba32P(byte r, byte g, byte b) |
|||
: this(r, g, b, byte.MaxValue) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
/// <param name="a">The alpha component.</param>
|
|||
public Rgba32P(byte r, byte g, byte b, byte a) |
|||
{ |
|||
this.R = r; |
|||
this.G = g; |
|||
this.B = b; |
|||
this.A = a; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
public Rgba32P(float r, float g, float b) |
|||
: this(new Vector4(r, g, b, 1F)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from associated components.
|
|||
/// </summary>
|
|||
/// <param name="r">The associated red component.</param>
|
|||
/// <param name="g">The associated green component.</param>
|
|||
/// <param name="b">The associated blue component.</param>
|
|||
/// <param name="a">The alpha component.</param>
|
|||
public Rgba32P(float r, float g, float b, float a) |
|||
: this(new Vector4(r, g, b, a)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from an associated vector.
|
|||
/// </summary>
|
|||
/// <param name="vector">The associated vector.</param>
|
|||
public Rgba32P(Vector3 vector) |
|||
: this(new Vector4(vector, 1F)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from an associated vector.
|
|||
/// </summary>
|
|||
/// <param name="vector">The associated vector.</param>
|
|||
public Rgba32P(Vector4 vector) |
|||
: this() => this = FromScaledVector4(vector); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from a packed associated value.
|
|||
/// </summary>
|
|||
/// <param name="packed">The packed associated value.</param>
|
|||
public Rgba32P(uint packed) |
|||
: this() => this.PackedValue = packed; |
|||
|
|||
/// <inheritdoc />
|
|||
public uint PackedValue |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
readonly get => Unsafe.As<Rgba32P, uint>(ref Unsafe.AsRef(in this)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
set => Unsafe.As<Rgba32P, uint>(ref this) = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Rgba32P"/> values for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are equal.</returns>
|
|||
public static bool operator ==(Rgba32P left, Rgba32P right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Rgba32P"/> values for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The left value.</param>
|
|||
/// <param name="right">The right value.</param>
|
|||
/// <returns><see langword="true"/> when the values are not equal.</returns>
|
|||
public static bool operator !=(Rgba32P left, Rgba32P right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Rgba32 ToRgba32() |
|||
=> Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) * ByteScale; |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly Vector4 ToVector4() => this.ToScaledVector4(); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelTypeInfo GetPixelTypeInfo() |
|||
=> PixelTypeInfo.Create<Rgba32P>( |
|||
PixelComponentInfo.Create<Rgba32P>(4, 8, 8, 8, 8), |
|||
PixelColorType.RGB | PixelColorType.Alpha, |
|||
PixelAlphaRepresentation.Associated); |
|||
|
|||
/// <inheritdoc />
|
|||
public static PixelOperations<Rgba32P> CreatePixelOperations() => new PixelOperations(); |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgba32P FromScaledVector4(Vector4 source) => Pack(source); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromVector4(Vector4 source) => FromScaledVector4(source); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public static Rgba32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly bool Equals(object? obj) => obj is Rgba32P other && this.Equals(other); |
|||
|
|||
/// <inheritdoc />
|
|||
public readonly bool Equals(Rgba32P other) => this.PackedValue.Equals(other.PackedValue); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly string ToString() => $"Rgba32P({this.R}, {this.G}, {this.B}, {this.A})"; |
|||
|
|||
/// <summary>
|
|||
/// Converts an unassociated scaled vector to associated representation.
|
|||
/// </summary>
|
|||
/// <param name="source">The unassociated scaled vector.</param>
|
|||
/// <returns>The associated pixel.</returns>
|
|||
private static Rgba32P FromUnassociatedScaledVector4(Vector4 source) |
|||
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Rgba32P Pack(Vector4 vector) |
|||
{ |
|||
vector *= MaxBytes; |
|||
vector += Half; |
|||
vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); |
|||
|
|||
Vector128<byte> result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); |
|||
return new Rgba32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); |
|||
} |
|||
} |
|||
@ -0,0 +1,595 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using System.Runtime.Intrinsics.X86; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.Utils; |
|||
|
|||
/// <content>
|
|||
/// Contains <see cref="AssociatedRgbaCompatible"/>.
|
|||
/// </content>
|
|||
internal static partial class Vector4Converters |
|||
{ |
|||
/// <summary>
|
|||
/// Provides efficient batched conversion for four-byte pixel types that store premultiplied alpha.
|
|||
/// </summary>
|
|||
public static class AssociatedRgbaCompatible |
|||
{ |
|||
/// <summary>
|
|||
/// Converts associated RGBA byte components to an unassociated scaled vector.
|
|||
/// </summary>
|
|||
/// <param name="red">The associated red component.</param>
|
|||
/// <param name="green">The associated green component.</param>
|
|||
/// <param name="blue">The associated blue component.</param>
|
|||
/// <param name="alpha">The alpha component.</param>
|
|||
/// <returns>The unassociated scaled vector.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static Vector4 ToUnassociatedVector4(byte red, byte green, byte blue, byte alpha) |
|||
{ |
|||
// Divide the original byte magnitudes before normalization so a floating-point intermediate cannot move an exact byte conversion across its rounding midpoint.
|
|||
Vector4 vector = new(red, green, blue, alpha); |
|||
|
|||
if (alpha == 0) |
|||
{ |
|||
// Numerics.UnPremultiply preserves RGB when alpha is zero. Normalize the stored components because they already are the unassociated value in this case.
|
|||
return vector / byte.MaxValue; |
|||
} |
|||
|
|||
Numerics.UnPremultiply(ref vector); |
|||
vector.W /= byte.MaxValue; |
|||
return vector; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an unassociated scaled vector to the associated representation of an unsigned-byte destination.
|
|||
/// </summary>
|
|||
/// <param name="source">The unassociated scaled vector.</param>
|
|||
/// <returns>The associated scaled vector.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static Vector4 Associate(Vector4 source) |
|||
{ |
|||
float storedAlpha = (byte)Numerics.Clamp((source.W * byte.MaxValue) + 0.5F, 0, byte.MaxValue); |
|||
|
|||
// Associate in byte magnitude before normalization. Multiplying by a normalized alpha and later restoring byte magnitude can move an exact half-byte across its rounding boundary.
|
|||
source *= storedAlpha; |
|||
source.W = storedAlpha; |
|||
return source / byte.MaxValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts unassociated scaled vectors to the associated representation of an unsigned-byte destination.
|
|||
/// </summary>
|
|||
/// <param name="source">The vectors to convert in place.</param>
|
|||
internal static void Associate(Span<Vector4> source) |
|||
{ |
|||
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, i)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an associated scaled vector to associated byte magnitudes using the alpha byte the destination stores.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated scaled vector.</param>
|
|||
/// <returns>The associated byte magnitudes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 ReassociateToByte(Vector4 source) |
|||
{ |
|||
float alpha = source.W; |
|||
|
|||
if (alpha == 0) |
|||
{ |
|||
return Vector4.Zero; |
|||
} |
|||
|
|||
float byteAlpha = alpha * byte.MaxValue; |
|||
float storedAlpha = (byte)Numerics.Clamp(byteAlpha + 0.5F, 0, byte.MaxValue); |
|||
|
|||
if (byteAlpha == storedAlpha) |
|||
{ |
|||
// Quantization leaves alpha unchanged, so RGB is already associated correctly. Avoiding division preserves exact byte midpoints produced by scaling stored components.
|
|||
source *= byte.MaxValue; |
|||
source.W = storedAlpha; |
|||
return source; |
|||
} |
|||
|
|||
// Recover straight RGB before multiplying by the stored alpha byte. Keeping the association in byte magnitude preserves exact half-byte rounding boundaries.
|
|||
Numerics.UnPremultiply(ref source); |
|||
source *= storedAlpha; |
|||
source.W = storedAlpha; |
|||
return source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores.
|
|||
/// </summary>
|
|||
/// <param name="source">The vectors to convert in place.</param>
|
|||
private static void ReassociateToByte(Span<Vector4> source) |
|||
{ |
|||
if (Avx512F.IsSupported) |
|||
{ |
|||
ref Vector512<float> vectorSourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source)); |
|||
ref Vector512<float> sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length / 4u); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd)) |
|||
{ |
|||
vectorSourceBase = ReassociateToByte(vectorSourceBase); |
|||
vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); |
|||
} |
|||
|
|||
source = source[(source.Length & ~3)..]; |
|||
} |
|||
else if (Avx.IsSupported) |
|||
{ |
|||
ref Vector256<float> vectorSourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source)); |
|||
ref Vector256<float> sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length / 2u); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd)) |
|||
{ |
|||
vectorSourceBase = ReassociateToByte(vectorSourceBase); |
|||
vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); |
|||
} |
|||
|
|||
source = source[(source.Length & ~1)..]; |
|||
} |
|||
|
|||
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref sourceBase, i) = ReassociateToByte(Unsafe.Add(ref sourceBase, i)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts four associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated scaled vectors.</param>
|
|||
/// <returns>The associated byte magnitudes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<float> ReassociateToByte(Vector512<float> source) |
|||
{ |
|||
Vector512<float> zero = Vector512<float>.Zero; |
|||
Vector512<float> byteMax = Vector512.Create((float)byte.MaxValue); |
|||
Vector512<float> alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); |
|||
Vector512<float> byteAlpha = alpha * byteMax; |
|||
Vector512<float> storedAlpha = Vector512.Floor(Vector512.Min(Vector512.Max(byteAlpha + Vector512.Create(.5F), zero), byteMax)); |
|||
Vector512<float> result = (source / alpha) * storedAlpha; |
|||
|
|||
// Exact byte alpha values need no reassociation. Multiplying by 255 directly preserves RGB values that already lie on byte midpoints.
|
|||
result = Vector512.ConditionalSelect(Vector512.Equals(byteAlpha, storedAlpha), source * byteMax, result); |
|||
Vector512<float> alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); |
|||
result = Vector512.ConditionalSelect(alphaMask, storedAlpha, result); |
|||
return Vector512.ConditionalSelect(Vector512.Equals(alpha, zero), zero, result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts two associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated scaled vectors.</param>
|
|||
/// <returns>The associated byte magnitudes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<float> ReassociateToByte(Vector256<float> source) |
|||
{ |
|||
Vector256<float> zero = Vector256<float>.Zero; |
|||
Vector256<float> byteMax = Vector256.Create((float)byte.MaxValue); |
|||
Vector256<float> alpha = Avx.Permute(source, 0b_11_11_11_11); |
|||
Vector256<float> byteAlpha = alpha * byteMax; |
|||
Vector256<float> storedAlpha = Avx.Floor(Avx.Min(Avx.Max(byteAlpha + Vector256.Create(.5F), zero), byteMax)); |
|||
Vector256<float> result = (source / alpha) * storedAlpha; |
|||
|
|||
// Exact byte alpha values need no reassociation. Multiplying by 255 directly preserves RGB values that already lie on byte midpoints.
|
|||
result = Avx.BlendVariable(result, source * byteMax, Avx.CompareEqual(byteAlpha, storedAlpha)); |
|||
result = Avx.Blend(result, storedAlpha, 0b_1000_1000); |
|||
return Avx.BlendVariable(result, zero, Avx.CompareEqual(alpha, zero)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an associated scaled vector to an <see cref="Rgba32P"/> pixel.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated scaled vector.</param>
|
|||
/// <returns>The associated pixel.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static Rgba32P FromAssociatedVector4ToRgba32P(Vector4 source) |
|||
{ |
|||
source = ReassociateToByte(source); |
|||
return new Rgba32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an associated scaled vector to a <see cref="Bgra32P"/> pixel.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated scaled vector.</param>
|
|||
/// <returns>The associated pixel.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static Bgra32P FromAssociatedVector4ToBgra32P(Vector4 source) |
|||
{ |
|||
source = ReassociateToByte(source); |
|||
return new Bgra32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an associated scaled vector to an <see cref="Argb32P"/> pixel.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated scaled vector.</param>
|
|||
/// <returns>The associated pixel.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static Argb32P FromAssociatedVector4ToArgb32P(Vector4 source) |
|||
{ |
|||
source = ReassociateToByte(source); |
|||
return new Argb32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an associated scaled vector to an <see cref="Abgr32P"/> pixel.
|
|||
/// </summary>
|
|||
/// <param name="source">The associated scaled vector.</param>
|
|||
/// <returns>The associated pixel.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static Abgr32P FromAssociatedVector4ToAbgr32P(Vector4 source) |
|||
{ |
|||
source = ReassociateToByte(source); |
|||
return new Abgr32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts premultiplied RGBA pixels to vectors.
|
|||
/// </summary>
|
|||
/// <param name="source">The source pixels.</param>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="modifiers">The conversion modifier flags.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static void ToVector4( |
|||
ReadOnlySpan<Rgba32P> source, |
|||
Span<Vector4> destination, |
|||
PixelConversionModifiers modifiers) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast<Rgba32P, byte>(source), MemoryMarshal.Cast<Vector4, float>(destination)); |
|||
ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts premultiplied RGBA pixels to unassociated scaled vectors.
|
|||
/// </summary>
|
|||
/// <param name="source">The source pixels.</param>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
internal static void ToUnassociatedVector4(ReadOnlySpan<Rgba32P> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
ref Rgba32P sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Rgba32P pixel = Unsafe.Add(ref sourceBase, i); |
|||
Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts vectors to premultiplied RGBA pixels.
|
|||
/// </summary>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
/// <param name="modifiers">The conversion modifier flags.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static void FromVector4( |
|||
Span<Vector4> source, |
|||
Span<Rgba32P> destination, |
|||
PixelConversionModifiers modifiers) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); |
|||
SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), MemoryMarshal.Cast<Rgba32P, byte>(destination)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts associated scaled vectors to premultiplied RGBA pixels.
|
|||
/// </summary>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
internal static void FromAssociatedVector4(Span<Vector4> source, Span<Rgba32P> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
ReassociateToByte(source); |
|||
SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), MemoryMarshal.Cast<Rgba32P, byte>(destination)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts premultiplied BGRA pixels to vectors.
|
|||
/// </summary>
|
|||
/// <param name="source">The source pixels.</param>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="modifiers">The conversion modifier flags.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static void ToVector4( |
|||
ReadOnlySpan<Bgra32P> source, |
|||
Span<Vector4> destination, |
|||
PixelConversionModifiers modifiers) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
|
|||
if (source.IsEmpty) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// ByteToNormalizedFloat preserves component order, so the packed BGRA bytes must first be shuffled to RGBA.
|
|||
// Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation.
|
|||
// The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed.
|
|||
int lastIndex = source.Length - 1; |
|||
Span<Rgba32> temporary = MemoryMarshal.Cast<Vector4, Rgba32>(destination).Slice((3 * source.Length) + 1, lastIndex); |
|||
PixelConverter.FromBgra32.ToRgba32(MemoryMarshal.Cast<Bgra32P, byte>(source[..lastIndex]), MemoryMarshal.Cast<Rgba32, byte>(temporary)); |
|||
SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast<Rgba32, byte>(temporary), MemoryMarshal.Cast<Vector4, float>(destination[..lastIndex])); |
|||
destination[lastIndex] = source[lastIndex].ToVector4(); |
|||
ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts premultiplied BGRA pixels to unassociated scaled vectors.
|
|||
/// </summary>
|
|||
/// <param name="source">The source pixels.</param>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
internal static void ToUnassociatedVector4(ReadOnlySpan<Bgra32P> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
ref Bgra32P sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Bgra32P pixel = Unsafe.Add(ref sourceBase, i); |
|||
Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts vectors to premultiplied BGRA pixels.
|
|||
/// </summary>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
/// <param name="modifiers">The conversion modifier flags.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static void FromVector4( |
|||
Span<Vector4> source, |
|||
Span<Bgra32P> destination, |
|||
PixelConversionModifiers modifiers) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); |
|||
|
|||
// NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer.
|
|||
Span<byte> destinationBytes = MemoryMarshal.Cast<Bgra32P, byte>(destination); |
|||
SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes); |
|||
PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts associated scaled vectors to premultiplied BGRA pixels.
|
|||
/// </summary>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
internal static void FromAssociatedVector4(Span<Vector4> source, Span<Bgra32P> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
ReassociateToByte(source); |
|||
|
|||
Span<byte> destinationBytes = MemoryMarshal.Cast<Bgra32P, byte>(destination); |
|||
SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes); |
|||
PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts premultiplied ARGB pixels to vectors.
|
|||
/// </summary>
|
|||
/// <param name="source">The source pixels.</param>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="modifiers">The conversion modifier flags.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static void ToVector4( |
|||
ReadOnlySpan<Argb32P> source, |
|||
Span<Vector4> destination, |
|||
PixelConversionModifiers modifiers) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
|
|||
if (source.IsEmpty) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// ByteToNormalizedFloat preserves component order, so the packed ARGB bytes must first be shuffled to RGBA.
|
|||
// Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation.
|
|||
// The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed.
|
|||
int lastIndex = source.Length - 1; |
|||
Span<Rgba32> temporary = MemoryMarshal.Cast<Vector4, Rgba32>(destination).Slice((3 * source.Length) + 1, lastIndex); |
|||
PixelConverter.FromArgb32.ToRgba32(MemoryMarshal.Cast<Argb32P, byte>(source[..lastIndex]), MemoryMarshal.Cast<Rgba32, byte>(temporary)); |
|||
SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast<Rgba32, byte>(temporary), MemoryMarshal.Cast<Vector4, float>(destination[..lastIndex])); |
|||
destination[lastIndex] = source[lastIndex].ToVector4(); |
|||
ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts premultiplied ARGB pixels to unassociated scaled vectors.
|
|||
/// </summary>
|
|||
/// <param name="source">The source pixels.</param>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
internal static void ToUnassociatedVector4(ReadOnlySpan<Argb32P> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
ref Argb32P sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Argb32P pixel = Unsafe.Add(ref sourceBase, i); |
|||
Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts vectors to premultiplied ARGB pixels.
|
|||
/// </summary>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
/// <param name="modifiers">The conversion modifier flags.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static void FromVector4( |
|||
Span<Vector4> source, |
|||
Span<Argb32P> destination, |
|||
PixelConversionModifiers modifiers) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); |
|||
|
|||
// NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer.
|
|||
Span<byte> destinationBytes = MemoryMarshal.Cast<Argb32P, byte>(destination); |
|||
SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes); |
|||
PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts associated scaled vectors to premultiplied ARGB pixels.
|
|||
/// </summary>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
internal static void FromAssociatedVector4(Span<Vector4> source, Span<Argb32P> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
ReassociateToByte(source); |
|||
|
|||
Span<byte> destinationBytes = MemoryMarshal.Cast<Argb32P, byte>(destination); |
|||
SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes); |
|||
PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts premultiplied ABGR pixels to vectors.
|
|||
/// </summary>
|
|||
/// <param name="source">The source pixels.</param>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="modifiers">The conversion modifier flags.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static void ToVector4( |
|||
ReadOnlySpan<Abgr32P> source, |
|||
Span<Vector4> destination, |
|||
PixelConversionModifiers modifiers) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
|
|||
if (source.IsEmpty) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// ByteToNormalizedFloat preserves component order, so the packed ABGR bytes must first be shuffled to RGBA.
|
|||
// Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation.
|
|||
// The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed.
|
|||
int lastIndex = source.Length - 1; |
|||
Span<Rgba32> temporary = MemoryMarshal.Cast<Vector4, Rgba32>(destination).Slice((3 * source.Length) + 1, lastIndex); |
|||
PixelConverter.FromAbgr32.ToRgba32(MemoryMarshal.Cast<Abgr32P, byte>(source[..lastIndex]), MemoryMarshal.Cast<Rgba32, byte>(temporary)); |
|||
SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast<Rgba32, byte>(temporary), MemoryMarshal.Cast<Vector4, float>(destination[..lastIndex])); |
|||
destination[lastIndex] = source[lastIndex].ToVector4(); |
|||
ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts premultiplied ABGR pixels to unassociated scaled vectors.
|
|||
/// </summary>
|
|||
/// <param name="source">The source pixels.</param>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
internal static void ToUnassociatedVector4(ReadOnlySpan<Abgr32P> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
ref Abgr32P sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i++) |
|||
{ |
|||
Abgr32P pixel = Unsafe.Add(ref sourceBase, i); |
|||
Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts vectors to premultiplied ABGR pixels.
|
|||
/// </summary>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
/// <param name="modifiers">The conversion modifier flags.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static void FromVector4( |
|||
Span<Vector4> source, |
|||
Span<Abgr32P> destination, |
|||
PixelConversionModifiers modifiers) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); |
|||
|
|||
// NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer.
|
|||
Span<byte> destinationBytes = MemoryMarshal.Cast<Abgr32P, byte>(destination); |
|||
SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes); |
|||
PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts associated scaled vectors to premultiplied ABGR pixels.
|
|||
/// </summary>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
internal static void FromAssociatedVector4(Span<Vector4> source, Span<Abgr32P> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
destination = destination[..source.Length]; |
|||
ReassociateToByte(source); |
|||
|
|||
Span<byte> destinationBytes = MemoryMarshal.Cast<Abgr32P, byte>(destination); |
|||
SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes); |
|||
PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a floating-point byte magnitude to a byte.
|
|||
/// </summary>
|
|||
/// <param name="value">The byte magnitude.</param>
|
|||
/// <returns>The rounded and clamped byte.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static byte ConvertToByte(float value) => (byte)Numerics.Clamp(value + .5F, 0, byte.MaxValue); |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.PixelBlenders; |
|||
|
|||
/// <summary>
|
|||
/// Compares straight-alpha and associated-alpha bulk pixel blending.
|
|||
/// </summary>
|
|||
[Config(typeof(Config.Short))] |
|||
public class AssociatedAlphaPixelBlenderBenchmark |
|||
{ |
|||
private const int PixelCount = 1024; |
|||
|
|||
private readonly Rgba32[] unassociatedDestination = new Rgba32[PixelCount]; |
|||
private readonly Rgba32[] unassociatedBackground = new Rgba32[PixelCount]; |
|||
private readonly Rgba32[] unassociatedSource = new Rgba32[PixelCount]; |
|||
private readonly Rgba32P[] associatedDestination = new Rgba32P[PixelCount]; |
|||
private readonly Rgba32P[] associatedBackground = new Rgba32P[PixelCount]; |
|||
private readonly Rgba32P[] associatedSource = new Rgba32P[PixelCount]; |
|||
private readonly float[] amounts = new float[PixelCount]; |
|||
private readonly Vector4[] unassociatedWorkingBuffer = new Vector4[PixelCount * 3]; |
|||
private readonly Vector4[] associatedWorkingBuffer = new Vector4[PixelCount * 3]; |
|||
private PixelBlender<Rgba32> unassociatedBlender; |
|||
private PixelBlender<Rgba32P> associatedBlender; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the color-blending mode measured by the benchmark.
|
|||
/// </summary>
|
|||
[ParamsAllValues] |
|||
public PixelColorBlendingMode ColorMode { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the alpha-composition mode measured by the benchmark.
|
|||
/// </summary>
|
|||
[ParamsAllValues] |
|||
public PixelAlphaCompositionMode AlphaMode { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Initializes equivalent straight-alpha and associated-alpha rows.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.unassociatedBlender = PixelOperations<Rgba32>.Instance.GetPixelBlender(this.ColorMode, this.AlphaMode); |
|||
this.associatedBlender = PixelOperations<Rgba32P>.Instance.GetPixelBlender(this.ColorMode, this.AlphaMode); |
|||
|
|||
Random random = new(42); |
|||
|
|||
for (int i = 0; i < PixelCount; i++) |
|||
{ |
|||
Rgba32 background = new((byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(64, 256)); |
|||
Rgba32 source = new((byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(64, 256)); |
|||
|
|||
this.unassociatedBackground[i] = background; |
|||
this.unassociatedSource[i] = source; |
|||
this.associatedBackground[i] = Rgba32P.FromRgba32(background); |
|||
this.associatedSource[i] = Rgba32P.FromRgba32(source); |
|||
this.amounts[i] = random.NextSingle(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends one row stored with straight alpha.
|
|||
/// </summary>
|
|||
/// <returns>The last destination pixel.</returns>
|
|||
[Benchmark(Description = "Straight alpha", Baseline = true)] |
|||
public Rgba32 BlendUnassociated() |
|||
{ |
|||
this.unassociatedBlender.Blend<Rgba32>( |
|||
Configuration.Default, |
|||
this.unassociatedDestination, |
|||
this.unassociatedBackground, |
|||
this.unassociatedSource, |
|||
this.amounts, |
|||
this.unassociatedWorkingBuffer); |
|||
|
|||
return this.unassociatedDestination[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends one row stored with associated alpha.
|
|||
/// </summary>
|
|||
/// <returns>The last destination pixel.</returns>
|
|||
[Benchmark(Description = "Associated alpha")] |
|||
public Rgba32P BlendAssociated() |
|||
{ |
|||
this.associatedBlender.Blend<Rgba32P>( |
|||
Configuration.Default, |
|||
this.associatedDestination, |
|||
this.associatedBackground, |
|||
this.associatedSource, |
|||
this.amounts, |
|||
this.associatedWorkingBuffer); |
|||
|
|||
return this.associatedDestination[^1]; |
|||
} |
|||
} |
|||
@ -0,0 +1,667 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Verifies the shared contract for pixel formats that store associated alpha.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The associated-alpha pixel format.</typeparam>
|
|||
[Trait("Category", "PixelFormats")] |
|||
public abstract class AssociatedAlphaPixelTests<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private static readonly ApproximateFloatComparer VectorComparer = new(.005F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the color channels described by the pixel format.
|
|||
/// </summary>
|
|||
protected virtual PixelColorType ExpectedColorType => PixelColorType.RGB | PixelColorType.Alpha; |
|||
|
|||
[Fact] |
|||
public void PixelInformationDescribesAssociatedAlpha() |
|||
{ |
|||
PixelTypeInfo info = TPixel.GetPixelTypeInfo(); |
|||
PixelComponentInfo componentInfo = info.ComponentInfo.Value; |
|||
int expectedComponentPrecision = (Unsafe.SizeOf<TPixel>() * 8) / 4; |
|||
|
|||
Assert.Equal(Unsafe.SizeOf<TPixel>() * 8, info.BitsPerPixel); |
|||
Assert.Equal(PixelAlphaRepresentation.Associated, info.AlphaRepresentation); |
|||
Assert.Equal(this.ExpectedColorType, info.ColorType); |
|||
Assert.Equal(4, componentInfo.ComponentCount); |
|||
Assert.Equal(0, componentInfo.Padding); |
|||
|
|||
for (int i = 0; i < componentInfo.ComponentCount; i++) |
|||
{ |
|||
Assert.Equal(expectedComponentPrecision, componentInfo.GetComponentPrecision(i)); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void ScaledVectorConversionsUseAssociatedComponents() |
|||
{ |
|||
Vector4 associated = new(.25F, .125F, .0625F, .5F); |
|||
|
|||
TPixel pixel = TPixel.FromScaledVector4(associated); |
|||
|
|||
Assert.Equal(associated, pixel.ToScaledVector4(), VectorComparer); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FromRgba32AssociatesColorComponents() |
|||
{ |
|||
Rgba32 source = new(192, 128, 64, 128); |
|||
Vector4 expected = source.ToScaledVector4(); |
|||
Numerics.Premultiply(ref expected); |
|||
|
|||
TPixel pixel = TPixel.FromRgba32(source); |
|||
|
|||
Assert.Equal(expected, pixel.ToScaledVector4(), VectorComparer); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ToRgba32ReturnsUnassociatedColorComponents() |
|||
{ |
|||
Rgba32 expected = new(192, 128, 64, 128); |
|||
TPixel pixel = TPixel.FromRgba32(expected); |
|||
|
|||
Rgba32 actual = pixel.ToRgba32(); |
|||
|
|||
AssertRgba32Equal(expected, actual, 3); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ColorConversionsPreserveUnassociatedColor() |
|||
{ |
|||
Rgba32 expected = new(192, 128, 64, 128); |
|||
TPixel source = TPixel.FromRgba32(expected); |
|||
|
|||
Color color = Color.FromPixel(source); |
|||
Rgba32 actual = color.ToPixel<Rgba32>(); |
|||
TPixel roundTrip = color.ToPixel<TPixel>(); |
|||
|
|||
AssertRgba32Equal(expected, actual, 3); |
|||
Assert.Equal(source.ToScaledVector4(), roundTrip.ToScaledVector4(), VectorComparer); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ScalarBlendingUsesUnassociatedColorValues() |
|||
{ |
|||
TPixel background = TPixel.FromRgba32(new Rgba32(200, 40, 80, 160)); |
|||
TPixel source = TPixel.FromRgba32(new Rgba32(20, 180, 100, 96)); |
|||
PixelBlender<TPixel> associatedBlender = PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); |
|||
PixelBlender<Rgba32> unassociatedBlender = new DefaultPixelBlenders<Rgba32>.NormalSrcOver(); |
|||
|
|||
Rgba32 expected = unassociatedBlender.Blend(background.ToRgba32(), source.ToRgba32(), .75F); |
|||
Rgba32 actual = associatedBlender.Blend(background, source, .75F).ToRgba32(); |
|||
|
|||
AssertRgba32Equal(expected, actual, 4); |
|||
} |
|||
|
|||
private static void AssertRgba32Equal(Rgba32 expected, Rgba32 actual, int tolerance) |
|||
{ |
|||
Assert.InRange(Math.Abs(expected.R - actual.R), 0, tolerance); |
|||
Assert.InRange(Math.Abs(expected.G - actual.G), 0, tolerance); |
|||
Assert.InRange(Math.Abs(expected.B - actual.B), 0, tolerance); |
|||
Assert.InRange(Math.Abs(expected.A - actual.A), 0, tolerance); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests the <see cref="Rgba32P"/> pixel format.
|
|||
/// </summary>
|
|||
public class Rgba32PTests : AssociatedAlphaPixelTests<Rgba32P> |
|||
{ |
|||
[Fact] |
|||
public void ByteLayoutAndPackedValue() |
|||
{ |
|||
Rgba32P[] pixels = [new(1, 2, 3, 4)]; |
|||
|
|||
Assert.Equal(new byte[] { 1, 2, 3, 4 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray()); |
|||
Assert.Equal(0x04030201U, pixels[0].PackedValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests the <see cref="Bgra32P"/> pixel format.
|
|||
/// </summary>
|
|||
public class Bgra32PTests : AssociatedAlphaPixelTests<Bgra32P> |
|||
{ |
|||
/// <inheritdoc />
|
|||
protected override PixelColorType ExpectedColorType => PixelColorType.BGR | PixelColorType.Alpha; |
|||
|
|||
[Fact] |
|||
public void ByteLayoutAndPackedValue() |
|||
{ |
|||
Bgra32P[] pixels = [new(1, 2, 3, 4)]; |
|||
|
|||
Assert.Equal(new byte[] { 3, 2, 1, 4 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray()); |
|||
Assert.Equal(0x04010203U, pixels[0].PackedValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests the <see cref="Argb32P"/> pixel format.
|
|||
/// </summary>
|
|||
public class Argb32PTests : AssociatedAlphaPixelTests<Argb32P> |
|||
{ |
|||
[Fact] |
|||
public void ByteLayoutAndPackedValue() |
|||
{ |
|||
Argb32P[] pixels = [new(1, 2, 3, 4)]; |
|||
|
|||
Assert.Equal(new byte[] { 4, 1, 2, 3 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray()); |
|||
Assert.Equal(0x03020104U, pixels[0].PackedValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests the <see cref="Abgr32P"/> pixel format.
|
|||
/// </summary>
|
|||
public class Abgr32PTests : AssociatedAlphaPixelTests<Abgr32P> |
|||
{ |
|||
/// <inheritdoc />
|
|||
protected override PixelColorType ExpectedColorType => PixelColorType.BGR | PixelColorType.Alpha; |
|||
|
|||
[Fact] |
|||
public void ByteLayoutAndPackedValue() |
|||
{ |
|||
Abgr32P[] pixels = [new(1, 2, 3, 4)]; |
|||
|
|||
Assert.Equal(new byte[] { 4, 3, 2, 1 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray()); |
|||
Assert.Equal(0x01020304U, pixels[0].PackedValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests the <see cref="NormalizedByte4P"/> pixel format.
|
|||
/// </summary>
|
|||
public class NormalizedByte4PTests : AssociatedAlphaPixelTests<NormalizedByte4P> |
|||
{ |
|||
[Fact] |
|||
public void PackedValueMatchesNormalizedByte4ForAssociatedVector() |
|||
{ |
|||
Vector4 associated = new(.1F, -.3F, .5F, -.7F); |
|||
|
|||
Assert.Equal(new NormalizedByte4(associated).PackedValue, new NormalizedByte4P(associated).PackedValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests the <see cref="HalfVector4P"/> pixel format.
|
|||
/// </summary>
|
|||
public class HalfVector4PTests : AssociatedAlphaPixelTests<HalfVector4P> |
|||
{ |
|||
[Fact] |
|||
public void PackedValueMatchesHalfVector4ForAssociatedVector() |
|||
{ |
|||
Vector4 associated = new(.1F, -.3F, .5F, -.7F); |
|||
|
|||
Assert.Equal(new HalfVector4(associated).PackedValue, new HalfVector4P(associated).PackedValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests conversion between associated-alpha packed byte layouts.
|
|||
/// </summary>
|
|||
public class AssociatedAlphaPackedPixelConversionTests |
|||
{ |
|||
[Fact] |
|||
public void Rgba32PToBgra32PRoundTripIsLossless() => AssertLosslessRoundTrip<Bgra32P>(); |
|||
|
|||
[Fact] |
|||
public void Rgba32PToArgb32PRoundTripIsLossless() => AssertLosslessRoundTrip<Argb32P>(); |
|||
|
|||
[Fact] |
|||
public void Rgba32PToAbgr32PRoundTripIsLossless() => AssertLosslessRoundTrip<Abgr32P>(); |
|||
|
|||
[Fact] |
|||
public void Rgba32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Rgba32P(r, g, b, a)); |
|||
|
|||
[Fact] |
|||
public void Bgra32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Bgra32P(r, g, b, a)); |
|||
|
|||
[Fact] |
|||
public void Argb32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Argb32P(r, g, b, a)); |
|||
|
|||
[Fact] |
|||
public void Abgr32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Abgr32P(r, g, b, a)); |
|||
|
|||
[Fact] |
|||
public void Rgba32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual<Rgba32P>(); |
|||
|
|||
[Fact] |
|||
public void Bgra32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual<Bgra32P>(); |
|||
|
|||
[Fact] |
|||
public void Argb32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual<Argb32P>(); |
|||
|
|||
[Fact] |
|||
public void Abgr32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual<Abgr32P>(); |
|||
|
|||
private static void AssertLosslessRoundTrip<TIntermediate>() |
|||
where TIntermediate : unmanaged, IPixel<TIntermediate> |
|||
{ |
|||
Rgba32P[] expected = |
|||
[ |
|||
new(0, 0, 0, 0), |
|||
new(1, 2, 3, 4), |
|||
new(31, 63, 95, 127), |
|||
new(64, 128, 192, 255), |
|||
new(255, 255, 255, 255), |
|||
]; |
|||
TIntermediate[] intermediate = new TIntermediate[expected.Length]; |
|||
Rgba32P[] actual = new Rgba32P[expected.Length]; |
|||
|
|||
PixelOperations<TIntermediate>.Instance.From<Rgba32P>(Configuration.Default, expected, intermediate); |
|||
PixelOperations<Rgba32P>.Instance.From<TIntermediate>(Configuration.Default, intermediate, actual); |
|||
|
|||
for (int i = 0; i < expected.Length; i++) |
|||
{ |
|||
Assert.Equal(expected[i].ToScaledVector4(), intermediate[i].ToScaledVector4()); |
|||
} |
|||
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
private static void AssertScalarAndBulkAssociatedVectorsAreEqual<TPixel>(Func<byte, byte, byte, byte, TPixel> createPixel) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
TPixel[] pixels = new TPixel[64]; |
|||
Vector4[] actual = new Vector4[pixels.Length]; |
|||
|
|||
for (int i = 0; i < pixels.Length; i++) |
|||
{ |
|||
int component = i * 4; |
|||
pixels[i] = createPixel((byte)component, (byte)(component + 1), (byte)(component + 2), (byte)(component + 3)); |
|||
} |
|||
|
|||
PixelOperations<TPixel>.Instance.ToAssociatedScaledVector4(Configuration.Default, pixels, actual); |
|||
|
|||
for (int i = 0; i < pixels.Length; i++) |
|||
{ |
|||
Assert.Equal(pixels[i].ToScaledVector4(), actual[i]); |
|||
} |
|||
} |
|||
|
|||
private static void AssertScalarAndBulkFromAssociatedVectorsAreEqual<TPixel>() |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
const int count = 259; |
|||
Vector4[] vectors = new Vector4[count]; |
|||
TPixel[] expected = new TPixel[count]; |
|||
TPixel[] actual = new TPixel[count]; |
|||
AssociatedAlphaPixelOperations<TPixel> operations = (AssociatedAlphaPixelOperations<TPixel>)PixelOperations<TPixel>.Instance; |
|||
|
|||
for (int i = 0; i < vectors.Length; i++) |
|||
{ |
|||
// Alternating exact byte alpha values with fractional values covers both reassociation branches. The odd length also exercises the SIMD remainder.
|
|||
float alpha = (i & 1) == 0 ? (i % 256) / 255F : ((i % 255) + .375F) / 255F; |
|||
vectors[i] = new Vector4(((i * 37) % 256) / 255F, ((i * 73) % 256) / 255F, ((i * 109) % 256) / 255F, 1F) * alpha; |
|||
vectors[i].W = alpha; |
|||
expected[i] = operations.FromAssociatedScaledVector4(vectors[i]); |
|||
} |
|||
|
|||
operations.FromAssociatedScaledVector4(Configuration.Default, vectors, actual); |
|||
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests conversion between associated and unassociated packed byte formats.
|
|||
/// </summary>
|
|||
public class AssociatedToUnassociatedPackedPixelConversionTests |
|||
{ |
|||
[Fact] |
|||
public void Rgba32PToRgba32ScalarRoundTripPreservesEveryValidAssociatedComponent() |
|||
{ |
|||
for (int alpha = 0; alpha <= byte.MaxValue; alpha++) |
|||
{ |
|||
// Associated components greater than alpha are invalid, so the exhaustive domain is triangular rather than 256 squared.
|
|||
for (int associated = 0; associated <= alpha; associated++) |
|||
{ |
|||
// This integer expression is the exact nearest 8-bit unassociated value for associated * 255 / alpha.
|
|||
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); |
|||
Rgba32P source = new((byte)associated, 0, 0, (byte)alpha); |
|||
Rgba32 expectedUnassociated = new(unassociated, 0, 0, (byte)alpha); |
|||
|
|||
Rgba32 actualUnassociated = source.ToRgba32(); |
|||
Rgba32P actualRoundTrip = Rgba32P.FromRgba32(actualUnassociated); |
|||
|
|||
Assert.Equal(expectedUnassociated, actualUnassociated); |
|||
Assert.Equal(source, actualRoundTrip); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void ColorFromRgba32PPreservesEveryValidAssociatedComponent() |
|||
{ |
|||
for (int alpha = 0; alpha <= byte.MaxValue; alpha++) |
|||
{ |
|||
for (int associated = 0; associated <= alpha; associated++) |
|||
{ |
|||
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); |
|||
Rgba32P source = new((byte)associated, 0, 0, (byte)alpha); |
|||
Color color = Color.FromPixel(source); |
|||
|
|||
Assert.Equal(new Rgba32(unassociated, 0, 0, (byte)alpha), color.ToPixel<Rgba32>()); |
|||
Assert.Equal(source, color.ToPixel<Rgba32P>()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() |
|||
=> AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Rgba32P(red, green, blue, alpha)); |
|||
|
|||
[Fact] |
|||
public void Bgra32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() |
|||
=> AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Bgra32P(red, green, blue, alpha)); |
|||
|
|||
[Fact] |
|||
public void Argb32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() |
|||
=> AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Argb32P(red, green, blue, alpha)); |
|||
|
|||
[Fact] |
|||
public void Abgr32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() |
|||
=> AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Abgr32P(red, green, blue, alpha)); |
|||
|
|||
[Fact] |
|||
public void NormalizedByte4PToRgba32ScalarRoundTripPreservesEveryValidAssociatedComponent() |
|||
{ |
|||
for (int alpha = 0; alpha < byte.MaxValue; alpha++) |
|||
{ |
|||
for (int associated = 0; associated <= alpha; associated++) |
|||
{ |
|||
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); |
|||
byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254); |
|||
NormalizedByte4P source = CreateNormalizedByte4P(associated, 0, 0, alpha); |
|||
|
|||
Rgba32 actualUnassociated = source.ToRgba32(); |
|||
NormalizedByte4P actualRoundTrip = NormalizedByte4P.FromRgba32(actualUnassociated); |
|||
|
|||
Assert.Equal(new Rgba32(unassociated, 0, 0, unassociatedAlpha), actualUnassociated); |
|||
Assert.Equal(source, actualRoundTrip); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void ColorFromNormalizedByte4PPreservesEveryValidAssociatedComponent() |
|||
{ |
|||
for (int alpha = 0; alpha < byte.MaxValue; alpha++) |
|||
{ |
|||
for (int associated = 0; associated <= alpha; associated++) |
|||
{ |
|||
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); |
|||
byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254); |
|||
NormalizedByte4P source = CreateNormalizedByte4P(associated, 0, 0, alpha); |
|||
Color color = Color.FromPixel(source); |
|||
|
|||
Assert.Equal(new Rgba32(unassociated, 0, 0, unassociatedAlpha), color.ToPixel<Rgba32>()); |
|||
Assert.Equal(source, color.ToPixel<NormalizedByte4P>()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void NormalizedByte4PToBgra32RoundTripPreservesEveryValidAssociatedComponent() |
|||
{ |
|||
const int pairCount = 32640; |
|||
const int channelCount = 3; |
|||
NormalizedByte4P[] source = new NormalizedByte4P[pairCount * channelCount]; |
|||
Bgra32[] expectedUnassociated = new Bgra32[source.Length]; |
|||
Bgra32[] actualUnassociated = new Bgra32[source.Length]; |
|||
NormalizedByte4P[] actualRoundTrip = new NormalizedByte4P[source.Length]; |
|||
int index = 0; |
|||
|
|||
for (int alpha = 0; alpha < byte.MaxValue; alpha++) |
|||
{ |
|||
for (int associated = 0; associated <= alpha; associated++) |
|||
{ |
|||
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); |
|||
byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254); |
|||
|
|||
source[index] = CreateNormalizedByte4P(associated, 0, 0, alpha); |
|||
expectedUnassociated[index++] = new Bgra32(unassociated, 0, 0, unassociatedAlpha); |
|||
source[index] = CreateNormalizedByte4P(0, associated, 0, alpha); |
|||
expectedUnassociated[index++] = new Bgra32(0, unassociated, 0, unassociatedAlpha); |
|||
source[index] = CreateNormalizedByte4P(0, 0, associated, alpha); |
|||
expectedUnassociated[index++] = new Bgra32(0, 0, unassociated, unassociatedAlpha); |
|||
} |
|||
} |
|||
|
|||
PixelOperations<Bgra32>.Instance.From<NormalizedByte4P>(Configuration.Default, source, actualUnassociated); |
|||
PixelOperations<NormalizedByte4P>.Instance.From<Bgra32>(Configuration.Default, actualUnassociated, actualRoundTrip); |
|||
|
|||
Assert.Equal(expectedUnassociated, actualUnassociated); |
|||
Assert.Equal(source, actualRoundTrip); |
|||
} |
|||
|
|||
private static void AssertUnsignedByteBulkRoundTrip<TPixel>(Func<byte, byte, byte, byte, TPixel> createPixel) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
const int pairCount = 32896; |
|||
const int channelCount = 3; |
|||
TPixel[] source = new TPixel[pairCount * channelCount]; |
|||
Bgra32[] expectedUnassociated = new Bgra32[source.Length]; |
|||
Bgra32[] actualUnassociated = new Bgra32[source.Length]; |
|||
TPixel[] actualRoundTrip = new TPixel[source.Length]; |
|||
int index = 0; |
|||
|
|||
for (int alpha = 0; alpha <= byte.MaxValue; alpha++) |
|||
{ |
|||
// Associated components greater than alpha are invalid, so the exhaustive domain is triangular rather than 256 squared.
|
|||
for (int associated = 0; associated <= alpha; associated++) |
|||
{ |
|||
// This integer expression is the exact nearest 8-bit unassociated value for associated * 255 / alpha.
|
|||
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); |
|||
|
|||
source[index] = createPixel((byte)associated, 0, 0, (byte)alpha); |
|||
expectedUnassociated[index++] = new Bgra32(unassociated, 0, 0, (byte)alpha); |
|||
source[index] = createPixel(0, (byte)associated, 0, (byte)alpha); |
|||
expectedUnassociated[index++] = new Bgra32(0, unassociated, 0, (byte)alpha); |
|||
source[index] = createPixel(0, 0, (byte)associated, (byte)alpha); |
|||
expectedUnassociated[index++] = new Bgra32(0, 0, unassociated, (byte)alpha); |
|||
} |
|||
} |
|||
|
|||
PixelOperations<Bgra32>.Instance.From<TPixel>(Configuration.Default, source, actualUnassociated); |
|||
PixelOperations<TPixel>.Instance.From<Bgra32>(Configuration.Default, actualUnassociated, actualRoundTrip); |
|||
|
|||
Assert.Equal(expectedUnassociated, actualUnassociated); |
|||
Assert.Equal(source, actualRoundTrip); |
|||
} |
|||
|
|||
private static NormalizedByte4P CreateNormalizedByte4P(int red, int green, int blue, int alpha) |
|||
{ |
|||
uint packed = (byte)(red - 127) |
|||
| ((uint)(byte)(green - 127) << 8) |
|||
| ((uint)(byte)(blue - 127) << 16) |
|||
| ((uint)(byte)(alpha - 127) << 24); |
|||
|
|||
return new NormalizedByte4P { PackedValue = packed }; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests that associated destinations use their stored alpha value when associating color components.
|
|||
/// </summary>
|
|||
public class AssociatedDestinationAlphaQuantizationTests |
|||
{ |
|||
[Fact] |
|||
public void Rgba32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation<Rgba32P>(); |
|||
|
|||
[Fact] |
|||
public void Bgra32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation<Bgra32P>(); |
|||
|
|||
[Fact] |
|||
public void Argb32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation<Argb32P>(); |
|||
|
|||
[Fact] |
|||
public void Abgr32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation<Abgr32P>(); |
|||
|
|||
[Fact] |
|||
public void NormalizedByte4PQuantizesDestinationAlphaBeforeAssociation() |
|||
{ |
|||
ReadOnlySpan<byte> components = [64, 127, 191]; |
|||
Rgba64[] source = new Rgba64[(ushort.MaxValue + 1) * components.Length]; |
|||
NormalizedByte4P[] actualBulk = new NormalizedByte4P[source.Length]; |
|||
int index = 0; |
|||
|
|||
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) |
|||
{ |
|||
foreach (byte component in components) |
|||
{ |
|||
source[index++] = new Rgba64((ushort)(component * 257), 0, 0, (ushort)alpha); |
|||
} |
|||
} |
|||
|
|||
PixelOperations<NormalizedByte4P>.Instance.From<Rgba64>(Configuration.Default, source, actualBulk); |
|||
index = 0; |
|||
|
|||
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) |
|||
{ |
|||
int expectedAlpha = ((alpha * 254) + 32767) / ushort.MaxValue; |
|||
|
|||
foreach (byte component in components) |
|||
{ |
|||
int expectedRed = ((component * expectedAlpha) + 127) / byte.MaxValue; |
|||
NormalizedByte4P actualScalar = NormalizedByte4P.FromRgba64(source[index]); |
|||
int scalarRed = (sbyte)actualScalar.PackedValue + 127; |
|||
int scalarAlpha = (sbyte)(actualScalar.PackedValue >> 24) + 127; |
|||
int bulkRed = (sbyte)actualBulk[index].PackedValue + 127; |
|||
int bulkAlpha = (sbyte)(actualBulk[index].PackedValue >> 24) + 127; |
|||
|
|||
Assert.Equal(expectedRed, scalarRed); |
|||
Assert.Equal(expectedAlpha, scalarAlpha); |
|||
Assert.Equal(expectedRed, bulkRed); |
|||
Assert.Equal(expectedAlpha, bulkAlpha); |
|||
index++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HalfVector4PQuantizesDestinationAlphaBeforeAssociation() |
|||
{ |
|||
ReadOnlySpan<byte> components = [64, 127, 191]; |
|||
Rgba64[] source = new Rgba64[(ushort.MaxValue + 1) * components.Length]; |
|||
HalfVector4P[] actualBulk = new HalfVector4P[source.Length]; |
|||
int index = 0; |
|||
|
|||
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) |
|||
{ |
|||
foreach (byte component in components) |
|||
{ |
|||
source[index++] = new Rgba64((ushort)(component * 257), 0, 0, (ushort)alpha); |
|||
} |
|||
} |
|||
|
|||
PixelOperations<HalfVector4P>.Instance.From<Rgba64>(Configuration.Default, source, actualBulk); |
|||
index = 0; |
|||
|
|||
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) |
|||
{ |
|||
float nativeAlpha = ((alpha / (float)ushort.MaxValue) * 2F) - 1F; |
|||
ushort expectedAlpha = BitConverter.HalfToUInt16Bits((Half)nativeAlpha); |
|||
float storedAlpha = ((float)BitConverter.UInt16BitsToHalf(expectedAlpha) + 1F) / 2F; |
|||
|
|||
foreach (byte component in components) |
|||
{ |
|||
float associatedRed = ((component / (float)byte.MaxValue) * storedAlpha * 2F) - 1F; |
|||
ushort expectedRed = BitConverter.HalfToUInt16Bits((Half)associatedRed); |
|||
HalfVector4P actualScalar = HalfVector4P.FromRgba64(source[index]); |
|||
|
|||
Assert.Equal(expectedRed, (ushort)actualScalar.PackedValue); |
|||
Assert.Equal(expectedAlpha, (ushort)(actualScalar.PackedValue >> 48)); |
|||
Assert.Equal(expectedRed, (ushort)actualBulk[index].PackedValue); |
|||
Assert.Equal(expectedAlpha, (ushort)(actualBulk[index].PackedValue >> 48)); |
|||
index++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void ColorToRgba32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<Rgba32P>(); |
|||
|
|||
[Fact] |
|||
public void ColorToBgra32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<Bgra32P>(); |
|||
|
|||
[Fact] |
|||
public void ColorToArgb32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<Argb32P>(); |
|||
|
|||
[Fact] |
|||
public void ColorToAbgr32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<Abgr32P>(); |
|||
|
|||
[Fact] |
|||
public void ColorToNormalizedByte4PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<NormalizedByte4P>(); |
|||
|
|||
[Fact] |
|||
public void ColorToHalfVector4PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<HalfVector4P>(); |
|||
|
|||
/// <summary>
|
|||
/// Verifies the unsigned-byte destination grid through scalar and bulk conversion entry points.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The associated unsigned-byte pixel format.</typeparam>
|
|||
private static void AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation<TPixel>() |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
ReadOnlySpan<byte> components = [64, 127, 191]; |
|||
Rgba64[] source = new Rgba64[(ushort.MaxValue + 1) * components.Length]; |
|||
TPixel[] actualBulk = new TPixel[source.Length]; |
|||
int index = 0; |
|||
|
|||
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) |
|||
{ |
|||
foreach (byte component in components) |
|||
{ |
|||
source[index++] = new Rgba64((ushort)(component * 257), 0, 0, (ushort)alpha); |
|||
} |
|||
} |
|||
|
|||
PixelOperations<TPixel>.Instance.From<Rgba64>(Configuration.Default, source, actualBulk); |
|||
index = 0; |
|||
|
|||
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) |
|||
{ |
|||
int expectedAlpha = ((alpha * byte.MaxValue) + 32767) / ushort.MaxValue; |
|||
|
|||
foreach (byte component in components) |
|||
{ |
|||
int expectedRed = ((component * expectedAlpha) + 127) / byte.MaxValue; |
|||
Vector4 expected = new Vector4(expectedRed, 0, 0, expectedAlpha) * (1F / byte.MaxValue); |
|||
|
|||
Assert.Equal(expected, TPixel.FromRgba64(source[index]).ToScaledVector4()); |
|||
Assert.Equal(expected, actualBulk[index].ToScaledVector4()); |
|||
index++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies that <see cref="Color"/> delegates association to the destination pixel operations.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The associated destination pixel format.</typeparam>
|
|||
private static void AssertColorUsesDestinationAlphaRepresentation<TPixel>() |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) |
|||
{ |
|||
ushort component = (ushort)((byte)alpha * 257); |
|||
Rgba64 source = new(component, 0, 0, (ushort)alpha); |
|||
TPixel expected = TPixel.FromRgba64(source); |
|||
TPixel actual = Color.FromPixel(source).ToPixel<TPixel>(); |
|||
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
} |
|||
} |
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:cfd3afda359646aa3d46e1cffbfa7060395fda4c36c419ed3a2d8865284d090d |
|||
size 4031 |
|||
oid sha256:43e15b35bd282ecbf5f3b0b08b0f6fdba2e9144b0c17875d27bb8366b213c999 |
|||
size 4020 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:1c87a09b28b3f857e13a2bb420d16caa131a67c45b0fe31404756042f30de6d0 |
|||
size 28139 |
|||
oid sha256:a6a0e244af51f47f725dfcf1ef60705dd3223269db98685a1078b57eb2273c31 |
|||
size 23988 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:cb67006d156cff0fa8d4d1b131ac0eaa98279ae6dcc477818b2fc65f2dfb77aa |
|||
size 23396 |
|||
oid sha256:d0ce3abf8024859d1375721c18049513106ade2f9529ec20864621e310ca0b1b |
|||
size 18786 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:96729898fee87fc4305913c111a5841af205564d032b916aeb04425a20c6b22c |
|||
size 46540 |
|||
oid sha256:a94178ba1f0bd2edecdfd14c93196d1e195912f98875d875e1d79c8632676d03 |
|||
size 46525 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:53b9dabffaae6a9250bf16f201ef8c9e933327874e8be91785c4fb6cd7e787a8 |
|||
size 10767 |
|||
oid sha256:10b471070b26a6aedfc0fce762dd71090409494ce0728790278d8584636672a2 |
|||
size 9940 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:d73ae8bfad75c8b169fb050653eb4f8351edf173d1cc121ff1e23e3f1e594a97 |
|||
size 36342 |
|||
oid sha256:c372b6317bc4806ffdbddee45ded7b3a8c9f8ee7b7a462d8ff770b60ce00e176 |
|||
size 28629 |
|||
|
|||
Loading…
Reference in new issue