From 3de317f6d589c01d6332584c79dd2bc07b14374a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Nov 2021 16:56:08 +0100 Subject: [PATCH 01/12] Change hashchain to use the memoryAllocator --- .../Webp/Lossless/BackwardReferenceEncoder.cs | 15 +++-- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 9 ++- .../Formats/Webp/Lossless/Vp8LHashChain.cs | 59 ++++++++++++++----- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs index dc546f8ac2..93f6372c64 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs @@ -3,10 +3,11 @@ using System; using System.Collections.Generic; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { - internal class BackwardReferenceEncoder + internal static class BackwardReferenceEncoder { /// /// Maximum bit length. @@ -41,6 +42,7 @@ public static Vp8LBackwardRefs GetBackwardReferences( int quality, int lz77TypesToTry, ref int cacheBits, + MemoryAllocator memoryAllocator, Vp8LHashChain hashChain, Vp8LBackwardRefs best, Vp8LBackwardRefs worst) @@ -69,7 +71,7 @@ public static Vp8LBackwardRefs GetBackwardReferences( BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst); break; case Vp8LLz77Type.Lz77Box: - hashChainBox = new Vp8LHashChain(width * height); + hashChainBox = new Vp8LHashChain(memoryAllocator, width * height); BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst); break; } @@ -617,7 +619,8 @@ private static void BackwardReferencesLz77Box(int xSize, int ySize, ReadOnlySpan } } - hashChain.OffsetLength[0] = 0; + Span hashChainOffsetLength = hashChain.OffsetLength.GetSpan(); + hashChainOffsetLength[0] = 0; for (i = 1; i < pixelCount; i++) { int ind; @@ -695,19 +698,19 @@ private static void BackwardReferencesLz77Box(int xSize, int ySize, ReadOnlySpan if (bestLength <= MinLength) { - hashChain.OffsetLength[i] = 0; + hashChainOffsetLength[i] = 0; bestOffsetPrev = 0; bestLengthPrev = 0; } else { - hashChain.OffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength); + hashChainOffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength); bestOffsetPrev = bestOffset; bestLengthPrev = bestLength; } } - hashChain.OffsetLength[0] = 0; + hashChainOffsetLength[0] = 0; BackwardReferencesLz77(xSize, ySize, bgra, cacheBits, hashChain, refs); } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index da815a479a..48f7d0e2b7 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -124,7 +124,7 @@ public Vp8LEncoder( this.EncodedData = memoryAllocator.Allocate(pixelCount); this.Palette = memoryAllocator.Allocate(WebpConstants.MaxPaletteSize); this.Refs = new Vp8LBackwardRefs[3]; - this.HashChain = new Vp8LHashChain(pixelCount); + this.HashChain = new Vp8LHashChain(memoryAllocator, pixelCount); // We round the block size up, so we're guaranteed to have at most MaxRefsBlockPerImage blocks used: int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; @@ -515,7 +515,7 @@ private void EncodeImage(int width, int height, bool useCache, CrunchConfig conf } // Calculate backward references from BGRA image. - this.HashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height, lowEffort); + this.HashChain.Fill(bgra, this.quality, width, height, lowEffort); Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; Vp8LBitWriter bwInit = this.bitWriter; @@ -529,6 +529,7 @@ private void EncodeImage(int width, int height, bool useCache, CrunchConfig conf this.quality, subConfig.Lz77, ref cacheBits, + this.memoryAllocator, this.HashChain, this.Refs[0], this.Refs[1]); @@ -735,7 +736,7 @@ private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8L } // Calculate backward references from the image pixels. - hashChain.Fill(this.memoryAllocator, bgra, quality, width, height, lowEffort); + hashChain.Fill(bgra, quality, width, height, lowEffort); Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( width, @@ -744,6 +745,7 @@ private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8L quality, (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle, ref cacheBits, + this.memoryAllocator, hashChain, refsTmp1, refsTmp2); @@ -1802,6 +1804,7 @@ public void Dispose() this.BgraScratch.Dispose(); this.Palette.Dispose(); this.TransformData.Dispose(); + this.HashChain.Dispose(); } } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs index 977a094bd1..2aa35e392e 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { - internal class Vp8LHashChain + internal class Vp8LHashChain : IDisposable { private const uint HashMultiplierHi = 0xc6a4a793u; @@ -28,14 +28,19 @@ internal class Vp8LHashChain /// private const int WindowSize = (1 << WindowSizeBits) - 120; + private readonly MemoryAllocator memoryAllocator; + + private bool disposed; + /// /// Initializes a new instance of the class. /// + /// The memory allocator. /// The size off the chain. - public Vp8LHashChain(int size) + public Vp8LHashChain(MemoryAllocator memoryAllocator, int size) { - this.OffsetLength = new uint[size]; - this.OffsetLength.AsSpan().Fill(0xcdcdcdcd); + this.memoryAllocator = memoryAllocator; + this.OffsetLength = this.memoryAllocator.Allocate(size, AllocationOptions.Clean); this.Size = size; } @@ -45,16 +50,16 @@ public Vp8LHashChain(int size) /// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 << 20). /// The lower 12 bits contain the length of the match. /// - public uint[] OffsetLength { get; } + public IMemoryOwner OffsetLength { get; } /// /// Gets the size of the hash chain. - /// This is the maximum size of the hash_chain that can be constructed. + /// This is the maximum size of the hashchain that can be constructed. /// Typically this is the pixel count (width x height) for a given image. /// public int Size { get; } - public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int quality, int xSize, int ySize, bool lowEffort) + public void Fill(ReadOnlySpan bgra, int quality, int xSize, int ySize, bool lowEffort) { int size = xSize * ySize; int iterMax = GetMaxItersForQuality(quality); @@ -63,20 +68,21 @@ public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int q if (size <= 2) { - this.OffsetLength[0] = 0; + this.OffsetLength.GetSpan()[0] = 0; return; } - using IMemoryOwner hashToFirstIndexBuffer = memoryAllocator.Allocate(HashSize); + using IMemoryOwner hashToFirstIndexBuffer = this.memoryAllocator.Allocate(HashSize); + using IMemoryOwner chainBuffer = this.memoryAllocator.Allocate(size, AllocationOptions.Clean); Span hashToFirstIndex = hashToFirstIndexBuffer.GetSpan(); + Span chain = chainBuffer.GetSpan(); // Initialize hashToFirstIndex array to -1. hashToFirstIndex.Fill(-1); - int[] chain = new int[size]; - // Fill the chain linking pixels with the same hash. bool bgraComp = bgra.Length > 1 && bgra[0] == bgra[1]; + Span tmp = stackalloc uint[2]; for (pos = 0; pos < size - 2;) { uint hashCode; @@ -85,7 +91,7 @@ public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int q { // Consecutive pixels with the same color will share the same hash. // We therefore use a different hash: the color and its repetition length. - uint[] tmp = new uint[2]; + tmp.Clear(); uint len = 1; tmp[0] = bgra[pos]; @@ -134,7 +140,8 @@ public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int q // Find the best match interval at each pixel, defined by an offset to the // pixel and a length. The right-most pixel cannot match anything to the right // (hence a best length of 0) and the left-most pixel nothing to the left (hence an offset of 0). - this.OffsetLength[0] = this.OffsetLength[size - 1] = 0; + Span offsetLength = this.OffsetLength.GetSpan(); + offsetLength[0] = offsetLength[size - 1] = 0; for (int basePosition = size - 2; basePosition > 0;) { int maxLen = LosslessUtils.MaxFindCopyLength(size - 1 - basePosition); @@ -208,7 +215,7 @@ public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int q uint maxBasePosition = (uint)basePosition; while (true) { - this.OffsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.MaxLengthBits) | (uint)bestLength; + offsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.MaxLengthBits) | (uint)bestLength; --basePosition; // Stop if we don't have a match or if we are out of bounds. @@ -242,10 +249,10 @@ public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int q } [MethodImpl(InliningOptions.ShortMethod)] - public int FindLength(int basePosition) => (int)(this.OffsetLength[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); + public int FindLength(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); [MethodImpl(InliningOptions.ShortMethod)] - public int FindOffset(int basePosition) => (int)(this.OffsetLength[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); + public int FindOffset(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); /// /// Calculates the hash for a pixel pair. @@ -280,5 +287,25 @@ private static int GetWindowSizeForHashChain(int quality, int xSize) return maxWindowSize > WindowSize ? WindowSize : maxWindowSize; } + + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + this.OffsetLength.Dispose(); + } + + this.disposed = true; + } + } + + /// + public void Dispose() + { + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } From 40b6f4e55bb12127bc84d33c8885dd21a74fb768 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Nov 2021 17:00:34 +0100 Subject: [PATCH 02/12] Change huffman code to a struct --- .../Formats/Webp/Lossless/HTreeGroup.cs | 4 -- .../Formats/Webp/Lossless/HuffmanCode.cs | 2 +- .../Formats/Webp/Lossless/HuffmanUtils.cs | 45 ++++++++++--------- .../Webp/Lossless/WebpLosslessDecoder.cs | 14 +++--- .../WebP/ColorSpaceTransformUtilsTests.cs | 2 +- .../Formats/WebP/LossyUtilsTests.cs | 2 +- .../Formats/WebP/QuantEncTests.cs | 2 +- .../Formats/WebP/Vp8EncodingTests.cs | 2 +- 8 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs index a038248f1a..09ceb0334a 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs @@ -19,10 +19,6 @@ public HTreeGroup(uint packedTableSize) { this.HTrees = new List(WebpConstants.HuffmanCodesPerMetaCode); this.PackedTable = new HuffmanCode[packedTableSize]; - for (int i = 0; i < packedTableSize; i++) - { - this.PackedTable[i] = new HuffmanCode(); - } } /// diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs index f75c64de11..efb9283568 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. /// [DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")] - internal class HuffmanCode + internal struct HuffmanCode { /// /// Gets or sets the number of bits used for this symbol. diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs index 5db01ca1c7..66170b85fd 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { @@ -307,9 +308,9 @@ public static int CreateCompressedHuffmanTree(HuffmanTreeCode tree, HuffmanTreeT public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) { - Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); - Guard.NotNull(codeLengths, nameof(codeLengths)); - Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); + DebugGuard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); + DebugGuard.NotNull(codeLengths, nameof(codeLengths)); + DebugGuard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); // sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length. int[] sorted = new int[codeLengthsSize]; @@ -467,27 +468,27 @@ private static int CodeRepeatedZeros(int repetitions, Span tok break; } - else if (repetitions < 11) + + if (repetitions < 11) { tokens[pos].Code = 17; tokens[pos].ExtraBits = (byte)(repetitions - 3); pos++; break; } - else if (repetitions < 139) + + if (repetitions < 139) { tokens[pos].Code = 18; tokens[pos].ExtraBits = (byte)(repetitions - 11); pos++; break; } - else - { - tokens[pos].Code = 18; - tokens[pos].ExtraBits = 0x7f; // 138 repeated 0s - pos++; - repetitions -= 138; - } + + tokens[pos].Code = 18; + tokens[pos].ExtraBits = 0x7f; // 138 repeated 0s + pos++; + repetitions -= 138; } return pos; @@ -519,20 +520,19 @@ private static int CodeRepeatedValues(int repetitions, Span to break; } - else if (repetitions < 7) + + if (repetitions < 7) { tokens[pos].Code = 16; tokens[pos].ExtraBits = (byte)(repetitions - 3); pos++; break; } - else - { - tokens[pos].Code = 16; - tokens[pos].ExtraBits = 3; - pos++; - repetitions -= 6; - } + + tokens[pos].Code = 16; + tokens[pos].ExtraBits = 3; + pos++; + repetitions -= 6; } return pos; @@ -541,7 +541,7 @@ private static int CodeRepeatedValues(int repetitions, Span to /// /// Get the actual bit values for a tree of bit depths. /// - /// The hiffman tree. + /// The huffman tree. private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree) { // 0 bit-depth means that the symbol does not exist. @@ -628,7 +628,7 @@ private static int NextTableBitSize(int[] count, int len, int rootBits) /// private static void ReplicateValue(Span table, int step, int end, HuffmanCode code) { - Guard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step"); + DebugGuard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step"); do { @@ -656,6 +656,7 @@ private static int GetNextKey(int key, int len) /// /// Heuristics for selecting the stride ranges to collapse. /// + [MethodImpl(InliningOptions.ShortMethod)] private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b) => Math.Abs(a - b) < 4; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index 4f7a4eb3d8..82fd13c7d5 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -834,10 +834,10 @@ private uint ReadPackedSymbols(Span group, Span pixelData, int private void BuildPackedTable(HTreeGroup hTreeGroup) { - for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; ++code) + for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; code++) { uint bits = code; - HuffmanCode huff = hTreeGroup.PackedTable[bits]; + ref HuffmanCode huff = ref hTreeGroup.PackedTable[bits]; HuffmanCode hCode = hTreeGroup.HTrees[HuffIndex.Green][bits]; if (hCode.Value >= WebpConstants.NumLiteralCodes) { @@ -848,10 +848,10 @@ private void BuildPackedTable(HTreeGroup hTreeGroup) { huff.BitsUsed = 0; huff.Value = 0; - bits >>= AccumulateHCode(hCode, 8, huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, huff); + bits >>= AccumulateHCode(hCode, 8, ref huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, ref huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, ref huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, ref huff); } } } @@ -992,7 +992,7 @@ private static void CopyBlock8B(Span data, int pos, int dist, int length) } [MethodImpl(InliningOptions.ShortMethod)] - private static int AccumulateHCode(HuffmanCode hCode, int shift, HuffmanCode huff) + private static int AccumulateHCode(HuffmanCode hCode, int shift, ref HuffmanCode huff) { huff.BitsUsed += hCode.BitsUsed; huff.Value |= hCode.Value << shift; diff --git a/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs index 5306a8c786..f7eef0d85c 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class ColorSpaceTransformUtilsTests diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index 69a24843ca..907b18300c 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class LossyUtilsTests diff --git a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs index 55738199b7..80b5f0a531 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class QuantEncTests diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs index 17c9beb9b7..6bcb4f21f4 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class Vp8EncodingTests From 44316b223157d59873753548cedfe9b176fd9e80 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Nov 2021 18:01:45 +0100 Subject: [PATCH 03/12] Make HTreeGroup a struct --- src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs index 09ceb0334a..6c2217eb6e 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs @@ -13,12 +13,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// - UsePackedTable: few enough literal symbols, so all the bit codes can fit into a small look-up table PackedTable[] /// The common literal base, if applicable, is stored in 'LiteralArb'. /// - internal class HTreeGroup + internal struct HTreeGroup { public HTreeGroup(uint packedTableSize) { this.HTrees = new List(WebpConstants.HuffmanCodesPerMetaCode); this.PackedTable = new HuffmanCode[packedTableSize]; + this.IsTrivialCode = false; + this.IsTrivialLiteral = false; + this.LiteralArb = 0; + this.UsePackedTable = false; } /// From c712f98e055caa7a23eedcce1bc8cfe476664453 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Nov 2021 18:17:55 +0100 Subject: [PATCH 04/12] Change CodeLengthCodeOrder, LiteralMap and Norm to ReadOnlySpan --- .../Formats/Webp/Lossless/WebpLosslessDecoder.cs | 13 ++++++------- src/ImageSharp/Formats/Webp/WebpLookupTables.cs | 3 ++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index 82fd13c7d5..82bd32a020 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -65,15 +65,8 @@ internal sealed class WebpLosslessDecoder FixedTableSize + 2704 }; - private static readonly byte[] CodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - private static readonly int NumCodeLengthCodes = CodeLengthCodeOrder.Length; - private static readonly byte[] LiteralMap = - { - 0, 1, 1, 1, 0 - }; - /// /// Initializes a new instance of the class. /// @@ -87,6 +80,12 @@ public WebpLosslessDecoder(Vp8LBitReader bitReader, MemoryAllocator memoryAlloca this.configuration = configuration; } + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan CodeLengthCodeOrder => new byte[] { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan LiteralMap => new byte[] { 0, 1, 1, 1, 0 }; + /// /// Decodes the image from the stream using the bitreader. /// diff --git a/src/ImageSharp/Formats/Webp/WebpLookupTables.cs b/src/ImageSharp/Formats/Webp/WebpLookupTables.cs index bf47b01bca..c894114354 100644 --- a/src/ImageSharp/Formats/Webp/WebpLookupTables.cs +++ b/src/ImageSharp/Formats/Webp/WebpLookupTables.cs @@ -239,7 +239,8 @@ internal static class WebpLookupTables } }; - public static readonly byte[] Norm = + // This uses C#'s compiler optimization to refer to assembly's static data directly. + public static ReadOnlySpan Norm => new byte[] { // renorm_sizes[i] = 8 - log2(i) 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, From 7c1f05b0a8aebad8a5515cb8c5c42780b0fc9a22 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 11:28:02 +0100 Subject: [PATCH 05/12] Avoid allocating too many CostInterval objects --- .../Formats/Webp/Lossless/CostManager.cs | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs index 94c7bd8470..213971764b 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs @@ -14,6 +14,10 @@ internal class CostManager { private CostInterval head; + private const int FreeIntervalsStartCount = 25; + + private readonly Stack freeIntervals = new(FreeIntervalsStartCount); + public CostManager(ushort[] distArray, int pixCount, CostModel costModel) { int costCacheSize = pixCount > BackwardReferenceEncoder.MaxLength ? BackwardReferenceEncoder.MaxLength : pixCount; @@ -24,6 +28,11 @@ public CostManager(ushort[] distArray, int pixCount, CostModel costModel) this.DistArray = distArray; this.Count = 0; + for (int i = 0; i < FreeIntervalsStartCount; i++) + { + this.freeIntervals.Push(new CostInterval()); + } + // Fill in the cost cache. this.CacheIntervalsSize++; this.CostCache.Add(costModel.GetLengthCost(0)); @@ -201,10 +210,8 @@ public void PushInterval(double distanceCost, int position, int len) this.InsertInterval(interval, interval.Cost, interval.Index, end, endOriginal); break; } - else - { - interval.End = start; - } + + interval.End = start; } } @@ -226,6 +233,10 @@ private void PopInterval(CostInterval interval) this.ConnectIntervals(interval.Previous, interval.Next); this.Count--; + + interval.Next = null; + interval.Previous = null; + this.freeIntervals.Push(interval); } private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end) @@ -236,13 +247,19 @@ private void InsertInterval(CostInterval intervalIn, float cost, int position, i } // TODO: should we use COST_CACHE_INTERVAL_SIZE_MAX? - var intervalNew = new CostInterval() + CostInterval intervalNew; + if (this.freeIntervals.Count > 0) { - Cost = cost, - Start = start, - End = end, - Index = position - }; + intervalNew = this.freeIntervals.Pop(); + intervalNew.Cost = cost; + intervalNew.Start = start; + intervalNew.End = end; + intervalNew.Index = position; + } + else + { + intervalNew = new CostInterval() { Cost = cost, Start = start, End = end, Index = position }; + } this.PositionOrphanInterval(intervalNew, intervalIn); this.Count++; From b8925e1aaf231c416a3b0e77d2e66f46c45797f8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 12:33:24 +0100 Subject: [PATCH 06/12] CostsManager now uses MemoryAllocator --- .../Webp/Lossless/BackwardReferenceEncoder.cs | 32 ++++++----- .../Formats/Webp/Lossless/CostManager.cs | 57 ++++++++++++++----- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs index 93f6372c64..82aa3ff7b1 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; using SixLabors.ImageSharp.Memory; @@ -102,7 +103,7 @@ public static Vp8LBackwardRefs GetBackwardReferences( if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25) { Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox; - BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst); + BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst); var histo = new Vp8LHistogram(worst, cacheBits); double bitCostTrace = histo.EstimateBits(stats, bitsEntropy); if (bitCostTrace < bitCostBest) @@ -236,6 +237,7 @@ private static int CalculateBestCacheSize(ReadOnlySpan bgra, int quality, private static void BackwardReferencesTraceBackwards( int xSize, int ySize, + MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChain, @@ -243,22 +245,24 @@ private static void BackwardReferencesTraceBackwards( Vp8LBackwardRefs refsDst) { int distArraySize = xSize * ySize; - ushort[] distArray = new ushort[distArraySize]; + using IMemoryOwner distArrayBuffer = memoryAllocator.Allocate(distArraySize); + Span distArray = distArrayBuffer.GetSpan(); - BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray); + BackwardReferencesHashChainDistanceOnly(xSize, ySize, memoryAllocator, bgra, cacheBits, hashChain, refsSrc, distArrayBuffer); int chosenPathSize = TraceBackwards(distArray, distArraySize); - Span chosenPath = distArray.AsSpan(distArraySize - chosenPathSize); + Span chosenPath = distArray.Slice(distArraySize - chosenPathSize); BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); } private static void BackwardReferencesHashChainDistanceOnly( int xSize, int ySize, + MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs, - ushort[] distArray) + IMemoryOwner distArrayBuffer) { int pixCount = xSize * ySize; bool useColorCache = cacheBits > 0; @@ -277,22 +281,24 @@ private static void BackwardReferencesHashChainDistanceOnly( } costModel.Build(xSize, cacheBits, refs); - var costManager = new CostManager(distArray, pixCount, costModel); + var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel); + Span costManagerCosts = costManager.Costs.GetSpan(); + Span distArray = distArrayBuffer.GetSpan(); // We loop one pixel at a time, but store all currently best points to non-processed locations from this point. distArray[0] = 0; // Add first pixel as literal. - AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManager.Costs, distArray); + AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManagerCosts, distArray); for (int i = 1; i < pixCount; i++) { - float prevCost = costManager.Costs[i - 1]; + float prevCost = costManagerCosts[i - 1]; int offset = hashChain.FindOffset(i); int len = hashChain.FindLength(i); // Try adding the pixel as a literal. - AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManager.Costs, distArray); + AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManagerCosts, distArray); // If we are dealing with a non-literal. if (len >= 2) @@ -336,7 +342,7 @@ private static void BackwardReferencesHashChainDistanceOnly( costManager.UpdateCostAtIndex(j - 1, false); costManager.UpdateCostAtIndex(j, false); - costManager.PushInterval(costManager.Costs[j - 1] + offsetCost, j, lenJ); + costManager.PushInterval(costManagerCosts[j - 1] + offsetCost, j, lenJ); reach = j + lenJ - 1; } } @@ -348,7 +354,7 @@ private static void BackwardReferencesHashChainDistanceOnly( } } - private static int TraceBackwards(ushort[] distArray, int distArraySize) + private static int TraceBackwards(Span distArray, int distArraySize) { int chosenPathSize = 0; int pathPos = distArraySize; @@ -428,8 +434,8 @@ private static void AddSingleLiteralWithCostModel( int idx, bool useColorCache, float prevCost, - float[] cost, - ushort[] distArray) + Span cost, + Span distArray) { double costVal = prevCost; uint color = bgra[idx]; diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs index 213971764b..3ee1021386 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs @@ -1,7 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers; using System.Collections.Generic; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { @@ -10,21 +13,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// It caches the different CostCacheInterval, caches the different /// GetLengthCost(costModel, k) in costCache and the CostInterval's. /// - internal class CostManager + internal class CostManager : IDisposable { + private bool disposed; + private CostInterval head; private const int FreeIntervalsStartCount = 25; private readonly Stack freeIntervals = new(FreeIntervalsStartCount); - public CostManager(ushort[] distArray, int pixCount, CostModel costModel) + public CostManager(MemoryAllocator memoryAllocator, IMemoryOwner distArray, int pixCount, CostModel costModel) { int costCacheSize = pixCount > BackwardReferenceEncoder.MaxLength ? BackwardReferenceEncoder.MaxLength : pixCount; this.CacheIntervals = new List(); this.CostCache = new List(); - this.Costs = new float[pixCount]; + this.Costs = memoryAllocator.Allocate(pixCount); this.DistArray = distArray; this.Count = 0; @@ -73,10 +78,7 @@ public CostManager(ushort[] distArray, int pixCount, CostModel costModel) } // Set the initial costs high for every pixel as we will keep the minimum. - for (int i = 0; i < pixCount; i++) - { - this.Costs[i] = 1e38f; - } + this.Costs.GetSpan().Fill(1e38f); } /// @@ -91,9 +93,9 @@ public CostManager(ushort[] distArray, int pixCount, CostModel costModel) public int CacheIntervalsSize { get; } - public float[] Costs { get; } + public IMemoryOwner Costs { get; } - public ushort[] DistArray { get; } + public IMemoryOwner DistArray { get; } public List CacheIntervals { get; } @@ -137,6 +139,8 @@ public void PushInterval(double distanceCost, int position, int len) // interval logic, just serialize it right away. This constant is empirical. int skipDistance = 10; + Span costs = this.Costs.GetSpan(); + Span distArray = this.DistArray.GetSpan(); if (len < skipDistance) { for (int j = position; j < position + len; j++) @@ -144,10 +148,10 @@ public void PushInterval(double distanceCost, int position, int len) int k = j - position; float costTmp = (float)(distanceCost + this.CostCache[k]); - if (this.Costs[j] > costTmp) + if (costs[j] > costTmp) { - this.Costs[j] = costTmp; - this.DistArray[j] = (ushort)(k + 1); + costs[j] = costTmp; + distArray[j] = (ushort)(k + 1); } } @@ -314,12 +318,35 @@ private void ConnectIntervals(CostInterval prev, CostInterval next) /// private void UpdateCost(int i, int position, float cost) { + Span costs = this.Costs.GetSpan(); + Span distArray = this.DistArray.GetSpan(); int k = i - position; - if (this.Costs[i] > cost) + if (costs[i] > cost) + { + costs[i] = cost; + distArray[i] = (ushort)(k + 1); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) { - this.Costs[i] = cost; - this.DistArray[i] = (ushort)(k + 1); + if (disposing) + { + this.Costs.Dispose(); + } + + this.disposed = true; } } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } From e03709d7b6d80ea0a1681cecfe92842a9ac39db6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 13:18:43 +0100 Subject: [PATCH 07/12] Make StorageOrder bytes a ReadOnlySpan --- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 48f7d0e2b7..8ca80d5ab4 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -137,6 +137,12 @@ public Vp8LEncoder( } } + // RFC 1951 will calm you down if you are worried about this funny sequence. + // This sequence is tuned from that, but more weighted for lower symbol count, + // and more spiking histograms. + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan StorageOrder => new byte[] { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + // This uses C#'s compiler optimization to refer to assembly's static data directly. private static ReadOnlySpan Order => new byte[] { 1, 2, 0, 3 }; @@ -942,16 +948,11 @@ private void StoreHuffmanTreeToBitMask(HuffmanTreeToken[] tokens, int numTokens, private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitDepth) { - // RFC 1951 will calm you down if you are worried about this funny sequence. - // This sequence is tuned from that, but more weighted for lower symbol count, - // and more spiking histograms. - byte[] storageOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - // Throw away trailing zeros: int codesToStore = WebpConstants.CodeLengthCodes; for (; codesToStore > 4; codesToStore--) { - if (codeLengthBitDepth[storageOrder[codesToStore - 1]] != 0) + if (codeLengthBitDepth[StorageOrder[codesToStore - 1]] != 0) { break; } @@ -960,7 +961,7 @@ private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitDepth) this.bitWriter.PutBits((uint)codesToStore - 4, 4); for (int i = 0; i < codesToStore; i++) { - this.bitWriter.PutBits(codeLengthBitDepth[storageOrder[i]], 3); + this.bitWriter.PutBits(codeLengthBitDepth[StorageOrder[i]], 3); } } From 92ac52221cf2a34b20b5c0aaca8124b522dba449 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 13:58:34 +0100 Subject: [PATCH 08/12] Remove not needed DeepClone --- src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs | 4 +--- src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs index 0376311ed9..07fec7f990 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Represents the Huffman tree. /// [DebuggerDisplay("TotalCount = {TotalCount}, Value = {Value}, Left = {PoolIndexLeft}, Right = {PoolIndexRight}")] - internal struct HuffmanTree : IDeepCloneable + internal struct HuffmanTree { /// /// Initializes a new instance of the struct. @@ -57,7 +57,5 @@ public static int Compare(HuffmanTree t1, HuffmanTree t2) return t1.Value < t2.Value ? -1 : 1; } - - public IDeepCloneable DeepClone() => new HuffmanTree(this); } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs index 66170b85fd..56f2ee9cef 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs @@ -219,8 +219,8 @@ public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int while (treeSize > 1) { // Finish when we have only one root. - treePool[treePoolSize++] = (HuffmanTree)tree[treeSize - 1].DeepClone(); - treePool[treePoolSize++] = (HuffmanTree)tree[treeSize - 2].DeepClone(); + treePool[treePoolSize++] = tree[treeSize - 1]; + treePool[treePoolSize++] = tree[treeSize - 2]; int count = treePool[treePoolSize - 1].TotalCount + treePool[treePoolSize - 2].TotalCount; treeSize -= 2; @@ -239,7 +239,7 @@ public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int int startIdx = endIdx + num - 1; for (int i = startIdx; i >= endIdx; i--) { - tree[i] = (HuffmanTree)tree[i - 1].DeepClone(); + tree[i] = tree[i - 1]; } tree[k].TotalCount = count; From d0382bbb03c1c4f7c1447bcd46d8378d85d7b314 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 14:51:12 +0100 Subject: [PATCH 09/12] Change PixOrCopyMode, HistoIx and EntropyIx enums to be a byte --- src/ImageSharp/Formats/Webp/EntropyIx.cs | 2 +- src/ImageSharp/Formats/Webp/HistoIx.cs | 2 +- src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/EntropyIx.cs b/src/ImageSharp/Formats/Webp/EntropyIx.cs index c72ddeb42d..98e8b7e164 100644 --- a/src/ImageSharp/Formats/Webp/EntropyIx.cs +++ b/src/ImageSharp/Formats/Webp/EntropyIx.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// These five modes are evaluated and their respective entropy is computed. /// - internal enum EntropyIx + internal enum EntropyIx : byte { Direct = 0, diff --git a/src/ImageSharp/Formats/Webp/HistoIx.cs b/src/ImageSharp/Formats/Webp/HistoIx.cs index 68b00394b0..83522f9da8 100644 --- a/src/ImageSharp/Formats/Webp/HistoIx.cs +++ b/src/ImageSharp/Formats/Webp/HistoIx.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { - internal enum HistoIx + internal enum HistoIx : byte { HistoAlpha = 0, diff --git a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs index 0d7023ffc2..26099b9023 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { - internal enum PixOrCopyMode + internal enum PixOrCopyMode : byte { Literal, From e011450a90380015893f12f1f5d27aecd8e021ee Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 15:15:24 +0100 Subject: [PATCH 10/12] Dispose cost manager and hashChainBox --- .../Formats/Webp/Lossless/BackwardReferenceEncoder.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs index 82aa3ff7b1..c394a8caa8 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs @@ -114,6 +114,8 @@ public static Vp8LBackwardRefs GetBackwardReferences( BackwardReferences2DLocality(width, best); + hashChainBox?.Dispose(); + return best; } @@ -281,7 +283,7 @@ private static void BackwardReferencesHashChainDistanceOnly( } costModel.Build(xSize, cacheBits, refs); - var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel); + using var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel); Span costManagerCosts = costManager.Costs.GetSpan(); Span distArray = distArrayBuffer.GetSpan(); From fae8f0dc319c4c46fa73e7412e9b5db6b58a9e93 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 16:17:29 +0100 Subject: [PATCH 11/12] Initialize backward refs with the pixel count --- src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs | 2 +- src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs index 502728b15f..fca4ec59f6 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class Vp8LBackwardRefs { - public Vp8LBackwardRefs() => this.Refs = new List(); + public Vp8LBackwardRefs(int pixels) => this.Refs = new List(pixels); /// /// Gets or sets the common block-size. diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 8ca80d5ab4..adabd0ac3f 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -130,7 +130,7 @@ public Vp8LEncoder( int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; for (int i = 0; i < this.Refs.Length; i++) { - this.Refs[i] = new Vp8LBackwardRefs + this.Refs[i] = new Vp8LBackwardRefs(pixelCount) { BlockSize = refsBlockSize < MinBlockSize ? MinBlockSize : refsBlockSize }; From 14508498d9f0d709e31d4d4e4e44a740cb3353c2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 23 Nov 2021 09:00:33 +0100 Subject: [PATCH 12/12] Seal classes and avoid calling SuppressFinalize --- .../Formats/Webp/Lossless/CostManager.cs | 24 ++----------------- .../Formats/Webp/Lossless/PixOrCopy.cs | 2 +- .../Formats/Webp/Lossless/Vp8LHashChain.cs | 23 ++---------------- 3 files changed, 5 insertions(+), 44 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs index 3ee1021386..c121a41a1a 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs @@ -13,10 +13,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// It caches the different CostCacheInterval, caches the different /// GetLengthCost(costModel, k) in costCache and the CostInterval's. /// - internal class CostManager : IDisposable + internal sealed class CostManager : IDisposable { - private bool disposed; - private CostInterval head; private const int FreeIntervalsStartCount = 25; @@ -328,25 +326,7 @@ private void UpdateCost(int i, int position, float cost) } } - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing) - { - this.Costs.Dispose(); - } - - this.disposed = true; - } - } - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - this.Dispose(disposing: true); - GC.SuppressFinalize(this); - } + public void Dispose() => this.Costs.Dispose(); } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs index 6cd109121d..96cdc3cbc5 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { [DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")] - internal class PixOrCopy + internal sealed class PixOrCopy { public PixOrCopyMode Mode { get; set; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs index 2aa35e392e..1bc7613a90 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { - internal class Vp8LHashChain : IDisposable + internal sealed class Vp8LHashChain : IDisposable { private const uint HashMultiplierHi = 0xc6a4a793u; @@ -30,8 +30,6 @@ internal class Vp8LHashChain : IDisposable private readonly MemoryAllocator memoryAllocator; - private bool disposed; - /// /// Initializes a new instance of the class. /// @@ -288,24 +286,7 @@ private static int GetWindowSizeForHashChain(int quality, int xSize) return maxWindowSize > WindowSize ? WindowSize : maxWindowSize; } - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing) - { - this.OffsetLength.Dispose(); - } - - this.disposed = true; - } - } - /// - public void Dispose() - { - this.Dispose(disposing: true); - GC.SuppressFinalize(this); - } + public void Dispose() => this.OffsetLength.Dispose(); } }