diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings index 432f4524a0..8e7b5dd488 100644 --- a/ImageSharp.sln.DotSettings +++ b/ImageSharp.sln.DotSettings @@ -382,6 +382,7 @@ True True True + True True True True diff --git a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs index 5ae4629137..bbd454fbe8 100644 --- a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -30,9 +30,7 @@ public static void Expand(Span vectors) for (int i = 0; i < vectors.Length; i++) { ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - v.X = Expand(v.X); - v.Y = Expand(v.Y); - v.Z = Expand(v.Z); + Expand(ref v); } } @@ -48,9 +46,7 @@ public static void Compress(Span vectors) for (int i = 0; i < vectors.Length; i++) { ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - v.X = Compress(v.X); - v.Y = Compress(v.Y); - v.Z = Compress(v.Z); + Compress(ref v); } } @@ -58,17 +54,25 @@ public static void Compress(Span vectors) /// Expands a companded vector to its linear equivalent with respect to the energy. /// /// The vector. - /// The representing the linear channel values. [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 Expand(Vector4 vector) => new Vector4(Expand(vector.X), Expand(vector.Y), Expand(vector.Z), vector.W); + public static void Expand(ref Vector4 vector) + { + vector.X = Expand(vector.X); + vector.Y = Expand(vector.Y); + vector.Z = Expand(vector.Z); + } /// /// Compresses an uncompanded vector (linear) to its nonlinear equivalent. /// /// The vector. - /// The representing the nonlinear channel values. [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 Compress(Vector4 vector) => new Vector4(Compress(vector.X), Compress(vector.Y), Compress(vector.Z), vector.W); + public static void Compress(ref Vector4 vector) + { + vector.X = Compress(vector.X); + vector.Y = Compress(vector.Y); + vector.Z = Compress(vector.Z); + } /// /// Expands a companded channel to its linear equivalent with respect to the energy. diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs index d8cf69a52e..e86da78e30 100644 --- a/src/ImageSharp/Common/Helpers/Guard.cs +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -257,6 +257,26 @@ public static void DestinationShouldNotBeTooShort( } } + /// + /// Verifies that the 'destination' span is not shorter than 'source'. + /// + /// The source element type + /// The destination element type + /// The source span + /// The destination span + /// The name of the argument for 'destination' + [MethodImpl(InliningOptions.ShortMethod)] + public static void DestinationShouldNotBeTooShort( + Span source, + Span destination, + string destinationParamName) + { + if (destination.Length < source.Length) + { + ThrowArgumentException($"Destination span is too short!", destinationParamName); + } + } + /// /// Verifies, that the `source` span has the length of 'minLength', or longer. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs index 438749abf9..2c81908843 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -173,7 +173,7 @@ private void ConvertColorsInto(ImageFrame destination) Span destRow = destination.GetPixelRowSpan(yy); // TODO: Investigate if slicing is actually necessary - PixelOperations.Instance.FromVector4(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); + PixelOperations.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 5c8e506ae1..d2a6fbf501 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -112,21 +112,15 @@ public void Blend( Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - PixelOperations.Instance.ToScaledVector4( - configuration, - background.Slice(0, background.Length), - backgroundSpan); - PixelOperations.Instance.ToScaledVector4( - configuration, - source.Slice(0, background.Length), - sourceSpan); + ReadOnlySpan sourcePixels = background.Slice(0, background.Length); + PixelOperations.Instance.ToVector4(configuration, sourcePixels, backgroundSpan, PixelConversionModifiers.Scale); + ReadOnlySpan sourcePixels1 = source.Slice(0, background.Length); + PixelOperations.Instance.ToVector4(configuration, sourcePixels1, sourceSpan, PixelConversionModifiers.Scale); this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); - PixelOperations.Instance.FromScaledVector4( - configuration, - destinationSpan.Slice(0, background.Length), - destination); + Span sourceVectors = destinationSpan.Slice(0, background.Length); + PixelOperations.Instance.FromVector4Destructive(configuration, sourceVectors, destination, PixelConversionModifiers.Scale); } } @@ -161,21 +155,15 @@ public void Blend( Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - PixelOperations.Instance.ToScaledVector4( - configuration, - background.Slice(0, background.Length), - backgroundSpan); - PixelOperations.Instance.ToScaledVector4( - configuration, - source.Slice(0, background.Length), - sourceSpan); + ReadOnlySpan sourcePixels = background.Slice(0, background.Length); + PixelOperations.Instance.ToVector4(configuration, sourcePixels, backgroundSpan, PixelConversionModifiers.Scale); + ReadOnlySpan sourcePixels1 = source.Slice(0, background.Length); + PixelOperations.Instance.ToVector4(configuration, sourcePixels1, sourceSpan, PixelConversionModifiers.Scale); this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); - PixelOperations.Instance.FromScaledVector4( - configuration, - destinationSpan.Slice(0, background.Length), - destination); + Span sourceVectors = destinationSpan.Slice(0, background.Length); + PixelOperations.Instance.FromVector4Destructive(configuration, sourceVectors, destination, PixelConversionModifiers.Scale); } } } diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs new file mode 100644 index 0000000000..5df5dc62bc --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Flags responsible to select additional operations which could be effitiently applied in + /// + /// or + /// + /// knowing the pixel type. + /// + [Flags] + internal enum PixelConversionModifiers + { + /// + /// No special operation is selected + /// + None = 0, + + /// + /// Select and instead the standard (non scaled) variants. + /// + Scale = 1 << 0, + + /// + /// Enable alpha premultiplication / unpremultiplication + /// + Premultiply = 1 << 1, + + /// + /// Enable SRGB companding (defined in ). + /// + SRgbCompand = 1 << 2, + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs new file mode 100644 index 0000000000..bf77f85114 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + internal static class PixelConversionModifiersExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDefined(this PixelConversionModifiers modifiers, PixelConversionModifiers expected) => + (modifiers & expected) == expected; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PixelConversionModifiers Remove( + this PixelConversionModifiers modifiers, + PixelConversionModifiers removeThis) => + modifiers & ~removeThis; + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs index 2ec965dfb0..4cff1f8c10 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs @@ -42,29 +42,16 @@ internal override void ToArgb32(Configuration configuration, ReadOnlySpan - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); } - - /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); - } - - /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); - } - /// internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) { diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs index 711a9d1c16..225e4b5e01 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs @@ -42,30 +42,17 @@ internal override void ToBgr24(Configuration configuration, ReadOnlySpan } /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } - /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); - } - - /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); - } - - /// internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) { diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs index b669dd5348..ce049115ea 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs @@ -42,29 +42,16 @@ internal override void ToBgra32(Configuration configuration, ReadOnlySpan - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); } - - /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); - } - - /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); - } - /// internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) { diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs index dbf3102c4a..02a8869ecc 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs @@ -42,30 +42,17 @@ internal override void ToRgb24(Configuration configuration, ReadOnlySpan } /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } - /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); - } - - /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); - } - - /// internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) { diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude index f0675cb5b3..584615532d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude @@ -22,6 +22,7 @@ using System.Runtime.InteropServices; // Types with Rgba32-combatible to/from Vector4 conversion static readonly string[] Rgba32CompatibleTypes = { "Argb32", "Bgra32", "Rgb24", "Bgr24" }; + void GenerateDefaultSelfConversionMethods(string pixelType) { #> @@ -107,33 +108,25 @@ using System.Runtime.InteropServices; <#+ } - void GenerateRgba32CompatibleVector4ConversionMethods(string pixelType) + void GenerateRgba32CompatibleVector4ConversionMethods(string pixelType, bool hasAlpha) { + string removeTheseModifiers = "PixelConversionModifiers.Scale"; + if (!hasAlpha) + { + removeTheseModifiers += " | PixelConversionModifiers.Premultiply"; + } #> /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span<<#=pixelType#>> destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); - } - - /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span destVectors) + internal override void FromVector4(Configuration configuration, Span sourceVectors, Span<<#=pixelType#>> destPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(<#=removeTheseModifiers#>)); } /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span<<#=pixelType#>> destPixels) + internal override void ToVector4(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(<#=removeTheseModifiers#>)); } - - /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); - } - <#+ } @@ -143,7 +136,7 @@ using System.Runtime.InteropServices; if (Rgba32CompatibleTypes.Contains(pixelType)) { - GenerateRgba32CompatibleVector4ConversionMethods(pixelType); + GenerateRgba32CompatibleVector4ConversionMethods(pixelType, pixelType.EndsWith("32")); } var matching32BitTypes = Optimized32BitTypes.Contains(pixelType) ? @@ -164,4 +157,4 @@ using System.Runtime.InteropServices; GenerateDefaultConvertToMethod(pixelType, destPixelType); } } -#> \ No newline at end of file +#> diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs index 004b25cd33..3da5d1855b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs @@ -4,6 +4,8 @@ using System; using System.Numerics; using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.PixelFormats.Utils; using SixLabors.Memory; namespace SixLabors.ImageSharp.PixelFormats @@ -22,49 +24,33 @@ internal partial class PixelOperations : PixelOperations internal override void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors) + Span destVectors, + PixelConversionModifiers modifiers) { Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); destVectors = destVectors.Slice(0, sourcePixels.Length); - SimdUtils.BulkConvertByteToNormalizedFloat( MemoryMarshal.Cast(sourcePixels), MemoryMarshal.Cast(destVectors)); + Vector4Converters.ApplyForwardConversionModifiers(destVectors, modifiers); } /// - internal override void FromVector4( + internal override void FromVector4Destructive( Configuration configuration, - ReadOnlySpan sourceVectors, - Span destPixels) + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) { Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); destPixels = destPixels.Slice(0, sourceVectors.Length); - + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows( MemoryMarshal.Cast(sourceVectors), MemoryMarshal.Cast(destPixels)); } - - /// - internal override void ToScaledVector4( - Configuration configuration, - ReadOnlySpan sourceColors, - Span destinationVectors) - { - this.ToVector4(configuration, sourceColors, destinationVectors); - } - - /// - internal override void FromScaledVector4( - Configuration configuration, - ReadOnlySpan sourceVectors, - Span destinationColors) - { - this.FromVector4(configuration, sourceVectors, destinationColors); - } } } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs index bffaf57ddd..3112126e76 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs @@ -5,6 +5,8 @@ using System.Numerics; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -18,32 +20,29 @@ public partial struct RgbaVector internal class PixelOperations : PixelOperations { /// - internal override void FromScaledVector4( + internal override void FromVector4Destructive( Configuration configuration, - ReadOnlySpan sourceVectors, - Span destinationColors) + Span sourceVectors, + Span destinationColors, + PixelConversionModifiers modifiers) { Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationColors, nameof(destinationColors)); + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); MemoryMarshal.Cast(sourceVectors).CopyTo(destinationColors); } - /// - internal override void ToScaledVector4( - Configuration configuration, - ReadOnlySpan sourceColors, - Span destinationVectors) - => this.ToVector4(configuration, sourceColors, destinationVectors); - /// internal override void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors) + Span destVectors, + PixelConversionModifiers modifiers) { Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); MemoryMarshal.Cast(sourcePixels).CopyTo(destVectors); + Vector4Converters.ApplyForwardConversionModifiers(destVectors, modifiers); } } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 115dd7a43d..d009822e80 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -24,71 +24,70 @@ public partial class PixelOperations /// /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. + /// The method is DESTRUCTIVE altering the contents of . /// + /// + /// The destructive behavior is a design choice for performance reasons. + /// In a typical use case the contents of are abandoned after the conversion. + /// /// A to configure internal operations /// The to the source vectors. /// The to the destination colors. - internal virtual void FromVector4( + /// The to apply during the conversion + internal virtual void FromVector4Destructive( Configuration configuration, - ReadOnlySpan sourceVectors, - Span destPixels) + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); - Utils.Vector4Converters.Default.DangerousFromVector4(sourceVectors, destPixels); + Utils.Vector4Converters.Default.FromVector4(sourceVectors, destPixels, modifiers); } + /// + /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. + /// The method is DESTRUCTIVE altering the contents of . + /// + /// + /// The destructive behavior is a design choice for performance reasons. + /// In a typical use case the contents of are abandoned after the conversion. + /// + /// A to configure internal operations + /// The to the source vectors. + /// The to the destination colors. + internal void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels) => + this.FromVector4Destructive(configuration, sourceVectors, destPixels, PixelConversionModifiers.None); + /// /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. /// /// A to configure internal operations /// The to the source colors. /// The to the destination vectors. + /// The to apply during the conversion internal virtual void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors) + Span destVectors, + PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); - Utils.Vector4Converters.Default.DangerousToVector4(sourcePixels, destVectors); + Utils.Vector4Converters.Default.ToVector4(sourcePixels, destVectors, modifiers); } /// - /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. - /// - /// A to configure internal operations - /// The to the source vectors. - /// The to the destination colors. - internal virtual void FromScaledVector4( - Configuration configuration, - ReadOnlySpan sourceVectors, - Span destinationColors) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationColors, nameof(destinationColors)); - - Utils.Vector4Converters.Default.DangerousFromScaledVector4(sourceVectors, destinationColors); - } - - /// - /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. + /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. /// /// A to configure internal operations - /// The to the source colors. - /// The to the destination vectors. - internal virtual void ToScaledVector4( + /// The to the source colors. + /// The to the destination vectors. + internal virtual void ToVector4( Configuration configuration, - ReadOnlySpan sourceColors, - Span destinationVectors) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourceColors, destinationVectors, nameof(destinationVectors)); - - Utils.Vector4Converters.Default.DangerousToScaledVector4(sourceColors, destinationVectors); - } + ReadOnlySpan sourcePixels, + Span destVectors) => + this.ToVector4(configuration, sourcePixels, destVectors, PixelConversionModifiers.None); /// /// Converts 'sourceColors.Length' pixels from 'sourceColors' into 'destinationColors'. diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs index e784e3b5a6..4c4e60276d 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs @@ -6,6 +6,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + namespace SixLabors.ImageSharp.PixelFormats.Utils { /// @@ -15,13 +17,75 @@ internal static partial class Vector4Converters { /// /// Provides default implementations for batched to/from conversion. - /// WARNING: The methods are operating without bounds checking and input validation! + /// WARNING: The methods prefixed with "Unsafe" are operating without bounds checking and input validation! /// Input validation is the responsibility of the caller! /// public static class Default { [MethodImpl(InliningOptions.ShortMethod)] - internal static void DangerousFromVector4( + public static void FromVector4( + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) + where TPixel : struct, IPixel + { + Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); + + UnsafeFromVector4(sourceVectors, destPixels, modifiers); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToVector4( + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + where TPixel : struct, IPixel + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); + + UnsafeToVector4(sourcePixels, destVectors, modifiers); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnsafeFromVector4( + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) + where TPixel : struct, IPixel + { + ApplyBackwardConversionModifiers(sourceVectors, modifiers); + + if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + { + UnsafeFromScaledVector4Core(sourceVectors, destPixels); + } + else + { + UnsafeFromVector4Core(sourceVectors, destPixels); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnsafeToVector4( + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + where TPixel : struct, IPixel + { + if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + { + UnsafeToScaledVector4Core(sourcePixels, destVectors); + } + else + { + UnsafeToVector4Core(sourcePixels, destVectors); + } + + ApplyForwardConversionModifiers(destVectors, modifiers); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeFromVector4Core( ReadOnlySpan sourceVectors, Span destPixels) where TPixel : struct, IPixel @@ -38,7 +102,7 @@ internal static void DangerousFromVector4( } [MethodImpl(InliningOptions.ShortMethod)] - internal static void DangerousToVector4( + private static void UnsafeToVector4Core( ReadOnlySpan sourcePixels, Span destVectors) where TPixel : struct, IPixel @@ -55,7 +119,7 @@ internal static void DangerousToVector4( } [MethodImpl(InliningOptions.ShortMethod)] - internal static void DangerousFromScaledVector4( + private static void UnsafeFromScaledVector4Core( ReadOnlySpan sourceVectors, Span destinationColors) where TPixel : struct, IPixel @@ -72,7 +136,7 @@ internal static void DangerousFromScaledVector4( } [MethodImpl(InliningOptions.ShortMethod)] - internal static void DangerousToScaledVector4( + private static void UnsafeToScaledVector4Core( ReadOnlySpan sourceColors, Span destinationVectors) where TPixel : struct, IPixel diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 06c33d79c7..fe8d7dec3b 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -7,6 +7,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + namespace SixLabors.ImageSharp.PixelFormats.Utils { /// @@ -29,11 +31,8 @@ public static class RgbaCompatible private static readonly int Vector4ConversionThreshold = CalculateVector4ConversionThreshold(); /// - /// Provides an efficient default implementation for - /// and - /// which is applicable for -compatible pixel types where - /// returns the same scaled result as . - /// The method is works by internally converting to a therefore it's not applicable for that type! + /// Provides an efficient default implementation for + /// The method works by internally converting to a therefore it's not applicable for that type! /// [MethodImpl(InliningOptions.ShortMethod)] internal static void ToVector4( @@ -41,7 +40,7 @@ internal static void ToVector4( PixelOperations pixelOperations, ReadOnlySpan sourcePixels, Span destVectors, - bool scaled) + PixelConversionModifiers modifiers) where TPixel : struct, IPixel { Guard.NotNull(configuration, nameof(configuration)); @@ -52,7 +51,7 @@ internal static void ToVector4( // Not worth for small buffers: if (count < Vector4ConversionThreshold) { - ToVector4Fallback(sourcePixels, destVectors, scaled); + Default.UnsafeToVector4(sourcePixels, destVectors, modifiers); return; } @@ -70,22 +69,22 @@ internal static void ToVector4( MemoryMarshal.Cast(destVectors.Slice(0, countWithoutLastItem))); destVectors[countWithoutLastItem] = sourcePixels[countWithoutLastItem].ToVector4(); + + // TODO: Investigate optimized 1-pass approach! + ApplyForwardConversionModifiers(destVectors, modifiers); } /// - /// Provides an efficient default implementation for - /// and - /// which is applicable for -compatible pixel types where - /// returns the same scaled result as . + /// Provides an efficient default implementation for /// The method is works by internally converting to a therefore it's not applicable for that type! /// [MethodImpl(InliningOptions.ShortMethod)] internal static void FromVector4( Configuration configuration, PixelOperations pixelOperations, - ReadOnlySpan sourceVectors, + Span sourceVectors, Span destPixels, - bool scaled) + PixelConversionModifiers modifiers) where TPixel : struct, IPixel { Guard.NotNull(configuration, nameof(configuration)); @@ -96,11 +95,14 @@ internal static void FromVector4( // Not worth for small buffers: if (count < Vector4ConversionThreshold) { - FromVector4Fallback(sourceVectors, destPixels, scaled); + Default.UnsafeFromVector4(sourceVectors, destPixels, modifiers); return; } + // TODO: Investigate optimized 1-pass approach! + ApplyBackwardConversionModifiers(sourceVectors, modifiers); + // For the opposite direction it's not easy to implement the trick used in RunRgba32CompatibleToVector4Conversion, // so let's allocate a temporary buffer as usually: using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(count)) @@ -115,34 +117,6 @@ internal static void FromVector4( } } - [MethodImpl(InliningOptions.ColdPath)] - private static void ToVector4Fallback(ReadOnlySpan sourcePixels, Span destVectors, bool scaled) - where TPixel : struct, IPixel - { - if (scaled) - { - Default.DangerousToScaledVector4(sourcePixels, destVectors); - } - else - { - Default.DangerousToVector4(sourcePixels, destVectors); - } - } - - [MethodImpl(InliningOptions.ColdPath)] - private static void FromVector4Fallback(ReadOnlySpan sourceVectors, Span destPixels, bool scaled) - where TPixel : struct, IPixel - { - if (scaled) - { - Default.DangerousFromScaledVector4(sourceVectors, destPixels); - } - else - { - Default.DangerousFromVector4(sourceVectors, destPixels); - } - } - private static int CalculateVector4ConversionThreshold() { if (!Vector.IsHardwareAccelerated) diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs new file mode 100644 index 0000000000..447869a7d5 --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.PixelFormats.Utils +{ + internal static partial class Vector4Converters + { + /// + /// Apply modifiers used requested by ToVector4() conversion. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ApplyForwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) + { + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + SRgbCompanding.Expand(vectors); + } + + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + Vector4Utils.Premultiply(vectors); + } + } + + /// + /// Apply modifiers used requested by FromVector4() conversion. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ApplyBackwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) + { + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + Vector4Utils.UnPremultiply(vectors); + } + + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + SRgbCompanding.Compress(vectors); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index bd1419e4bb..341a26ae84 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -82,7 +82,7 @@ protected override void OnFrameApply( DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); } - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan); } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index 05007c3706..9de4673289 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -100,7 +100,7 @@ private void ApplyConvolution( DenseMatrixUtils.Convolve(in matrix, sourcePixels, vectorSpan, y, x, maxY, maxX, startX); } - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan); } }); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 8ef64bdacc..7e003cb03e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -66,7 +66,7 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source DenseMatrixUtils.Convolve(in matrix, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); } - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan); } }); diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs index 43f6c8a454..6313b74c94 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs @@ -98,7 +98,7 @@ protected override void BeforeFrameApply(ImageFrame source, Rectangle so if (this.paletteVector is null) { this.paletteVector = new Vector4[this.Palette.Length]; - PixelOperations.Instance.ToScaledVector4(configuration, this.Palette, this.paletteVector); + PixelOperations.Instance.ToVector4(configuration, (ReadOnlySpan)this.Palette, (Span)this.paletteVector, PixelConversionModifiers.Scale); } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs index d6a32d8899..dbd8433410 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs @@ -51,7 +51,7 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source Vector4Utils.Transform(vectorSpan, ref matrix); - PixelOperations.Instance.FromVector4(configuration, vectorSpan, rowSpan); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, rowSpan); } }); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs index 3b9b046a00..a70cfb6601 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs @@ -79,7 +79,7 @@ public virtual QuantizedFrame QuantizeFrame(ImageFrame image) // Collect the palette. Required before the second pass runs. TPixel[] palette = this.GetPalette(); this.paletteVector = new Vector4[palette.Length]; - PixelOperations.Instance.ToScaledVector4(image.Configuration, palette, this.paletteVector); + PixelOperations.Instance.ToVector4(image.Configuration, (ReadOnlySpan)palette, (Span)this.paletteVector, PixelConversionModifiers.Scale); var quantizedFrame = new QuantizedFrame(image.MemoryAllocator, width, height, palette); if (this.Dither) diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 5a3b5943b7..0f71c732dd 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -129,7 +129,7 @@ protected override void OnFrameApply( kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan); } - PixelOperations.Instance.FromVector4(configuration, vectorSpan, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); } }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index 0b5627e19b..8a5d7563f4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -128,7 +128,7 @@ protected override void OnFrameApply(ImageFrame source, ImageFrame.Instance.FromVector4(configuration, vectorSpan, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); } }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index 189e21de7a..2c186a276b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -9,7 +9,6 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ColorSpaces.Companding; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; @@ -216,27 +215,33 @@ protected override void OnFrameApply(ImageFrame source, ImageFrame + { + for (int y = rows.Min; y < rows.Max; y++) { - for (int y = rows.Min; y < rows.Max; y++) + // Y coordinates of source points + Span sourceRow = + source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); + Span targetRow = destination.GetPixelRowSpan(y); + + for (int x = minX; x < maxX; x++) { - // Y coordinates of source points - Span sourceRow = - source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); - Span targetRow = destination.GetPixelRowSpan(y); - - for (int x = minX; x < maxX; x++) - { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; - } + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; } - }); + } + }); return; } int sourceHeight = source.Height; + PixelConversionModifiers conversionModifiers = PixelConversionModifiers.Premultiply; + if (this.Compand) + { + conversionModifiers |= PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand; + } + // Interpolate the image using the calculated weights. // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY @@ -251,30 +256,23 @@ protected override void OnFrameApply(ImageFrame source, ImageFrame + { + for (int y = rows.Min; y < rows.Max; y++) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y).Slice(sourceX); - Span tempRowSpan = tempRowBuffer.Span.Slice(sourceX); - - PixelOperations.Instance.ToVector4(configuration, sourceRow, tempRowSpan); - Vector4Utils.Premultiply(tempRowSpan); + Span sourceRow = source.GetPixelRowSpan(y).Slice(sourceX); + Span tempRowSpan = tempRowBuffer.Span.Slice(sourceX); - ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; + PixelOperations.Instance.ToVector4(configuration, sourceRow, tempRowSpan, conversionModifiers); - if (this.Compand) - { - SRgbCompanding.Expand(tempRowSpan); - } + ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; - for (int x = minX; x < maxX; x++) - { - ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - startX); - Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = - kernel.Convolve(tempRowSpan); - } + for (int x = minX; x < maxX; x++) + { + ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - startX); + Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = kernel.Convolve(tempRowSpan); } - }); + } + }); var processRowsRect = Rectangle.FromLTRB(0, minY, width, maxY); @@ -283,35 +281,29 @@ protected override void OnFrameApply(ImageFrame source, ImageFrame - { - Span tempRowSpan = tempRowBuffer.Span; - - for (int y = rows.Min; y < rows.Max; y++) - { - // Ensure offsets are normalized for cropping and padding. - ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - startY); + { + Span tempRowSpan = tempRowBuffer.Span; - ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan); + for (int y = rows.Min; y < rows.Max; y++) + { + // Ensure offsets are normalized for cropping and padding. + ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - startY); - for (int x = 0; x < width; x++) - { - Span firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x).Slice(sourceY); + ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan); - // Destination color components - Unsafe.Add(ref tempRowBase, x) = kernel.Convolve(firstPassColumn); - } + for (int x = 0; x < width; x++) + { + Span firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x).Slice(sourceY); - Vector4Utils.UnPremultiply(tempRowSpan); + // Destination color components + Unsafe.Add(ref tempRowBase, x) = kernel.Convolve(firstPassColumn); + } - if (this.Compand) - { - SRgbCompanding.Compress(tempRowSpan); - } + Span targetRowSpan = destination.GetPixelRowSpan(y); - Span targetRowSpan = destination.GetPixelRowSpan(y); - PixelOperations.Instance.FromVector4(configuration, tempRowSpan, targetRowSpan); - } - }); + PixelOperations.Instance.FromVector4Destructive(configuration, tempRowSpan, targetRowSpan, conversionModifiers); + } + }); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs index 2b9573ed73..8b2d08e66a 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs @@ -61,13 +61,13 @@ public void PerElement() [Benchmark] public void PixelOperations_Base() { - new PixelOperations().FromVector4(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); + new PixelOperations().FromVector4Destructive(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); } [Benchmark] public void PixelOperations_Specialized() { - PixelOperations.Instance.FromVector4(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); + PixelOperations.Instance.FromVector4Destructive(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); } } diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index cdd56fa074..ce4e16c446 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -45,7 +45,7 @@ private void BulkVectorConvert( destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); } - PixelOperations.Instance.FromVector4(this.Configuration, destinationSpan, destination); + PixelOperations.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination); } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs index 148b253281..51f3a5653a 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs @@ -14,11 +14,12 @@ namespace SixLabors.ImageSharp.Benchmarks { [Config(typeof(Config.ShortClr))] - public abstract class ResizeBenchmarkBase + public abstract class ResizeBenchmarkBase + where TPixel : struct, IPixel { protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule()); - private Image sourceImage; + private Image sourceImage; private Bitmap sourceBitmap; @@ -31,7 +32,7 @@ public abstract class ResizeBenchmarkBase [GlobalSetup] public void Setup() { - this.sourceImage = new Image(this.Configuration, this.SourceSize, this.SourceSize); + this.sourceImage = new Image(this.Configuration, this.SourceSize, this.SourceSize); this.sourceBitmap = new Bitmap(this.SourceSize, this.SourceSize); } @@ -65,83 +66,137 @@ public int SystemDrawing() [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 1")] public int ImageSharp_P1() => this.RunImageSharpResize(1); - [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 4")] - public int ImageSharp_P4() => this.RunImageSharpResize(4); + // Parallel cases have been disabled for fast benchmark execution. + // Uncomment, if you are interested in parallel speedup - [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 8")] - public int ImageSharp_P8() => this.RunImageSharpResize(8); + //[Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 4")] + //public int ImageSharp_P4() => this.RunImageSharpResize(4); + + //[Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 8")] + //public int ImageSharp_P8() => this.RunImageSharpResize(8); protected int RunImageSharpResize(int maxDegreeOfParallelism) { this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism; - using (Image clone = this.sourceImage.Clone(this.ExecuteResizeOperation)) + using (Image clone = this.sourceImage.Clone(this.ExecuteResizeOperation)) { return clone.Width; } } - protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); + protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); } - - public class Resize_Bicubic : ResizeBenchmarkBase + + public class Resize_Bicubic_Rgba32 : ResizeBenchmarkBase { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) { ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); } - // RESULTS (2018 October): + // RESULTS (2019 April): // - // BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134 + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742191 Hz, Resolution=364.6719 ns, Timer=TSC - // .NET Core SDK=2.1.403 - // [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - // Job-IGUFBA : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 - // Job-DZFERG : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // + // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032 | 400 | 118.71 ms | 4.884 ms | 0.2677 ms | 1.00 | - | - | - | 2048 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 94.55 ms | 16.160 ms | 0.8858 ms | 0.80 | - | - | - | 16384 B | + // | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 118.38 ms | 2.814 ms | 0.1542 ms | 1.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 90.28 ms | 4.679 ms | 0.2565 ms | 0.76 | - | - | - | 15712 B | + + } + + public class Resize_Bicubic_Bgra32 : ResizeBenchmarkBase + { + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + { + ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); + } + + // RESULTS (2019 April): // - // Method | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Scaled | ScaledSD | Allocated | - // ----------------------------------------- |-------- |----------- |--------- |----------:|----------:|----------:|-------:|---------:|----------:| - // SystemDrawing | Clr | 3032 | 400 | 101.13 ms | 18.659 ms | 1.0542 ms | 1.00 | 0.00 | 0 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | 3032 | 400 | 122.05 ms | 19.622 ms | 1.1087 ms | 1.21 | 0.01 | 21856 B | - // 'ImageSharp, MaxDegreeOfParallelism = 4' | Clr | 3032 | 400 | 41.34 ms | 54.841 ms | 3.0986 ms | 0.41 | 0.03 | 28000 B | - // 'ImageSharp, MaxDegreeOfParallelism = 8' | Clr | 3032 | 400 | 31.68 ms | 12.782 ms | 0.7222 ms | 0.31 | 0.01 | 28256 B | - // | | | | | | | | | | - // SystemDrawing | Core | 3032 | 400 | 100.37 ms | 18.479 ms | 1.0441 ms | 1.00 | 0.00 | 0 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | 3032 | 400 | 73.03 ms | 10.540 ms | 0.5955 ms | 0.73 | 0.01 | 21368 B | - // 'ImageSharp, MaxDegreeOfParallelism = 4' | Core | 3032 | 400 | 22.59 ms | 4.863 ms | 0.2748 ms | 0.23 | 0.00 | 25220 B | - // 'ImageSharp, MaxDegreeOfParallelism = 8' | Core | 3032 | 400 | 21.10 ms | 23.362 ms | 1.3200 ms | 0.21 | 0.01 | 25539 B | + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // + // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032 | 400 | 119.01 ms | 18.513 ms | 1.0147 ms | 1.00 | - | - | - | 1638 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 104.71 ms | 16.078 ms | 0.8813 ms | 0.88 | - | - | - | 45056 B | + // | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 121.58 ms | 50.084 ms | 2.7453 ms | 1.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 96.96 ms | 7.899 ms | 0.4329 ms | 0.80 | - | - | - | 44512 B | + } + + public class Resize_Bicubic_Rgb24 : ResizeBenchmarkBase + { + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + { + ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); + } + // RESULTS (2019 April): + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032 | 400 | 121.37 ms | 48.580 ms | 2.6628 ms | 1.00 | 0.00 | - | - | - | 2048 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 99.36 ms | 11.356 ms | 0.6224 ms | 0.82 | 0.02 | - | - | - | 45056 B | + // | | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 118.06 ms | 15.667 ms | 0.8587 ms | 1.00 | 0.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 92.47 ms | 5.683 ms | 0.3115 ms | 0.78 | 0.01 | - | - | - | 44512 B | } - public class Resize_BicubicCompand : ResizeBenchmarkBase + + public class Resize_BicubicCompand_Rgba32 : ResizeBenchmarkBase { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) { ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true); } - // RESULTS (2018 October): + // RESULTS (2019 April): // - // BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134 + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742191 Hz, Resolution=364.6719 ns, Timer=TSC - // .NET Core SDK=2.1.403 - // [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - // Job-IGUFBA : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 - // Job-DZFERG : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - // - // Method | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Scaled | ScaledSD | Allocated | - // ----------------------------------------- |-------- |----------- |--------- |----------:|----------:|----------:|-------:|---------:|----------:| - // SystemDrawing | Clr | 3032 | 400 | 100.63 ms | 13.864 ms | 0.7833 ms | 1.00 | 0.00 | 0 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | 3032 | 400 | 156.83 ms | 28.631 ms | 1.6177 ms | 1.56 | 0.02 | 21856 B | - // 'ImageSharp, MaxDegreeOfParallelism = 4' | Clr | 3032 | 400 | 53.43 ms | 38.493 ms | 2.1749 ms | 0.53 | 0.02 | 28512 B | - // 'ImageSharp, MaxDegreeOfParallelism = 8' | Clr | 3032 | 400 | 38.47 ms | 11.969 ms | 0.6763 ms | 0.38 | 0.01 | 28000 B | - // | | | | | | | | | | - // SystemDrawing | Core | 3032 | 400 | 99.87 ms | 23.459 ms | 1.3255 ms | 1.00 | 0.00 | 0 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | 3032 | 400 | 108.19 ms | 38.562 ms | 2.1788 ms | 1.08 | 0.02 | 21368 B | - // 'ImageSharp, MaxDegreeOfParallelism = 4' | Core | 3032 | 400 | 36.21 ms | 53.802 ms | 3.0399 ms | 0.36 | 0.03 | 25300 B | - // 'ImageSharp, MaxDegreeOfParallelism = 8' | Core | 3032 | 400 | 26.52 ms | 2.173 ms | 0.1228 ms | 0.27 | 0.00 | 25589 B | + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // + // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |----------- |--------- |---------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032 | 400 | 120.7 ms | 68.985 ms | 3.7813 ms | 1.00 | 0.00 | - | - | - | 1638 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 132.2 ms | 15.976 ms | 0.8757 ms | 1.10 | 0.04 | - | - | - | 16384 B | + // | | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 118.3 ms | 6.899 ms | 0.3781 ms | 1.00 | 0.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 122.4 ms | 15.069 ms | 0.8260 ms | 1.03 | 0.01 | - | - | - | 15712 B | } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs index 91cacfe3f0..b2ee030483 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs @@ -53,7 +53,15 @@ public void SRgbCompanding_Expand_VectorSpan(int length) { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => SRgbCompanding.Expand(v)).ToArray(); + var expected = new Vector4[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + Vector4 s = source[i]; + ref Vector4 e = ref expected[i]; + SRgbCompanding.Expand(ref s); + e = s; + } SRgbCompanding.Expand(source); @@ -68,7 +76,15 @@ public void SRgbCompanding_Compress_VectorSpan(int length) { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => SRgbCompanding.Compress(v)).ToArray(); + var expected = new Vector4[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + Vector4 s = source[i]; + ref Vector4 e = ref expected[i]; + SRgbCompanding.Compress(ref s); + e = s; + } SRgbCompanding.Compress(source); diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgr24OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgr24OperationsTests.cs index 323d3914cf..afcec79385 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgr24OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgr24OperationsTests.cs @@ -15,6 +15,7 @@ public class Bgr24OperationsTests : PixelOperationsTests public Bgr24OperationsTests(ITestOutputHelper output) : base(output) { + this.HasAlpha = false; } [Fact] diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs new file mode 100644 index 0000000000..1c7d312d30 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class Rgb24OperationsTests : PixelOperationsTests + { + public Rgb24OperationsTests(ITestOutputHelper output) + : base(output) + { + this.HasAlpha = false; + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index d9845e4741..d9ae9131ff 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -6,7 +6,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - +using SixLabors.ImageSharp.ColorSpaces.Companding; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -33,6 +33,8 @@ public abstract class PixelOperationsTests : MeasureFixture null; #endif + protected bool HasAlpha { get; set; } = true; + protected PixelOperationsTests(ITestOutputHelper output) : base(output) { @@ -70,25 +72,33 @@ protected PixelOperationsTests(ITestOutputHelper output) internal static PixelOperations Operations => PixelOperations.Instance; - internal static TPixel[] CreateExpectedPixelData(Vector4[] source) + internal static TPixel[] CreateExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) { var expected = new TPixel[source.Length]; for (int i = 0; i < expected.Length; i++) { - expected[i].FromVector4(source[i]); + Vector4 v = source[i]; + vectorModifier?.Invoke(ref v); + + expected[i].FromVector4(v); } + return expected; } - internal static TPixel[] CreateScaledExpectedPixelData(Vector4[] source) + internal static TPixel[] CreateScaledExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) { var expected = new TPixel[source.Length]; for (int i = 0; i < expected.Length; i++) { - expected[i].FromScaledVector4(source[i]); + Vector4 v = source[i]; + vectorModifier?.Invoke(ref v); + + expected[i].FromScaledVector4(v); } + return expected; } @@ -102,7 +112,7 @@ public void FromVector4(int count) TestOperation( source, expected, - (s, d) => Operations.FromVector4(this.Configuration, s, d.GetSpan()) + (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan()) ); } @@ -116,7 +126,140 @@ public void FromScaledVector4(int count) TestOperation( source, expected, - (s, d) => Operations.FromScaledVector4(this.Configuration, s, d.GetSpan()) + (s, d) => + { + Span destPixels = d.GetSpan(); + Operations.FromVector4Destructive(this.Configuration, (Span)s, destPixels, PixelConversionModifiers.Scale); + }); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromCompandedScaledVector4(int count) + { + void sourceAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); + } + + void expectedAction(ref Vector4 v) + { + SRgbCompanding.Compress(ref v); + } + + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromPremultipliedVector4(int count) + { + void sourceAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utils.Premultiply(ref v); + } + } + + void expectedAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utils.UnPremultiply(ref v); + } + } + + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); + TPixel[] expected = CreateExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromPremultipliedScaledVector4(int count) + { + void sourceAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utils.Premultiply(ref v); + } + } + + void expectedAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utils.UnPremultiply(ref v); + } + } + + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromCompandedPremultipliedScaledVector4(int count) + { + void sourceAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); + + if (this.HasAlpha) + { + Vector4Utils.Premultiply(ref v); + } + } + + void expectedAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utils.UnPremultiply(ref v); + } + + SRgbCompanding.Compress(ref v); + } + + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) ); } @@ -144,7 +287,119 @@ public void ToScaledVector4(int count) TestOperation( source, expected, - (s, d) => Operations.ToScaledVector4(this.Configuration, s, d.GetSpan()) + (s, d) => + { + Span destVectors = d.GetSpan(); + Operations.ToVector4(this.Configuration, (ReadOnlySpan)s, destVectors, PixelConversionModifiers.Scale); + }); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToCompandedScaledVector4(int count) + { + void sourceAction(ref Vector4 v) + { + SRgbCompanding.Compress(ref v); + } + + void expectedAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); + } + + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToPremultipliedVector4(int count) + { + void sourceAction(ref Vector4 v) + { + Vector4Utils.UnPremultiply(ref v); + } + + void expectedAction(ref Vector4 v) + { + Vector4Utils.Premultiply(ref v); + } + + TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); + Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToPremultipliedScaledVector4(int count) + { + void sourceAction(ref Vector4 v) + { + Vector4Utils.UnPremultiply(ref v); + } + + void expectedAction(ref Vector4 v) + { + Vector4Utils.Premultiply(ref v); + } + + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToCompandedPremultipliedScaledVector4(int count) + { + void sourceAction(ref Vector4 v) + { + Vector4Utils.UnPremultiply(ref v); + SRgbCompanding.Compress(ref v); + } + + void expectedAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); + Vector4Utils.Premultiply(ref v); + } + + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) ); } @@ -477,25 +732,37 @@ public void ToRgba64Bytes(int count) ); } - internal static Vector4[] CreateExpectedVector4Data(TPixel[] source) + public delegate void RefAction(ref T1 arg1); + + internal static Vector4[] CreateExpectedVector4Data(TPixel[] source, RefAction vectorModifier = null) { var expected = new Vector4[source.Length]; for (int i = 0; i < expected.Length; i++) { - expected[i] = source[i].ToVector4(); + var v = source[i].ToVector4(); + + vectorModifier?.Invoke(ref v); + + expected[i] = v; } + return expected; } - internal static Vector4[] CreateExpectedScaledVector4Data(TPixel[] source) + internal static Vector4[] CreateExpectedScaledVector4Data(TPixel[] source, RefAction vectorModifier = null) { var expected = new Vector4[source.Length]; for (int i = 0; i < expected.Length; i++) { - expected[i] = source[i].ToScaledVector4(); + Vector4 v = source[i].ToScaledVector4(); + + vectorModifier?.Invoke(ref v); + + expected[i] = v; } + return expected; } @@ -513,19 +780,22 @@ internal static void TestOperation( } } - internal static Vector4[] CreateVector4TestData(int length) + internal static Vector4[] CreateVector4TestData(int length, RefAction vectorModifier = null) { var result = new Vector4[length]; var rnd = new Random(42); // Deterministic random values for (int i = 0; i < result.Length; i++) { - result[i] = GetVector(rnd); + Vector4 v = GetVector(rnd); + vectorModifier?.Invoke(ref v); + + result[i] = v; } return result; } - internal static TPixel[] CreatePixelTestData(int length) + internal static TPixel[] CreatePixelTestData(int length, RefAction vectorModifier = null) { var result = new TPixel[length]; @@ -534,13 +804,16 @@ internal static TPixel[] CreatePixelTestData(int length) for (int i = 0; i < result.Length; i++) { Vector4 v = GetVector(rnd); + + vectorModifier?.Invoke(ref v); + result[i].FromVector4(v); } return result; } - internal static TPixel[] CreateScaledPixelTestData(int length) + internal static TPixel[] CreateScaledPixelTestData(int length, RefAction vectorModifier = null) { var result = new TPixel[length]; @@ -549,6 +822,9 @@ internal static TPixel[] CreateScaledPixelTestData(int length) for (int i = 0; i < result.Length; i++) { Vector4 v = GetVector(rnd); + + vectorModifier?.Invoke(ref v); + result[i].FromScaledVector4(v); } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 29d39596b7..6ca3ed6f9f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -41,7 +41,7 @@ public static void MakeOpaque(this IImageProcessingContext ctx) { Span pixelSpan = frame.GetPixelSpan(); - PixelOperations.Instance.ToScaledVector4(configuration, pixelSpan, tempSpan); + PixelOperations.Instance.ToVector4(configuration, pixelSpan, tempSpan, PixelConversionModifiers.Scale); for (int i = 0; i < tempSpan.Length; i++) { @@ -49,7 +49,7 @@ public static void MakeOpaque(this IImageProcessingContext ctx) v.W = 1F; } - PixelOperations.Instance.FromScaledVector4(configuration, tempSpan, pixelSpan); + PixelOperations.Instance.FromVector4Destructive(configuration, tempSpan, pixelSpan, PixelConversionModifiers.Scale); } } }); diff --git a/tests/Images/External b/tests/Images/External index c4098e463a..9d71985545 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit c4098e463ab0e7128ae196b7f963369271df8fd3 +Subproject commit 9d71985545bb827270ca34af3f78e4c220560ef1