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
{