// 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;
///
/// Verifies the shared contract for pixel formats that store associated alpha.
///
/// The associated-alpha pixel format.
[Trait("Category", "PixelFormats")]
public abstract class AssociatedAlphaPixelTests
where TPixel : unmanaged, IPixel
{
private static readonly ApproximateFloatComparer VectorComparer = new(.005F);
///
/// Gets the color channels described by the pixel format.
///
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() * 8) / 4;
Assert.Equal(Unsafe.SizeOf() * 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();
TPixel roundTrip = color.ToPixel();
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 associatedBlender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
PixelBlender unassociatedBlender = new DefaultPixelBlenders.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);
}
}
///
/// Tests the pixel format.
///
public class Rgba32PTests : AssociatedAlphaPixelTests
{
[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);
}
}
///
/// Tests the pixel format.
///
public class Bgra32PTests : AssociatedAlphaPixelTests
{
///
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);
}
}
///
/// Tests the pixel format.
///
public class Argb32PTests : AssociatedAlphaPixelTests
{
[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);
}
}
///
/// Tests the pixel format.
///
public class Abgr32PTests : AssociatedAlphaPixelTests
{
///
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);
}
}
///
/// Tests the pixel format.
///
public class NormalizedByte4PTests : AssociatedAlphaPixelTests
{
[Fact]
public void PackedValueMatchesNormalizedByte4ForAssociatedVector()
{
Vector4 associated = new(.1F, -.3F, .5F, -.7F);
Assert.Equal(new NormalizedByte4(associated).PackedValue, new NormalizedByte4P(associated).PackedValue);
}
}
///
/// Tests the pixel format.
///
public class HalfVector4PTests : AssociatedAlphaPixelTests
{
[Fact]
public void PackedValueMatchesHalfVector4ForAssociatedVector()
{
Vector4 associated = new(.1F, -.3F, .5F, -.7F);
Assert.Equal(new HalfVector4(associated).PackedValue, new HalfVector4P(associated).PackedValue);
}
}
///
/// Tests conversion between associated-alpha packed byte layouts.
///
public class AssociatedAlphaPackedPixelConversionTests
{
[Fact]
public void Rgba32PToBgra32PRoundTripIsLossless() => AssertLosslessRoundTrip();
[Fact]
public void Rgba32PToArgb32PRoundTripIsLossless() => AssertLosslessRoundTrip();
[Fact]
public void Rgba32PToAbgr32PRoundTripIsLossless() => AssertLosslessRoundTrip();
[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();
[Fact]
public void Bgra32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual();
[Fact]
public void Argb32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual();
[Fact]
public void Abgr32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual();
private static void AssertLosslessRoundTrip()
where TIntermediate : unmanaged, IPixel
{
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.Instance.From(Configuration.Default, expected, intermediate);
PixelOperations.Instance.From(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(Func createPixel)
where TPixel : unmanaged, IPixel
{
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.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()
where TPixel : unmanaged, IPixel
{
const int count = 259;
Vector4[] vectors = new Vector4[count];
TPixel[] expected = new TPixel[count];
TPixel[] actual = new TPixel[count];
AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.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);
}
}
///
/// Tests conversion between associated and unassociated packed byte formats.
///
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());
Assert.Equal(source, color.ToPixel());
}
}
}
[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());
Assert.Equal(source, color.ToPixel());
}
}
}
[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.Instance.From(Configuration.Default, source, actualUnassociated);
PixelOperations.Instance.From(Configuration.Default, actualUnassociated, actualRoundTrip);
Assert.Equal(expectedUnassociated, actualUnassociated);
Assert.Equal(source, actualRoundTrip);
}
private static void AssertUnsignedByteBulkRoundTrip(Func createPixel)
where TPixel : unmanaged, IPixel
{
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.Instance.From(Configuration.Default, source, actualUnassociated);
PixelOperations.Instance.From(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 };
}
}
///
/// Tests that associated destinations use their stored alpha value when associating color components.
///
public class AssociatedDestinationAlphaQuantizationTests
{
[Fact]
public void Rgba32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation();
[Fact]
public void Bgra32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation();
[Fact]
public void Argb32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation();
[Fact]
public void Abgr32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation();
[Fact]
public void NormalizedByte4PQuantizesDestinationAlphaBeforeAssociation()
{
ReadOnlySpan 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.Instance.From(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 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.Instance.From(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();
[Fact]
public void ColorToBgra32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation();
[Fact]
public void ColorToArgb32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation();
[Fact]
public void ColorToAbgr32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation();
[Fact]
public void ColorToNormalizedByte4PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation();
[Fact]
public void ColorToHalfVector4PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation();
///
/// Verifies the unsigned-byte destination grid through scalar and bulk conversion entry points.
///
/// The associated unsigned-byte pixel format.
private static void AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation()
where TPixel : unmanaged, IPixel
{
ReadOnlySpan 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.Instance.From(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++;
}
}
}
///
/// Verifies that delegates association to the destination pixel operations.
///
/// The associated destination pixel format.
private static void AssertColorUsesDestinationAlphaRepresentation()
where TPixel : unmanaged, IPixel
{
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();
Assert.Equal(expected, actual);
}
}
}