// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Numerics; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.PixelFormats; [Trait("Category", "PixelFormats")] public class FloatingPointPixelNormalizationTests { /// /// HalfSingle normalizes scaled input identically in scalar and bulk conversions. /// [Fact] public void HalfSingle_ScaledInputIsNormalized() => AssertScaledInputIsNormalized(); /// /// HalfVector2 normalizes scaled input identically in scalar and bulk conversions. /// [Fact] public void HalfVector2_ScaledInputIsNormalized() => AssertScaledInputIsNormalized(); /// /// HalfVector4 normalizes scaled input identically in scalar and bulk conversions. /// [Fact] public void HalfVector4_ScaledInputIsNormalized() => AssertScaledInputIsNormalized(); /// /// HalfVector4P normalizes scaled input identically in scalar and bulk conversions. /// [Fact] public void HalfVector4P_ScaledInputIsNormalized() => AssertScaledInputIsNormalized(); /// /// RgbaVector normalizes scaled input identically in scalar and bulk conversions. /// [Fact] public void RgbaVector_ScaledInputIsNormalized() => AssertScaledInputIsNormalized(); /// /// RgbaHalf normalizes scaled input identically in scalar and bulk conversions. /// [Fact] public void RgbaHalf_ScaledInputIsNormalized() => AssertScaledInputIsNormalized(); /// /// RgbaHalfP normalizes scaled input identically in scalar and bulk conversions. /// [Fact] public void RgbaHalfP_ScaledInputIsNormalized() => AssertScaledInputIsNormalized(); /// /// Raw half storage preserves IEEE special values while its scaled representation remains finite. /// [Fact] public void HalfVector4_NativeSpecialValuesHaveNormalizedScaledOutput() => AssertNativeSpecialValuesHaveNormalizedScaledOutput(); /// /// Associated half-vector conversion uses the stored alpha ratio before normalizing RGB. /// [Fact] public void HalfVector4P_AssociatedScaledInputIsNormalized() => AssertAssociatedScaledInputIsNormalized(); /// /// Associated half-RGBA conversion uses the stored alpha ratio before normalizing RGB. /// [Fact] public void RgbaHalfP_AssociatedScaledInputIsNormalized() => AssertAssociatedScaledInputIsNormalized(); /// /// Checks saturation and NaN handling without deriving expectations from the invalid-input path. /// /// The destination pixel format. private static void AssertScaledInputIsNormalized() where TPixel : unmanaged, IPixel { Vector4[] inputs = [ new(float.PositiveInfinity, float.NegativeInfinity, float.NaN, 1F), new(2F, -2F, .5F, 1F), new(.25F, .5F, .75F, .5F), new(.25F, .5F, .75F, float.NaN), new(.25F, .5F, .75F, float.PositiveInfinity) ]; Vector4[] normalized = [ new(1F, 0F, 0F, 1F), new(1F, 0F, .5F, 1F), new(.25F, .5F, .75F, .5F), new(.25F, .5F, .75F, 0F), new(.25F, .5F, .75F, 1F) ]; // Seventeen pixels exercise wide registers and the narrower remainder paths. Vector4[] source = new Vector4[17]; TPixel[] expected = new TPixel[source.Length]; TPixel[] actual = new TPixel[source.Length]; for (int i = 0; i < source.Length; i++) { int sample = i % inputs.Length; source[i] = inputs[sample]; expected[i] = TPixel.FromUnassociatedScaledVector4(normalized[sample]); Assert.Equal(expected[i], TPixel.FromUnassociatedScaledVector4(source[i])); } // Associated formats otherwise interpret the vectors using their native alpha representation. PixelOperations.Instance.FromVector4Destructive(Configuration.Default, source, actual, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); Assert.Equal(expected, actual); } /// /// Checks associated input against finite control values with the same represented color. /// /// The associated destination pixel format. private static void AssertAssociatedScaledInputIsNormalized() where TPixel : unmanaged, IPixel { Vector4[] inputs = [ new(float.PositiveInfinity, float.NegativeInfinity, float.NaN, 1F), new(1F, .5F, 1.5F, 2F), new(.125F, .25F, .375F, .5F), new(.25F, .5F, .75F, float.NaN), new(.25F, .5F, .75F, float.PositiveInfinity) ]; Vector4[] normalized = [ new(1F, 0F, 0F, 1F), new(.5F, .25F, .75F, 1F), new(.125F, .25F, .375F, .5F), Vector4.Zero, new(0F, 0F, 0F, 1F) ]; Vector4[] source = new Vector4[17]; TPixel[] expected = new TPixel[source.Length]; TPixel[] actual = new TPixel[source.Length]; for (int i = 0; i < source.Length; i++) { int sample = i % inputs.Length; source[i] = inputs[sample]; expected[i] = TPixel.FromAssociatedScaledVector4(normalized[sample]); Assert.Equal(expected[i], TPixel.FromAssociatedScaledVector4(source[i])); } PixelOperations.Instance.FromVector4Destructive(Configuration.Default, source, actual, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); Assert.Equal(expected, actual); } /// /// Checks native storage and every scaled output lane independently of integer conversion semantics. /// private static void AssertNativeSpecialValuesHaveNormalizedScaledOutput() { Vector4 native = new(float.PositiveInfinity, float.NegativeInfinity, float.NaN, 65504F); HalfVector4 pixel = HalfVector4.FromVector4(native); Assert.True(float.IsPositiveInfinity(pixel.ToVector4().X)); Assert.True(float.IsNegativeInfinity(pixel.ToVector4().Y)); Assert.True(float.IsNaN(pixel.ToVector4().Z)); Vector4 expected = new(1F, 0F, 0F, 1F); Assert.Equal(expected, pixel.ToScaledVector4()); Assert.Equal(1F, new HalfSingle(float.PositiveInfinity).ToScaledVector4().X); Assert.Equal(0F, new HalfSingle(float.NaN).ToScaledVector4().X); Assert.Equal(new Vector4(1F, 0F, 0F, 1F), new HalfVector2(new Vector2(float.PositiveInfinity, float.NaN)).ToScaledVector4()); HalfVector4[] source = new HalfVector4[17]; Vector4[] nativeSource = new Vector4[source.Length]; Array.Fill(nativeSource, native); PixelOperations.Instance.FromVector4Destructive(Configuration.Default, nativeSource, source, PixelConversionModifiers.None); Assert.All(source, value => Assert.Equal(pixel.PackedValue, value.PackedValue)); Vector4[] actual = new Vector4[source.Length]; PixelOperations.Instance.ToVector4(Configuration.Default, source, actual, PixelConversionModifiers.Scale); Assert.All(actual, value => Assert.Equal(expected, value)); } }