From 75ec80a803e4809ebf93bcccfdce6369b962b21c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 7 Oct 2018 02:52:11 +0200 Subject: [PATCH] better separation + optimized row processing --- .../Common/Extensions/Vector4Extensions.cs | 70 +++++++++++++++++ src/ImageSharp/Common/Helpers/ImageMaths.cs | 39 ---------- .../Processors/Transforms/ResizeProcessor.cs | 14 +++- .../Helpers/ImageMathsTests.cs | 33 -------- .../Helpers/Vector4ExtensionsTests.cs | 76 +++++++++++++++++++ .../Processors/Transforms/ResizeTests.cs | 12 +++ 6 files changed, 168 insertions(+), 76 deletions(-) create mode 100644 tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs diff --git a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs index 7fb5fd8ee3..8dc9c96a06 100644 --- a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs +++ b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs @@ -43,6 +43,42 @@ namespace SixLabors.ImageSharp 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); + s.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); + s.W = 1; + v *= s; + } + } + /// /// Compresses a linear color signal to its sRGB equivalent. /// @@ -71,6 +107,40 @@ namespace SixLabors.ImageSharp return new Vector4(Expand(gamma.X), Expand(gamma.Y), 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 = Compress(v.X); + v.Y = Compress(v.Y); + v.Z = 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 = Expand(v.X); + v.Y = Expand(v.Y); + v.Z = Expand(v.Z); + } + } + /// /// Gets the compressed sRGB value from an linear signal. /// diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index 6accad43fc..8cd34f5402 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -278,44 +278,5 @@ namespace SixLabors.ImageSharp return GetBoundingRectangle(topLeft, bottomRight); } - - /// - /// Pre-multiply all vectors. - /// "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. - /// - /// - /// 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); - s.W = 1; - v *= s; - } - } - - /// - /// Revers - /// - /// - /// 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); - s.W = 1; - v *= s; - } - } } } \ 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 3c13d781e0..9481be48b9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -229,11 +229,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return; } + int sourceHeight = source.Height; + // 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 // are the upper and lower bounds of the source rectangle. - using (Buffer2D firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D(source.Height, width)) + using (Buffer2D firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D(sourceHeight, width)) { firstPassPixelsTransposed.MemorySource.Clear(); @@ -250,14 +252,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Span tempRowSpan = tempRowBuffer.Span; PixelOperations.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length); - ImageMaths.Premultiply(tempRowSpan); + Vector4Extensions.Premultiply(tempRowSpan); + + ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; if (this.Compand) { for (int x = minX; x < maxX; x++) { ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX]; - firstPassPixelsTransposed[y, x] = window.ConvolveExpand(tempRowSpan, sourceX).UnPremultiply(); + + Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = + window.ConvolveExpand(tempRowSpan, sourceX).UnPremultiply(); } } else @@ -265,7 +271,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int x = minX; x < maxX; x++) { ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX]; - firstPassPixelsTransposed[y, x] = + Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = window.Convolve(tempRowSpan, sourceX); } } diff --git a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs index 3f41a9955e..61f06da9f0 100644 --- a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs @@ -1,10 +1,6 @@ // 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 @@ -39,35 +35,6 @@ namespace SixLabors.ImageSharp.Tests.Helpers Assert.Equal(expected, actual); } - [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(); - - ImageMaths.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(); - - ImageMaths.UnPremultiply(source); - - Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); - } // TODO: We need to test all ImageMaths methods! } diff --git a/tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs new file mode 100644 index 0000000000..68f71d88f8 --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs @@ -0,0 +1,76 @@ +// 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/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 1e0f86dcb8..c74b40622a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -144,6 +144,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } + [Theory] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType)] + public void Resize_Compand_DoesNotBleedAlphaPixels(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, true)); + image.DebugSave(provider); + } + } + [Theory] [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] public void Resize_IsAppliedToAllFrames(TestImageProvider provider)