diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 9e7624480..eb6991e6a 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -15,6 +18,17 @@ namespace SixLabors.ImageSharp.Advanced /// public static class AotCompilerTools { + static AotCompilerTools() + { + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + } + /// /// Seeds the compiler using the given pixel format. /// @@ -27,6 +41,13 @@ namespace SixLabors.ImageSharp.Advanced AotCompileWuQuantizer(); AotCompileDithering(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + + AotCodec(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder()); + AotCodec(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder()); + AotCodec(new Formats.Gif.GifDecoder(), new Formats.Gif.GifEncoder()); + AotCodec(new Formats.Jpeg.JpegDecoder(), new Formats.Jpeg.JpegEncoder()); + // TODO: Do the discovery work to figure out what works and what doesn't. } @@ -99,5 +120,31 @@ namespace SixLabors.ImageSharp.Advanced TPixel pixel = default; test.Dither(new ImageFrame(Configuration.Default, 1, 1), pixel, pixel, 0, 0, 0, 0, 0, 0); } + + /// + /// This method pre-seeds the decoder and encoder for a given pixel format in the AoT compiler for iOS. + /// + /// The image decoder to seed. + /// The image encoder to seed. + /// The pixel format. + private static void AotCodec(IImageDecoder decoder, IImageEncoder encoder) + where TPixel : struct, IPixel + { + try + { + decoder.Decode(Configuration.Default, null); + } + catch + { + } + + try + { + encoder.Encode(null, null); + } + catch + { + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index 7cfa98ec1..170292e29 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Primitives /// The at the specified position. public ref T this[int row, int column] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] get { this.CheckCoordinates(row, column); @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Primitives /// /// The representation on the source data. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static implicit operator DenseMatrix(T[,] data) => new DenseMatrix(data); /// @@ -135,9 +135,9 @@ namespace SixLabors.ImageSharp.Primitives /// /// The representation on the source data. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] #pragma warning disable SA1008 // Opening parenthesis should be spaced correctly - public static implicit operator T[,] (DenseMatrix data) + public static implicit operator T[,] (in DenseMatrix data) #pragma warning restore SA1008 // Opening parenthesis should be spaced correctly { var result = new T[data.Rows, data.Columns]; @@ -154,17 +154,38 @@ namespace SixLabors.ImageSharp.Primitives return result; } + /// + /// Transposes the rows and columns of the dense matrix. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public DenseMatrix Transpose() + { + var result = new DenseMatrix(this.Rows, this.Columns); + + for (int y = 0; y < this.Rows; y++) + { + for (int x = 0; x < this.Columns; x++) + { + ref T value = ref result[x, y]; + value = this[y, x]; + } + } + + return result; + } + /// /// Fills the matrix with the given value /// /// The value to fill each item with - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public void Fill(T value) => this.Span.Fill(value); /// /// Clears the matrix setting each value to the default value for the element type /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public void Clear() => this.Span.Clear(); /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs index 38dc638b9..644d6c9e1 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -29,8 +29,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { this.Radius = radius; this.kernelSize = (radius * 2) + 1; - this.KernelX = this.CreateBoxKernel(true); - this.KernelY = this.CreateBoxKernel(false); + this.KernelX = this.CreateBoxKernel(); + this.KernelY = this.KernelX.Transpose(); } /// @@ -49,24 +49,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution public DenseMatrix KernelY { get; } /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); - } + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); /// /// Create a 1 dimensional Box kernel. /// - /// Whether to calculate a horizontal kernel. /// The - private DenseMatrix CreateBoxKernel(bool horizontal) + private DenseMatrix CreateBoxKernel() { int size = this.kernelSize; - DenseMatrix kernel = horizontal - ? new DenseMatrix(size, 1) - : new DenseMatrix(1, size); + var kernel = new DenseMatrix(size, 1); - kernel.Fill(1.0F / size); + kernel.Fill(1F / size); return kernel; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs index 3045b9993..b3bc15d39 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -4,7 +4,6 @@ using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing.Processors; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -26,11 +25,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The 'sigma' value representing the weight of the blur. public GaussianBlurProcessor(float sigma = 3F) + : this(sigma, (int)MathF.Ceiling(sigma * 3)) { - this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1; - this.Sigma = sigma; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); + // Kernel radius is calculated using the minimum viable value. + // http://chemaguerra.com/gaussian-filter-radius/ } /// @@ -40,11 +38,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// The 'radius' value representing the size of the area to sample. /// public GaussianBlurProcessor(int radius) + : this(radius / 3F, radius) { - this.kernelSize = (radius * 2) + 1; - this.Sigma = radius; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); } /// @@ -61,8 +56,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { this.kernelSize = (radius * 2) + 1; this.Sigma = sigma; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); + this.KernelX = this.CreateGaussianKernel(); + this.KernelY = this.KernelX.Transpose(); } /// @@ -82,22 +77,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); - } + => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); /// /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function /// - /// Whether to calculate a horizontal kernel. /// The - private DenseMatrix CreateGaussianKernel(bool horizontal) + private DenseMatrix CreateGaussianKernel() { int size = this.kernelSize; float weight = this.Sigma; - DenseMatrix kernel = horizontal - ? new DenseMatrix(size, 1) - : new DenseMatrix(1, size); + var kernel = new DenseMatrix(size, 1); float sum = 0F; float midpoint = (size - 1) / 2F; @@ -107,30 +97,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution float x = i - midpoint; float gx = ImageMaths.Gaussian(x, weight); sum += gx; - if (horizontal) - { - kernel[0, i] = gx; - } - else - { - kernel[i, 0] = gx; - } + kernel[0, i] = gx; } // Normalize kernel so that the sum of all weights equals 1 - if (horizontal) - { - for (int i = 0; i < size; i++) - { - kernel[0, i] = kernel[0, i] / sum; - } - } - else + for (int i = 0; i < size; i++) { - for (int i = 0; i < size; i++) - { - kernel[i, 0] = kernel[i, 0] / sum; - } + kernel[0, i] /= sum; } return kernel; diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs index 18963c73c..786bf7757 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -27,11 +27,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// The 'sigma' value representing the weight of the sharpening. /// public GaussianSharpenProcessor(float sigma = 3F) + : this(sigma, (int)MathF.Ceiling(sigma * 3)) { - this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1; - this.Sigma = sigma; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); + // Kernel radius is calculated using the minimum viable value. + // http://chemaguerra.com/gaussian-filter-radius/ } /// @@ -41,11 +40,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// The 'radius' value representing the size of the area to sample. /// public GaussianSharpenProcessor(int radius) + : this(radius / 3F, radius) { - this.kernelSize = (radius * 2) + 1; - this.Sigma = radius; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); } /// @@ -62,8 +58,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { this.kernelSize = (radius * 2) + 1; this.Sigma = sigma; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); + this.KernelX = this.CreateGaussianKernel(); + this.KernelY = this.KernelX.Transpose(); } /// @@ -83,91 +79,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); - } + => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); /// /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function /// - /// Whether to calculate a horizontal kernel. /// The - private DenseMatrix CreateGaussianKernel(bool horizontal) + private DenseMatrix CreateGaussianKernel() { int size = this.kernelSize; float weight = this.Sigma; - DenseMatrix kernel = horizontal - ? new DenseMatrix(size, 1) - : new DenseMatrix(1, size); + var kernel = new DenseMatrix(size, 1); float sum = 0; - float midpoint = (size - 1) / 2f; + float midpoint = (size - 1) / 2F; for (int i = 0; i < size; i++) { float x = i - midpoint; float gx = ImageMaths.Gaussian(x, weight); sum += gx; - if (horizontal) - { - kernel[0, i] = gx; - } - else - { - kernel[i, 0] = gx; - } + kernel[0, i] = gx; } // Invert the kernel for sharpening. int midpointRounded = (int)midpoint; - - if (horizontal) + for (int i = 0; i < size; i++) { - for (int i = 0; i < size; i++) + if (i == midpointRounded) { - if (i == midpointRounded) - { - // Calculate central value - kernel[0, i] = (2F * sum) - kernel[0, i]; - } - else - { - // invert value - kernel[0, i] = -kernel[0, i]; - } + // Calculate central value + kernel[0, i] = (2F * sum) - kernel[0, i]; } - } - else - { - for (int i = 0; i < size; i++) + else { - if (i == midpointRounded) - { - // Calculate central value - kernel[i, 0] = (2 * sum) - kernel[i, 0]; - } - else - { - // invert value - kernel[i, 0] = -kernel[i, 0]; - } + // invert value + kernel[0, i] = -kernel[0, i]; } } // Normalize kernel so that the sum of all weights equals 1 - if (horizontal) - { - for (int i = 0; i < size; i++) - { - kernel[0, i] /= sum; - } - } - else + for (int i = 0; i < size; i++) { - for (int i = 0; i < size; i++) - { - kernel[i, 0] /= sum; - } + kernel[0, i] /= sum; } return kernel; diff --git a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs index fa4862293..0af8ae45f 100644 --- a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs @@ -106,5 +106,29 @@ namespace SixLabors.ImageSharp.Tests.Primitives Assert.Equal(0, dense.Data[i]); } } + + [Fact] + public void DenseMatrixCorrectlyCasts() + { + float[,] actual = new DenseMatrix(FloydSteinbergMatrix); + Assert.Equal(FloydSteinbergMatrix, actual); + } + + [Fact] + public void DenseMatrixCanTranspose() + { + var dense = new DenseMatrix(3, 1); + dense[0, 0] = 1; + dense[0, 1] = 2; + dense[0, 2] = 3; + + DenseMatrix transposed = dense.Transpose(); + + Assert.Equal(dense.Columns, transposed.Rows); + Assert.Equal(dense.Rows, transposed.Columns); + Assert.Equal(1, transposed[0, 0]); + Assert.Equal(2, transposed[1, 0]); + Assert.Equal(3, transposed[2, 0]); + } } } \ No newline at end of file