diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs b/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs index 2698c9b99b..d7198c4ee9 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs @@ -17,6 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib [MethodImpl(InliningOptions.ColdPath)] public static void ThrowUnknownCompression() => throw new InvalidOperationException("Unknown compression function."); + [MethodImpl(InliningOptions.ColdPath)] public static void ThrowNotProcessed() => throw new InvalidOperationException("Old input was not completely processed."); [MethodImpl(InliningOptions.ColdPath)] @@ -24,5 +25,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib [MethodImpl(InliningOptions.ColdPath)] 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."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowHeapViolated() => throw new InvalidOperationException("Huffman heap invariant violated."); } } diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs index 1a2d661c11..36cf0d5594 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -2,8 +2,8 @@ // 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 @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// author of the original java version : Jochen Hoenicke /// - public sealed class DeflaterHuffman : IDisposable + public sealed unsafe class DeflaterHuffman : IDisposable { private const int BufferSize = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); @@ -29,13 +29,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // Number of codes used to transfer bit lengths private const int BitLengthNumber = 19; - // repeat previous bit length 3-6 times (2 bits of repeat count) + // Repeat previous bit length 3-6 times (2 bits of repeat count) private const int Repeat3To6 = 16; - // repeat a zero length 3-10 times (3 bits of repeat count) + // Repeat a zero length 3-10 times (3 bits of repeat count) private const int Repeat3To10 = 17; - // repeat a zero length 11-138 times (7 bits of repeat count) + // Repeat a zero length 11-138 times (7 bits of repeat count) private const int Repeat11To138 = 18; private const int EofSymbol = 256; @@ -46,19 +46,24 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private static readonly byte[] Bit4Reverse = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 }; - private static short[] staticLCodes; - private static byte[] staticLLength; - private static short[] staticDCodes; - private static byte[] staticDLength; + private static readonly short[] StaticLCodes; + private static readonly byte[] StaticLLength; + private static readonly short[] StaticDCodes; + private static readonly byte[] StaticDLength; private Tree literalTree; private Tree distTree; private Tree blTree; // Buffer for distances - private short[] distanceBuffer; + private readonly IMemoryOwner distanceManagedBuffer; + private readonly short* pinnedDistanceBuffer; + private MemoryHandle distanceBufferHandle; + + private readonly IMemoryOwner literalManagedBuffer; + private readonly short* pinnedLiteralBuffer; + private MemoryHandle literalBufferHandle; - private byte[] literalBuffer; private int lastLiteral; private int extraBits; private bool isDisposed; @@ -67,41 +72,41 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { // See RFC 1951 3.2.6 // Literal codes - staticLCodes = new short[LiteralNumber]; - staticLLength = new byte[LiteralNumber]; + StaticLCodes = new short[LiteralNumber]; + StaticLLength = new byte[LiteralNumber]; int i = 0; while (i < 144) { - staticLCodes[i] = BitReverse((0x030 + i) << 8); - staticLLength[i++] = 8; + StaticLCodes[i] = BitReverse((0x030 + i) << 8); + StaticLLength[i++] = 8; } while (i < 256) { - staticLCodes[i] = BitReverse((0x190 - 144 + i) << 7); - staticLLength[i++] = 9; + StaticLCodes[i] = BitReverse((0x190 - 144 + i) << 7); + StaticLLength[i++] = 9; } while (i < 280) { - staticLCodes[i] = BitReverse((0x000 - 256 + i) << 9); - staticLLength[i++] = 7; + StaticLCodes[i] = BitReverse((0x000 - 256 + i) << 9); + StaticLLength[i++] = 7; } while (i < LiteralNumber) { - staticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8); - staticLLength[i++] = 8; + StaticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8); + StaticLLength[i++] = 8; } // Distance codes - staticDCodes = new short[DistanceNumber]; - staticDLength = new byte[DistanceNumber]; + StaticDCodes = new short[DistanceNumber]; + StaticDLength = new byte[DistanceNumber]; for (i = 0; i < DistanceNumber; i++) { - staticDCodes[i] = BitReverse(i << 11); - staticDLength[i] = 5; + StaticDCodes[i] = BitReverse(i << 11); + StaticDLength[i] = 5; } } @@ -113,12 +118,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { this.Pending = new DeflaterPendingBuffer(memoryAllocator); - this.literalTree = new Tree(this, LiteralNumber, 257, 15); - this.distTree = new Tree(this, DistanceNumber, 1, 15); - this.blTree = new Tree(this, BitLengthNumber, 4, 7); + this.literalTree = new Tree(LiteralNumber, 257, 15); + this.distTree = new Tree(DistanceNumber, 1, 15); + this.blTree = new Tree(BitLengthNumber, 4, 7); + + this.distanceManagedBuffer = memoryAllocator.Allocate(BufferSize); + this.distanceBufferHandle = this.distanceManagedBuffer.Memory.Pin(); + this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer; - this.distanceBuffer = new short[BufferSize]; - this.literalBuffer = new byte[BufferSize]; + this.literalManagedBuffer = memoryAllocator.Allocate(BufferSize); + this.literalBufferHandle = this.literalManagedBuffer.Memory.Pin(); + this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; } /// @@ -155,8 +165,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.Pending.WriteBits(this.blTree.Length[BitLengthOrder[rank]], 3); } - this.literalTree.WriteTree(this.blTree); - this.distTree.WriteTree(this.blTree); + this.literalTree.WriteTree(this.Pending, this.blTree); + this.distTree.WriteTree(this.Pending, this.blTree); } /// @@ -164,14 +174,18 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// public void CompressBlock() { + DeflaterPendingBuffer pendingBuffer = this.Pending; + short* pinnedDistance = this.pinnedDistanceBuffer; + short* pinnedLiteral = this.pinnedLiteralBuffer; + for (int i = 0; i < this.lastLiteral; i++) { - int litlen = this.literalBuffer[i] & 0xff; - int dist = this.distanceBuffer[i]; + int litlen = pinnedLiteral[i] & 0xFF; + int dist = pinnedDistance[i]; if (dist-- != 0) { int lc = Lcode(litlen); - this.literalTree.WriteSymbol(lc); + this.literalTree.WriteSymbol(pendingBuffer, lc); int bits = (lc - 261) / 4; if (bits > 0 && bits <= 5) @@ -180,7 +194,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } int dc = Dcode(dist); - this.distTree.WriteSymbol(dc); + this.distTree.WriteSymbol(pendingBuffer, dc); bits = (dc / 2) - 1; if (bits > 0) @@ -190,11 +204,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } else { - this.literalTree.WriteSymbol(litlen); + this.literalTree.WriteSymbol(pendingBuffer, litlen); } } - this.literalTree.WriteSymbol(EofSymbol); + this.literalTree.WriteSymbol(pendingBuffer, EofSymbol); } /// @@ -245,19 +259,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } } - int opt_len = 14 + (blTreeCodes * 3) + this.blTree.GetEncodedLength() + - this.literalTree.GetEncodedLength() + this.distTree.GetEncodedLength() + - this.extraBits; + int opt_len = 14 + (blTreeCodes * 3) + this.blTree.GetEncodedLength() + + this.literalTree.GetEncodedLength() + this.distTree.GetEncodedLength() + + this.extraBits; int static_len = this.extraBits; for (int i = 0; i < LiteralNumber; i++) { - static_len += this.literalTree.Freqs[i] * staticLLength[i]; + static_len += this.literalTree.Freqs[i] * StaticLLength[i]; } for (int i = 0; i < DistanceNumber; i++) { - static_len += this.distTree.Freqs[i] * staticDLength[i]; + static_len += this.distTree.Freqs[i] * StaticDLength[i]; } if (opt_len >= static_len) @@ -275,8 +289,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { // Encode with static tree this.Pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3); - this.literalTree.SetStaticCodes(staticLCodes, staticLLength); - this.distTree.SetStaticCodes(staticDCodes, staticDLength); + this.literalTree.SetStaticCodes(StaticLCodes, StaticLLength); + this.distTree.SetStaticCodes(StaticDCodes, StaticDLength); this.CompressBlock(); this.Reset(); } @@ -294,20 +308,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// Get value indicating if internal buffer is full /// /// true if buffer is full - public bool IsFull() - { - return this.lastLiteral >= BufferSize; - } + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsFull() => this.lastLiteral >= BufferSize; /// /// Add literal to buffer /// /// Literal value to add to buffer. /// Value indicating internal buffer is full + [MethodImpl(InliningOptions.ShortMethod)] public bool TallyLit(int literal) { - this.distanceBuffer[this.lastLiteral] = 0; - this.literalBuffer[this.lastLiteral++] = (byte)literal; + this.pinnedDistanceBuffer[this.lastLiteral] = 0; + this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)literal; this.literalTree.Freqs[literal]++; return this.IsFull(); } @@ -318,10 +331,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// Distance code /// Length /// Value indicating if internal buffer is full + [MethodImpl(InliningOptions.ShortMethod)] public bool TallyDist(int distance, int length) { - this.distanceBuffer[this.lastLiteral] = (short)distance; - this.literalBuffer[this.lastLiteral++] = (byte)(length - 3); + this.pinnedDistanceBuffer[this.lastLiteral] = (short)distance; + this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)(length - 3); int lc = Lcode(length - 3); this.literalTree.Freqs[lc]++; @@ -345,12 +359,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// Value to reverse bits /// Value with bits reversed + [MethodImpl(InliningOptions.ShortMethod)] public static short BitReverse(int toReverse) { - return (short)(Bit4Reverse[toReverse & 0xF] << 12 | - Bit4Reverse[(toReverse >> 4) & 0xF] << 8 | - Bit4Reverse[(toReverse >> 8) & 0xF] << 4 | - Bit4Reverse[toReverse >> 12]); + return (short)(Bit4Reverse[toReverse & 0xF] << 12 + | Bit4Reverse[(toReverse >> 4) & 0xF] << 8 + | Bit4Reverse[(toReverse >> 8) & 0xF] << 4 + | Bit4Reverse[toReverse >> 12]); } /// @@ -360,6 +375,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib GC.SuppressFinalize(this); } + [MethodImpl(InliningOptions.ShortMethod)] private static int Lcode(int length) { if (length == 255) @@ -377,6 +393,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib return code + length; } + [MethodImpl(InliningOptions.ShortMethod)] private static int Dcode(int distance) { int code = 0; @@ -396,6 +413,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib if (disposing) { this.Pending.Dispose(); + this.distanceBufferHandle.Dispose(); + this.distanceManagedBuffer.Dispose(); + this.literalBufferHandle.Dispose(); + this.literalManagedBuffer.Dispose(); } this.Pending = null; @@ -403,20 +424,18 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } } - private class Tree + private sealed class Tree { private readonly int minNumCodes; private short[] codes; private readonly int[] bitLengthCounts; private readonly int maxLength; - private readonly DeflaterHuffman dh; - public Tree(DeflaterHuffman dh, int elems, int minCodes, int maxLength) + public Tree(int elements, int minCodes, int maxLength) { - this.dh = dh; this.minNumCodes = minCodes; this.maxLength = maxLength; - this.Freqs = new short[elems]; + this.Freqs = new short[elements]; this.bitLengthCounts = new int[maxLength]; } @@ -429,6 +448,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// Resets the internal state of the tree /// + [MethodImpl(InliningOptions.ShortMethod)] public void Reset() { for (int i = 0; i < this.Freqs.Length; i++) @@ -440,17 +460,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.Length = null; } - public void WriteSymbol(int code) - { - this.dh.Pending.WriteBits(this.codes[code] & 0xffff, this.Length[code]); - } + [MethodImpl(InliningOptions.ShortMethod)] + 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; @@ -461,7 +481,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib if (!empty) { - throw new ImageFormatException("!Empty"); + DeflateThrowHelper.ThrowFrequencyNotEmpty(); } } @@ -481,7 +501,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// public void BuildCodes() { - int numSymbols = this.Freqs.Length; int[] nextCode = new int[this.maxLength]; int code = 0; @@ -544,8 +563,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // this case, both literals get a 1 bit code. while (heapLen < 2) { - int node = maxCode < 2 ? ++maxCode : 0; - heap[heapLen++] = node; + heap[heapLen++] = maxCode < 2 ? ++maxCode : 0; } this.NumCodes = Math.Max(maxCode + 1, this.minNumCodes); @@ -602,7 +620,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib last = numNodes++; childs[2 * last] = first; childs[(2 * last) + 1] = second; - int mindepth = Math.Min(values[first] & 0xff, values[second] & 0xff); + int mindepth = Math.Min(values[first] & 0xFF, values[second] & 0xFF); values[last] = lastVal = values[first] + values[second] - mindepth + 1; // Again, propagate the hole to the leafs @@ -633,7 +651,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib if (heap[0] != (childs.Length / 2) - 1) { - throw new ImageFormatException("Heap invariant violated"); + DeflateThrowHelper.ThrowHeapViolated(); } this.BuildLength(childs); @@ -718,10 +736,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } /// - /// Write tree values + /// Write the tree values. /// - /// Tree to write - public void WriteTree(Tree blTree) + /// The pending buffer. + /// The tree to write. + public void WriteTree(DeflaterPendingBuffer pendingBuffer, Tree bitLengthTree) { int max_count; // max repeat count int min_count; // min repeat count @@ -744,7 +763,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib min_count = 3; if (curlen != nextlen) { - blTree.WriteSymbol(nextlen); + bitLengthTree.WriteSymbol(pendingBuffer, nextlen); count = 0; } } @@ -765,31 +784,31 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { while (count-- > 0) { - blTree.WriteSymbol(curlen); + bitLengthTree.WriteSymbol(pendingBuffer, curlen); } } else if (curlen != 0) { - blTree.WriteSymbol(Repeat3To6); - this.dh.Pending.WriteBits(count - 3, 2); + bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To6); + pendingBuffer.WriteBits(count - 3, 2); } else if (count <= 10) { - blTree.WriteSymbol(Repeat3To10); - this.dh.Pending.WriteBits(count - 3, 3); + bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To10); + pendingBuffer.WriteBits(count - 3, 3); } else { - blTree.WriteSymbol(Repeat11To138); - this.dh.Pending.WriteBits(count - 11, 7); + bitLengthTree.WriteSymbol(pendingBuffer, Repeat11To138); + pendingBuffer.WriteBits(count - 11, 7); } } } - private void BuildLength(int[] childs) + private void BuildLength(int[] children) { this.Length = new byte[this.Freqs.Length]; - int numNodes = childs.Length / 2; + int numNodes = children.Length / 2; int numLeafs = (numNodes + 1) / 2; int overflow = 0; @@ -804,7 +823,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib for (int i = numNodes - 1; i >= 0; i--) { - if (childs[(2 * i) + 1] != -1) + if (children[(2 * i) + 1] != -1) { int bitLength = lengths[i] + 1; if (bitLength > this.maxLength) @@ -813,14 +832,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib overflow++; } - lengths[childs[2 * i]] = lengths[childs[(2 * i) + 1]] = bitLength; + lengths[children[2 * i]] = lengths[children[(2 * i) + 1]] = bitLength; } else { // A leaf node int bitLength = lengths[i]; this.bitLengthCounts[bitLength - 1]++; - this.Length[childs[2 * i]] = (byte)lengths[i]; + this.Length[children[2 * i]] = (byte)lengths[i]; } } @@ -867,11 +886,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib int n = this.bitLengthCounts[bits - 1]; while (n > 0) { - int childPtr = 2 * childs[nodePtr++]; - if (childs[childPtr + 1] == -1) + int childPtr = 2 * children[nodePtr++]; + if (children[childPtr + 1] == -1) { // We found another leaf - this.Length[childs[childPtr]] = (byte)bits; + this.Length[children[childPtr]] = (byte)bits; n--; } } diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs index 64214b47e6..da4d890402 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs @@ -29,7 +29,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// The memory allocator to use for buffer allocations. public DeflaterPendingBuffer(MemoryAllocator memoryAllocator) { - this.buffer = new byte[DeflaterConstants.PENDING_BUF_SIZE]; this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE); this.buffer = this.managedBuffer.Array; this.handle = this.managedBuffer.Memory.Pin();