From 4071c65f4c7e9364c3e8ee08c978136c7a5b0d17 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 1 Mar 2020 22:27:49 +1100 Subject: [PATCH] Better performance --- .../DiscontiguousBuffers/IMemoryGroup{T}.cs | 4 +- .../Processors/Dithering/ErrorDither.cs | 2 +- .../IPaletteDitherImageProcessor{TPixel}.cs | 4 +- .../Dithering/OrderedDither.KnownTypes.cs | 2 +- .../Processors/Dithering/OrderedDither.cs | 4 +- .../PaletteDitherProcessor{TPixel}.cs | 2 +- .../Quantization/EuclideanPixelMap{TPixel}.cs | 36 +++++++++--------- .../Quantization/FrameQuantizerExtensions.cs | 2 +- .../OctreeFrameQuantizer{TPixel}.cs | 2 +- .../Quantization/OctreeQuantizer.cs | 4 +- .../PaletteFrameQuantizer{TPixel}.cs | 4 +- .../Quantization/PaletteQuantizer.cs | 3 +- .../Quantization/WebSafePaletteQuantizer.cs | 4 +- .../Quantization/WernerPaletteQuantizer.cs | 4 +- .../Quantization/WuFrameQuantizer{TPixel}.cs | 38 +++++++++---------- .../Processors/Quantization/WuQuantizer.cs | 4 +- 16 files changed, 64 insertions(+), 55 deletions(-) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs index 2649b7fb16..89aca914d0 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -18,12 +18,12 @@ namespace SixLabors.ImageSharp.Memory /// Gets the number of elements per contiguous sub-buffer preceding the last buffer. /// The last buffer is allowed to be smaller. /// - public int BufferLength { get; } + int BufferLength { get; } /// /// Gets the aggregate number of elements in the group. /// - public long TotalLength { get; } + long TotalLength { get; } /// /// Gets a value indicating whether the group has been invalidated. diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 9217d1c3f7..6aa6eeca69 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering for (int x = bounds.Left; x < bounds.Right; x++) { TPixel sourcePixel = row[x]; - TPixel transformed = processor.GetPaletteColor(sourcePixel, palette); + TPixel transformed = Unsafe.AsRef(processor).GetPaletteColor(sourcePixel, palette); this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); row[x] = transformed; } diff --git a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs index 3e4bf4d836..a890e929dc 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs @@ -14,9 +14,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TPixel : unmanaged, IPixel { /// - /// Gets the configration instance to use when performing operations. + /// Gets the configuration instance to use when performing operations. /// - public Configuration Configuration { get; } + Configuration Configuration { get; } /// /// Gets the dithering palette. diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs index d3e7107826..f6026a64f7 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// /// An ordered dithering matrix with equal sides of arbitrary length /// - public readonly partial struct OrderedDither : IDither + public readonly partial struct OrderedDither { /// /// Applies order dithering using the 2x2 Bayer dithering matrix. diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index c5b4135f56..9e97fe7e65 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -237,7 +237,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering for (int x = this.bounds.Left; x < this.bounds.Right; x++) { TPixel dithered = this.dither.Dither(sourceRow[x], x, y, this.bitDepth, scale); - destinationRow[x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _); + destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, paletteSpan, out TPixel _); } } } @@ -281,7 +281,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering { ref TPixel sourcePixel = ref row[x]; TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale); - sourcePixel = this.processor.GetPaletteColor(dithered, paletteSpan); + sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered, paletteSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index d25048a7f7..4d4ccf1ab4 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering float ditherScale) { this.Configuration = configuration; - this.pixelMap = new EuclideanPixelMap(palette); + this.pixelMap = new EuclideanPixelMap(configuration, palette); this.Palette = palette; this.DitherScale = ditherScale; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 615a7238b8..7147886297 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Concurrent; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization @@ -17,27 +18,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization internal readonly struct EuclideanPixelMap : IPixelMap, IEquatable> where TPixel : unmanaged, IPixel { - private readonly ConcurrentDictionary vectorCache; + private readonly Vector4[] vectorCache; private readonly ConcurrentDictionary distanceCache; /// /// Initializes a new instance of the struct. /// + /// The configuration. /// The color palette to map from. [MethodImpl(InliningOptions.ShortMethod)] - public EuclideanPixelMap(ReadOnlyMemory palette) + public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) { Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); this.Palette = palette; ReadOnlySpan paletteSpan = this.Palette.Span; - this.vectorCache = new ConcurrentDictionary(); + this.vectorCache = new Vector4[paletteSpan.Length]; this.distanceCache = new ConcurrentDictionary(); - for (int i = 0; i < paletteSpan.Length; i++) - { - this.vectorCache[i] = paletteSpan[i].ToScaledVector4(); - } + PixelOperations.Instance.ToVector4(configuration, paletteSpan, this.vectorCache); } /// @@ -81,31 +80,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization int index = 0; float leastDistance = float.MaxValue; Vector4 vector = color.ToScaledVector4(); + ref TPixel paletteRef = ref MemoryMarshal.GetReference(palette); + ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference(this.vectorCache); for (int i = 0; i < palette.Length; i++) { - Vector4 candidate = this.vectorCache[i]; + Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i); float distance = Vector4.DistanceSquared(vector, candidate); - if (!(distance < leastDistance)) + // If it's an exact match, exit the loop + if (distance == 0) { - continue; + index = i; + break; } - // Less than... assign. - index = i; - leastDistance = distance; - - // And if it's an exact match, exit the loop - if (distance == 0) + if (distance < leastDistance) { - break; + // Less than... assign. + index = i; + leastDistance = distance; } } // Now I have the index, pop it into the cache for next time this.distanceCache[color] = index; - match = palette[index]; + match = Unsafe.Add(ref paletteRef, index); return index; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs index d6d8b98dae..ef97f57e3d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - destinationRow[x - offsetX] = this.quantizer.GetQuantizedColor(sourceRow[x], paletteSpan, out TPixel _); + destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(sourceRow[x], paletteSpan, out TPixel _); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 946ae399bb..e80449b09f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.octree.Palletize(paletteSpan, this.colors); // TODO: Cannot make method readonly due to this line. - this.pixelMap = new EuclideanPixelMap(this.palette.Memory); + this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette.Memory); return paletteSpan; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index 3328fd6c7c..9e04edef0b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -11,12 +11,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class OctreeQuantizer : IQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); + /// /// Initializes a new instance of the class /// using the default . /// public OctreeQuantizer() - : this(new QuantizerOptions()) + : this(DefaultOptions) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index 7960f728af..3dbf77a3a9 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.paletteOwner = configuration.MemoryAllocator.Allocate(maxLength); Color.ToPixel(configuration, colors, this.paletteOwner.GetSpan()); - this.pixelMap = new EuclideanPixelMap(this.paletteOwner.Memory); + this.pixelMap = new EuclideanPixelMap(configuration, this.paletteOwner.Memory); this.isDisposed = false; } @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.paletteOwner = configuration.MemoryAllocator.Allocate(maxLength); palette.CopyTo(this.paletteOwner.GetSpan()); - this.pixelMap = new EuclideanPixelMap(this.paletteOwner.Memory); + this.pixelMap = new EuclideanPixelMap(configuration, this.paletteOwner.Memory); this.isDisposed = false; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index 75a0f39389..e95f8c5db5 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class PaletteQuantizer : IQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); private readonly ReadOnlyMemory palette; /// @@ -18,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The color palette. public PaletteQuantizer(ReadOnlyMemory palette) - : this(palette, new QuantizerOptions()) + : this(palette, DefaultOptions) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs index 8aa634b9ff..d95ed5aab9 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs @@ -10,11 +10,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WebSafePaletteQuantizer : PaletteQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); + /// /// Initializes a new instance of the class. /// public WebSafePaletteQuantizer() - : this(new QuantizerOptions()) + : this(DefaultOptions) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs index 168c837d57..8f8e38dd9d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs @@ -9,11 +9,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WernerPaletteQuantizer : PaletteQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); + /// /// Initializes a new instance of the class. /// public WernerPaletteQuantizer() - : this(new QuantizerOptions()) + : this(DefaultOptions) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 5054772582..6f98ce121e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -132,30 +132,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } // TODO: Cannot make methods readonly due to this line. - this.pixelMap = new EuclideanPixelMap(this.palette.Memory); + this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette.Memory); return paletteSpan; } /// public readonly byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) { - if (!this.isDithering) + if (this.isDithering) { - Rgba32 rgba = default; - color.ToRgba32(ref rgba); - - int r = rgba.R >> (8 - IndexBits); - int g = rgba.G >> (8 - IndexBits); - int b = rgba.B >> (8 - IndexBits); - int a = rgba.A >> (8 - IndexAlphaBits); - - ReadOnlySpan tagSpan = this.tag.GetSpan(); - byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; - match = palette[index]; - return index; + return (byte)this.pixelMap.GetClosestColor(color, out match); } - return (byte)this.pixelMap.GetClosestColor(color, out match); + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + + int r = rgba.R >> (8 - IndexBits); + int g = rgba.G >> (8 - IndexBits); + int b = rgba.B >> (8 - IndexBits); + int a = rgba.A >> (8 - IndexAlphaBits); + + ReadOnlySpan tagSpan = this.tag.GetSpan(); + byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + match = palette[index]; + return index; } /// @@ -376,11 +376,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// - /// The memory allocator used for allocating buffers. - private void Get3DMoments(MemoryAllocator memoryAllocator) + /// The memory allocator used for allocating buffers. + private void Get3DMoments(MemoryAllocator allocator) { - using IMemoryOwner volume = memoryAllocator.Allocate(IndexCount * IndexAlphaCount); - using IMemoryOwner area = memoryAllocator.Allocate(IndexAlphaCount); + using IMemoryOwner volume = allocator.Allocate(IndexCount * IndexAlphaCount); + using IMemoryOwner area = allocator.Allocate(IndexAlphaCount); Span momentSpan = this.moments.GetSpan(); Span volumeSpan = volume.GetSpan(); diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs index 872e6d5bd4..d2e33aa1f1 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -10,12 +10,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WuQuantizer : IQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); + /// /// Initializes a new instance of the class /// using the default . /// public WuQuantizer() - : this(new QuantizerOptions()) + : this(DefaultOptions) { }