From 3079574c3cd43a1db5a4b0aeb693d7db93f2f662 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 12 Nov 2019 21:38:03 +1100 Subject: [PATCH] Make DeflaterEngine disposable --- src/ImageSharp/Formats/Png/Zlib/Deflater.cs | 40 +-- .../Formats/Png/Zlib/DeflaterEngine.cs | 273 ++++++++---------- 2 files changed, 129 insertions(+), 184 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs index 33b3019b8..5732f82d2 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs @@ -54,7 +54,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private DeflaterEngine engine; private bool isDisposed; - private const int IsSetDict = 0x01; private const int IsFlushing = 0x04; private const int IsFinishing = 0x08; private const int BusyState = 0x10; @@ -82,8 +81,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } this.pending = new DeflaterPendingBuffer(memoryAllocator); - this.engine = new DeflaterEngine(this.pending, true); - this.engine.Strategy = DeflateStrategy.Default; + + // TODO: Possibly provide DeflateStrategy as an option. + this.engine = new DeflaterEngine(memoryAllocator, this.pending, DeflateStrategy.Default); + this.SetLevel(level); this.Reset(); } @@ -233,37 +234,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib DeflateThrowHelper.ThrowAlreadyClosed(); } - if (this.state < BusyState) - { - // Output header - int header = (Deflated + ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8; - int levelFlags = (this.level - 1) >> 1; - if (levelFlags < 0 || levelFlags > 3) - { - levelFlags = 3; - } - - header |= levelFlags << 6; - if ((this.state & IsSetDict) != 0) - { - // Dictionary was set - header |= DeflaterConstants.PRESET_DICT; - } - - header += 31 - (header % 31); - - this.pending.WriteShortMSB(header); - if ((this.state & IsSetDict) != 0) - { - int chksum = this.engine.Adler; - this.engine.ResetAdler(); - this.pending.WriteShortMSB(chksum >> 16); - this.pending.WriteShortMSB(chksum & 0xffff); - } - - this.state = BusyState | (this.state & (IsFlushing | IsFinishing)); - } - while (true) { int count = this.pending.Flush(output, offset, length); @@ -326,9 +296,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib if (disposing) { this.pending.Dispose(); + this.engine.Dispose(); } this.pending = null; + this.engine = null; this.isDisposed = true; } } diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs index ddf571884..0870f629c 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs @@ -2,8 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; -using System.Text; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Png.Zlib { @@ -48,30 +49,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// Low level compression engine for deflate algorithm which uses a 32K sliding window /// with secondary compression from Huffman/Shannon-Fano codes. /// - public class DeflaterEngine + public sealed unsafe class DeflaterEngine : IDisposable { private const int TooFar = 4096; // Hash index of string to be inserted private int insertHashIndex; - /// - /// Hashtable, hashing three characters to an index for window, so - /// that window[index]..window[index+2] have this hash code. - /// Note that the array should really be unsigned short, so you need - /// to and the values with 0xFFFF. - /// - private short[] head; - - /// - /// prev[index & WMASK] points to the previous index that has the - /// same hash code as the string starting at index. This way - /// entries with the same hash code are in a linked list. - /// Note that the array should really be unsigned short, so you need - /// to and the values with 0xFFFF. - /// - private short[] prev; - private int matchStart; // Length of best match @@ -95,16 +79,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// private int lookahead; - /// - /// This array contains the part of the uncompressed stream that - /// is of relevance. The current character is indexed by strstart. - /// - private byte[] window; - private int maxChain; - private int maxLazy; - private int niceLength; - private int goodLength; - /// /// The current compression function. /// @@ -115,11 +89,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// private byte[] inputBuf; - /// - /// The total bytes of input read. - /// - private long totalIn; - /// /// The offset into inputBuf, where input data starts. /// @@ -130,31 +99,59 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// private int inputEnd; + private DeflateStrategy strategy; private DeflaterPendingBuffer pending; private DeflaterHuffman huffman; + private bool isDisposed; /// - /// The adler checksum + /// Hashtable, hashing three characters to an index for window, so + /// that window[index]..window[index+2] have this hash code. + /// Note that the array should really be unsigned short, so you need + /// to and the values with 0xFFFF. /// - private Adler32 adler; + private short[] head; + + /// + /// prev[index & WMASK] points to the previous index that has the + /// same hash code as the string starting at index. This way + /// entries with the same hash code are in a linked list. + /// Note that the array should really be unsigned short, so you need + /// to and the values with 0xFFFF. + /// + private short[] prev; + + /// + /// This array contains the part of the uncompressed stream that + /// is of relevance. The current character is indexed by strstart. + /// + private readonly IManagedByteBuffer windowBuffer; + private MemoryHandle windowBufferHandle; + private byte[] window; + private readonly byte* pinnedWindowPointer; + + private int maxChain; + private int maxLazy; + private int niceLength; + private int goodLength; /// /// Initializes a new instance of the class. /// + /// The memory allocator to use for buffer allocations. /// The pending buffer to use. - /// - /// If no adler calculation should be performed - /// - public DeflaterEngine(DeflaterPendingBuffer pending, bool noAdlerCalculation) + /// The deflate strategy to use. + public DeflaterEngine(MemoryAllocator memoryAllocator, DeflaterPendingBuffer pending, DeflateStrategy strategy) { this.pending = pending; this.huffman = new DeflaterHuffman(pending); - if (!noAdlerCalculation) - { - this.adler = new Adler32(); - } + this.strategy = strategy; + + this.windowBuffer = memoryAllocator.AllocateManagedByteBuffer(2 * DeflaterConstants.WSIZE); + this.window = this.windowBuffer.Array; + this.windowBufferHandle = this.windowBuffer.Memory.Pin(); + this.pinnedWindowPointer = (byte*)this.windowBufferHandle.Pointer; - this.window = new byte[2 * DeflaterConstants.WSIZE]; this.head = new short[DeflaterConstants.HASH_SIZE]; this.prev = new short[DeflaterConstants.WSIZE]; @@ -163,33 +160,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.blockStart = this.strstart = 1; } - /// - /// Gets the current value of Adler checksum - /// - public int Adler - { - get - { - return (this.adler != null) ? unchecked((int)this.adler.Value) : 0; - } - } - - /// - /// Gets the total data processed - /// - public long TotalIn - { - get - { - return this.totalIn; - } - } - - /// - /// Gets or sets the deflate strategy - /// - public DeflateStrategy Strategy { get; set; } - /// /// Deflate drives actual compression of data /// @@ -286,7 +256,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// The length of the dictionary data. public void SetDictionary(byte[] buffer, int offset, int length) { - this.adler?.Update(new ArraySegment(buffer, offset, length)); if (length < DeflaterConstants.MIN_MATCH) { return; @@ -318,10 +287,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib public void Reset() { this.huffman.Reset(); - this.adler?.Reset(); this.blockStart = this.strstart = 1; this.lookahead = 0; - this.totalIn = 0; this.prevAvailable = false; this.matchLen = DeflaterConstants.MIN_MATCH - 1; @@ -336,14 +303,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } } - /// - /// Reset Adler checksum - /// - public void ResetAdler() - { - this.adler?.Reset(); - } - /// /// Set the deflate level (0-9) /// @@ -386,7 +345,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib case DeflaterConstants.DEFLATE_SLOW: if (this.prevAvailable) { - this.huffman.TallyLit(this.window[this.strstart - 1] & 0xff); + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xFF); } if (this.strstart > this.blockStart) @@ -427,10 +386,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } Array.Copy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more); - this.adler?.Update(new ArraySegment(this.inputBuf, this.inputOff, more)); this.inputOff += more; - this.totalIn += more; this.lookahead += more; } @@ -440,9 +397,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } } + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + this.Dispose(true); + GC.SuppressFinalize(this); + } + + [MethodImpl(InliningOptions.ShortMethod)] private void UpdateHash() { - this.insertHashIndex = (this.window[this.strstart] << DeflaterConstants.HASH_SHIFT) ^ this.window[this.strstart + 1]; + byte* pinned = this.pinnedWindowPointer; + this.insertHashIndex = (pinned[this.strstart] << DeflaterConstants.HASH_SHIFT) ^ pinned[this.strstart + 1]; } /// @@ -453,7 +420,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private int InsertString() { short match; - int hash = ((this.insertHashIndex << DeflaterConstants.HASH_SHIFT) ^ this.window[this.strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK; + int hash = ((this.insertHashIndex << DeflaterConstants.HASH_SHIFT) ^ this.pinnedWindowPointer[this.strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK; this.prev[this.strstart & DeflaterConstants.WMASK] = match = this.head[hash]; this.head[hash] = unchecked((short)this.strstart); @@ -463,7 +430,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private void SlideWindow() { - Array.Copy(this.window, DeflaterConstants.WSIZE, this.window, 0, DeflaterConstants.WSIZE); + Unsafe.CopyBlockUnaligned(ref this.window[0], ref this.window[DeflaterConstants.WSIZE], DeflaterConstants.WSIZE); this.matchStart -= DeflaterConstants.WSIZE; this.strstart -= DeflaterConstants.WSIZE; this.blockStart -= DeflaterConstants.WSIZE; @@ -518,8 +485,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib return false; } - byte scan_end1 = window[scan + this.matchLen - 1]; - byte scan_end = window[scan + this.matchLen]; + byte* pinnedWindow = this.pinnedWindowPointer; + byte scan_end1 = pinnedWindow[scan + this.matchLen - 1]; + byte scan_end = pinnedWindow[scan + this.matchLen]; // Do not waste too much time if we already have a good match: if (this.matchLen >= this.goodLength) @@ -532,10 +500,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib match = curMatch; scan = this.strstart; - if (window[match + this.matchLen] != scan_end - || window[match + this.matchLen - 1] != scan_end1 - || window[match] != window[scan] - || window[++match] != window[++scan]) + if (pinnedWindow[match + this.matchLen] != scan_end + || pinnedWindow[match + this.matchLen - 1] != scan_end1 + || pinnedWindow[match] != pinnedWindow[scan] + || pinnedWindow[++match] != pinnedWindow[++scan]) { continue; } @@ -547,7 +515,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib switch ((scanMax - scan) % 8) { case 1: - if (window[++scan] == window[++match]) + if (pinnedWindow[++scan] == pinnedWindow[++match]) { break; } @@ -555,8 +523,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib break; case 2: - if (window[++scan] == window[++match] - && window[++scan] == window[++match]) + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) { break; } @@ -564,9 +532,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib break; case 3: - if (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]) + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) { break; } @@ -574,10 +542,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib break; case 4: - if (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]) + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) { break; } @@ -585,11 +553,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib break; case 5: - if (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]) + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) { break; } @@ -597,12 +565,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib break; case 6: - if (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]) + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) { break; } @@ -610,13 +578,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib break; case 7: - if (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]) + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) { break; } @@ -624,7 +592,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib break; } - if (window[scan] == window[match]) + if (pinnedWindow[scan] == pinnedWindow[match]) { // We check for insufficient lookahead only every 8th comparison; // the 256th check will be made at strstart + 258 unless lookahead is @@ -639,14 +607,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib break; } } - while (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]); + while (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]); } if (scan - this.strstart > this.matchLen) @@ -659,8 +627,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib break; } - scan_end1 = window[scan - 1]; - scan_end = window[scan]; + scan_end1 = pinnedWindow[scan - 1]; + scan_end = pinnedWindow[scan]; } } while ((curMatch = prev[curMatch & DeflaterConstants.WMASK] & 0xFFFF) > limit && --chainLength != 0); @@ -727,7 +695,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib int hashHead; if (this.lookahead >= DeflaterConstants.MIN_MATCH && (hashHead = this.InsertString()) != 0 && - this.Strategy != DeflateStrategy.HuffmanOnly && + this.strategy != DeflateStrategy.HuffmanOnly && this.strstart - hashHead <= DeflaterConstants.MAX_DIST && this.FindLongestMatch(hashHead)) { @@ -763,7 +731,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib else { // No match found - this.huffman.TallyLit(this.window[this.strstart] & 0xff); + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart] & 0xff); ++this.strstart; --this.lookahead; } @@ -793,7 +761,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { if (this.prevAvailable) { - this.huffman.TallyLit(this.window[this.strstart - 1] & 0xff); + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff); } this.prevAvailable = false; @@ -818,14 +786,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { int hashHead = this.InsertString(); - if (this.Strategy != DeflateStrategy.HuffmanOnly && + if (this.strategy != DeflateStrategy.HuffmanOnly && hashHead != 0 && this.strstart - hashHead <= DeflaterConstants.MAX_DIST && this.FindLongestMatch(hashHead)) { // longestMatch sets matchStart and matchLen // Discard match if too small and too far away - if (this.matchLen <= 5 && (this.Strategy == DeflateStrategy.Filtered || (this.matchLen == DeflaterConstants.MIN_MATCH && this.strstart - this.matchStart > TooFar))) + if (this.matchLen <= 5 && (this.strategy == DeflateStrategy.Filtered || (this.matchLen == DeflaterConstants.MIN_MATCH && this.strstart - this.matchStart > TooFar))) { this.matchLen = DeflaterConstants.MIN_MATCH - 1; } @@ -835,15 +803,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // previous match was better if ((prevLen >= DeflaterConstants.MIN_MATCH) && (this.matchLen <= prevLen)) { -#if DebugDeflation - if (DeflaterConstants.DEBUGGING) - { - for (int i = 0 ; i < matchLen; i++) { - if (window[strstart-1+i] != window[prevMatch + i]) - throw new ImageFormatException(); - } - } -#endif this.huffman.TallyDist(this.strstart - 1 - prevMatch, prevLen); prevLen -= 2; do @@ -866,7 +825,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { if (this.prevAvailable) { - this.huffman.TallyLit(this.window[this.strstart - 1] & 0xff); + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff); } this.prevAvailable = true; @@ -891,5 +850,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib return true; } + + private void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + this.windowBufferHandle.Dispose(); + this.windowBuffer.Dispose(); + } + + this.isDisposed = true; + } + } } }