// 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); } } }