// 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.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; /// /// Tests the unit-range binary16 RGBA pixel formats. /// [Trait("Category", "PixelFormats")] public class RgbaHalfTests { /// /// Verifies that the unassociated format has the native layout required by RGBA binary16 surfaces. /// [Fact] public void RgbaHalfHasRgbaBinary16Layout() { RgbaHalf pixel = new(.25F, .5F, .75F, 1F); ulong expected = BitConverter.HalfToUInt16Bits((Half).25F) | ((ulong)BitConverter.HalfToUInt16Bits((Half).5F) << 16) | ((ulong)BitConverter.HalfToUInt16Bits((Half).75F) << 32) | ((ulong)BitConverter.HalfToUInt16Bits((Half)1F) << 48); Assert.Equal(8, Unsafe.SizeOf()); Assert.Equal(expected, pixel.PackedValue); Assert.Equal(new Vector4(.25F, .5F, .75F, 1F), pixel.ToVector4()); Assert.Equal(pixel.ToVector4(), pixel.ToScaledVector4()); } /// /// Verifies that zero-filled binary16 storage represents transparent black without affine remapping. /// [Fact] public void RgbaHalfDefaultIsTransparentBlack() { RgbaHalf pixel = default; Assert.Equal(Vector4.Zero, pixel.ToVector4()); Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); } /// /// Verifies that scaled input is clamped to the pixel format's unit color range. /// [Fact] public void RgbaHalfFromScaledVector4ClampsToUnitRange() { RgbaHalf pixel = RgbaHalf.FromScaledVector4(new Vector4(-1F, .5F, 2F, 1F)); Assert.Equal(new Vector4(0F, .5F, 1F, 1F), pixel.ToScaledVector4()); } /// /// Verifies that the associated format stores associated binary16 components in the same RGBA order. /// [Fact] public void RgbaHalfPHasAssociatedRgbaBinary16Layout() { RgbaHalfP pixel = new(.125F, .25F, .375F, .5F); ulong expected = BitConverter.HalfToUInt16Bits((Half).125F) | ((ulong)BitConverter.HalfToUInt16Bits((Half).25F) << 16) | ((ulong)BitConverter.HalfToUInt16Bits((Half).375F) << 32) | ((ulong)BitConverter.HalfToUInt16Bits((Half).5F) << 48); Assert.Equal(8, Unsafe.SizeOf()); Assert.Equal(expected, pixel.PackedValue); Assert.Equal(new Vector4(.125F, .25F, .375F, .5F), pixel.ToAssociatedScaledVector4()); } /// /// Verifies that zero-filled associated binary16 storage represents transparent black. /// [Fact] public void RgbaHalfPDefaultIsTransparentBlack() { RgbaHalfP pixel = default; Assert.Equal(Vector4.Zero, pixel.ToVector4()); Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); Assert.Equal(Vector4.Zero, pixel.ToUnassociatedScaledVector4()); } /// /// Verifies that association uses the alpha value that survives binary16 quantization. /// [Fact] public void RgbaHalfPQuantizesAlphaBeforeAssociation() { Vector4 source = new(.75F, .5F, .25F, 1F / 3F); float storedAlpha = (float)(Half)source.W; RgbaHalfP pixel = RgbaHalfP.FromUnassociatedScaledVector4(source); Assert.Equal((Half)(source.X * storedAlpha), pixel.R); Assert.Equal((Half)(source.Y * storedAlpha), pixel.G); Assert.Equal((Half)(source.Z * storedAlpha), pixel.B); Assert.Equal((Half)storedAlpha, pixel.A); } /// /// Verifies every bulk representation path against its scalar pixel contract at each SIMD width and tail boundary. /// [Fact] public void RgbaHalfBulkConversionsMatchScalarAcrossHardwareWidths() => FeatureTestRunner.RunWithHwIntrinsicsFeature( static () => AssertBulkConversionsMatchScalar(), HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); /// /// Verifies every associated bulk representation path against its scalar pixel contract at each SIMD width and tail boundary. /// [Fact] public void RgbaHalfPBulkConversionsMatchScalarAcrossHardwareWidths() => FeatureTestRunner.RunWithHwIntrinsicsFeature( static () => AssertBulkConversionsMatchScalar(), HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); /// /// Verifies the declared component precision and alpha representation for the unassociated format. /// [Fact] public void RgbaHalfPixelInformationIsCorrect() => AssertPixelInformation(PixelAlphaRepresentation.Unassociated); /// /// Verifies the declared component precision and alpha representation for the associated format. /// [Fact] public void RgbaHalfPPixelInformationIsCorrect() => AssertPixelInformation(PixelAlphaRepresentation.Associated); /// /// Compares bulk conversions with the corresponding scalar pixel operations for representative vector widths and remainders. /// /// The binary16 pixel format to test. private static void AssertBulkConversionsMatchScalar() where TPixel : unmanaged, IPixel { int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; PixelConversionModifiers[] modifiers = [ PixelConversionModifiers.None, PixelConversionModifiers.Scale, PixelConversionModifiers.Premultiply, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply, PixelConversionModifiers.UnPremultiply, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply ]; foreach (int length in lengths) { TPixel[] pixels = new TPixel[length]; for (int i = 0; i < length; i++) { pixels[i] = TPixel.FromUnassociatedScaledVector4(CreateUnassociatedVector(i)); } foreach (PixelConversionModifiers modifier in modifiers) { Vector4[] expectedVectors = new Vector4[length]; Vector4[] actualVectors = new Vector4[length]; Vector4[] sourceVectors = new Vector4[length]; TPixel[] expectedPixels = new TPixel[length]; TPixel[] actualPixels = new TPixel[length]; bool associated = RequestsAssociated(modifier); bool scaled = modifier.IsDefined(PixelConversionModifiers.Scale); for (int i = 0; i < length; i++) { expectedVectors[i] = ToScalarVector(pixels[i], associated, scaled); sourceVectors[i] = CreateUnassociatedVector(i + 19); if (associated) { Numerics.Premultiply(ref sourceVectors[i]); } expectedPixels[i] = FromScalarVector(sourceVectors[i], associated, scaled); } PixelOperations.Instance.ToVector4(Configuration.Default, pixels, actualVectors, modifier); PixelOperations.Instance.FromVector4Destructive(Configuration.Default, sourceVectors.AsSpan(), actualPixels, modifier); Assert.Equal(expectedVectors, actualVectors); Assert.Equal(expectedPixels, actualPixels); } } } /// /// Creates a deterministic unassociated unit-range vector for bulk conversion tests. /// /// The source index used to vary the component values. /// The generated vector. private static Vector4 CreateUnassociatedVector(int index) => new( ((index * 37) % 101) / 100F, ((index * 53) % 101) / 100F, ((index * 71) % 101) / 100F, ((index * 89) % 101) / 100F); /// /// Resolves whether a modifier combination requests associated output under the pixel format's native representation. /// /// The pixel format whose native representation participates in modifier resolution. /// The conversion modifiers. /// when the resulting vector representation is associated. private static bool RequestsAssociated(PixelConversionModifiers modifiers) where TPixel : unmanaged, IPixel => modifiers.IsDefined(PixelConversionModifiers.Premultiply) || (TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Associated && !modifiers.IsDefined(PixelConversionModifiers.UnPremultiply)); /// /// Converts one pixel through the scalar API selected by the requested representation and range. /// /// The source pixel format. /// The source pixel. /// Whether the result should use associated alpha. /// Whether the result should use the scaled range. /// The converted vector. private static Vector4 ToScalarVector(TPixel pixel, bool associated, bool scaled) where TPixel : unmanaged, IPixel => (associated, scaled) switch { (true, true) => pixel.ToAssociatedScaledVector4(), (true, false) => pixel.ToAssociatedVector4(), (false, true) => pixel.ToUnassociatedScaledVector4(), _ => pixel.ToUnassociatedVector4() }; /// /// Converts one vector through the scalar API selected by its representation and range. /// /// The destination pixel format. /// The source vector. /// Whether the source uses associated alpha. /// Whether the source uses the scaled range. /// The converted pixel. private static TPixel FromScalarVector(Vector4 vector, bool associated, bool scaled) where TPixel : unmanaged, IPixel => (associated, scaled) switch { (true, true) => TPixel.FromAssociatedScaledVector4(vector), (true, false) => TPixel.FromAssociatedVector4(vector), (false, true) => TPixel.FromUnassociatedScaledVector4(vector), _ => TPixel.FromUnassociatedVector4(vector) }; /// /// Verifies the component layout and alpha metadata exposed by a binary16 pixel format. /// /// The pixel format to inspect. /// The expected alpha representation. private static void AssertPixelInformation(PixelAlphaRepresentation alphaRepresentation) where TPixel : unmanaged, IPixel { PixelTypeInfo info = TPixel.GetPixelTypeInfo(); PixelComponentInfo componentInfo = info.ComponentInfo.Value; Assert.Equal(64, info.BitsPerPixel); Assert.Equal(alphaRepresentation, info.AlphaRepresentation); Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); Assert.Equal(4, componentInfo.ComponentCount); Assert.Equal(0, componentInfo.Padding); for (int i = 0; i < componentInfo.ComponentCount; i++) { Assert.Equal(16, componentInfo.GetComponentPrecision(i)); } } } /// /// Applies the shared associated-alpha contract to . /// public class RgbaHalfPAssociatedAlphaTests : AssociatedAlphaPixelTests { }