From b0b2d55cbb184ac96376e3cdae823547cd821013 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 11 Oct 2018 10:36:02 +0100 Subject: [PATCH 1/5] Move compaing classes and migrate vector code to srgbcompaning --- .../GammaCompanding.cs | 2 +- .../LCompanding.cs | 3 +- .../Rec2020Companding.cs | 2 +- .../Rec709Companding.cs | 2 +- .../ColorSpaces/Companding/SRgbCompanding.cs | 89 ++++++++++ .../WorkingSpaces/GammaWorkingSpace.cs | 1 + .../WorkingSpaces/LWorkingSpace.cs | 1 + .../WorkingSpaces/Rec2020WorkingSpace.cs | 1 + .../WorkingSpaces/Rec709WorkingSpace.cs | 1 + .../WorkingSpaces/SRgbCompanding.cs | 35 ---- .../WorkingSpaces/SRgbWorkingSpace.cs | 1 + .../ColorSpaces/RgbWorkingSpaces.cs | 1 + .../Common/Extensions/Vector4Extensions.cs | 156 ------------------ src/ImageSharp/Common/Helpers/Vector4Utils.cs | 86 ++++++++++ .../Convolution/Convolution2DProcessor.cs | 4 +- .../Convolution/Convolution2PassProcessor.cs | 7 +- .../Convolution/ConvolutionProcessor.cs | 4 +- .../Transforms/AffineTransformProcessor.cs | 7 +- .../ProjectiveTransformProcessor.cs | 12 +- .../Processors/Transforms/ResizeProcessor.cs | 9 +- .../CompandingTests.cs | 49 +++++- .../Helpers/Vector4ExtensionsTests.cs | 74 --------- .../Helpers/Vector4UtilsTests.cs | 44 +++++ 23 files changed, 287 insertions(+), 304 deletions(-) rename src/ImageSharp/ColorSpaces/{Conversion/Implementation/WorkingSpaces => Companding}/GammaCompanding.cs (95%) rename src/ImageSharp/ColorSpaces/{Conversion/Implementation/WorkingSpaces => Companding}/LCompanding.cs (93%) rename src/ImageSharp/ColorSpaces/{Conversion/Implementation/WorkingSpaces => Companding}/Rec2020Companding.cs (95%) rename src/ImageSharp/ColorSpaces/{Conversion/Implementation/WorkingSpaces => Companding}/Rec709Companding.cs (95%) create mode 100644 src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs delete mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbCompanding.cs delete mode 100644 src/ImageSharp/Common/Extensions/Vector4Extensions.cs create mode 100644 src/ImageSharp/Common/Helpers/Vector4Utils.cs rename tests/ImageSharp.Tests/Colorspaces/{Conversion => Companding}/CompandingTests.cs (59%) delete mode 100644 tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs create mode 100644 tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs similarity index 95% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaCompanding.cs rename to src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs index d9babc7ef..13cca1582 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Companding { /// /// Implements gamma companding diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs similarity index 93% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LCompanding.cs rename to src/ImageSharp/ColorSpaces/Companding/LCompanding.cs index ebe7ebe93..9e2cf8ad8 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs @@ -3,8 +3,9 @@ using System; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Companding { /// /// Implements L* companding diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020Companding.cs b/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs similarity index 95% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020Companding.cs rename to src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs index ba77e78f0..a3a912172 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020Companding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Companding { /// /// Implements Rec. 2020 companding function (for 12-bits). diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709Companding.cs b/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs similarity index 95% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709Companding.cs rename to src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs index e281339a6..e2e802d08 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709Companding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Companding { /// /// Implements the Rec. 709 companding function. diff --git a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs new file mode 100644 index 000000000..5ae462913 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Companding +{ + /// + /// Implements sRGB companding + /// + /// + /// For more info see: + /// + /// + /// + public static class SRgbCompanding + { + /// + /// Expands the companded vectors to their linear equivalents with respect to the energy. + /// + /// The span of vectors. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Expand(Span vectors) + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(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); + } + } + + /// + /// Compresses the uncompanded vectors to their nonlinear equivalents with respect to the energy. + /// + /// The span of vectors. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Compress(Span vectors) + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(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); + } + } + + /// + /// 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); + + /// + /// 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); + + /// + /// Expands a companded channel to its linear equivalent with respect to the energy. + /// + /// The channel value. + /// The representing the linear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Expand(float channel) => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); + + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. + /// + /// The channel value. + /// The representing the nonlinear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Compress(float channel) => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs index 6d8b25e9d..73aa60b6c 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs index cbc4be596..16617ea24 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs index 11f1f8401..9ba1ff881 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs index 090efcd79..88623e958 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbCompanding.cs deleted file mode 100644 index 61b3b1cf1..000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbCompanding.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation -{ - /// - /// Implements sRGB companding - /// - /// - /// For more info see: - /// - /// - /// - public static class SRgbCompanding - { - /// - /// Expands a companded channel to its linear equivalent with respect to the energy. - /// - /// The channel value - /// The representing the linear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Expand(float channel) => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); - - /// - /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. - /// - /// The channel value - /// The representing the nonlinear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Compress(float channel) => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs index 369f91c76..b44db0681 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { diff --git a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs index 11884ca81..ee3822c15 100644 --- a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs +++ b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.ColorSpaces.Companding; using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; // ReSharper disable InconsistentNaming diff --git a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs deleted file mode 100644 index 50afc6a4b..000000000 --- a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp -{ - /// - /// Extension methods for the struct. - /// - internal static class Vector4Extensions - { - /// - /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. - /// - /// The to premultiply - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 Premultiply(this Vector4 source) - { - float w = source.W; - Vector4 premultiplied = source * w; - premultiplied.W = w; - return premultiplied; - } - - /// - /// Reverses the result of premultiplying a vector via . - /// - /// The to premultiply - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 UnPremultiply(this Vector4 source) - { - float w = source.W; - Vector4 unpremultiplied = source / w; - unpremultiplied.W = w; - return unpremultiplied; - } - - /// - /// Bulk variant of - /// - /// The span of vectors - public static void Premultiply(Span vectors) - { - // TODO: This method can be AVX2 optimized using Vector - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); - - for (int i = 0; i < vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - var s = new Vector4(v.W) - { - W = 1 - }; - v *= s; - } - } - - /// - /// Bulk variant of - /// - /// The span of vectors - public static void UnPremultiply(Span vectors) - { - // TODO: This method can be AVX2 optimized using Vector - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); - - for (int i = 0; i < vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - var s = new Vector4(1 / v.W) - { - W = 1 - }; - v *= s; - } - } - - /// - /// Compresses a linear color signal to its sRGB equivalent. - /// - /// - /// - /// The whose signal to compress. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 Compress(this Vector4 linear) - { - // TODO: Is there a faster way to do this? - return new Vector4( - SRgbCompanding.Compress(linear.X), - SRgbCompanding.Compress(linear.Y), - SRgbCompanding.Compress(linear.Z), - linear.W); - } - - /// - /// Expands an sRGB color signal to its linear equivalent. - /// - /// - /// - /// The whose signal to expand. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 Expand(this Vector4 gamma) - { - // TODO: Is there a faster way to do this? - return new Vector4( - SRgbCompanding.Expand(gamma.X), - SRgbCompanding.Expand(gamma.Y), - SRgbCompanding.Expand(gamma.Z), - gamma.W); - } - - /// - /// Bulk variant of - /// - /// The span of vectors - public static void Compress(Span vectors) - { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); - - for (int i = 0; i < vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - v.X = SRgbCompanding.Compress(v.X); - v.Y = SRgbCompanding.Compress(v.Y); - v.Z = SRgbCompanding.Compress(v.Z); - } - } - - /// - /// Bulk variant of - /// - /// The span of vectors - public static void Expand(Span vectors) - { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); - - for (int i = 0; i < vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - v.X = SRgbCompanding.Expand(v.X); - v.Y = SRgbCompanding.Expand(v.Y); - v.Z = SRgbCompanding.Expand(v.Z); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/Vector4Utils.cs b/src/ImageSharp/Common/Helpers/Vector4Utils.cs new file mode 100644 index 000000000..4545d797c --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Vector4Utils.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Utility methods for the struct. + /// + internal static class Vector4Utils + { + /// + /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. + /// + /// The to premultiply + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector4 Premultiply(Vector4 source) + { + float w = source.W; + Vector4 premultiplied = source * w; + premultiplied.W = w; + return premultiplied; + } + + /// + /// Reverses the result of premultiplying a vector via . + /// + /// The to premultiply + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector4 UnPremultiply(Vector4 source) + { + float w = source.W; + Vector4 unpremultiplied = source / w; + unpremultiplied.W = w; + return unpremultiplied; + } + + /// + /// Bulk variant of + /// + /// The span of vectors + [MethodImpl(InliningOptions.ShortMethod)] + public static void Premultiply(Span vectors) + { + // TODO: This method can be AVX2 optimized using Vector + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + var s = new Vector4(v.W) + { + W = 1 + }; + v *= s; + } + } + + /// + /// Bulk variant of + /// + /// The span of vectors + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnPremultiply(Span vectors) + { + // TODO: This method can be AVX2 optimized using Vector + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + var s = new Vector4(1 / v.W) + { + W = 1 + }; + v *= s; + } + } + } +} \ 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 d2282ec0e..c358b316c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply(); + Vector4 currentColor = Vector4Utils.Premultiply(sourceOffsetRow[offsetX].ToVector4()); if (fy < kernelXHeight) { @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4( - new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply()); + Vector4Utils.UnPremultiply(new Vector4(red, green, blue, sourceRow[x].ToVector4().W))); } } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index e45bb3ab2..3135ff614 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -3,14 +3,11 @@ using System; using System.Numerics; -using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -114,13 +111,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = row[offsetX].ToVector4().Premultiply(); + Vector4 currentColor = Vector4Utils.Premultiply(row[offsetX].ToVector4()); destination += kernel[fy, fx] * currentColor; } } ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4(destination.UnPremultiply()); + pixel.PackFromVector4(Vector4Utils.UnPremultiply(destination)); } } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index bac9a86cf..1d1755ccd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply(); + Vector4 currentColor = Vector4Utils.Premultiply(sourceOffsetRow[offsetX].ToVector4()); currentColor *= this.KernelXY[fy, fx]; red += currentColor.X; @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4( - new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply()); + Vector4Utils.UnPremultiply(new Vector4(red, green, blue, sourceRow[x].ToVector4().W))); } } }); diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 3469161e6..225c687d8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; @@ -207,18 +206,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int xx = 0, i = minX; i <= maxX; i++, xx++) { float xWeight = Unsafe.Add(ref xSpanRef, xx); - var vector = source[i, j].ToVector4(); // Values are first premultiplied to prevent darkening of edge pixels - Vector4 multiplied = vector.Premultiply(); - sum += multiplied * xWeight * yWeight; + sum += Vector4Utils.Premultiply(source[i, j].ToVector4()) * xWeight * yWeight; } } ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); // Reverse the premultiplication - dest.PackFromVector4(sum.UnPremultiply()); + dest.PackFromVector4(Vector4Utils.UnPremultiply(sum)); } } }); diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index b03dec032..f860264af 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; @@ -216,18 +215,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int xx = 0, i = minX; i <= maxX; i++, xx++) { float xWeight = Unsafe.Add(ref xSpanRef, xx); - var vector = source[i, j].ToVector4(); // Values are first premultiplied to prevent darkening of edge pixels - Vector4 multiplied = vector.Premultiply(); - sum += multiplied * xWeight * yWeight; + sum += Vector4Utils.Premultiply(source[i, j].ToVector4()) * xWeight * yWeight; } } ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); // Reverse the premultiplication - dest.PackFromVector4(sum.UnPremultiply()); + dest.PackFromVector4(Vector4Utils.UnPremultiply(sum)); } } }); @@ -242,9 +239,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The . /// - protected virtual Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) - { - return this.TransformMatrix; - } + protected virtual Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) => this.TransformMatrix; } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index d353c1fd2..812c0578b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; 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; @@ -257,13 +258,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Span tempRowSpan = tempRowBuffer.Span; PixelOperations.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length); - Vector4Extensions.Premultiply(tempRowSpan); + Vector4Utils.Premultiply(tempRowSpan); ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; if (this.Compand) { - Vector4Extensions.Expand(tempRowSpan); + SRgbCompanding.Expand(tempRowSpan); } for (int x = minX; x < maxX; x++) @@ -300,11 +301,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Unsafe.Add(ref tempRowBase, x) = window.Convolve(firstPassColumn, sourceY); } - Vector4Extensions.UnPremultiply(tempRowSpan); + Vector4Utils.UnPremultiply(tempRowSpan); if (this.Compand) { - Vector4Extensions.Compress(tempRowSpan); + SRgbCompanding.Compress(tempRowSpan); } Span targetRowSpan = destination.GetPixelRowSpan(y); diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CompandingTests.cs b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs similarity index 59% rename from tests/ImageSharp.Tests/Colorspaces/Conversion/CompandingTests.cs rename to tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs index 125f8f994..91cacfe3f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CompandingTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs @@ -1,10 +1,13 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; +using System; +using System.Linq; +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces.Companding; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding { /// /// Tests various companding algorithms. Numbers are hand calculated from formulas online. @@ -16,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(.00001F); [Fact] - public void Rec2020CompandingIsCorrect() + public void Rec2020Companding_IsCorrect() { const float input = .667F; float e = Rec2020Companding.Expand(input); @@ -25,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion } [Fact] - public void Rec709CompandingIsCorrect() + public void Rec709Companding_IsCorrect() { const float input = .667F; float e = Rec709Companding.Expand(input); @@ -34,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion } [Fact] - public void SRgbCompandingIsCorrect() + public void SRgbCompanding_IsCorrect() { const float input = .667F; float e = SRgbCompanding.Expand(input); @@ -42,8 +45,38 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion CompandingIsCorrectImpl(e, c, .40242353F, .667F); } + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + 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(); + + SRgbCompanding.Expand(source); + + Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + 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(); + + SRgbCompanding.Compress(source); + + Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + } + [Fact] - public void GammaCompandingIsCorrect() + public void GammaCompanding_IsCorrect() { const float gamma = 2.2F; const float input = .667F; @@ -53,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion } [Fact] - public void LCompandingIsCorrect() + public void LCompanding_IsCorrect() { const float input = .667F; float e = LCompanding.Expand(input); @@ -67,4 +100,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Assert.Equal(compressed, c, FloatComparer); } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs deleted file mode 100644 index 2d2a2795d..000000000 --- a/tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Linq; -using System.Numerics; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Helpers -{ - public class Vector4ExtensionsTests - { - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - public void Premultiply_VectorSpan(int length) - { - var rnd = new Random(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => v.Premultiply()).ToArray(); - - Vector4Extensions.Premultiply(source); - - Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - public void UnPremultiply_VectorSpan(int length) - { - var rnd = new Random(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => v.UnPremultiply()).ToArray(); - - Vector4Extensions.UnPremultiply(source); - - Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - public void Expand_VectorSpan(int length) - { - var rnd = new Random(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => v.Expand()).ToArray(); - - Vector4Extensions.Expand(source); - - Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - public void Compress_VectorSpan(int length) - { - var rnd = new Random(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => v.Compress()).ToArray(); - - Vector4Extensions.Compress(source); - - Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); - } - } -} diff --git a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs new file mode 100644 index 000000000..d2e1ddf4e --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + public class Vector4UtilsTests + { + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + public void Premultiply_VectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + Vector4[] expected = source.Select(v => Vector4Utils.Premultiply(v)).ToArray(); + + Vector4Utils.Premultiply(source); + + Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + public void UnPremultiply_VectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + Vector4[] expected = source.Select(v => Vector4Utils.UnPremultiply(v)).ToArray(); + + Vector4Utils.UnPremultiply(source); + + Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + } + } +} From 400a3cbe89abd1f9726022243c405f1fb2d11751 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 12 Oct 2018 12:36:55 +0100 Subject: [PATCH 2/5] Refactor Vector4Utils and ConvolutionProcessors utilizing them. --- .../Common/Helpers/DenseMatrixUtils.cs | 131 ++++++++++++++++++ src/ImageSharp/Common/Helpers/Vector4Utils.cs | 34 ++--- src/ImageSharp/Primitives/DenseMatrix{T}.cs | 10 +- .../Convolution/Convolution2DProcessor.cs | 90 +++--------- .../Convolution/Convolution2PassProcessor.cs | 56 +++----- .../Convolution/ConvolutionProcessor.cs | 75 +++------- .../Transforms/AffineTransformProcessor.cs | 7 +- .../ProjectiveTransformProcessor.cs | 7 +- .../General/Vectorization/Premultiply.cs | 59 ++++++++ .../Helpers/Vector4UtilsTests.cs | 4 +- 10 files changed, 279 insertions(+), 194 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs new file mode 100644 index 000000000..0755ae8a1 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -0,0 +1,131 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for . + /// + internal static class DenseMatrixUtils + { + /// + /// Computes the sum of vectors in weighted by the kernel weight values. + /// + /// The pixel format. + /// The dense matrix. + /// The source frame. + /// The target row. + /// The current row. + /// The current column. + /// The maximum working area row. + /// The maximum working area column. + /// The column offset to apply to source sampling. + public static void Convolve( + in DenseMatrix matrix, + Buffer2D sourcePixels, + Span targetRow, + int row, + int column, + int maxRow, + int maxColumn, + int offsetColumn) + where TPixel : struct, IPixel + { + Vector4 vector = default; + int matrixHeight = matrix.Rows; + int matrixWidth = matrix.Columns; + int radiusY = matrixHeight >> 1; + int radiusX = matrixWidth >> 1; + + for (int y = 0; y < matrixHeight; y++) + { + int offsetY = (row + y - radiusY).Clamp(0, maxRow); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + + for (int x = 0; x < matrixWidth; x++) + { + int offsetX = (column + offsetColumn + x - radiusX).Clamp(offsetColumn, maxColumn); + var currentColor = sourceRowSpan[offsetX].ToVector4(); + Vector4Utils.Premultiply(ref currentColor); + currentColor *= matrix[y, x]; + vector += currentColor; + } + } + + ref Vector4 target = ref targetRow[column]; + vector.W = target.W; + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + + /// + /// Computes the sum of vectors in weighted by the two kernel weight values. + /// + /// The pixel format. + /// The vertical dense matrix. + /// The horizontal dense matrix. + /// The source frame. + /// The target row. + /// The current row. + /// The current column. + /// The maximum working area row. + /// The maximum working area column. + /// The column offset to apply to source sampling. + public static void Convolve2D( + in DenseMatrix matrixY, + in DenseMatrix matrixX, + Buffer2D sourcePixels, + Span targetRow, + int row, + int column, + int maxRow, + int maxColumn, + int offsetColumn) + where TPixel : struct, IPixel + { + Vector4 vectorY = default; + Vector4 vectorX = default; + int matrixYHeight = matrixY.Rows; + int matrixYWidth = matrixY.Columns; + int matrixXHeight = matrixX.Rows; + int matrixXWidth = matrixX.Columns; + int radiusY = matrixYHeight >> 1; + int radiusX = matrixXWidth >> 1; + + for (int y = 0; y < matrixYHeight; y++) + { + int offsetY = (row + y - radiusY).Clamp(0, maxRow); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + + for (int x = 0; x < matrixXWidth; x++) + { + int offsetX = (column + offsetColumn + x - radiusX).Clamp(offsetColumn, maxColumn); + var currentColor = sourceRowSpan[offsetX].ToVector4(); + Vector4Utils.Premultiply(ref currentColor); + + if (y < matrixXHeight) + { + vectorX += matrixX[y, x] * currentColor; + } + + if (x < matrixYWidth) + { + vectorY += matrixY[y, x] * currentColor; + } + } + } + + var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + ref Vector4 target = ref targetRow[column]; + vector.W = target.W; + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/Vector4Utils.cs b/src/ImageSharp/Common/Helpers/Vector4Utils.cs index 4545d797c..75bb00b6a 100644 --- a/src/ImageSharp/Common/Helpers/Vector4Utils.cs +++ b/src/ImageSharp/Common/Helpers/Vector4Utils.cs @@ -17,32 +17,28 @@ namespace SixLabors.ImageSharp /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. /// /// The to premultiply - /// The [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 Premultiply(Vector4 source) + public static void Premultiply(ref Vector4 source) { float w = source.W; - Vector4 premultiplied = source * w; - premultiplied.W = w; - return premultiplied; + source *= w; + source.W = w; } /// - /// Reverses the result of premultiplying a vector via . + /// Reverses the result of premultiplying a vector via . /// /// The to premultiply - /// The [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 UnPremultiply(Vector4 source) + public static void UnPremultiply(ref Vector4 source) { float w = source.W; - Vector4 unpremultiplied = source / w; - unpremultiplied.W = w; - return unpremultiplied; + source /= w; + source.W = w; } /// - /// Bulk variant of + /// Bulk variant of /// /// The span of vectors [MethodImpl(InliningOptions.ShortMethod)] @@ -54,16 +50,12 @@ namespace SixLabors.ImageSharp for (int i = 0; i < vectors.Length; i++) { ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - var s = new Vector4(v.W) - { - W = 1 - }; - v *= s; + Premultiply(ref v); } } /// - /// Bulk variant of + /// Bulk variant of /// /// The span of vectors [MethodImpl(InliningOptions.ShortMethod)] @@ -75,11 +67,7 @@ namespace SixLabors.ImageSharp for (int i = 0; i < vectors.Length; i++) { ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - var s = new Vector4(1 / v.W) - { - W = 1 - }; - v *= s; + UnPremultiply(ref v); } } } diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index ef1abc897..2a4b9dc07 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -182,13 +182,13 @@ namespace SixLabors.ImageSharp.Primitives } /// - public bool Equals(DenseMatrix other) => - this.Columns == other.Columns && - this.Rows == other.Rows && - this.Span.SequenceEqual(other.Span); + public override bool Equals(object obj) => obj is DenseMatrix other && this.Equals(other); /// - public override bool Equals(object obj) => obj is DenseMatrix other && this.Equals(other); + public bool Equals(DenseMatrix other) => + this.Columns == other.Columns + && this.Rows == other.Rows + && this.Span.SequenceEqual(other.Span); /// public override int GetHashCode() => this.Data.GetHashCode(); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index c358b316c..1ecf9b759 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -3,8 +3,6 @@ using System; using System.Numerics; - -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; @@ -47,89 +45,43 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Rectangle sourceRectangle, Configuration configuration) { - int kernelYHeight = this.KernelY.Rows; - int kernelYWidth = this.KernelY.Columns; - int kernelXHeight = this.KernelX.Rows; - int kernelXWidth = this.KernelX.Columns; - int radiusY = kernelYHeight >> 1; - int radiusX = kernelXWidth >> 1; - - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; + DenseMatrix matrixY = this.KernelY; + DenseMatrix matrixX = this.KernelX; + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; int maxY = endY - 1; int maxX = endX - 1; - using (Buffer2D targetPixels = - configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) + using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) { source.CopyTo(targetPixels); var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; - ParallelHelper.IterateRows( + ParallelHelper.IterateRowsWithTempBuffer( workingRectangle, configuration, - rows => + (rows, vectorBuffer) => { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = targetPixels.GetRowSpan(y); + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(targetRowSpan, vectorSpan, length); - for (int x = startX; x < endX; x++) + for (int x = 0; x < width; x++) { - float rX = 0; - float gX = 0; - float bX = 0; - float rY = 0; - float gY = 0; - float bY = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelYHeight; fy++) - { - int fyr = fy - radiusY; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - Span sourceOffsetRow = source.GetPixelRowSpan(offsetY); - - for (int fx = 0; fx < kernelXWidth; fx++) - { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = Vector4Utils.Premultiply(sourceOffsetRow[offsetX].ToVector4()); - - if (fy < kernelXHeight) - { - Vector4 kx = this.KernelX[fy, fx] * currentColor; - rX += kx.X; - gX += kx.Y; - bX += kx.Z; - } - - if (fx < kernelYWidth) - { - Vector4 ky = this.KernelY[fy, fx] * currentColor; - rY += ky.X; - gY += ky.Y; - bY += ky.Z; - } - } - } - - float red = MathF.Sqrt((rX * rX) + (rY * rY)); - float green = MathF.Sqrt((gX * gX) + (gY * gY)); - float blue = MathF.Sqrt((bX * bX) + (bY * bY)); - - ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4( - Vector4Utils.UnPremultiply(new Vector4(red, green, blue, sourceRow[x].ToVector4().W))); + DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); } + + PixelOperations.Instance.PackFromVector4(vectorSpan, targetRowSpan, length); } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index 3135ff614..1f47649e6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -45,8 +45,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { using (Buffer2D firstPassPixels = configuration.MemoryAllocator.Allocate2D(source.Size())) { - this.ApplyConvolution(firstPassPixels, source.PixelBuffer, source.Bounds(), this.KernelX, configuration); - this.ApplyConvolution(source.PixelBuffer, firstPassPixels, sourceRectangle, this.KernelY, configuration); + source.CopyTo(firstPassPixels); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, configuration); + this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, configuration); } } @@ -65,14 +68,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Buffer2D targetPixels, Buffer2D sourcePixels, Rectangle sourceRectangle, - DenseMatrix kernel, // TODO: Can't use 'in' as pass by ref to lambda expression. + in DenseMatrix kernel, Configuration configuration) { - int kernelHeight = kernel.Rows; - int kernelWidth = kernel.Columns; - int radiusY = kernelHeight >> 1; - int radiusX = kernelWidth >> 1; - + DenseMatrix matrix = kernel; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; @@ -81,44 +80,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int maxX = endX - 1; var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; - ParallelHelper.IterateRows( + ParallelHelper.IterateRowsWithTempBuffer( workingRectangle, configuration, - rows => + (rows, vectorBuffer) => { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + for (int y = rows.Min; y < rows.Max; y++) { - Span targetRow = targetPixels.GetRowSpan(y); + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(targetRowSpan, vectorSpan, length); - for (int x = startX; x < endX; x++) + for (int x = 0; x < width; x++) { - Vector4 destination = default; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelHeight; fy++) - { - int fyr = fy - radiusY; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - Span row = sourcePixels.GetRowSpan(offsetY); - - for (int fx = 0; fx < kernelWidth; fx++) - { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = Vector4Utils.Premultiply(row[offsetX].ToVector4()); - destination += kernel[fy, fx] * currentColor; - } - } - - ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4(Vector4Utils.UnPremultiply(destination)); + DenseMatrixUtils.Convolve(in matrix, sourcePixels, vectorSpan, y, x, maxY, maxX, startX); } + + PixelOperations.Instance.PackFromVector4(vectorSpan, targetRowSpan, length); } }); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 1d1755ccd..d2f3f8fc5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -3,14 +3,10 @@ using System; using System.Numerics; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -26,10 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// Initializes a new instance of the class. /// /// The 2d gradient operator. - public ConvolutionProcessor(DenseMatrix kernelXY) - { - this.KernelXY = kernelXY; - } + public ConvolutionProcessor(DenseMatrix kernelXY) => this.KernelXY = kernelXY; /// /// Gets the 2d gradient operator. @@ -39,13 +32,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - int kernelLength = this.KernelXY.Rows; - int radius = kernelLength >> 1; - - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; + DenseMatrix matrix = this.KernelXY; + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; int maxY = endY - 1; int maxX = endX - 1; @@ -53,53 +45,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { source.CopyTo(targetPixels); - var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; - ParallelHelper.IterateRows( - workingRect, + ParallelHelper.IterateRowsWithTempBuffer( + workingRectangle, configuration, - rows => + (rows, vectorBuffer) => { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = targetPixels.GetRowSpan(y); + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(targetRowSpan, vectorSpan, length); - for (int x = startX; x < endX; x++) + for (int x = 0; x < width; x++) { - float red = 0; - float green = 0; - float blue = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelLength; fy++) - { - int fyr = fy - radius; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - Span sourceOffsetRow = source.GetPixelRowSpan(offsetY); - - for (int fx = 0; fx < kernelLength; fx++) - { - int fxr = fx - radius; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = Vector4Utils.Premultiply(sourceOffsetRow[offsetX].ToVector4()); - currentColor *= this.KernelXY[fy, fx]; - - red += currentColor.X; - green += currentColor.Y; - blue += currentColor.Z; - } - } - - ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4( - Vector4Utils.UnPremultiply(new Vector4(red, green, blue, sourceRow[x].ToVector4().W))); + DenseMatrixUtils.Convolve(in matrix, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); } + + PixelOperations.Instance.PackFromVector4(vectorSpan, targetRowSpan, length); } }); diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 225c687d8..790eb8048 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -208,14 +208,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float xWeight = Unsafe.Add(ref xSpanRef, xx); // Values are first premultiplied to prevent darkening of edge pixels - sum += Vector4Utils.Premultiply(source[i, j].ToVector4()) * xWeight * yWeight; + var current = source[i, j].ToVector4(); + Vector4Utils.Premultiply(ref current); + sum += current * xWeight * yWeight; } } ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); // Reverse the premultiplication - dest.PackFromVector4(Vector4Utils.UnPremultiply(sum)); + Vector4Utils.UnPremultiply(ref sum); + dest.PackFromVector4(sum); } } }); diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index f860264af..bad8eab3a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -217,14 +217,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float xWeight = Unsafe.Add(ref xSpanRef, xx); // Values are first premultiplied to prevent darkening of edge pixels - sum += Vector4Utils.Premultiply(source[i, j].ToVector4()) * xWeight * yWeight; + var current = source[i, j].ToVector4(); + Vector4Utils.Premultiply(ref current); + sum += current * xWeight * yWeight; } } ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); // Reverse the premultiplication - dest.PackFromVector4(Vector4Utils.UnPremultiply(sum)); + Vector4Utils.UnPremultiply(ref sum); + dest.PackFromVector4(sum); } } }); diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs new file mode 100644 index 000000000..23f13c89b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs @@ -0,0 +1,59 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ + public class Premultiply + { + [Benchmark(Baseline = true)] + public Vector4 PremultiplyByVal() + { + var input = new Vector4(.5F); + return Vector4Utils.Premultiply(input); + } + + [Benchmark] + public Vector4 PremultiplyByRef() + { + var input = new Vector4(.5F); + Vector4Utils.PremultiplyRef(ref input); + return input; + } + + [Benchmark] + public Vector4 PremultiplyRefWithPropertyAssign() + { + var input = new Vector4(.5F); + Vector4Utils.PremultiplyRefWithPropertyAssign(ref input); + return input; + } + } + + internal static class Vector4Utils + { + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector4 Premultiply(Vector4 source) + { + float w = source.W; + Vector4 premultiplied = source * w; + premultiplied.W = w; + return premultiplied; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PremultiplyRef(ref Vector4 source) + { + float w = source.W; + source *= w; + source.W = w; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PremultiplyRefWithPropertyAssign(ref Vector4 source) + { + float w = source.W; + source *= new Vector4(w) { W = 1 }; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs index d2e1ddf4e..9416be740 100644 --- a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => Vector4Utils.Premultiply(v)).ToArray(); + Vector4[] expected = source.Select(v => { Vector4Utils.Premultiply(ref v); return v; }).ToArray(); Vector4Utils.Premultiply(source); @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => Vector4Utils.UnPremultiply(v)).ToArray(); + Vector4[] expected = source.Select(v => { Vector4Utils.UnPremultiply(ref v); return v; }).ToArray(); Vector4Utils.UnPremultiply(source); From 5db580ffcc1b157578588a9441291541515bea84 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 12 Oct 2018 13:19:51 +0100 Subject: [PATCH 3/5] Update tests/Images/External --- tests/Images/External | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Images/External b/tests/Images/External index 03c7fa758..ee90e5f32 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 03c7fa7582dea75cea0d49514ccb7e1b6dc9e780 +Subproject commit ee90e5f32218027744b5d40058b587cc1047b76f From 9fd210926853bcbf65c0542726b8e6ca177ff70a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 12 Oct 2018 13:58:51 +0100 Subject: [PATCH 4/5] Update test to match new reference naming. --- .../ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 3522ade7c..d65796d37 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -319,13 +319,14 @@ namespace SixLabors.ImageSharp.Tests.Drawing var coloringVariant = new StringBuilder(); ColorStop[] colorStops = new ColorStop[stopPositions.Length]; + Rgba32 rgba = default; for (int i = 0; i < stopPositions.Length; i++) { TPixel color = colors[stopColorCodes[i % colors.Length]]; float position = stopPositions[i]; - + color.ToRgba32(ref rgba); colorStops[i] = new ColorStop(position, color); - coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", color, position); + coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position); } FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; From ba93355937eaab8d3cc2cda97d59776161770a67 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 12 Oct 2018 22:21:23 +0100 Subject: [PATCH 5/5] Remove conditionals from loop and enforce equal matrice dimensions. --- .../Common/Helpers/DenseMatrixUtils.cs | 35 ++++++++----------- src/ImageSharp/Primitives/DenseMatrix{T}.cs | 8 +++++ .../Convolution/Convolution2DProcessor.cs | 1 + .../Convolution/EdgeDetector2DProcessor.cs | 6 ++-- .../Primitives/DenseMatrixTests.cs | 2 ++ 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index 0755ae8a1..2e700c9d6 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -42,6 +42,7 @@ namespace SixLabors.ImageSharp int matrixWidth = matrix.Columns; int radiusY = matrixHeight >> 1; int radiusX = matrixWidth >> 1; + int sourceOffsetColumnBase = column + offsetColumn; for (int y = 0; y < matrixHeight; y++) { @@ -50,11 +51,11 @@ namespace SixLabors.ImageSharp for (int x = 0; x < matrixWidth; x++) { - int offsetX = (column + offsetColumn + x - radiusX).Clamp(offsetColumn, maxColumn); + int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn); var currentColor = sourceRowSpan[offsetX].ToVector4(); Vector4Utils.Premultiply(ref currentColor); - currentColor *= matrix[y, x]; - vector += currentColor; + + vector += matrix[y, x] * currentColor; } } @@ -91,33 +92,25 @@ namespace SixLabors.ImageSharp { Vector4 vectorY = default; Vector4 vectorX = default; - int matrixYHeight = matrixY.Rows; - int matrixYWidth = matrixY.Columns; - int matrixXHeight = matrixX.Rows; - int matrixXWidth = matrixX.Columns; - int radiusY = matrixYHeight >> 1; - int radiusX = matrixXWidth >> 1; + int matrixHeight = matrixY.Rows; + int matrixWidth = matrixY.Columns; + int radiusY = matrixHeight >> 1; + int radiusX = matrixWidth >> 1; + int sourceOffsetColumnBase = column + offsetColumn; - for (int y = 0; y < matrixYHeight; y++) + for (int y = 0; y < matrixHeight; y++) { int offsetY = (row + y - radiusY).Clamp(0, maxRow); Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); - for (int x = 0; x < matrixXWidth; x++) + for (int x = 0; x < matrixWidth; x++) { - int offsetX = (column + offsetColumn + x - radiusX).Clamp(offsetColumn, maxColumn); + int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn); var currentColor = sourceRowSpan[offsetX].ToVector4(); Vector4Utils.Premultiply(ref currentColor); - if (y < matrixXHeight) - { - vectorX += matrixX[y, x] * currentColor; - } - - if (x < matrixYWidth) - { - vectorY += matrixY[y, x] * currentColor; - } + vectorX += matrixX[y, x] * currentColor; + vectorY += matrixY[y, x] * currentColor; } } diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index 2a4b9dc07..7cfa98ec1 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using SixLabors.Primitives; namespace SixLabors.ImageSharp.Primitives { @@ -31,6 +32,11 @@ namespace SixLabors.ImageSharp.Primitives /// public readonly int Rows; + /// + /// Gets the size of the dense matrix. + /// + public readonly Size Size; + /// /// Gets the number of items in the array. /// @@ -57,6 +63,7 @@ namespace SixLabors.ImageSharp.Primitives this.Rows = rows; this.Columns = columns; + this.Size = new Size(columns, rows); this.Count = columns * rows; this.Data = new T[this.Columns * this.Rows]; } @@ -76,6 +83,7 @@ namespace SixLabors.ImageSharp.Primitives this.Rows = rows; this.Columns = columns; + this.Size = new Size(columns, rows); this.Count = this.Columns * this.Rows; this.Data = new T[this.Columns * this.Rows]; diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index 1ecf9b759..0669a1247 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -25,6 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// The vertical gradient operator. public Convolution2DProcessor(DenseMatrix kernelX, DenseMatrix kernelY) { + Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); this.KernelX = kernelX; this.KernelY = kernelY; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs index dd43d3e15..892771649 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs @@ -23,6 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// Whether to convert the image to grayscale before performing edge detection. protected EdgeDetector2DProcessor(DenseMatrix kernelX, DenseMatrix kernelY, bool grayscale) { + Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); this.KernelX = kernelX; this.KernelY = kernelY; this.Grayscale = grayscale; @@ -42,10 +43,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution public bool Grayscale { get; set; } /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new Convolution2DProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); - } + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => new Convolution2DProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); /// protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) diff --git a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs index 7d161d35f..fa4862293 100644 --- a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs @@ -3,6 +3,7 @@ using System; using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Primitives @@ -59,6 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives Assert.True(dense.Rows == FloydSteinbergMatrix.GetLength(0)); Assert.Equal(3, dense.Columns); Assert.Equal(2, dense.Rows); + Assert.Equal(new Size(3, 2), dense.Size); } [Fact]