diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 09575bb28..19c6af27f 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -699,7 +699,7 @@ namespace SixLabors.ImageSharp.Formats.Png { using (var memoryStream = new MemoryStream()) { - using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel)) + using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel)) { deflateStream.Write(textBytes); } @@ -790,7 +790,7 @@ namespace SixLabors.ImageSharp.Formats.Png using (var memoryStream = new MemoryStream()) { - using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel)) + using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel)) { if (this.options.InterlaceMethod == PngInterlaceMode.Adam7) { diff --git a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs index 358112549..90e25fb58 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Text; +using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Png.Zlib { @@ -18,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// author of the original java version : Jochen Hoenicke /// - public class Deflater + public sealed class Deflater : IDisposable { #region Deflater Documentation @@ -149,28 +150,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib #region Constructors - /// - /// Creates a new deflater with default compression level. - /// - public Deflater() : this(DEFAULT_COMPRESSION, false) - { - } - - /// - /// Creates a new deflater with given compression level. - /// - /// - /// the compression level, a value between NO_COMPRESSION - /// and BEST_COMPRESSION, or DEFAULT_COMPRESSION. - /// - /// if lvl is out of range. - public Deflater(int level) : this(level, false) - { - } - /// /// Creates a new deflater with given compression level. /// + /// The memory allocator to use for buffer allocations. /// /// the compression level, a value between NO_COMPRESSION /// and BEST_COMPRESSION. @@ -181,7 +164,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// useful for the GZIP/PKZIP formats. /// /// if lvl is out of range. - public Deflater(int level, bool noZlibHeaderOrFooter) + public Deflater(MemoryAllocator memoryAllocator, int level, bool noZlibHeaderOrFooter) { if (level == DEFAULT_COMPRESSION) { @@ -192,7 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib throw new ArgumentOutOfRangeException(nameof(level)); } - pending = new DeflaterPending(); + pending = new DeflaterPendingBuffer(memoryAllocator); engine = new DeflaterEngine(pending, noZlibHeaderOrFooter); this.noZlibHeaderOrFooter = noZlibHeaderOrFooter; SetStrategy(DeflateStrategy.Default); @@ -598,13 +581,41 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// The pending output. /// - private DeflaterPending pending; + private DeflaterPendingBuffer pending; /// /// The deflater engine. /// private DeflaterEngine engine; + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + this.pending.Dispose(); + // TODO: dispose managed state (managed objects). + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + this.pending = null; + disposedValue = true; + } + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + #endregion Instance Fields } } diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs index 7ac5b6c69..c9967056e 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// Pending buffer to use /// - public DeflaterEngine(DeflaterPending pending) + public DeflaterEngine(DeflaterPendingBuffer pending) : this(pending, false) { } @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// If no adler calculation should be performed /// - public DeflaterEngine(DeflaterPending pending, bool noAdlerCalculation) + public DeflaterEngine(DeflaterPendingBuffer pending, bool noAdlerCalculation) { this.pending = pending; huffman = new DeflaterHuffman(pending); @@ -938,7 +938,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// private int inputEnd; - private DeflaterPending pending; + private DeflaterPendingBuffer pending; private DeflaterHuffman huffman; /// diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs index bf506d14c..3d2856b0e 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -590,7 +590,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// Pending buffer to use /// - public DeflaterPending pending; + public DeflaterPendingBuffer pending; private Tree literalTree; private Tree distTree; @@ -651,7 +651,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// Construct instance with pending buffer /// /// Pending buffer to use - public DeflaterHuffman(DeflaterPending pending) + public DeflaterHuffman(DeflaterPendingBuffer pending) { this.pending = pending; diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs index 31db032ae..ac5f229aa 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs @@ -17,18 +17,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib public class DeflaterOutputStream : Stream { #region Constructors - - /// - /// Creates a new DeflaterOutputStream with a default Deflater and default buffer size. - /// - /// - /// the output stream where deflated output should be written. - /// - public DeflaterOutputStream(Stream baseOutputStream) - : this(baseOutputStream, new Deflater(), 512) - { - } - /// /// Creates a new DeflaterOutputStream with the given Deflater and /// default buffer size. diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterPending.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterPending.cs deleted file mode 100644 index cc421c5b7..000000000 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterPending.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -// -namespace SixLabors.ImageSharp.Formats.Png.Zlib -{ - /// - /// This class stores the pending output of the Deflater. - /// - /// author of the original java version : Jochen Hoenicke - /// - public class DeflaterPending : PendingBuffer - { - /// - /// Construct instance with default buffer size - /// - public DeflaterPending() : base(DeflaterConstants.PENDING_BUF_SIZE) - { - } - } -} diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs new file mode 100644 index 000000000..64214b47e --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// Stores pending data for writing data to the Deflater. + /// + public sealed unsafe class DeflaterPendingBuffer : IDisposable + { + private readonly byte[] buffer; + private readonly byte* pinnedBuffer; + private readonly IManagedByteBuffer managedBuffer; + private MemoryHandle handle; + + private int start; + private int end; + private uint bits; + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// 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(); + this.pinnedBuffer = (byte*)this.handle.Pointer; + } + + /// + /// Gets the number of bits written to the buffer. + /// + public int BitCount { get; private set; } + + /// + /// Gets a value indicating whether indicates the buffer has been flushed. + /// + public bool IsFlushed => this.end == 0; + + /// + /// Clear internal state/buffers. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() => this.start = this.end = this.BitCount = 0; + + /// + /// Write a short value to buffer LSB first. + /// + /// The value to write. + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteShort(int value) + { + byte* pinned = this.pinnedBuffer; + pinned[this.end++] = unchecked((byte)value); + pinned[this.end++] = unchecked((byte)(value >> 8)); + } + + /// + /// Write a block of data to the internal buffer. + /// + /// The data to write. + /// The offset of first byte to write. + /// The number of bytes to write. + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteBlock(byte[] block, int offset, int length) + { + Unsafe.CopyBlockUnaligned(ref this.buffer[this.end], ref block[offset], unchecked((uint)length)); + this.end += length; + } + + /// + /// Aligns internal buffer on a byte boundary. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void AlignToByte() + { + if (this.BitCount > 0) + { + byte* pinned = this.pinnedBuffer; + pinned[this.end++] = unchecked((byte)this.bits); + if (this.BitCount > 8) + { + pinned[this.end++] = unchecked((byte)(this.bits >> 8)); + } + } + + this.bits = 0; + this.BitCount = 0; + } + + /// + /// Write bits to internal buffer + /// + /// source of bits + /// number of bits to write + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteBits(int b, int count) + { + this.bits |= (uint)(b << this.BitCount); + this.BitCount += count; + if (this.BitCount >= 16) + { + byte* pinned = this.pinnedBuffer; + pinned[this.end++] = unchecked((byte)this.bits); + pinned[this.end++] = unchecked((byte)(this.bits >> 8)); + this.bits >>= 16; + this.BitCount -= 16; + } + } + + /// + /// Write a short value to internal buffer most significant byte first + /// + /// The value to write + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteShortMSB(int value) + { + byte* pinned = this.pinnedBuffer; + pinned[this.end++] = unchecked((byte)(value >> 8)); + pinned[this.end++] = unchecked((byte)value); + } + + /// + /// Flushes the pending buffer into the given output array. + /// If the output array is to small, only a partial flush is done. + /// + /// The output array. + /// The offset into output array. + /// The maximum number of bytes to store. + /// The number of bytes flushed. + public int Flush(byte[] output, int offset, int length) + { + if (this.BitCount >= 8) + { + this.pinnedBuffer[this.end++] = unchecked((byte)this.bits); + this.bits >>= 8; + this.BitCount -= 8; + } + + if (length > this.end - this.start) + { + length = this.end - this.start; + + Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length)); + this.start = 0; + this.end = 0; + } + else + { + Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length)); + this.start += length; + } + + return length; + } + + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + this.handle.Dispose(); + this.managedBuffer.Dispose(); + } + + this.isDisposed = true; + } + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/PendingBuffer.cs b/src/ImageSharp/Formats/Png/Zlib/PendingBuffer.cs deleted file mode 100644 index ae02c5113..000000000 --- a/src/ImageSharp/Formats/Png/Zlib/PendingBuffer.cs +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -// -using System; -using System.Collections.Generic; -using System.Text; - -namespace SixLabors.ImageSharp.Formats.Png.Zlib -{ - /// - /// This class is general purpose class for writing data to a buffer. - /// - /// It allows you to write bits as well as bytes - /// Based on DeflaterPending.java - /// - /// author of the original java version : Jochen Hoenicke - /// - public class PendingBuffer - { - #region Instance Fields - - /// - /// Internal work buffer - /// - private readonly byte[] buffer; - - private int start; - private int end; - - private uint bits; - private int bitCount; - - #endregion Instance Fields - - #region Constructors - - /// - /// construct instance using default buffer size of 4096 - /// - public PendingBuffer() : this(4096) - { - } - - /// - /// construct instance using specified buffer size - /// - /// - /// size to use for internal buffer - /// - public PendingBuffer(int bufferSize) - { - buffer = new byte[bufferSize]; - } - - #endregion Constructors - - /// - /// Clear internal state/buffers - /// - public void Reset() - { - start = end = bitCount = 0; - } - - /// - /// Write a byte to buffer - /// - /// - /// The value to write - /// - public void WriteByte(int value) - { -#if DebugDeflation - if (DeflaterConstants.DEBUGGING && (start != 0) ) - { - throw new ImageFormatException("Debug check: start != 0"); - } -#endif - buffer[end++] = unchecked((byte)value); - } - - /// - /// Write a short value to buffer LSB first - /// - /// - /// The value to write. - /// - public void WriteShort(int value) - { -#if DebugDeflation - if (DeflaterConstants.DEBUGGING && (start != 0) ) - { - throw new ImageFormatException("Debug check: start != 0"); - } -#endif - buffer[end++] = unchecked((byte)value); - buffer[end++] = unchecked((byte)(value >> 8)); - } - - /// - /// write an integer LSB first - /// - /// The value to write. - public void WriteInt(int value) - { -#if DebugDeflation - if (DeflaterConstants.DEBUGGING && (start != 0) ) - { - throw new ImageFormatException("Debug check: start != 0"); - } -#endif - buffer[end++] = unchecked((byte)value); - buffer[end++] = unchecked((byte)(value >> 8)); - buffer[end++] = unchecked((byte)(value >> 16)); - buffer[end++] = unchecked((byte)(value >> 24)); - } - - /// - /// Write a block of data to buffer - /// - /// data to write - /// offset of first byte to write - /// number of bytes to write - public void WriteBlock(byte[] block, int offset, int length) - { -#if DebugDeflation - if (DeflaterConstants.DEBUGGING && (start != 0) ) - { - throw new ImageFormatException("Debug check: start != 0"); - } -#endif - System.Array.Copy(block, offset, buffer, end, length); - end += length; - } - - /// - /// The number of bits written to the buffer - /// - public int BitCount - { - get - { - return bitCount; - } - } - - /// - /// Align internal buffer on a byte boundary - /// - public void AlignToByte() - { -#if DebugDeflation - if (DeflaterConstants.DEBUGGING && (start != 0) ) - { - throw new ImageFormatException("Debug check: start != 0"); - } -#endif - if (bitCount > 0) - { - buffer[end++] = unchecked((byte)bits); - if (bitCount > 8) - { - buffer[end++] = unchecked((byte)(bits >> 8)); - } - } - bits = 0; - bitCount = 0; - } - - /// - /// Write bits to internal buffer - /// - /// source of bits - /// number of bits to write - public void WriteBits(int b, int count) - { -#if DebugDeflation - if (DeflaterConstants.DEBUGGING && (start != 0) ) - { - throw new ImageFormatException("Debug check: start != 0"); - } - - // if (DeflaterConstants.DEBUGGING) { - // //Console.WriteLine("writeBits("+b+","+count+")"); - // } -#endif - bits |= (uint)(b << bitCount); - bitCount += count; - if (bitCount >= 16) - { - buffer[end++] = unchecked((byte)bits); - buffer[end++] = unchecked((byte)(bits >> 8)); - bits >>= 16; - bitCount -= 16; - } - } - - /// - /// Write a short value to internal buffer most significant byte first - /// - /// value to write - public void WriteShortMSB(int s) - { -#if DebugDeflation - if (DeflaterConstants.DEBUGGING && (start != 0) ) - { - throw new ImageFormatException("Debug check: start != 0"); - } -#endif - buffer[end++] = unchecked((byte)(s >> 8)); - buffer[end++] = unchecked((byte)s); - } - - /// - /// Indicates if buffer has been flushed - /// - public bool IsFlushed - { - get - { - return end == 0; - } - } - - /// - /// Flushes the pending buffer into the given output array. If the - /// output array is to small, only a partial flush is done. - /// - /// The output array. - /// The offset into output array. - /// The maximum number of bytes to store. - /// The number of bytes flushed. - public int Flush(byte[] output, int offset, int length) - { - if (bitCount >= 8) - { - buffer[end++] = unchecked((byte)bits); - bits >>= 8; - bitCount -= 8; - } - - if (length > end - start) - { - length = end - start; - System.Array.Copy(buffer, start, output, offset, length); - start = 0; - end = 0; - } - else - { - System.Array.Copy(buffer, start, output, offset, length); - start += length; - } - return length; - } - - /// - /// Convert internal buffer to byte array. - /// Buffer is empty on completion - /// - /// - /// The internal buffer contents converted to a byte array. - /// - public byte[] ToByteArray() - { - AlignToByte(); - - byte[] result = new byte[end - start]; - System.Array.Copy(buffer, start, result, 0, result.Length); - start = 0; - end = 0; - return result; - } - } -} diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs index ffe176368..69b3c0602 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.IO.Compression; +using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Png.Zlib { @@ -41,12 +42,15 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // private DeflateStream deflateStream; private DeflaterOutputStream deflateStream; + private Deflater deflater; + /// /// Initializes a new instance of the class. /// + /// The memory allocator to use for buffer allocations. /// The stream to compress. /// The compression level. - public ZlibDeflateStream(Stream stream, int compressionLevel) + public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, int compressionLevel) { this.rawStream = stream; @@ -100,7 +104,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // { // level = CompressionLevel.NoCompression; // } - this.deflateStream = new DeflaterOutputStream(this.rawStream, new Deflater(compressionLevel, true)) { IsStreamOwner = false }; + this.deflater = new Deflater(memoryAllocator, compressionLevel, true); + this.deflateStream = new DeflaterOutputStream(this.rawStream, this.deflater) { IsStreamOwner = false }; // this.deflateStream = new DeflateStream(this.rawStream, level, true); } @@ -170,6 +175,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { this.deflateStream.Dispose(); this.deflateStream = null; + + // TODO: Remove temporal coupling here. + this.deflater.Dispose(); + this.deflater = null; } else {