From 1d0834ddde712a76b01292222dbfd1755519c74b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 26 Oct 2019 00:34:31 +1100 Subject: [PATCH 1/3] Optimize effor diffusion. Fix #757 --- src/ImageSharp/Advanced/AotCompilerTools.cs | 2 +- .../Extensions/DiffuseExtensions.cs | 4 +- .../BinaryErrorDiffusionProcessor{TPixel}.cs | 4 +- .../Processors/Dithering/AtkinsonDiffuser.cs | 12 ++- .../Processors/Dithering/BurksDiffuser.cs | 12 ++- .../Processors/Dithering/ErrorDiffuser.cs | 96 ++++++------------- .../ErrorDiffusionPaletteProcessor{TPixel}.cs | 4 +- .../Dithering/FloydSteinbergDiffuser.cs | 12 ++- .../Processors/Dithering/IErrorDiffuser.cs | 7 +- .../Dithering/JarvisJudiceNinkeDiffuser.cs | 14 +-- .../Processors/Dithering/Sierra2Diffuser.cs | 12 ++- .../Processors/Dithering/Sierra3Diffuser.cs | 14 +-- .../Dithering/SierraLiteDiffuser.cs | 12 ++- .../Dithering/StevensonArceDiffuser.cs | 16 ++-- .../Processors/Dithering/StuckiDiffuser.cs | 14 +-- .../Processors/Dithering/error_diffusion.txt | 3 + .../OctreeFrameQuantizer{TPixel}.cs | 6 +- .../PaletteFrameQuantizer{TPixel}.cs | 6 +- .../Quantization/WuFrameQuantizer{TPixel}.cs | 2 +- .../ImageSharp.Benchmarks/Samplers/Diffuse.cs | 49 ++++++++++ 20 files changed, 168 insertions(+), 133 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 1ceba5f90..5d172d93f 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Advanced TPixel pixel = default; using (var image = new ImageFrame(Configuration.Default, 1, 1)) { - test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0, 0); + test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0); } } diff --git a/src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs b/src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs index f9a1bdde0..b72111099 100644 --- a/src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -95,4 +95,4 @@ namespace SixLabors.ImageSharp.Processing.Dithering Rectangle rectangle) => source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs index 7e3458ae3..012457548 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// Performs binary threshold filtering against an image using error diffusion. /// /// The pixel format. - internal class BinaryErrorDiffusionProcessor : ImageProcessor + internal sealed class BinaryErrorDiffusionProcessor : ImageProcessor where TPixel : struct, IPixel { private readonly BinaryErrorDiffusionProcessor definition; @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization } TPixel transformedPixel = luminance >= threshold ? upperColor : lowerColor; - diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); + diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, endX, endY); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs index 0461d179f..f167ac5cb 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Primitives; @@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public sealed class AtkinsonDiffuser : ErrorDiffuser { + private const float Divisor = 8F; + /// /// The diffusion matrix /// private static readonly DenseMatrix AtkinsonMatrix = new float[,] { - { 0, 0, 1, 1 }, - { 1, 1, 1, 0 }, - { 0, 1, 0, 0 } + { 0, 0, 1 / Divisor, 1 / Divisor }, + { 1 / Divisor, 1 / Divisor, 1 / Divisor, 0 }, + { 0, 1 / Divisor, 0, 0 } }; /// /// Initializes a new instance of the class. /// public AtkinsonDiffuser() - : base(AtkinsonMatrix, 8) + : base(AtkinsonMatrix) { } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs index 23d4321e9..3c1ff75f4 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Primitives; @@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public sealed class BurksDiffuser : ErrorDiffuser { + private const float Divisor = 32F; + /// /// The diffusion matrix /// private static readonly DenseMatrix BurksMatrix = new float[,] { - { 0, 0, 0, 8, 4 }, - { 2, 4, 8, 4, 2 } + { 0, 0, 0, 8 / Divisor, 4 / Divisor }, + { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor } }; /// /// Initializes a new instance of the class. /// public BurksDiffuser() - : base(BurksMatrix, 32) + : base(BurksMatrix) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs index 1c8156bf5..a6f666c98 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -15,61 +15,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public abstract class ErrorDiffuser : IErrorDiffuser { - /// - /// The vector to perform division. - /// - private readonly Vector4 divisorVector; - - /// - /// The matrix width. - /// - private readonly int matrixHeight; - - /// - /// The matrix height. - /// - private readonly int matrixWidth; - - /// - /// The offset at which to start the dithering operation. - /// private readonly int startingOffset; - - /// - /// The diffusion matrix. - /// private readonly DenseMatrix matrix; /// /// Initializes a new instance of the class. /// /// The dithering matrix. - /// The divisor. - internal ErrorDiffuser(in DenseMatrix matrix, byte divisor) + internal ErrorDiffuser(in DenseMatrix matrix) { - Guard.MustBeGreaterThan(divisor, 0, nameof(divisor)); - - this.matrix = matrix; - this.matrixWidth = this.matrix.Columns; - this.matrixHeight = this.matrix.Rows; - this.divisorVector = new Vector4(divisor); - this.startingOffset = 0; - for (int i = 0; i < this.matrixWidth; i++) + + for (int col = 0; col < matrix.Columns; col++) { - // Good to disable here as we are not comparing mathematical output. - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (matrix[0, i] != 0) + if (matrix[0, col] != 0) { - this.startingOffset = (byte)(i - 1); + this.startingOffset = col - 1; break; } } + + this.matrix = matrix; } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) + [MethodImpl(InliningOptions.ShortMethod)] + public void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int maxX, int maxY) where TPixel : struct, IPixel { image[x, y] = transformed; @@ -82,45 +53,40 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering // Calculate the error Vector4 error = source.ToVector4() - transformed.ToVector4(); - this.DoDither(image, x, y, minX, minY, maxX, maxY, error); + this.DoDither(image, x, y, minX, maxX, maxY, error); } - [MethodImpl(MethodImplOptions.NoInlining)] - private void DoDither(ImageFrame image, int x, int y, int minX, int minY, int maxX, int maxY, Vector4 error) + [MethodImpl(InliningOptions.ShortMethod)] + private void DoDither(ImageFrame image, int x, int y, int minX, int maxX, int maxY, Vector4 error) where TPixel : struct, IPixel { + int offset = this.startingOffset; + DenseMatrix matrix = this.matrix; + // Loop through and distribute the error amongst neighboring pixels. - for (int row = 0; row < this.matrixHeight; row++) + for (int row = 0, targetY = y + row; row < matrix.Rows && targetY < maxY; row++) { - int matrixY = y + row; - if (matrixY > minY && matrixY < maxY) - { - Span rowSpan = image.GetPixelRowSpan(matrixY); + Span rowSpan = image.GetPixelRowSpan(targetY); - for (int col = 0; col < this.matrixWidth; col++) + for (int col = 0; col < matrix.Columns; col++) + { + int targetX = x + (col - offset); + if (targetX > minX && targetX < maxX) { - int matrixX = x + (col - this.startingOffset); - - if (matrixX > minX && matrixX < maxX) + float coefficient = matrix[row, col]; + if (coefficient == 0) { - float coefficient = this.matrix[row, col]; - - // Good to disable here as we are not comparing mathematical output. - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (coefficient == 0) - { - continue; - } + continue; + } - ref TPixel pixel = ref rowSpan[matrixX]; - var offsetColor = pixel.ToVector4(); + ref TPixel pixel = ref rowSpan[targetX]; + var offsetColor = pixel.ToVector4(); - Vector4 result = ((error * coefficient) / this.divisorVector) + offsetColor; - pixel.FromVector4(result); - } + Vector4 result = (error * coefficient) + offsetColor; + pixel.FromVector4(result); } } } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs index 557a31c33..a14a191c6 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// An that dithers an image using error diffusion. /// /// The pixel format. - internal class ErrorDiffusionPaletteProcessor : PaletteDitherProcessor + internal sealed class ErrorDiffusionPaletteProcessor : PaletteDitherProcessor where TPixel : struct, IPixel { /// @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First; - this.Definition.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); + this.Definition.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, endX, endY); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs index 78a28a693..ca0e3c647 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Primitives; @@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public sealed class FloydSteinbergDiffuser : ErrorDiffuser { + private const float Divisor = 16F; + /// /// The diffusion matrix /// private static readonly DenseMatrix FloydSteinbergMatrix = new float[,] { - { 0, 0, 7 }, - { 3, 5, 1 } + { 0, 0, 7 / Divisor }, + { 3 / Divisor, 5 / Divisor, 1 / Divisor } }; /// /// Initializes a new instance of the class. /// public FloydSteinbergDiffuser() - : base(FloydSteinbergMatrix, 16) + : base(FloydSteinbergMatrix) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs index 5b30c0dc4..8f4381d30 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -19,11 +19,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// The column index. /// The row index. /// The minimum column value. - /// The minimum row value. /// The maximum column value. /// The maximum row value. /// The pixel format. - void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) + void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int maxX, int maxY) where TPixel : struct, IPixel; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs index 64c861083..682db8352 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Primitives; @@ -11,23 +11,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public sealed class JarvisJudiceNinkeDiffuser : ErrorDiffuser { + private const float Divisor = 48F; + /// /// The diffusion matrix /// private static readonly DenseMatrix JarvisJudiceNinkeMatrix = new float[,] { - { 0, 0, 0, 7, 5 }, - { 3, 5, 7, 5, 3 }, - { 1, 3, 5, 3, 1 } + { 0, 0, 0, 7 / Divisor, 5 / Divisor }, + { 3 / Divisor, 5 / Divisor, 7 / Divisor, 5 / Divisor, 3 / Divisor }, + { 1 / Divisor, 3 / Divisor, 5 / Divisor, 3 / Divisor, 1 / Divisor } }; /// /// Initializes a new instance of the class. /// public JarvisJudiceNinkeDiffuser() - : base(JarvisJudiceNinkeMatrix, 48) + : base(JarvisJudiceNinkeMatrix) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs index b489f8f28..03791bff2 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Primitives; @@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public sealed class Sierra2Diffuser : ErrorDiffuser { + private const float Divisor = 16F; + /// /// The diffusion matrix /// private static readonly DenseMatrix Sierra2Matrix = new float[,] { - { 0, 0, 0, 4, 3 }, - { 1, 2, 3, 2, 1 } + { 0, 0, 0, 4 / Divisor, 3 / Divisor }, + { 1 / Divisor, 2 / Divisor, 3 / Divisor, 2 / Divisor, 1 / Divisor } }; /// /// Initializes a new instance of the class. /// public Sierra2Diffuser() - : base(Sierra2Matrix, 16) + : base(Sierra2Matrix) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs index 04abc782a..c7d7acc82 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Primitives; @@ -11,23 +11,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public sealed class Sierra3Diffuser : ErrorDiffuser { + private const float Divisor = 32F; + /// /// The diffusion matrix /// private static readonly DenseMatrix Sierra3Matrix = new float[,] { - { 0, 0, 0, 5, 3 }, - { 2, 4, 5, 4, 2 }, - { 0, 2, 3, 2, 0 } + { 0, 0, 0, 5 / Divisor, 3 / Divisor }, + { 2 / Divisor, 4 / Divisor, 5 / Divisor, 4 / Divisor, 2 / Divisor }, + { 0, 2 / Divisor, 3 / Divisor, 2 / Divisor, 0 } }; /// /// Initializes a new instance of the class. /// public Sierra3Diffuser() - : base(Sierra3Matrix, 32) + : base(Sierra3Matrix) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs index 2ac69cf45..e969f1b70 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Primitives; @@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public sealed class SierraLiteDiffuser : ErrorDiffuser { + private const float Divisor = 4F; + /// /// The diffusion matrix /// private static readonly DenseMatrix SierraLiteMatrix = new float[,] { - { 0, 0, 2 }, - { 1, 1, 0 } + { 0, 0, 2 / Divisor }, + { 1 / Divisor, 1 / Divisor, 0 } }; /// /// Initializes a new instance of the class. /// public SierraLiteDiffuser() - : base(SierraLiteMatrix, 4) + : base(SierraLiteMatrix) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs index b929a28d3..61727325a 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Primitives; @@ -10,24 +10,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public sealed class StevensonArceDiffuser : ErrorDiffuser { + private const float Divisor = 200F; + /// /// The diffusion matrix /// private static readonly DenseMatrix StevensonArceMatrix = new float[,] { - { 0, 0, 0, 0, 0, 32, 0 }, - { 12, 0, 26, 0, 30, 0, 16 }, - { 0, 12, 0, 26, 0, 12, 0 }, - { 5, 0, 12, 0, 12, 0, 5 } + { 0, 0, 0, 0, 0, 32 / Divisor, 0 }, + { 12 / Divisor, 0, 26 / Divisor, 0, 30 / Divisor, 0, 16 / Divisor }, + { 0, 12 / Divisor, 0, 26 / Divisor, 0, 12 / Divisor, 0 }, + { 5 / Divisor, 0, 12 / Divisor, 0, 12 / Divisor, 0, 5 / Divisor } }; /// /// Initializes a new instance of the class. /// public StevensonArceDiffuser() - : base(StevensonArceMatrix, 200) + : base(StevensonArceMatrix) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs index bb3aebc3f..76203201c 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Primitives; @@ -11,23 +11,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public sealed class StuckiDiffuser : ErrorDiffuser { + private const float Divisor = 42F; + /// /// The diffusion matrix /// private static readonly DenseMatrix StuckiMatrix = new float[,] { - { 0, 0, 0, 8, 4 }, - { 2, 4, 8, 4, 2 }, - { 1, 2, 4, 2, 1 } + { 0, 0, 0, 8 / Divisor, 4 / Divisor }, + { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor }, + { 1 / Divisor, 2 / Divisor, 4 / Divisor, 2 / Divisor, 1 / Divisor } }; /// /// Initializes a new instance of the class. /// public StuckiDiffuser() - : base(StuckiMatrix, 42) + : base(StuckiMatrix) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt b/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt index ea412f635..27dea8af1 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt +++ b/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt @@ -1,3 +1,6 @@ +Reference: +http://bisqwit.iki.fi/jutut/kuvat/ordered_dither/error_diffusion.txt + List of error diffusion schemes. Quantization error of *current* pixel is added to the pixels diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 85a4d2029..393cb5f60 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, width, height); } output[(y * source.Width) + x] = pixelValue; @@ -571,4 +571,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index 265c343e6..f774f80be 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, width, height); } output[(y * source.Width) + x] = pixelValue; @@ -98,4 +98,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [MethodImpl(MethodImplOptions.AggressiveInlining)] private byte QuantizePixel(ref TPixel pixel) => this.GetClosestPixel(ref pixel); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 87d696dc9..64a5010a8 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -260,7 +260,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, width, height); } output[(y * source.Width) + x] = pixelValue; diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs new file mode 100644 index 000000000..c0a9d2030 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs @@ -0,0 +1,49 @@ +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Dithering; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Benchmarks.Samplers +{ + [Config(typeof(Config.ShortClr))] + public class Diffuse + { + [Benchmark] + public Size DoDiffuse() + { + using (var image = new Image(Configuration.Default, 800, 800, Rgba32.BlanchedAlmond)) + { + image.Mutate(x => x.Diffuse()); + + return image.Size(); + } + } + } +} + +// #### 25th October 2019 #### +// +// BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362 +// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK = 3.0.100 +// +// [Host] : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT +// Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.8.4018.0 +// Core : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT +// +// IterationCount=3 LaunchCount=1 WarmupCount=3 +// +// #### Before #### +// +// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |---------- |----- |-------- |----------:|---------:|---------:|------:|------:|------:|----------:| +// | DoDiffuse | Clr | Clr | 129.58 ms | 24.60 ms | 1.349 ms | - | - | - | 6 KB | +// | DoDiffuse | Core | Core | 92.63 ms | 89.78 ms | 4.921 ms | - | - | - | 4.58 KB | +// +// #### After #### +// +// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |---------- |----- |-------- |---------:|---------:|---------:|------:|------:|------:|----------:| +// | DoDiffuse | Clr | Clr | 12.94 ms | 22.48 ms | 1.232 ms | - | - | - | 4.25 KB | +// | DoDiffuse | Core | Core | 10.95 ms | 19.31 ms | 1.058 ms | - | - | - | 4.13 KB | From 1911fb536d4be257e9652be30717dd84ffa2eae2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 26 Oct 2019 00:35:39 +1100 Subject: [PATCH 2/3] Update External --- tests/Images/External | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Images/External b/tests/Images/External index 1d3d4e365..54e075785 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 1d3d4e3652dc95bd8bd420346bfe0f189addc587 +Subproject commit 54e075785697c9d6aa371282d492f16d9d916888 From 7cb840a86a2c65eb2174fffc23433ae8f1da94c2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 27 Oct 2019 00:17:49 +1100 Subject: [PATCH 3/3] Fix output, cleanup and minor optimizations. --- .../Processors/Dithering/ErrorDiffuser.cs | 20 +++--- .../ErrorDiffusionPaletteProcessor{TPixel}.cs | 6 +- .../OrderedDitherPaletteProcessor{TPixel}.cs | 6 +- .../PaletteDitherProcessor{TPixel}.cs | 69 ++++++++++++------- .../Quantization/FrameQuantizer{TPixel}.cs | 55 +++++++++++---- .../Quantization/WuFrameQuantizer{TPixel}.cs | 29 +++++--- .../ImageSharp.Benchmarks/Samplers/Diffuse.cs | 8 +-- tests/Images/External | 2 +- 8 files changed, 130 insertions(+), 65 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs index a6f666c98..7911c6ca9 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public abstract class ErrorDiffuser : IErrorDiffuser { - private readonly int startingOffset; + private readonly int offset; private readonly DenseMatrix matrix; /// @@ -24,13 +24,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// The dithering matrix. internal ErrorDiffuser(in DenseMatrix matrix) { - this.startingOffset = 0; + // Calculate the offset position of the pixel relative to + // the diffusion matrix. + this.offset = 0; for (int col = 0; col < matrix.Columns; col++) { if (matrix[0, col] != 0) { - this.startingOffset = col - 1; + this.offset = col - 1; break; } } @@ -45,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering { image[x, y] = transformed; - // Equal? Break out as there's nothing to pass. + // Equal? Break out as there's no error to pass. if (source.Equals(transformed)) { return; @@ -60,18 +62,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering private void DoDither(ImageFrame image, int x, int y, int minX, int maxX, int maxY, Vector4 error) where TPixel : struct, IPixel { - int offset = this.startingOffset; + int offset = this.offset; DenseMatrix matrix = this.matrix; // Loop through and distribute the error amongst neighboring pixels. - for (int row = 0, targetY = y + row; row < matrix.Rows && targetY < maxY; row++) + for (int row = 0, targetY = y; row < matrix.Rows && targetY < maxY; row++, targetY++) { Span rowSpan = image.GetPixelRowSpan(targetY); for (int col = 0; col < matrix.Columns; col++) { int targetX = x + (col - offset); - if (targetX > minX && targetX < maxX) + if (targetX >= minX && targetX < maxX) { float coefficient = matrix[row, col]; if (coefficient == 0) @@ -80,9 +82,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } ref TPixel pixel = ref rowSpan[targetX]; - var offsetColor = pixel.ToVector4(); + var result = pixel.ToVector4(); - Vector4 result = (error * coefficient) + offsetColor; + result += error * coefficient; pixel.FromVector4(result); } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs index a14a191c6..37dcd7d5c 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs @@ -33,8 +33,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering protected override void OnFrameApply(ImageFrame source) { byte threshold = (byte)MathF.Round(this.Definition.Threshold * 255F); - bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); int startY = interest.Y; int endY = interest.Bottom; @@ -49,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering sourcePixel.ToRgba32(ref rgba); // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + byte luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); for (int y = startY; y < endY; y++) { @@ -72,7 +70,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } sourcePixel.ToRgba32(ref rgba); - luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); // Setup the previous pointer previousPixel = sourcePixel; diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs index 08eaec503..8cde8943e 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs @@ -32,8 +32,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// protected override void OnFrameApply(ImageFrame source) { - bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); int startY = interest.Y; int endY = interest.Bottom; @@ -48,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering sourcePixel.ToRgba32(ref rgba); // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + byte luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); for (int y = startY; y < endY; y++) { @@ -71,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } sourcePixel.ToRgba32(ref rgba); - luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); // Setup the previous pointer previousPixel = sourcePixel; diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 9f817267f..10e963942 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -2,11 +2,13 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; - +using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Dithering @@ -19,13 +21,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TPixel : struct, IPixel { private readonly Dictionary> cache = new Dictionary>(); - - private TPixel[] palette; - - /// - /// The vector representation of the image palette. - /// - private Vector4[] paletteVector; + private IMemoryOwner palette; + private IMemoryOwner paletteVector; + private bool palleteVectorMapped; + private bool isDisposed; /// /// Initializes a new instance of the class. @@ -37,6 +36,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering : base(source, sourceRectangle) { this.Definition = definition; + this.palette = this.Configuration.MemoryAllocator.Allocate(definition.Palette.Length); + this.paletteVector = this.Configuration.MemoryAllocator.Allocate(definition.Palette.Length); } protected PaletteDitherProcessor Definition { get; } @@ -44,28 +45,45 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// protected override void BeforeFrameApply(ImageFrame source) { - // Lazy init palette: - if (this.palette is null) + // Lazy init palettes: + if (!this.palleteVectorMapped) { ReadOnlySpan sourcePalette = this.Definition.Palette.Span; - this.palette = new TPixel[sourcePalette.Length]; - Color.ToPixel(this.Configuration, sourcePalette, this.palette); - } + Color.ToPixel(this.Configuration, sourcePalette, this.palette.Memory.Span); - // Lazy init paletteVector: - if (this.paletteVector is null) - { - this.paletteVector = new Vector4[this.palette.Length]; PixelOperations.Instance.ToVector4( this.Configuration, - (ReadOnlySpan)this.palette, - (Span)this.paletteVector, + this.palette.Memory.Span, + this.paletteVector.Memory.Span, PixelConversionModifiers.Scale); } + this.palleteVectorMapped = true; + base.BeforeFrameApply(source); } + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + this.palette?.Dispose(); + this.paletteVector?.Dispose(); + } + + this.palette = null; + this.paletteVector = null; + + this.isDisposed = true; + base.Dispose(disposing); + } + /// /// Returns the two closest colors from the palette calculated via Euclidean distance in the Rgba space. /// @@ -93,21 +111,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering TPixel closest = default; TPixel secondClosest = default; - for (int index = 0; index < this.paletteVector.Length; index++) + Span paletteSpan = this.palette.Memory.Span; + ref TPixel paletteSpanBase = ref MemoryMarshal.GetReference(paletteSpan); + Span paletteVectorSpan = this.paletteVector.Memory.Span; + ref Vector4 paletteVectorSpanBase = ref MemoryMarshal.GetReference(paletteVectorSpan); + + for (int index = 0; index < paletteVectorSpan.Length; index++) { - ref Vector4 candidate = ref this.paletteVector[index]; + ref Vector4 candidate = ref Unsafe.Add(ref paletteVectorSpanBase, index); float distance = Vector4.DistanceSquared(vector, candidate); if (distance < leastDistance) { leastDistance = distance; secondClosest = closest; - closest = this.palette[index]; + closest = Unsafe.Add(ref paletteSpanBase, index); } else if (distance < secondLeastDistance) { secondLeastDistance = distance; - secondClosest = this.palette[index]; + secondClosest = Unsafe.Add(ref paletteSpanBase, index); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs index e6ffecc84..71013548b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs @@ -1,11 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; - +using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; @@ -31,7 +32,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The vector representation of the image palette. /// - private Vector4[] paletteVector; + private IMemoryOwner paletteVector; + + private bool isDisposed; /// /// Initializes a new instance of the class. @@ -80,8 +83,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public bool Dither { get; } /// - public virtual void Dispose() + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); } /// @@ -103,11 +108,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // Collect the palette. Required before the second pass runs. ReadOnlyMemory palette = this.GetPalette(); - this.paletteVector = new Vector4[palette.Length]; + this.paletteVector = image.Configuration.MemoryAllocator.Allocate(palette.Length); PixelOperations.Instance.ToVector4( image.Configuration, palette.Span, - (Span)this.paletteVector, + this.paletteVector.Memory.Span, PixelConversionModifiers.Scale); var quantizedFrame = new QuantizedFrame(image.MemoryAllocator, width, height, palette); @@ -129,6 +134,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return quantizedFrame; } + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose managed and unmanaged objects. + protected virtual void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + this.paletteVector?.Dispose(); + } + + this.paletteVector = null; + + this.isDisposed = true; + } + /// /// Execute the first pass through the pixels in the image to create the palette. /// @@ -161,7 +187,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Retrieve the palette for the quantized image. /// /// - /// + /// /// protected abstract ReadOnlyMemory GetPalette(); @@ -173,12 +199,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization protected byte GetTransparentIndex() { // Transparent pixels are much more likely to be found at the end of a palette. - int paletteVectorLengthMinus1 = this.paletteVector.Length - 1; + Span paletteVectorSpan = this.paletteVector.Memory.Span; + ref Vector4 paletteVectorSpanBase = ref MemoryMarshal.GetReference(paletteVectorSpan); + + int paletteVectorLengthMinus1 = paletteVectorSpan.Length - 1; int index = paletteVectorLengthMinus1; for (int i = paletteVectorLengthMinus1; i >= 0; i--) { - ref Vector4 candidate = ref this.paletteVector[i]; + ref Vector4 candidate = ref Unsafe.Add(ref paletteVectorSpanBase, i); if (candidate.Equals(default)) { index = i; @@ -211,10 +240,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization float leastDistance = float.MaxValue; Vector4 vector = pixel.ToScaledVector4(); float epsilon = Constants.EpsilonSquared; + Span paletteVectorSpan = this.paletteVector.Memory.Span; + ref Vector4 paletteVectorSpanBase = ref MemoryMarshal.GetReference(paletteVectorSpan); - for (int index = 0; index < this.paletteVector.Length; index++) + for (int index = 0; index < paletteVectorSpan.Length; index++) { - ref Vector4 candidate = ref this.paletteVector[index]; + ref Vector4 candidate = ref Unsafe.Add(ref paletteVectorSpanBase, index); float distance = Vector4.DistanceSquared(vector, candidate); // Greater... Move on. @@ -239,4 +270,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return result; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 64a5010a8..ee2751eaf 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -117,6 +117,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private Box[] colorCube; + private bool isDisposed; + /// /// Initializes a new instance of the class. /// @@ -158,15 +160,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - public override void Dispose() + protected override void Dispose(bool disposing) { - this.vwt?.Dispose(); - this.vmr?.Dispose(); - this.vmg?.Dispose(); - this.vmb?.Dispose(); - this.vma?.Dispose(); - this.m2?.Dispose(); - this.tag?.Dispose(); + if (this.isDisposed) + { + return; + } + + if (disposing) + { + this.vwt?.Dispose(); + this.vmr?.Dispose(); + this.vmg?.Dispose(); + this.vmb?.Dispose(); + this.vma?.Dispose(); + this.m2?.Dispose(); + this.tag?.Dispose(); + } this.vwt = null; this.vmr = null; @@ -175,6 +185,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.vma = null; this.m2 = null; this.tag = null; + + this.isDisposed = true; + base.Dispose(true); } internal ReadOnlyMemory AotGetPalette() => this.GetPalette(); diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs index c0a9d2030..6e67d11ef 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers // // #### After #### // -// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------- |----- |-------- |---------:|---------:|---------:|------:|------:|------:|----------:| -// | DoDiffuse | Clr | Clr | 12.94 ms | 22.48 ms | 1.232 ms | - | - | - | 4.25 KB | -// | DoDiffuse | Core | Core | 10.95 ms | 19.31 ms | 1.058 ms | - | - | - | 4.13 KB | +// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |---------- |----- |-------- |----------:|----------:|----------:|------:|------:|------:|----------:| +// | DoDiffuse | Clr | Clr | 124.93 ms | 33.297 ms | 1.8251 ms | - | - | - | 2 KB | +// | DoDiffuse | Core | Core | 89.63 ms | 9.895 ms | 0.5424 ms | - | - | - | 1.91 KB | diff --git a/tests/Images/External b/tests/Images/External index 54e075785..563ec6f77 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 54e075785697c9d6aa371282d492f16d9d916888 +Subproject commit 563ec6f7774734ba39924174c8961705a1ea6fa2