diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs index d1588c384..77355e908 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs @@ -1,8 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Png.Zlib { @@ -141,9 +142,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { this.crc ^= CrcSeed; + ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan()); for (int i = 0; i < data.Length; i++) { - this.crc = CrcTable[(this.crc ^ data[i]) & 0xFF] ^ (this.crc >> 8); + this.crc = Unsafe.Add(ref crcTableRef, (int)((this.crc ^ data[i]) & 0xFF)) ^ (this.crc >> 8); } this.crc ^= CrcSeed; diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs b/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs index d7198c4ee..5f62b13c7 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs @@ -27,9 +27,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib public static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name); [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowFrequencyNotEmpty() => throw new InvalidOperationException("Huffman frequency entry non empty."); + public static void ThrowHeapViolated() => throw new InvalidOperationException("Huffman heap invariant violated."); [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowHeapViolated() => throw new InvalidOperationException("Huffman heap invariant violated."); + public static void ThrowNoDeflate() => throw new ImageFormatException("Cannot deflate all input."); } } diff --git a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs index d1560eb4b..fb2538f8c 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// This class compresses input with the deflate algorithm described in RFC 1951. /// It has several compression levels and three different strategies described below. /// - public sealed class Deflater : IDisposable + internal sealed class Deflater : IDisposable { /// /// The best and slowest compression level. This tries to find very diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs index 8f57f51f9..327279e72 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs @@ -49,7 +49,7 @@ 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 sealed unsafe class DeflaterEngine : IDisposable + internal sealed unsafe class DeflaterEngine : IDisposable { private const int TooFar = 4096; @@ -109,8 +109,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// Note that the array should really be unsigned short, so you need /// to and the values with 0xFFFF. /// - private readonly IMemoryOwner headBuffer; - private MemoryHandle headBufferHandle; + private IMemoryOwner headMemoryOwner; + private MemoryHandle headMemoryHandle; private readonly Memory head; private readonly short* pinnedHeadPointer; @@ -121,17 +121,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// Note that the array should really be unsigned short, so you need /// to and the values with 0xFFFF. /// - private readonly IMemoryOwner prevBuffer; - private MemoryHandle prevBufferHandle; + private IMemoryOwner prevMemoryOwner; + private MemoryHandle prevMemoryHandle; private readonly Memory prev; private readonly short* pinnedPrevPointer; /// /// This array contains the part of the uncompressed stream that - /// is of relevance. The current character is indexed by strstart. + /// is of relevance. The current character is indexed by strstart. /// - private readonly IManagedByteBuffer windowBuffer; - private MemoryHandle windowBufferHandle; + private IManagedByteBuffer windowMemoryOwner; + private MemoryHandle windowMemoryHandle; private readonly byte[] window; private readonly byte* pinnedWindowPointer; @@ -153,20 +153,20 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // Create pinned pointers to the various buffers to allow indexing // without bounds checks. - 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.windowMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(2 * DeflaterConstants.WSIZE); + this.window = this.windowMemoryOwner.Array; + this.windowMemoryHandle = this.windowMemoryOwner.Memory.Pin(); + this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer; - this.headBuffer = memoryAllocator.Allocate(DeflaterConstants.HASH_SIZE); - this.head = this.headBuffer.Memory; - this.headBufferHandle = this.headBuffer.Memory.Pin(); - this.pinnedHeadPointer = (short*)this.headBufferHandle.Pointer; + this.headMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.HASH_SIZE); + this.head = this.headMemoryOwner.Memory; + this.headMemoryHandle = this.headMemoryOwner.Memory.Pin(); + this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer; - this.prevBuffer = memoryAllocator.Allocate(DeflaterConstants.WSIZE); - this.prev = this.prevBuffer.Memory; - this.prevBufferHandle = this.prevBuffer.Memory.Pin(); - this.pinnedPrevPointer = (short*)this.prevBufferHandle.Pointer; + this.prevMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.WSIZE); + this.prev = this.prevMemoryOwner.Memory; + this.prevMemoryHandle = this.prevMemoryOwner.Memory.Pin(); + this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer; // We start at index 1, to avoid an implementation deficiency, that // we cannot build a repeat pattern at index 0. @@ -377,8 +377,27 @@ 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); + if (!this.isDisposed) + { + this.huffman.Dispose(); + + this.windowMemoryHandle.Dispose(); + this.windowMemoryOwner.Dispose(); + + this.headMemoryHandle.Dispose(); + this.headMemoryOwner.Dispose(); + + this.prevMemoryHandle.Dispose(); + this.prevMemoryOwner.Dispose(); + + this.windowMemoryOwner = null; + this.headMemoryOwner = null; + this.prevMemoryOwner = null; + this.huffman = null; + + this.isDisposed = true; + } + GC.SuppressFinalize(this); } @@ -830,29 +849,5 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib return true; } - - private void Dispose(bool disposing) - { - if (!this.isDisposed) - { - if (disposing) - { - this.huffman.Dispose(); - - this.windowBufferHandle.Dispose(); - this.windowBuffer.Dispose(); - - this.headBufferHandle.Dispose(); - this.headBuffer.Dispose(); - - this.prevBufferHandle.Dispose(); - this.prevBuffer.Dispose(); - } - - this.huffman = null; - - this.isDisposed = true; - } - } } } diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs index 50aa1c095..7118703d0 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// Performs Deflate Huffman encoding. /// - public sealed unsafe class DeflaterHuffman : IDisposable + internal sealed unsafe class DeflaterHuffman : IDisposable { private const int BufferSize = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); @@ -194,7 +194,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib int dc = Dcode(dist); this.distTree.WriteSymbol(pendingBuffer, dc); - bits = (dc / 2) - 1; + bits = (dc >> 1) - 1; if (bits > 0) { this.Pending.WriteBits(dist & ((1 << bits) - 1), bits); @@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.distTree.Frequencies[dc]++; if (dc >= 4) { - this.extraBits += (dc / 2) - 1; + this.extraBits += (dc >> 1) - 1; } return this.IsFull(); @@ -443,9 +443,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private readonly int elementCount; + private readonly MemoryAllocator memoryAllocator; + private IMemoryOwner codesMemoryOwner; private MemoryHandle codesMemoryHandle; - private short* codes; + private readonly short* codes; private IMemoryOwner frequenciesMemoryOwner; private MemoryHandle frequenciesMemoryHandle; @@ -455,8 +457,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength) { + this.memoryAllocator = memoryAllocator; this.elementCount = elements; - this.minNumCodes = minCodes; this.maxLength = maxLength; @@ -497,27 +499,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib public void WriteSymbol(DeflaterPendingBuffer pendingBuffer, int code) => pendingBuffer.WriteBits(this.codes[code] & 0xFFFF, this.Length[code]); - /// - /// Check that all frequencies are zero - /// - /// - /// At least one frequency is non-zero - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void CheckEmpty() - { - bool empty = true; - for (int i = 0; i < this.elementCount; i++) - { - empty &= this.Frequencies[i] == 0; - } - - if (!empty) - { - DeflateThrowHelper.ThrowFrequencyNotEmpty(); - } - } - /// /// Set static codes and length /// @@ -569,130 +550,141 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // // The binary tree is encoded in an array: 0 is root node and // the nodes 2*n+1, 2*n+2 are the child nodes of node n. - // Maxes out at 286 * 4 - Span heap = stackalloc int[numSymbols]; - ref int heapRef = ref MemoryMarshal.GetReference(heap); - - int heapLen = 0; - int maxCode = 0; - for (int n = 0; n < numSymbols; n++) - { - int freq = this.Frequencies[n]; - if (freq != 0) - { - // Insert n into heap - int pos = heapLen++; - int ppos; - while (pos > 0 && this.Frequencies[Unsafe.Add(ref heapRef, ppos = (pos - 1) / 2)] > freq) - { - Unsafe.Add(ref heapRef, pos) = Unsafe.Add(ref heapRef, ppos); - pos = ppos; - } - - heap[pos] = n; - - maxCode = n; - } - } - - // We could encode a single literal with 0 bits but then we - // don't see the literals. Therefore we force at least two - // literals to avoid this case. We don't care about order in - // this case, both literals get a 1 bit code. - while (heapLen < 2) - { - Unsafe.Add(ref heapRef, heapLen++) = maxCode < 2 ? ++maxCode : 0; - } - - this.NumCodes = Math.Max(maxCode + 1, this.minNumCodes); - - int numLeafs = heapLen; - int[] childs = new int[(4 * heapLen) - 2]; - int[] values = new int[(2 * heapLen) - 1]; - int numNodes = numLeafs; - for (int i = 0; i < heapLen; i++) - { - int node = Unsafe.Add(ref heapRef, i); - childs[2 * i] = node; - childs[(2 * i) + 1] = -1; - values[i] = this.Frequencies[node] << 8; - heap[i] = i; - } - - // Construct the Huffman tree by repeatedly combining the least two - // frequent nodes. - do + // Maxes out at 286 * 4 so too large for the stack. + using (IMemoryOwner heapMemoryOwner = this.memoryAllocator.Allocate(numSymbols)) { - int first = heap[0]; - int last = Unsafe.Add(ref heapRef, --heapLen); - - // Propagate the hole to the leafs of the heap - int ppos = 0; - int path = 1; + ref int heapRef = ref MemoryMarshal.GetReference(heapMemoryOwner.Memory.Span); - while (path < heapLen) + int heapLen = 0; + int maxCode = 0; + for (int n = 0; n < numSymbols; n++) { - if (path + 1 < heapLen && values[Unsafe.Add(ref heapRef, path)] > values[Unsafe.Add(ref heapRef, path + 1)]) + int freq = this.Frequencies[n]; + if (freq != 0) { - path++; + // Insert n into heap + int pos = heapLen++; + int ppos; + while (pos > 0 && this.Frequencies[Unsafe.Add(ref heapRef, ppos = (pos - 1) >> 1)] > freq) + { + Unsafe.Add(ref heapRef, pos) = Unsafe.Add(ref heapRef, ppos); + pos = ppos; + } + + Unsafe.Add(ref heapRef, pos) = n; + + maxCode = n; } - - Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path); - ppos = path; - path = (path * 2) + 1; } - // Now propagate the last element down along path. Normally - // it shouldn't go too deep. - int lastVal = values[last]; - while ((path = ppos) > 0 && values[Unsafe.Add(ref heapRef, ppos = (path - 1) / 2)] > lastVal) + // We could encode a single literal with 0 bits but then we + // don't see the literals. Therefore we force at least two + // literals to avoid this case. We don't care about order in + // this case, both literals get a 1 bit code. + while (heapLen < 2) { - Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); + Unsafe.Add(ref heapRef, heapLen++) = maxCode < 2 ? ++maxCode : 0; } - Unsafe.Add(ref heapRef, path) = last; - - int second = Unsafe.Add(ref heapRef, 0); + this.NumCodes = Math.Max(maxCode + 1, this.minNumCodes); - // Create a new node father of first and second - last = numNodes++; - childs[2 * last] = first; - childs[(2 * last) + 1] = second; - int mindepth = Math.Min(values[first] & 0xFF, values[second] & 0xFF); - values[last] = lastVal = values[first] + values[second] - mindepth + 1; + int numLeafs = heapLen; + int childrenLength = (4 * heapLen) - 2; + using (IMemoryOwner childrenMemoryOwner = this.memoryAllocator.Allocate(childrenLength)) + using (IMemoryOwner valuesMemoryOwner = this.memoryAllocator.Allocate((2 * heapLen) - 1)) + { + ref int childrenRef = ref MemoryMarshal.GetReference(childrenMemoryOwner.Memory.Span); + ref int valuesRef = ref MemoryMarshal.GetReference(valuesMemoryOwner.Memory.Span); + int numNodes = numLeafs; - // Again, propagate the hole to the leafs - ppos = 0; - path = 1; + for (int i = 0; i < heapLen; i++) + { + int node = Unsafe.Add(ref heapRef, i); + int i2 = 2 * i; + Unsafe.Add(ref childrenRef, i2) = node; + Unsafe.Add(ref childrenRef, i2 + 1) = -1; + Unsafe.Add(ref valuesRef, i) = this.Frequencies[node] << 8; + Unsafe.Add(ref heapRef, i) = i; + } - while (path < heapLen) - { - if (path + 1 < heapLen && values[Unsafe.Add(ref heapRef, path)] > values[Unsafe.Add(ref heapRef, path + 1)]) + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + do { - path++; + int first = Unsafe.Add(ref heapRef, 0); + int last = Unsafe.Add(ref heapRef, --heapLen); + + // Propagate the hole to the leafs of the heap + int ppos = 0; + int path = 1; + + while (path < heapLen) + { + if (path + 1 < heapLen && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1))) + { + path++; + } + + Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path); + ppos = path; + path = (path * 2) + 1; + } + + // Now propagate the last element down along path. Normally + // it shouldn't go too deep. + int lastVal = Unsafe.Add(ref valuesRef, last); + while ((path = ppos) > 0 + && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal) + { + Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); + } + + Unsafe.Add(ref heapRef, path) = last; + + int second = Unsafe.Add(ref heapRef, 0); + + // Create a new node father of first and second + last = numNodes++; + Unsafe.Add(ref childrenRef, 2 * last) = first; + Unsafe.Add(ref childrenRef, (2 * last) + 1) = second; + int mindepth = Math.Min(Unsafe.Add(ref valuesRef, first) & 0xFF, Unsafe.Add(ref valuesRef, second) & 0xFF); + Unsafe.Add(ref valuesRef, last) = lastVal = Unsafe.Add(ref valuesRef, first) + Unsafe.Add(ref valuesRef, second) - mindepth + 1; + + // Again, propagate the hole to the leafs + ppos = 0; + path = 1; + + while (path < heapLen) + { + if (path + 1 < heapLen + && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1))) + { + path++; + } + + Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path); + ppos = path; + path = (ppos * 2) + 1; + } + + // Now propagate the new element down along path + while ((path = ppos) > 0 && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal) + { + Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); + } + + Unsafe.Add(ref heapRef, path) = last; } + while (heapLen > 1); - Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path); - ppos = path; - path = (ppos * 2) + 1; - } + if (Unsafe.Add(ref heapRef, 0) != (childrenLength >> 1) - 1) + { + DeflateThrowHelper.ThrowHeapViolated(); + } - // Now propagate the new element down along path - while ((path = ppos) > 0 && values[Unsafe.Add(ref heapRef, ppos = (path - 1) / 2)] > lastVal) - { - Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); + this.BuildLength(childrenMemoryOwner.Memory.Span); } - - Unsafe.Add(ref heapRef, path) = last; } - while (heapLen > 1); - - if (Unsafe.Add(ref heapRef, 0) != (childs.Length / 2) - 1) - { - DeflateThrowHelper.ThrowHeapViolated(); - } - - this.BuildLength(childs); } /// @@ -717,10 +709,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// public void CalcBLFreq(Tree blTree) { - int max_count; /* max repeat count */ - int min_count; /* min repeat count */ - int count; /* repeat count of the current code */ - int curlen = -1; /* length of current code */ + int maxCount; // max repeat count + int minCount; // min repeat count + int count; // repeat count of the current code + int curLen = -1; // length of current code int i = 0; while (i < this.NumCodes) @@ -729,37 +721,37 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib int nextlen = this.Length[i]; if (nextlen == 0) { - max_count = 138; - min_count = 3; + maxCount = 138; + minCount = 3; } else { - max_count = 6; - min_count = 3; - if (curlen != nextlen) + maxCount = 6; + minCount = 3; + if (curLen != nextlen) { blTree.Frequencies[nextlen]++; count = 0; } } - curlen = nextlen; + curLen = nextlen; i++; - while (i < this.NumCodes && curlen == this.Length[i]) + while (i < this.NumCodes && curLen == this.Length[i]) { i++; - if (++count >= max_count) + if (++count >= maxCount) { break; } } - if (count < min_count) + if (count < minCount) { - blTree.Frequencies[curlen] += (short)count; + blTree.Frequencies[curLen] += (short)count; } - else if (curlen != 0) + else if (curLen != 0) { blTree.Frequencies[Repeat3To6]++; } @@ -781,10 +773,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// The tree to write. public void WriteTree(DeflaterPendingBuffer pendingBuffer, Tree bitLengthTree) { - int max_count; // max repeat count - int min_count; // min repeat count - int count; // repeat count of the current code - int curlen = -1; // length of current code + int maxCount; // max repeat count + int minCount; // min repeat count + int count; // repeat count of the current code + int curLen = -1; // length of current code int i = 0; while (i < this.NumCodes) @@ -793,40 +785,40 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib int nextlen = this.Length[i]; if (nextlen == 0) { - max_count = 138; - min_count = 3; + maxCount = 138; + minCount = 3; } else { - max_count = 6; - min_count = 3; - if (curlen != nextlen) + maxCount = 6; + minCount = 3; + if (curLen != nextlen) { bitLengthTree.WriteSymbol(pendingBuffer, nextlen); count = 0; } } - curlen = nextlen; + curLen = nextlen; i++; - while (i < this.NumCodes && curlen == this.Length[i]) + while (i < this.NumCodes && curLen == this.Length[i]) { i++; - if (++count >= max_count) + if (++count >= maxCount) { break; } } - if (count < min_count) + if (count < minCount) { while (count-- > 0) { - bitLengthTree.WriteSymbol(pendingBuffer, curlen); + bitLengthTree.WriteSymbol(pendingBuffer, curLen); } } - else if (curlen != 0) + else if (curLen != 0) { bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To6); pendingBuffer.WriteBits(count - 3, 2); @@ -844,10 +836,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } } - private void BuildLength(int[] children) + private void BuildLength(ReadOnlySpan children) { - int numNodes = children.Length / 2; - int numLeafs = (numNodes + 1) / 2; + int numNodes = children.Length >> 1; + int numLeafs = (numNodes + 1) >> 1; int overflow = 0; for (int i = 0; i < this.maxLength; i++) @@ -936,26 +928,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) { if (!this.isDisposed) { - if (disposing) - { - this.frequenciesMemoryHandle.Dispose(); - this.frequenciesMemoryOwner.Dispose(); + this.frequenciesMemoryHandle.Dispose(); + this.frequenciesMemoryOwner.Dispose(); - this.lengthsMemoryHandle.Dispose(); - this.lengthsMemoryOwner.Dispose(); + this.lengthsMemoryHandle.Dispose(); + this.lengthsMemoryOwner.Dispose(); - this.codesMemoryHandle.Dispose(); - this.codesMemoryOwner.Dispose(); - } + this.codesMemoryHandle.Dispose(); + this.codesMemoryOwner.Dispose(); this.frequenciesMemoryOwner = null; this.lengthsMemoryOwner = null; @@ -963,6 +946,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.isDisposed = true; } + + GC.SuppressFinalize(this); } } } diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs index 837e8b795..eb214aae2 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs @@ -1,466 +1,155 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using System; -using System.Collections.Generic; using System.IO; -using System.Text; +using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Png.Zlib { /// /// A special stream deflating or compressing the bytes that are - /// written to it. It uses a Deflater to perform actual deflating.
- /// Authors of the original java version : Tom Tromey, Jochen Hoenicke + /// written to it. It uses a Deflater to perform actual deflating. ///
- public class DeflaterOutputStream : Stream + internal sealed class DeflaterOutputStream : Stream { - #region Constructors - /// - /// Creates a new DeflaterOutputStream with the given Deflater and - /// default buffer size. - /// - /// - /// the output stream where deflated output should be written. - /// - /// - /// the underlying deflater. - /// - public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater) - : this(baseOutputStream, deflater, 512) - { - } + private const int BufferLength = 512; + private IManagedByteBuffer memoryOwner; + private readonly byte[] buffer; + private Deflater deflater; + private readonly Stream rawStream; + private bool isDisposed; /// - /// Creates a new DeflaterOutputStream with the given Deflater and - /// buffer size. + /// Initializes a new instance of the class. /// - /// - /// The output stream where deflated output is written. - /// - /// - /// The underlying deflater to use - /// - /// - /// The buffer size in bytes to use when deflating (minimum value 512) - /// - /// - /// bufsize is less than or equal to zero. - /// - /// - /// baseOutputStream does not support writing - /// - /// - /// deflater instance is null - /// - public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater, int bufferSize) + /// The memory allocator to use for buffer allocations. + /// The output stream where deflated output is written. + /// The compression level. + public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel) { - if (baseOutputStream == null) - { - throw new ArgumentNullException(nameof(baseOutputStream)); - } - - if (baseOutputStream.CanWrite == false) - { - throw new ArgumentException("Must support writing", nameof(baseOutputStream)); - } - - if (bufferSize < 512) - { - throw new ArgumentOutOfRangeException(nameof(bufferSize)); - } - - baseOutputStream_ = baseOutputStream; - buffer_ = new byte[bufferSize]; - deflater_ = deflater ?? throw new ArgumentNullException(nameof(deflater)); + this.rawStream = rawStream; + this.memoryOwner = memoryAllocator.AllocateManagedByteBuffer(BufferLength); + this.buffer = this.memoryOwner.Array; + this.deflater = new Deflater(memoryAllocator, compressionLevel); } - #endregion Constructors + /// + public override bool CanRead => false; - #region Public API + /// + public override bool CanSeek => false; - /// - /// Finishes the stream by calling finish() on the deflater. - /// - /// - /// Not all input is deflated - /// - public virtual void Finish() - { - deflater_.Finish(); - while (!deflater_.IsFinished) - { - int len = deflater_.Deflate(buffer_, 0, buffer_.Length); - if (len <= 0) - { - break; - } + /// + public override bool CanWrite => this.rawStream.CanWrite; - baseOutputStream_.Write(buffer_, 0, len); - } + /// + public override long Length => this.rawStream.Length; - if (!deflater_.IsFinished) + /// + public override long Position + { + get { - throw new ImageFormatException("Can't deflate all input?"); + return this.rawStream.Position; } - baseOutputStream_.Flush(); - } - - /// - /// Gets or sets a flag indicating ownership of underlying stream. - /// When the flag is true will close the underlying stream also. - /// - /// The default value is true. - public bool IsStreamOwner { get; set; } = true; - - /// - /// Allows client to determine if an entry can be patched after its added - /// - public bool CanPatchEntries - { - get + set { - return baseOutputStream_.CanSeek; + throw new NotSupportedException(); } } - #endregion Public API - - //#region Encryption - - //private string password; - - //private ICryptoTransform cryptoTransform_; + /// + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - ///// - ///// Returns the 10 byte AUTH CODE to be appended immediately following the AES data stream. - ///// - //protected byte[] AESAuthCode; + /// + public override void SetLength(long value) => throw new NotSupportedException(); - ///// - ///// Get/set the password used for encryption. - ///// - ///// When set to null or if the password is empty no encryption is performed - //public string Password - //{ - // get - // { - // return password; - // } - // set - // { - // if ((value != null) && (value.Length == 0)) - // { - // password = null; - // } - // else - // { - // password = value; - // } - // } - //} + /// + public override int ReadByte() => throw new NotSupportedException(); - ///// - ///// Encrypt a block of data - ///// - ///// - ///// Data to encrypt. NOTE the original contents of the buffer are lost - ///// - ///// - ///// Offset of first byte in buffer to encrypt - ///// - ///// - ///// Number of bytes in buffer to encrypt - ///// - //protected void EncryptBlock(byte[] buffer, int offset, int length) - //{ - // cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0); - //} + /// + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - ///// - ///// Initializes encryption keys based on given . - ///// - ///// The password. - //protected void InitializePassword(string password) - //{ - // var pkManaged = new PkzipClassicManaged(); - // byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password)); - // cryptoTransform_ = pkManaged.CreateEncryptor(key, null); - //} - - ///// - ///// Initializes encryption keys based on given password. - ///// - //protected void InitializeAESPassword(ZipEntry entry, string rawPassword, - // out byte[] salt, out byte[] pwdVerifier) - //{ - // salt = new byte[entry.AESSaltLen]; - // // Salt needs to be cryptographically random, and unique per file - // if (_aesRnd == null) - // _aesRnd = RandomNumberGenerator.Create(); - // _aesRnd.GetBytes(salt); - // int blockSize = entry.AESKeySize / 8; // bits to bytes - - // cryptoTransform_ = new ZipAESTransform(rawPassword, salt, blockSize, true); - // pwdVerifier = ((ZipAESTransform)cryptoTransform_).PwdVerifier; - //} - - //#endregion Encryption - - #region Deflation Support + /// + public override void Flush() + { + this.deflater.Flush(); + this.Deflate(true); + this.rawStream.Flush(); + } - /// - /// Deflates everything in the input buffers. This will call - /// def.deflate() until all bytes from the input buffers - /// are processed. - /// - protected void Deflate() + /// + public override void Write(byte[] buffer, int offset, int count) { - Deflate(false); + this.deflater.SetInput(buffer, offset, count); + this.Deflate(); } + private void Deflate() => this.Deflate(false); + private void Deflate(bool flushing) { - while (flushing || !deflater_.IsNeedingInput) + while (flushing || !this.deflater.IsNeedingInput) { - int deflateCount = deflater_.Deflate(buffer_, 0, buffer_.Length); + int deflateCount = this.deflater.Deflate(this.buffer, 0, BufferLength); if (deflateCount <= 0) { break; } - //if (cryptoTransform_ != null) - //{ - // EncryptBlock(buffer_, 0, deflateCount); - //} - baseOutputStream_.Write(buffer_, 0, deflateCount); + this.rawStream.Write(this.buffer, 0, deflateCount); } - if (!deflater_.IsNeedingInput) + if (!this.deflater.IsNeedingInput) { - throw new ImageFormatException("DeflaterOutputStream can't deflate all input?"); + DeflateThrowHelper.ThrowNoDeflate(); } } - #endregion Deflation Support - - #region Stream Overrides - - /// - /// Gets value indicating stream can be read from - /// - public override bool CanRead + private void Finish() { - get + this.deflater.Finish(); + while (!this.deflater.IsFinished) { - return false; - } - } + int len = this.deflater.Deflate(this.buffer, 0, BufferLength); + if (len <= 0) + { + break; + } - /// - /// Gets a value indicating if seeking is supported for this stream - /// This property always returns false - /// - public override bool CanSeek - { - get - { - return false; + this.rawStream.Write(this.buffer, 0, len); } - } - /// - /// Get value indicating if this stream supports writing - /// - public override bool CanWrite - { - get + if (!this.deflater.IsFinished) { - return baseOutputStream_.CanWrite; + DeflateThrowHelper.ThrowNoDeflate(); } - } - /// - /// Get current length of stream - /// - public override long Length - { - get - { - return baseOutputStream_.Length; - } + this.rawStream.Flush(); } - /// - /// Gets the current position within the stream. - /// - /// Any attempt to set position - public override long Position + /// + protected override void Dispose(bool disposing) { - get - { - return baseOutputStream_.Position; - } - set + if (this.isDisposed) { - throw new NotSupportedException("Position property not supported"); + return; } - } - - /// - /// Sets the current position of this stream to the given value. Not supported by this class! - /// - /// The offset relative to the to seek. - /// The to seek from. - /// The new position in the stream. - /// Any access - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("DeflaterOutputStream Seek not supported"); - } - - /// - /// Sets the length of this stream to the given value. Not supported by this class! - /// - /// The new stream length. - /// Any access - public override void SetLength(long value) - { - throw new NotSupportedException("DeflaterOutputStream SetLength not supported"); - } - - /// - /// Read a byte from stream advancing position by one - /// - /// The byte read cast to an int. THe value is -1 if at the end of the stream. - /// Any access - public override int ReadByte() - { - throw new NotSupportedException("DeflaterOutputStream ReadByte not supported"); - } - - /// - /// Read a block of bytes from stream - /// - /// The buffer to store read data in. - /// The offset to start storing at. - /// The maximum number of bytes to read. - /// The actual number of bytes read. Zero if end of stream is detected. - /// Any access - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException("DeflaterOutputStream Read not supported"); - } - - /// - /// Flushes the stream by calling Flush on the deflater and then - /// on the underlying stream. This ensures that all bytes are flushed. - /// - public override void Flush() - { - deflater_.Flush(); - Deflate(true); - baseOutputStream_.Flush(); - } - /// - /// Calls and closes the underlying - /// stream when is true. - /// - protected override void Dispose(bool disposing) - { - if (!isClosed_) + if (disposing) { - isClosed_ = true; - - try - { - Finish(); - } - finally - { - if (IsStreamOwner) - { - baseOutputStream_.Dispose(); - } - } + this.Finish(); + this.deflater.Dispose(); + this.memoryOwner.Dispose(); } - } - - ///// - ///// Get the Auth code for AES encrypted entries - ///// - //protected void GetAuthCodeIfAES() - //{ - // if (cryptoTransform_ is ZipAESTransform) - // { - // AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode(); - // } - //} - /// - /// Writes a single byte to the compressed output stream. - /// - /// - /// The byte value. - /// - public override void WriteByte(byte value) - { - byte[] b = new byte[1]; - b[0] = value; - Write(b, 0, 1); - } - - /// - /// Writes bytes from an array to the compressed stream. - /// - /// - /// The byte array - /// - /// - /// The offset into the byte array where to start. - /// - /// - /// The number of bytes to write. - /// - public override void Write(byte[] buffer, int offset, int count) - { - deflater_.SetInput(buffer, offset, count); - Deflate(); + this.deflater = null; + this.memoryOwner = null; + this.isDisposed = true; + base.Dispose(disposing); } - - #endregion Stream Overrides - - #region Instance Fields - - /// - /// This buffer is used temporarily to retrieve the bytes from the - /// deflater and write them to the underlying output stream. - /// - private byte[] buffer_; - - /// - /// The deflater which is used to deflate the stream. - /// - protected Deflater deflater_; - - /// - /// Base stream the deflater depends on. - /// - protected Stream baseOutputStream_; - - private bool isClosed_; - - #endregion Instance Fields - - #region Static Fields - - // Static to help ensure that multiple files within a zip will get different random salt - //private static RandomNumberGenerator _aesRnd = RandomNumberGenerator.Create(); - - #endregion Static Fields } } diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs index d4af8cb5a..a5f00f03c 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// Stores pending data for writing data to the Deflater. /// - public sealed unsafe class DeflaterPendingBuffer : IDisposable + internal sealed unsafe class DeflaterPendingBuffer : IDisposable { private readonly byte[] buffer; private readonly byte* pinnedBuffer; @@ -164,25 +164,16 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) { if (!this.isDisposed) { - if (disposing) - { - this.bufferMemoryHandle.Dispose(); - this.bufferMemoryOwner.Dispose(); - } - + this.bufferMemoryHandle.Dispose(); + this.bufferMemoryOwner.Dispose(); this.bufferMemoryOwner = null; - this.isDisposed = true; } + + GC.SuppressFinalize(this); } } } diff --git a/src/ImageSharp/Formats/Png/Zlib/README.md b/src/ImageSharp/Formats/Png/Zlib/README.md index c297a91d5..59f75d05f 100644 --- a/src/ImageSharp/Formats/Png/Zlib/README.md +++ b/src/ImageSharp/Formats/Png/Zlib/README.md @@ -1,2 +1,5 @@ -Adler32.cs and Crc32.cs have been copied from -https://github.com/ygrenier/SharpZipLib.Portable +Deflatestream implementation adapted from + +https://github.com/icsharpcode/SharpZipLib + +LIcensed under MIT diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs index 5724e027d..36bacc5ec 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs @@ -41,8 +41,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // private DeflateStream deflateStream; private DeflaterOutputStream deflateStream; - private Deflater deflater; - /// /// Initializes a new instance of the class. /// @@ -92,21 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.rawStream.WriteByte(Cmf); this.rawStream.WriteByte((byte)flg); - // Initialize the deflate Stream. - // CompressionLevel level = CompressionLevel.Optimal; - // - // if (compressionLevel >= 1 && compressionLevel <= 5) - // { - // level = CompressionLevel.Fastest; - // } - // else if (compressionLevel == 0) - // { - // level = CompressionLevel.NoCompression; - // } - this.deflater = new Deflater(memoryAllocator, compressionLevel); - this.deflateStream = new DeflaterOutputStream(this.rawStream, this.deflater) { IsStreamOwner = false }; - - // this.deflateStream = new DeflateStream(this.rawStream, level, true); + this.deflateStream = new DeflaterOutputStream(memoryAllocator, this.rawStream, compressionLevel); } /// @@ -116,16 +100,23 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib public override bool CanSeek => false; /// - public override bool CanWrite => true; + public override bool CanWrite => this.rawStream.CanWrite; /// - public override long Length => throw new NotSupportedException(); + public override long Length => this.rawStream.Length; /// public override long Position { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); + get + { + return this.rawStream.Position; + } + + set + { + throw new NotSupportedException(); + } } /// @@ -174,10 +165,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { this.deflateStream.Dispose(); this.deflateStream = null; - - // TODO: Remove temporal coupling here. - this.deflater.Dispose(); - this.deflater = null; } else { @@ -195,10 +182,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } base.Dispose(disposing); - - // Call the appropriate methods to clean up - // unmanaged resources here. - // Note disposing is done. this.isDisposed = true; } }