diff --git a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs
index a06983b9ed..f6f6edd124 100644
--- a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs
+++ b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -133,8 +133,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
count -= n;
while (--n >= 0)
{
- s1 = s1 + (uint)(data[offset++] & 0xff);
- s2 = s2 + s1;
+ s1 += (uint)(data[offset++] & 0xff);
+ s2 += s1;
}
s1 %= Base;
@@ -144,4 +144,4 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.checksum = (s2 << 16) | s1;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs
new file mode 100644
index 0000000000..3581125492
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs
@@ -0,0 +1,610 @@
+// 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 is the Deflater class. The deflater class compresses input
+ /// with the deflate algorithm described in RFC 1951. It has several
+ /// compression levels and three different strategies described below.
+ ///
+ /// This class is not thread safe. This is inherent in the API, due
+ /// to the split of deflate and setInput.
+ ///
+ /// author of the original java version : Jochen Hoenicke
+ ///
+ public class Deflater
+ {
+ #region Deflater Documentation
+
+ /*
+ * The Deflater can do the following state transitions:
+ *
+ * (1) -> INIT_STATE ----> INIT_FINISHING_STATE ---.
+ * / | (2) (5) |
+ * / v (5) |
+ * (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3)
+ * \ | (3) | ,--------'
+ * | | | (3) /
+ * v v (5) v v
+ * (1) -> BUSY_STATE ----> FINISHING_STATE
+ * | (6)
+ * v
+ * FINISHED_STATE
+ * \_____________________________________/
+ * | (7)
+ * v
+ * CLOSED_STATE
+ *
+ * (1) If we should produce a header we start in INIT_STATE, otherwise
+ * we start in BUSY_STATE.
+ * (2) A dictionary may be set only when we are in INIT_STATE, then
+ * we change the state as indicated.
+ * (3) Whether a dictionary is set or not, on the first call of deflate
+ * we change to BUSY_STATE.
+ * (4) -- intentionally left blank -- :)
+ * (5) FINISHING_STATE is entered, when flush() is called to indicate that
+ * there is no more INPUT. There are also states indicating, that
+ * the header wasn't written yet.
+ * (6) FINISHED_STATE is entered, when everything has been flushed to the
+ * internal pending output buffer.
+ * (7) At any time (7)
+ *
+ */
+
+ #endregion Deflater Documentation
+
+ #region Public Constants
+
+ ///
+ /// The best and slowest compression level. This tries to find very
+ /// long and distant string repetitions.
+ ///
+ public const int BEST_COMPRESSION = 9;
+
+ ///
+ /// The worst but fastest compression level.
+ ///
+ public const int BEST_SPEED = 1;
+
+ ///
+ /// The default compression level.
+ ///
+ public const int DEFAULT_COMPRESSION = -1;
+
+ ///
+ /// This level won't compress at all but output uncompressed blocks.
+ ///
+ public const int NO_COMPRESSION = 0;
+
+ ///
+ /// The compression method. This is the only method supported so far.
+ /// There is no need to use this constant at all.
+ ///
+ public const int DEFLATED = 8;
+
+ #endregion Public Constants
+
+ #region Public Enum
+
+ ///
+ /// Compression Level as an enum for safer use
+ ///
+ public enum CompressionLevel
+ {
+ ///
+ /// The best and slowest compression level. This tries to find very
+ /// long and distant string repetitions.
+ ///
+ BEST_COMPRESSION = Deflater.BEST_COMPRESSION,
+
+ ///
+ /// The worst but fastest compression level.
+ ///
+ BEST_SPEED = Deflater.BEST_SPEED,
+
+ ///
+ /// The default compression level.
+ ///
+ DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION,
+
+ ///
+ /// This level won't compress at all but output uncompressed blocks.
+ ///
+ NO_COMPRESSION = Deflater.NO_COMPRESSION,
+
+ ///
+ /// The compression method. This is the only method supported so far.
+ /// There is no need to use this constant at all.
+ ///
+ DEFLATED = Deflater.DEFLATED
+ }
+
+ #endregion Public Enum
+
+ #region Local Constants
+
+ private const int IS_SETDICT = 0x01;
+ private const int IS_FLUSHING = 0x04;
+ private const int IS_FINISHING = 0x08;
+
+ private const int INIT_STATE = 0x00;
+ private const int SETDICT_STATE = 0x01;
+
+ // private static int INIT_FINISHING_STATE = 0x08;
+ // private static int SETDICT_FINISHING_STATE = 0x09;
+ private const int BUSY_STATE = 0x10;
+
+ private const int FLUSHING_STATE = 0x14;
+ private const int FINISHING_STATE = 0x1c;
+ private const int FINISHED_STATE = 0x1e;
+ private const int CLOSED_STATE = 0x7f;
+
+ #endregion Local Constants
+
+ #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 compression level, a value between NO_COMPRESSION
+ /// and BEST_COMPRESSION.
+ ///
+ ///
+ /// true, if we should suppress the Zlib/RFC1950 header at the
+ /// beginning and the adler checksum at the end of the output. This is
+ /// useful for the GZIP/PKZIP formats.
+ ///
+ /// if lvl is out of range.
+ public Deflater(int level, bool noZlibHeaderOrFooter)
+ {
+ if (level == DEFAULT_COMPRESSION)
+ {
+ level = 6;
+ }
+ else if (level < NO_COMPRESSION || level > BEST_COMPRESSION)
+ {
+ throw new ArgumentOutOfRangeException(nameof(level));
+ }
+
+ pending = new DeflaterPending();
+ engine = new DeflaterEngine(pending, noZlibHeaderOrFooter);
+ this.noZlibHeaderOrFooter = noZlibHeaderOrFooter;
+ SetStrategy(DeflateStrategy.Default);
+ SetLevel(level);
+ Reset();
+ }
+
+ #endregion Constructors
+
+ ///
+ /// Resets the deflater. The deflater acts afterwards as if it was
+ /// just created with the same compression level and strategy as it
+ /// had before.
+ ///
+ public void Reset()
+ {
+ state = (noZlibHeaderOrFooter ? BUSY_STATE : INIT_STATE);
+ totalOut = 0;
+ pending.Reset();
+ engine.Reset();
+ }
+
+ ///
+ /// Gets the current adler checksum of the data that was processed so far.
+ ///
+ public int Adler
+ {
+ get
+ {
+ return engine.Adler;
+ }
+ }
+
+ ///
+ /// Gets the number of input bytes processed so far.
+ ///
+ public long TotalIn
+ {
+ get
+ {
+ return engine.TotalIn;
+ }
+ }
+
+ ///
+ /// Gets the number of output bytes so far.
+ ///
+ public long TotalOut
+ {
+ get
+ {
+ return totalOut;
+ }
+ }
+
+ ///
+ /// Flushes the current input block. Further calls to deflate() will
+ /// produce enough output to inflate everything in the current input
+ /// block. This is not part of Sun's JDK so I have made it package
+ /// private. It is used by DeflaterOutputStream to implement
+ /// flush().
+ ///
+ public void Flush()
+ {
+ state |= IS_FLUSHING;
+ }
+
+ ///
+ /// Finishes the deflater with the current input block. It is an error
+ /// to give more input after this method was called. This method must
+ /// be called to force all bytes to be flushed.
+ ///
+ public void Finish()
+ {
+ state |= (IS_FLUSHING | IS_FINISHING);
+ }
+
+ ///
+ /// Returns true if the stream was finished and no more output bytes
+ /// are available.
+ ///
+ public bool IsFinished
+ {
+ get
+ {
+ return (state == FINISHED_STATE) && pending.IsFlushed;
+ }
+ }
+
+ ///
+ /// Returns true, if the input buffer is empty.
+ /// You should then call setInput().
+ /// NOTE: This method can also return true when the stream
+ /// was finished.
+ ///
+ public bool IsNeedingInput
+ {
+ get
+ {
+ return engine.NeedsInput();
+ }
+ }
+
+ ///
+ /// Sets the data which should be compressed next. This should be only
+ /// called when needsInput indicates that more input is needed.
+ /// If you call setInput when needsInput() returns false, the
+ /// previous input that is still pending will be thrown away.
+ /// The given byte array should not be changed, before needsInput() returns
+ /// true again.
+ /// This call is equivalent to setInput(input, 0, input.length).
+ ///
+ ///
+ /// the buffer containing the input data.
+ ///
+ ///
+ /// if the buffer was finished() or ended().
+ ///
+ public void SetInput(byte[] input)
+ {
+ SetInput(input, 0, input.Length);
+ }
+
+ ///
+ /// Sets the data which should be compressed next. This should be
+ /// only called when needsInput indicates that more input is needed.
+ /// The given byte array should not be changed, before needsInput() returns
+ /// true again.
+ ///
+ ///
+ /// the buffer containing the input data.
+ ///
+ ///
+ /// the start of the data.
+ ///
+ ///
+ /// the number of data bytes of input.
+ ///
+ ///
+ /// if the buffer was Finish()ed or if previous input is still pending.
+ ///
+ public void SetInput(byte[] input, int offset, int count)
+ {
+ if ((state & IS_FINISHING) != 0)
+ {
+ throw new InvalidOperationException("Finish() already called");
+ }
+ engine.SetInput(input, offset, count);
+ }
+
+ ///
+ /// Sets the compression level. There is no guarantee of the exact
+ /// position of the change, but if you call this when needsInput is
+ /// true the change of compression level will occur somewhere near
+ /// before the end of the so far given input.
+ ///
+ ///
+ /// the new compression level.
+ ///
+ public void SetLevel(int level)
+ {
+ if (level == DEFAULT_COMPRESSION)
+ {
+ level = 6;
+ }
+ else if (level < NO_COMPRESSION || level > BEST_COMPRESSION)
+ {
+ throw new ArgumentOutOfRangeException(nameof(level));
+ }
+
+ if (this.level != level)
+ {
+ this.level = level;
+ engine.SetLevel(level);
+ }
+ }
+
+ ///
+ /// Get current compression level
+ ///
+ /// Returns the current compression level
+ public int GetLevel()
+ {
+ return level;
+ }
+
+ ///
+ /// Sets the compression strategy. Strategy is one of
+ /// DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact
+ /// position where the strategy is changed, the same as for
+ /// SetLevel() applies.
+ ///
+ ///
+ /// The new compression strategy.
+ ///
+ public void SetStrategy(DeflateStrategy strategy)
+ {
+ engine.Strategy = strategy;
+ }
+
+ ///
+ /// Deflates the current input block with to the given array.
+ ///
+ ///
+ /// The buffer where compressed data is stored
+ ///
+ ///
+ /// The number of compressed bytes added to the output, or 0 if either
+ /// IsNeedingInput() or IsFinished returns true or length is zero.
+ ///
+ public int Deflate(byte[] output)
+ {
+ return Deflate(output, 0, output.Length);
+ }
+
+ ///
+ /// Deflates the current input block to the given array.
+ ///
+ ///
+ /// Buffer to store the compressed data.
+ ///
+ ///
+ /// Offset into the output array.
+ ///
+ ///
+ /// The maximum number of bytes that may be stored.
+ ///
+ ///
+ /// The number of compressed bytes added to the output, or 0 if either
+ /// needsInput() or finished() returns true or length is zero.
+ ///
+ ///
+ /// If Finish() was previously called.
+ ///
+ ///
+ /// If offset or length don't match the array length.
+ ///
+ public int Deflate(byte[] output, int offset, int length)
+ {
+ int origLength = length;
+
+ if (state == CLOSED_STATE)
+ {
+ throw new InvalidOperationException("Deflater closed");
+ }
+
+ if (state < BUSY_STATE)
+ {
+ // output header
+ int header = (DEFLATED +
+ ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8;
+ int level_flags = (level - 1) >> 1;
+ if (level_flags < 0 || level_flags > 3)
+ {
+ level_flags = 3;
+ }
+ header |= level_flags << 6;
+ if ((state & IS_SETDICT) != 0)
+ {
+ // Dictionary was set
+ header |= DeflaterConstants.PRESET_DICT;
+ }
+ header += 31 - (header % 31);
+
+ pending.WriteShortMSB(header);
+ if ((state & IS_SETDICT) != 0)
+ {
+ int chksum = engine.Adler;
+ engine.ResetAdler();
+ pending.WriteShortMSB(chksum >> 16);
+ pending.WriteShortMSB(chksum & 0xffff);
+ }
+
+ state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING));
+ }
+
+ for (; ; )
+ {
+ int count = pending.Flush(output, offset, length);
+ offset += count;
+ totalOut += count;
+ length -= count;
+
+ if (length == 0 || state == FINISHED_STATE)
+ {
+ break;
+ }
+
+ if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0))
+ {
+ switch (state)
+ {
+ case BUSY_STATE:
+ // We need more input now
+ return origLength - length;
+
+ case FLUSHING_STATE:
+ if (level != NO_COMPRESSION)
+ {
+ /* We have to supply some lookahead. 8 bit lookahead
+ * is needed by the zlib inflater, and we must fill
+ * the next byte, so that all bits are flushed.
+ */
+ int neededbits = 8 + ((-pending.BitCount) & 7);
+ while (neededbits > 0)
+ {
+ /* write a static tree block consisting solely of
+ * an EOF:
+ */
+ pending.WriteBits(2, 10);
+ neededbits -= 10;
+ }
+ }
+ state = BUSY_STATE;
+ break;
+
+ case FINISHING_STATE:
+ pending.AlignToByte();
+
+ // Compressed data is complete. Write footer information if required.
+ if (!noZlibHeaderOrFooter)
+ {
+ int adler = engine.Adler;
+ pending.WriteShortMSB(adler >> 16);
+ pending.WriteShortMSB(adler & 0xffff);
+ }
+ state = FINISHED_STATE;
+ break;
+ }
+ }
+ }
+ return origLength - length;
+ }
+
+ ///
+ /// Sets the dictionary which should be used in the deflate process.
+ /// This call is equivalent to setDictionary(dict, 0, dict.Length).
+ ///
+ ///
+ /// the dictionary.
+ ///
+ ///
+ /// if SetInput () or Deflate () were already called or another dictionary was already set.
+ ///
+ public void SetDictionary(byte[] dictionary)
+ {
+ SetDictionary(dictionary, 0, dictionary.Length);
+ }
+
+ ///
+ /// Sets the dictionary which should be used in the deflate process.
+ /// The dictionary is a byte array containing strings that are
+ /// likely to occur in the data which should be compressed. The
+ /// dictionary is not stored in the compressed output, only a
+ /// checksum. To decompress the output you need to supply the same
+ /// dictionary again.
+ ///
+ ///
+ /// The dictionary data
+ ///
+ ///
+ /// The index where dictionary information commences.
+ ///
+ ///
+ /// The number of bytes in the dictionary.
+ ///
+ ///
+ /// If SetInput () or Deflate() were already called or another dictionary was already set.
+ ///
+ public void SetDictionary(byte[] dictionary, int index, int count)
+ {
+ if (state != INIT_STATE)
+ {
+ throw new InvalidOperationException();
+ }
+
+ state = SETDICT_STATE;
+ engine.SetDictionary(dictionary, index, count);
+ }
+
+ #region Instance Fields
+
+ ///
+ /// Compression level.
+ ///
+ private int level;
+
+ ///
+ /// If true no Zlib/RFC1950 headers or footers are generated
+ ///
+ private bool noZlibHeaderOrFooter;
+
+ ///
+ /// The current state.
+ ///
+ private int state;
+
+ ///
+ /// The total bytes of output written.
+ ///
+ private long totalOut;
+
+ ///
+ /// The pending output.
+ ///
+ private DeflaterPending pending;
+
+ ///
+ /// The deflater engine.
+ ///
+ private DeflaterEngine engine;
+
+ #endregion Instance Fields
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs
new file mode 100644
index 0000000000..67e8c6900b
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs
@@ -0,0 +1,151 @@
+// 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 contains constants used for deflation.
+ ///
+ public static class DeflaterConstants
+ {
+ ///
+ /// Set to true to enable debugging
+ ///
+ public const bool DEBUGGING = false;
+
+ ///
+ /// Written to Zip file to identify a stored block
+ ///
+ public const int STORED_BLOCK = 0;
+
+ ///
+ /// Identifies static tree in Zip file
+ ///
+ public const int STATIC_TREES = 1;
+
+ ///
+ /// Identifies dynamic tree in Zip file
+ ///
+ public const int DYN_TREES = 2;
+
+ ///
+ /// Header flag indicating a preset dictionary for deflation
+ ///
+ public const int PRESET_DICT = 0x20;
+
+ ///
+ /// Sets internal buffer sizes for Huffman encoding
+ ///
+ public const int DEFAULT_MEM_LEVEL = 8;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int MAX_MATCH = 258;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int MIN_MATCH = 3;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int MAX_WBITS = 15;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int WSIZE = 1 << MAX_WBITS;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int WMASK = WSIZE - 1;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int HASH_SIZE = 1 << HASH_BITS;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int HASH_MASK = HASH_SIZE - 1;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8);
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5);
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int DEFLATE_STORED = 0;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int DEFLATE_FAST = 1;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int DEFLATE_SLOW = 2;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 };
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 };
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 };
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 };
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 };
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
new file mode 100644
index 0000000000..7ac5b6c69f
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
@@ -0,0 +1,951 @@
+// 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
+{
+ ///
+ /// Strategies for deflater
+ ///
+ public enum DeflateStrategy
+ {
+ ///
+ /// The default strategy
+ ///
+ Default = 0,
+
+ ///
+ /// This strategy will only allow longer string repetitions. It is
+ /// useful for random data with a small character set.
+ ///
+ Filtered = 1,
+
+ ///
+ /// This strategy will not look for string repetitions at all. It
+ /// only encodes with Huffman trees (which means, that more common
+ /// characters get a smaller encoding.
+ ///
+ HuffmanOnly = 2
+ }
+
+ // DEFLATE ALGORITHM:
+ //
+ // The uncompressed stream is inserted into the window array. When
+ // the window array is full the first half is thrown away and the
+ // second half is copied to the beginning.
+ //
+ // The head array is a hash table. Three characters build a hash value
+ // and they the value points to the corresponding index in window of
+ // the last string with this hash. The prev array implements a
+ // linked list of matches with the same hash: prev[index & WMASK] points
+ // to the previous index with the same hash.
+ //
+
+ ///
+ /// Low level compression engine for deflate algorithm which uses a 32K sliding window
+ /// with secondary compression from Huffman/Shannon-Fano codes.
+ ///
+ public class DeflaterEngine
+ {
+ #region Constants
+
+ private const int TooFar = 4096;
+
+ #endregion Constants
+
+ #region Constructors
+
+ ///
+ /// Construct instance with pending buffer
+ /// Adler calculation will be peformed
+ ///
+ ///
+ /// Pending buffer to use
+ ///
+ public DeflaterEngine(DeflaterPending pending)
+ : this(pending, false)
+ {
+ }
+
+
+
+ ///
+ /// Construct instance with pending buffer
+ ///
+ ///
+ /// Pending buffer to use
+ ///
+ ///
+ /// If no adler calculation should be performed
+ ///
+ public DeflaterEngine(DeflaterPending pending, bool noAdlerCalculation)
+ {
+ this.pending = pending;
+ huffman = new DeflaterHuffman(pending);
+ if (!noAdlerCalculation)
+ adler = new Adler32();
+
+ window = new byte[2 * DeflaterConstants.WSIZE];
+ head = new short[DeflaterConstants.HASH_SIZE];
+ prev = new short[DeflaterConstants.WSIZE];
+
+ // We start at index 1, to avoid an implementation deficiency, that
+ // we cannot build a repeat pattern at index 0.
+ blockStart = strstart = 1;
+ }
+
+ #endregion Constructors
+
+ ///
+ /// Deflate drives actual compression of data
+ ///
+ /// True to flush input buffers
+ /// Finish deflation with the current input.
+ /// Returns true if progress has been made.
+ public bool Deflate(bool flush, bool finish)
+ {
+ bool progress;
+ do
+ {
+ FillWindow();
+ bool canFlush = flush && (inputOff == inputEnd);
+
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING) {
+ Console.WriteLine("window: [" + blockStart + "," + strstart + ","
+ + lookahead + "], " + compressionFunction + "," + canFlush);
+ }
+#endif
+ switch (compressionFunction)
+ {
+ case DeflaterConstants.DEFLATE_STORED:
+ progress = DeflateStored(canFlush, finish);
+ break;
+
+ case DeflaterConstants.DEFLATE_FAST:
+ progress = DeflateFast(canFlush, finish);
+ break;
+
+ case DeflaterConstants.DEFLATE_SLOW:
+ progress = DeflateSlow(canFlush, finish);
+ break;
+
+ default:
+ throw new InvalidOperationException("unknown compressionFunction");
+ }
+ } while (pending.IsFlushed && progress); // repeat while we have no pending output and progress was made
+ return progress;
+ }
+
+ ///
+ /// Sets input data to be deflated. Should only be called when NeedsInput()
+ /// returns true
+ ///
+ /// The buffer containing input data.
+ /// The offset of the first byte of data.
+ /// The number of bytes of data to use as input.
+ public void SetInput(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+
+ if (inputOff < inputEnd)
+ {
+ throw new InvalidOperationException("Old input was not completely processed");
+ }
+
+ int end = offset + count;
+
+ /* We want to throw an ArrayIndexOutOfBoundsException early. The
+ * check is very tricky: it also handles integer wrap around.
+ */
+ if ((offset > end) || (end > buffer.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+
+ inputBuf = buffer;
+ inputOff = offset;
+ inputEnd = end;
+ }
+
+ ///
+ /// Determines if more input is needed.
+ ///
+ /// Return true if input is needed via SetInput
+ public bool NeedsInput()
+ {
+ return (inputEnd == inputOff);
+ }
+
+ ///
+ /// Set compression dictionary
+ ///
+ /// The buffer containing the dictionary data
+ /// The offset in the buffer for the first byte of data
+ /// The length of the dictionary data.
+ public void SetDictionary(byte[] buffer, int offset, int length)
+ {
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING && (strstart != 1) )
+ {
+ throw new InvalidOperationException("strstart not 1");
+ }
+#endif
+ adler?.Update(new ArraySegment(buffer, offset, length));
+ if (length < DeflaterConstants.MIN_MATCH)
+ {
+ return;
+ }
+
+ if (length > DeflaterConstants.MAX_DIST)
+ {
+ offset += length - DeflaterConstants.MAX_DIST;
+ length = DeflaterConstants.MAX_DIST;
+ }
+
+ System.Array.Copy(buffer, offset, window, strstart, length);
+
+ UpdateHash();
+ --length;
+ while (--length > 0)
+ {
+ InsertString();
+ strstart++;
+ }
+ strstart += 2;
+ blockStart = strstart;
+ }
+
+ ///
+ /// Reset internal state
+ ///
+ public void Reset()
+ {
+ huffman.Reset();
+ adler?.Reset();
+ blockStart = strstart = 1;
+ lookahead = 0;
+ totalIn = 0;
+ prevAvailable = false;
+ matchLen = DeflaterConstants.MIN_MATCH - 1;
+
+ for (int i = 0; i < DeflaterConstants.HASH_SIZE; i++)
+ {
+ head[i] = 0;
+ }
+
+ for (int i = 0; i < DeflaterConstants.WSIZE; i++)
+ {
+ prev[i] = 0;
+ }
+ }
+
+ ///
+ /// Reset Adler checksum
+ ///
+ public void ResetAdler()
+ {
+ adler?.Reset();
+ }
+
+ ///
+ /// Get current value of Adler checksum
+ ///
+ public int Adler
+ {
+ get
+ {
+ return (adler != null) ? unchecked((int)adler.Value) : 0;
+ }
+ }
+
+ ///
+ /// Total data processed
+ ///
+ public long TotalIn
+ {
+ get
+ {
+ return totalIn;
+ }
+ }
+
+ ///
+ /// Get/set the deflate strategy
+ ///
+ public DeflateStrategy Strategy
+ {
+ get
+ {
+ return strategy;
+ }
+ set
+ {
+ strategy = value;
+ }
+ }
+
+ ///
+ /// Set the deflate level (0-9)
+ ///
+ /// The value to set the level to.
+ public void SetLevel(int level)
+ {
+ if ((level < 0) || (level > 9))
+ {
+ throw new ArgumentOutOfRangeException(nameof(level));
+ }
+
+ goodLength = DeflaterConstants.GOOD_LENGTH[level];
+ max_lazy = DeflaterConstants.MAX_LAZY[level];
+ niceLength = DeflaterConstants.NICE_LENGTH[level];
+ max_chain = DeflaterConstants.MAX_CHAIN[level];
+
+ if (DeflaterConstants.COMPR_FUNC[level] != compressionFunction)
+ {
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING) {
+ Console.WriteLine("Change from " + compressionFunction + " to "
+ + DeflaterConstants.COMPR_FUNC[level]);
+ }
+#endif
+ switch (compressionFunction)
+ {
+ case DeflaterConstants.DEFLATE_STORED:
+ if (strstart > blockStart)
+ {
+ huffman.FlushStoredBlock(window, blockStart,
+ strstart - blockStart, false);
+ blockStart = strstart;
+ }
+ UpdateHash();
+ break;
+
+ case DeflaterConstants.DEFLATE_FAST:
+ if (strstart > blockStart)
+ {
+ huffman.FlushBlock(window, blockStart, strstart - blockStart,
+ false);
+ blockStart = strstart;
+ }
+ break;
+
+ case DeflaterConstants.DEFLATE_SLOW:
+ if (prevAvailable)
+ {
+ huffman.TallyLit(window[strstart - 1] & 0xff);
+ }
+ if (strstart > blockStart)
+ {
+ huffman.FlushBlock(window, blockStart, strstart - blockStart, false);
+ blockStart = strstart;
+ }
+ prevAvailable = false;
+ matchLen = DeflaterConstants.MIN_MATCH - 1;
+ break;
+ }
+ compressionFunction = DeflaterConstants.COMPR_FUNC[level];
+ }
+ }
+
+ ///
+ /// Fill the window
+ ///
+ public void FillWindow()
+ {
+ /* If the window is almost full and there is insufficient lookahead,
+ * move the upper half to the lower one to make room in the upper half.
+ */
+ if (strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST)
+ {
+ SlideWindow();
+ }
+
+ /* If there is not enough lookahead, but still some input left,
+ * read in the input
+ */
+ if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd)
+ {
+ int more = 2 * DeflaterConstants.WSIZE - lookahead - strstart;
+
+ if (more > inputEnd - inputOff)
+ {
+ more = inputEnd - inputOff;
+ }
+
+ System.Array.Copy(inputBuf, inputOff, window, strstart + lookahead, more);
+ adler?.Update(new ArraySegment(inputBuf, inputOff, more));
+
+ inputOff += more;
+ totalIn += more;
+ lookahead += more;
+ }
+
+ if (lookahead >= DeflaterConstants.MIN_MATCH)
+ {
+ UpdateHash();
+ }
+ }
+
+ private void UpdateHash()
+ {
+ /*
+ if (DEBUGGING) {
+ Console.WriteLine("updateHash: "+strstart);
+ }
+ */
+ ins_h = (window[strstart] << DeflaterConstants.HASH_SHIFT) ^ window[strstart + 1];
+ }
+
+ ///
+ /// Inserts the current string in the head hash and returns the previous
+ /// value for this hash.
+ ///
+ /// The previous hash value
+ private int InsertString()
+ {
+ short match;
+ int hash = ((ins_h << DeflaterConstants.HASH_SHIFT) ^ window[strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK;
+
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING)
+ {
+ if (hash != (((window[strstart] << (2*HASH_SHIFT)) ^
+ (window[strstart + 1] << HASH_SHIFT) ^
+ (window[strstart + 2])) & HASH_MASK)) {
+ throw new ImageFormatException("hash inconsistent: " + hash + "/"
+ +window[strstart] + ","
+ +window[strstart + 1] + ","
+ +window[strstart + 2] + "," + HASH_SHIFT);
+ }
+ }
+#endif
+ prev[strstart & DeflaterConstants.WMASK] = match = head[hash];
+ head[hash] = unchecked((short)strstart);
+ ins_h = hash;
+ return match & 0xffff;
+ }
+
+ private void SlideWindow()
+ {
+ Array.Copy(window, DeflaterConstants.WSIZE, window, 0, DeflaterConstants.WSIZE);
+ matchStart -= DeflaterConstants.WSIZE;
+ strstart -= DeflaterConstants.WSIZE;
+ blockStart -= DeflaterConstants.WSIZE;
+
+ // Slide the hash table (could be avoided with 32 bit values
+ // at the expense of memory usage).
+ for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i)
+ {
+ int m = head[i] & 0xffff;
+ head[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
+ }
+
+ // Slide the prev table.
+ for (int i = 0; i < DeflaterConstants.WSIZE; i++)
+ {
+ int m = prev[i] & 0xffff;
+ prev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
+ }
+ }
+
+ ///
+ /// Find the best (longest) string in the window matching the
+ /// string starting at strstart.
+ ///
+ /// Preconditions:
+ ///
+ /// strstart + DeflaterConstants.MAX_MATCH <= window.length.
+ ///
+ ///
+ /// True if a match greater than the minimum length is found
+ private bool FindLongestMatch(int curMatch)
+ {
+ int match;
+ int scan = strstart;
+ // scanMax is the highest position that we can look at
+ int scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, lookahead) - 1;
+ int limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0);
+
+ byte[] window = this.window;
+ short[] prev = this.prev;
+ int chainLength = this.max_chain;
+ int niceLength = Math.Min(this.niceLength, lookahead);
+
+ matchLen = Math.Max(matchLen, DeflaterConstants.MIN_MATCH - 1);
+
+ if (scan + matchLen > scanMax) return false;
+
+ byte scan_end1 = window[scan + matchLen - 1];
+ byte scan_end = window[scan + matchLen];
+
+ // Do not waste too much time if we already have a good match:
+ if (matchLen >= this.goodLength) chainLength >>= 2;
+
+ do
+ {
+ match = curMatch;
+ scan = strstart;
+
+ if (window[match + matchLen] != scan_end
+ || window[match + matchLen - 1] != scan_end1
+ || window[match] != window[scan]
+ || window[++match] != window[++scan])
+ {
+ continue;
+ }
+
+ // scan is set to strstart+1 and the comparison passed, so
+ // scanMax - scan is the maximum number of bytes we can compare.
+ // below we compare 8 bytes at a time, so first we compare
+ // (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8
+
+ switch ((scanMax - scan) % 8)
+ {
+ case 1:
+ if (window[++scan] == window[++match]) break;
+ break;
+
+ case 2:
+ if (window[++scan] == window[++match]
+ && window[++scan] == window[++match]) break;
+ break;
+
+ case 3:
+ if (window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]) break;
+ break;
+
+ case 4:
+ if (window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]) break;
+ break;
+
+ case 5:
+ if (window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]) break;
+ break;
+
+ case 6:
+ if (window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]) break;
+ break;
+
+ case 7:
+ if (window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]) break;
+ break;
+ }
+
+ if (window[scan] == window[match])
+ {
+ /* We check for insufficient lookahead only every 8th comparison;
+ * the 256th check will be made at strstart + 258 unless lookahead is
+ * exhausted first.
+ */
+ do
+ {
+ if (scan == scanMax)
+ {
+ ++scan; // advance to first position not matched
+ ++match;
+
+ break;
+ }
+ }
+ while (window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]
+ && window[++scan] == window[++match]);
+ }
+
+ if (scan - strstart > matchLen)
+ {
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING && (ins_h == 0) )
+ Console.Error.WriteLine("Found match: " + curMatch + "-" + (scan - strstart));
+#endif
+
+ matchStart = curMatch;
+ matchLen = scan - strstart;
+
+ if (matchLen >= niceLength)
+ break;
+
+ scan_end1 = window[scan - 1];
+ scan_end = window[scan];
+ }
+ } while ((curMatch = (prev[curMatch & DeflaterConstants.WMASK] & 0xffff)) > limit && 0 != --chainLength);
+
+ return matchLen >= DeflaterConstants.MIN_MATCH;
+ }
+
+ private bool DeflateStored(bool flush, bool finish)
+ {
+ if (!flush && (lookahead == 0))
+ {
+ return false;
+ }
+
+ strstart += lookahead;
+ lookahead = 0;
+
+ int storedLength = strstart - blockStart;
+
+ if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full
+ (blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window
+ flush)
+ {
+ bool lastBlock = finish;
+ if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE)
+ {
+ storedLength = DeflaterConstants.MAX_BLOCK_SIZE;
+ lastBlock = false;
+ }
+
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING)
+ {
+ Console.WriteLine("storedBlock[" + storedLength + "," + lastBlock + "]");
+ }
+#endif
+
+ huffman.FlushStoredBlock(window, blockStart, storedLength, lastBlock);
+ blockStart += storedLength;
+ return !(lastBlock || storedLength == 0);
+ }
+ return true;
+ }
+
+ private bool DeflateFast(bool flush, bool finish)
+ {
+ if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush)
+ {
+ return false;
+ }
+
+ while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
+ {
+ if (lookahead == 0)
+ {
+ // We are flushing everything
+ huffman.FlushBlock(window, blockStart, strstart - blockStart, finish);
+ blockStart = strstart;
+ return false;
+ }
+
+ if (strstart > 2 * DeflaterConstants.WSIZE - DeflaterConstants.MIN_LOOKAHEAD)
+ {
+ /* slide window, as FindLongestMatch needs this.
+ * This should only happen when flushing and the window
+ * is almost full.
+ */
+ SlideWindow();
+ }
+
+ int hashHead;
+ if (lookahead >= DeflaterConstants.MIN_MATCH &&
+ (hashHead = InsertString()) != 0 &&
+ strategy != DeflateStrategy.HuffmanOnly &&
+ strstart - hashHead <= DeflaterConstants.MAX_DIST &&
+ FindLongestMatch(hashHead))
+ {
+ // longestMatch sets matchStart and matchLen
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING)
+ {
+ for (int i = 0 ; i < matchLen; i++) {
+ if (window[strstart + i] != window[matchStart + i]) {
+ throw new ImageFormatException("Match failure");
+ }
+ }
+ }
+#endif
+
+ bool full = huffman.TallyDist(strstart - matchStart, matchLen);
+
+ lookahead -= matchLen;
+ if (matchLen <= max_lazy && lookahead >= DeflaterConstants.MIN_MATCH)
+ {
+ while (--matchLen > 0)
+ {
+ ++strstart;
+ InsertString();
+ }
+ ++strstart;
+ }
+ else
+ {
+ strstart += matchLen;
+ if (lookahead >= DeflaterConstants.MIN_MATCH - 1)
+ {
+ UpdateHash();
+ }
+ }
+ matchLen = DeflaterConstants.MIN_MATCH - 1;
+ if (!full)
+ {
+ continue;
+ }
+ }
+ else
+ {
+ // No match found
+ huffman.TallyLit(window[strstart] & 0xff);
+ ++strstart;
+ --lookahead;
+ }
+
+ if (huffman.IsFull())
+ {
+ bool lastBlock = finish && (lookahead == 0);
+ huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock);
+ blockStart = strstart;
+ return !lastBlock;
+ }
+ }
+ return true;
+ }
+
+ private bool DeflateSlow(bool flush, bool finish)
+ {
+ if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush)
+ {
+ return false;
+ }
+
+ while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
+ {
+ if (lookahead == 0)
+ {
+ if (prevAvailable)
+ {
+ huffman.TallyLit(window[strstart - 1] & 0xff);
+ }
+ prevAvailable = false;
+
+ // We are flushing everything
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING && !flush)
+ {
+ throw new ImageFormatException("Not flushing, but no lookahead");
+ }
+#endif
+ huffman.FlushBlock(window, blockStart, strstart - blockStart,
+ finish);
+ blockStart = strstart;
+ return false;
+ }
+
+ if (strstart >= 2 * DeflaterConstants.WSIZE - DeflaterConstants.MIN_LOOKAHEAD)
+ {
+ /* slide window, as FindLongestMatch needs this.
+ * This should only happen when flushing and the window
+ * is almost full.
+ */
+ SlideWindow();
+ }
+
+ int prevMatch = matchStart;
+ int prevLen = matchLen;
+ if (lookahead >= DeflaterConstants.MIN_MATCH)
+ {
+ int hashHead = InsertString();
+
+ if (strategy != DeflateStrategy.HuffmanOnly &&
+ hashHead != 0 &&
+ strstart - hashHead <= DeflaterConstants.MAX_DIST &&
+ FindLongestMatch(hashHead))
+ {
+ // longestMatch sets matchStart and matchLen
+
+ // Discard match if too small and too far away
+ if (matchLen <= 5 && (strategy == DeflateStrategy.Filtered || (matchLen == DeflaterConstants.MIN_MATCH && strstart - matchStart > TooFar)))
+ {
+ matchLen = DeflaterConstants.MIN_MATCH - 1;
+ }
+ }
+ }
+
+ // previous match was better
+ if ((prevLen >= DeflaterConstants.MIN_MATCH) && (matchLen <= prevLen))
+ {
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING)
+ {
+ for (int i = 0 ; i < matchLen; i++) {
+ if (window[strstart-1+i] != window[prevMatch + i])
+ throw new ImageFormatException();
+ }
+ }
+#endif
+ huffman.TallyDist(strstart - 1 - prevMatch, prevLen);
+ prevLen -= 2;
+ do
+ {
+ strstart++;
+ lookahead--;
+ if (lookahead >= DeflaterConstants.MIN_MATCH)
+ {
+ InsertString();
+ }
+ } while (--prevLen > 0);
+
+ strstart++;
+ lookahead--;
+ prevAvailable = false;
+ matchLen = DeflaterConstants.MIN_MATCH - 1;
+ }
+ else
+ {
+ if (prevAvailable)
+ {
+ huffman.TallyLit(window[strstart - 1] & 0xff);
+ }
+ prevAvailable = true;
+ strstart++;
+ lookahead--;
+ }
+
+ if (huffman.IsFull())
+ {
+ int len = strstart - blockStart;
+ if (prevAvailable)
+ {
+ len--;
+ }
+ bool lastBlock = (finish && (lookahead == 0) && !prevAvailable);
+ huffman.FlushBlock(window, blockStart, len, lastBlock);
+ blockStart += len;
+ return !lastBlock;
+ }
+ }
+ return true;
+ }
+
+ #region Instance Fields
+
+ // Hash index of string to be inserted
+ private int ins_h;
+
+ ///
+ /// Hashtable, hashing three characters to an index for window, so
+ /// that window[index]..window[index+2] have this hash code.
+ /// Note that the array should really be unsigned short, so you need
+ /// to and the values with 0xffff.
+ ///
+ private short[] head;
+
+ ///
+ /// prev[index & WMASK] points to the previous index that has the
+ /// same hash code as the string starting at index. This way
+ /// entries with the same hash code are in a linked list.
+ /// Note that the array should really be unsigned short, so you need
+ /// to and the values with 0xffff.
+ ///
+ private short[] prev;
+
+ private int matchStart;
+
+ // Length of best match
+ private int matchLen;
+
+ // Set if previous match exists
+ private bool prevAvailable;
+
+ private int blockStart;
+
+ ///
+ /// Points to the current character in the window.
+ ///
+ private int strstart;
+
+ ///
+ /// lookahead is the number of characters starting at strstart in
+ /// window that are valid.
+ /// So window[strstart] until window[strstart+lookahead-1] are valid
+ /// characters.
+ ///
+ private int lookahead;
+
+ ///
+ /// This array contains the part of the uncompressed stream that
+ /// is of relevance. The current character is indexed by strstart.
+ ///
+ private byte[] window;
+
+ private DeflateStrategy strategy;
+ private int max_chain, max_lazy, niceLength, goodLength;
+
+ ///
+ /// The current compression function.
+ ///
+ private int compressionFunction;
+
+ ///
+ /// The input data for compression.
+ ///
+ private byte[] inputBuf;
+
+ ///
+ /// The total bytes of input read.
+ ///
+ private long totalIn;
+
+ ///
+ /// The offset into inputBuf, where input data starts.
+ ///
+ private int inputOff;
+
+ ///
+ /// The end offset of the input data.
+ ///
+ private int inputEnd;
+
+ private DeflaterPending pending;
+ private DeflaterHuffman huffman;
+
+ ///
+ /// The adler checksum
+ ///
+ private Adler32 adler;
+
+ #endregion Instance Fields
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
new file mode 100644
index 0000000000..bf506d14c9
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
@@ -0,0 +1,965 @@
+// 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 is the DeflaterHuffman class.
+ ///
+ /// This class is not thread safe. This is inherent in the API, due
+ /// to the split of Deflate and SetInput.
+ ///
+ /// author of the original java version : Jochen Hoenicke
+ ///
+ public class DeflaterHuffman
+ {
+ private const int BUFSIZE = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6);
+ private const int LITERAL_NUM = 286;
+
+ // Number of distance codes
+ private const int DIST_NUM = 30;
+
+ // Number of codes used to transfer bit lengths
+ private const int BITLEN_NUM = 19;
+
+ // repeat previous bit length 3-6 times (2 bits of repeat count)
+ private const int REP_3_6 = 16;
+
+ // repeat a zero length 3-10 times (3 bits of repeat count)
+ private const int REP_3_10 = 17;
+
+ // repeat a zero length 11-138 times (7 bits of repeat count)
+ private const int REP_11_138 = 18;
+
+ private const int EOF_SYMBOL = 256;
+
+ // The lengths of the bit length codes are sent in order of decreasing
+ // probability, to avoid transmitting the lengths for unused bit length codes.
+ private static readonly int[] BL_ORDER = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+
+ 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 class Tree
+ {
+ #region Instance Fields
+
+ public short[] freqs;
+
+ public byte[] length;
+
+ public int minNumCodes;
+
+ public int numCodes;
+
+ private short[] codes;
+ private readonly int[] bl_counts;
+ private readonly int maxLength;
+ private DeflaterHuffman dh;
+
+ #endregion Instance Fields
+
+ #region Constructors
+
+ public Tree(DeflaterHuffman dh, int elems, int minCodes, int maxLength)
+ {
+ this.dh = dh;
+ this.minNumCodes = minCodes;
+ this.maxLength = maxLength;
+ freqs = new short[elems];
+ bl_counts = new int[maxLength];
+ }
+
+ #endregion Constructors
+
+ ///
+ /// Resets the internal state of the tree
+ ///
+ public void Reset()
+ {
+ for (int i = 0; i < freqs.Length; i++)
+ {
+ freqs[i] = 0;
+ }
+ codes = null;
+ length = null;
+ }
+
+ public void WriteSymbol(int code)
+ {
+ // if (DeflaterConstants.DEBUGGING) {
+ // freqs[code]--;
+ // // Console.Write("writeSymbol("+freqs.length+","+code+"): ");
+ // }
+ dh.pending.WriteBits(codes[code] & 0xffff, length[code]);
+ }
+
+ ///
+ /// Check that all frequencies are zero
+ ///
+ ///
+ /// At least one frequency is non-zero
+ ///
+ public void CheckEmpty()
+ {
+ bool empty = true;
+ for (int i = 0; i < freqs.Length; i++)
+ {
+ empty &= freqs[i] == 0;
+ }
+
+ if (!empty)
+ {
+ throw new ImageFormatException("!Empty");
+ }
+ }
+
+ ///
+ /// Set static codes and length
+ ///
+ /// new codes
+ /// length for new codes
+ public void SetStaticCodes(short[] staticCodes, byte[] staticLengths)
+ {
+ codes = staticCodes;
+ length = staticLengths;
+ }
+
+ ///
+ /// Build dynamic codes and lengths
+ ///
+ public void BuildCodes()
+ {
+ int numSymbols = freqs.Length;
+ int[] nextCode = new int[maxLength];
+ int code = 0;
+
+ codes = new short[freqs.Length];
+
+ // if (DeflaterConstants.DEBUGGING) {
+ // //Console.WriteLine("buildCodes: "+freqs.Length);
+ // }
+
+ for (int bits = 0; bits < maxLength; bits++)
+ {
+ nextCode[bits] = code;
+ code += bl_counts[bits] << (15 - bits);
+
+ // if (DeflaterConstants.DEBUGGING) {
+ // //Console.WriteLine("bits: " + ( bits + 1) + " count: " + bl_counts[bits]
+ // +" nextCode: "+code);
+ // }
+ }
+
+#if DebugDeflation
+ if ( DeflaterConstants.DEBUGGING && (code != 65536) )
+ {
+ throw new ImageFormatException("Inconsistent bl_counts!");
+ }
+#endif
+ for (int i = 0; i < numCodes; i++)
+ {
+ int bits = length[i];
+ if (bits > 0)
+ {
+ // if (DeflaterConstants.DEBUGGING) {
+ // //Console.WriteLine("codes["+i+"] = rev(" + nextCode[bits-1]+"),
+ // +bits);
+ // }
+
+ codes[i] = BitReverse(nextCode[bits - 1]);
+ nextCode[bits - 1] += 1 << (16 - bits);
+ }
+ }
+ }
+
+ public void BuildTree()
+ {
+ int numSymbols = freqs.Length;
+
+ /* heap is a priority queue, sorted by frequency, least frequent
+ * nodes first. The heap is a binary tree, with the property, that
+ * the parent node is smaller than both child nodes. This assures
+ * that the smallest node is the first parent.
+ *
+ * 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.
+ */
+ int[] heap = new int[numSymbols];
+ int heapLen = 0;
+ int maxCode = 0;
+ for (int n = 0; n < numSymbols; n++)
+ {
+ int freq = freqs[n];
+ if (freq != 0)
+ {
+ // Insert n into heap
+ int pos = heapLen++;
+ int ppos;
+ while (pos > 0 && freqs[heap[ppos = (pos - 1) / 2]] > freq)
+ {
+ heap[pos] = heap[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)
+ {
+ int node = maxCode < 2 ? ++maxCode : 0;
+ heap[heapLen++] = node;
+ }
+
+ numCodes = Math.Max(maxCode + 1, 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 = heap[i];
+ childs[2 * i] = node;
+ childs[2 * i + 1] = -1;
+ values[i] = freqs[node] << 8;
+ heap[i] = i;
+ }
+
+ /* Construct the Huffman tree by repeatedly combining the least two
+ * frequent nodes.
+ */
+ do
+ {
+ int first = heap[0];
+ int last = heap[--heapLen];
+
+ // Propagate the hole to the leafs of the heap
+ int ppos = 0;
+ int path = 1;
+
+ while (path < heapLen)
+ {
+ if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]])
+ {
+ path++;
+ }
+
+ heap[ppos] = heap[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[heap[ppos = (path - 1) / 2]] > lastVal)
+ {
+ heap[path] = heap[ppos];
+ }
+ heap[path] = last;
+
+ int second = heap[0];
+
+ // 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;
+
+ // Again, propagate the hole to the leafs
+ ppos = 0;
+ path = 1;
+
+ while (path < heapLen)
+ {
+ if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]])
+ {
+ path++;
+ }
+
+ heap[ppos] = heap[path];
+ ppos = path;
+ path = ppos * 2 + 1;
+ }
+
+ // Now propagate the new element down along path
+ while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal)
+ {
+ heap[path] = heap[ppos];
+ }
+ heap[path] = last;
+ } while (heapLen > 1);
+
+ if (heap[0] != childs.Length / 2 - 1)
+ {
+ throw new ImageFormatException("Heap invariant violated");
+ }
+
+ BuildLength(childs);
+ }
+
+ ///
+ /// Get encoded length
+ ///
+ /// Encoded length, the sum of frequencies * lengths
+ public int GetEncodedLength()
+ {
+ int len = 0;
+ for (int i = 0; i < freqs.Length; i++)
+ {
+ len += freqs[i] * length[i];
+ }
+ return len;
+ }
+
+ ///
+ /// Scan a literal or distance tree to determine the frequencies of the codes
+ /// in the bit length tree.
+ ///
+ 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 i = 0;
+ while (i < numCodes)
+ {
+ count = 1;
+ int nextlen = length[i];
+ if (nextlen == 0)
+ {
+ max_count = 138;
+ min_count = 3;
+ }
+ else
+ {
+ max_count = 6;
+ min_count = 3;
+ if (curlen != nextlen)
+ {
+ blTree.freqs[nextlen]++;
+ count = 0;
+ }
+ }
+ curlen = nextlen;
+ i++;
+
+ while (i < numCodes && curlen == length[i])
+ {
+ i++;
+ if (++count >= max_count)
+ {
+ break;
+ }
+ }
+
+ if (count < min_count)
+ {
+ blTree.freqs[curlen] += (short)count;
+ }
+ else if (curlen != 0)
+ {
+ blTree.freqs[REP_3_6]++;
+ }
+ else if (count <= 10)
+ {
+ blTree.freqs[REP_3_10]++;
+ }
+ else
+ {
+ blTree.freqs[REP_11_138]++;
+ }
+ }
+ }
+
+ ///
+ /// Write tree values
+ ///
+ /// Tree to write
+ public void WriteTree(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 i = 0;
+ while (i < numCodes)
+ {
+ count = 1;
+ int nextlen = length[i];
+ if (nextlen == 0)
+ {
+ max_count = 138;
+ min_count = 3;
+ }
+ else
+ {
+ max_count = 6;
+ min_count = 3;
+ if (curlen != nextlen)
+ {
+ blTree.WriteSymbol(nextlen);
+ count = 0;
+ }
+ }
+ curlen = nextlen;
+ i++;
+
+ while (i < numCodes && curlen == length[i])
+ {
+ i++;
+ if (++count >= max_count)
+ {
+ break;
+ }
+ }
+
+ if (count < min_count)
+ {
+ while (count-- > 0)
+ {
+ blTree.WriteSymbol(curlen);
+ }
+ }
+ else if (curlen != 0)
+ {
+ blTree.WriteSymbol(REP_3_6);
+ dh.pending.WriteBits(count - 3, 2);
+ }
+ else if (count <= 10)
+ {
+ blTree.WriteSymbol(REP_3_10);
+ dh.pending.WriteBits(count - 3, 3);
+ }
+ else
+ {
+ blTree.WriteSymbol(REP_11_138);
+ dh.pending.WriteBits(count - 11, 7);
+ }
+ }
+ }
+
+ private void BuildLength(int[] childs)
+ {
+ this.length = new byte[freqs.Length];
+ int numNodes = childs.Length / 2;
+ int numLeafs = (numNodes + 1) / 2;
+ int overflow = 0;
+
+ for (int i = 0; i < maxLength; i++)
+ {
+ bl_counts[i] = 0;
+ }
+
+ // First calculate optimal bit lengths
+ int[] lengths = new int[numNodes];
+ lengths[numNodes - 1] = 0;
+
+ for (int i = numNodes - 1; i >= 0; i--)
+ {
+ if (childs[2 * i + 1] != -1)
+ {
+ int bitLength = lengths[i] + 1;
+ if (bitLength > maxLength)
+ {
+ bitLength = maxLength;
+ overflow++;
+ }
+ lengths[childs[2 * i]] = lengths[childs[2 * i + 1]] = bitLength;
+ }
+ else
+ {
+ // A leaf node
+ int bitLength = lengths[i];
+ bl_counts[bitLength - 1]++;
+ this.length[childs[2 * i]] = (byte)lengths[i];
+ }
+ }
+
+ // if (DeflaterConstants.DEBUGGING) {
+ // //Console.WriteLine("Tree "+freqs.Length+" lengths:");
+ // for (int i=0; i < numLeafs; i++) {
+ // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]]
+ // + " len: "+length[childs[2*i]]);
+ // }
+ // }
+
+ if (overflow == 0)
+ {
+ return;
+ }
+
+ int incrBitLen = maxLength - 1;
+ do
+ {
+ // Find the first bit length which could increase:
+ while (bl_counts[--incrBitLen] == 0)
+ {
+ }
+
+ // Move this node one down and remove a corresponding
+ // number of overflow nodes.
+ do
+ {
+ bl_counts[incrBitLen]--;
+ bl_counts[++incrBitLen]++;
+ overflow -= 1 << (maxLength - 1 - incrBitLen);
+ } while (overflow > 0 && incrBitLen < maxLength - 1);
+ } while (overflow > 0);
+
+ /* We may have overshot above. Move some nodes from maxLength to
+ * maxLength-1 in that case.
+ */
+ bl_counts[maxLength - 1] += overflow;
+ bl_counts[maxLength - 2] -= overflow;
+
+ /* Now recompute all bit lengths, scanning in increasing
+ * frequency. It is simpler to reconstruct all lengths instead of
+ * fixing only the wrong ones. This idea is taken from 'ar'
+ * written by Haruhiko Okumura.
+ *
+ * The nodes were inserted with decreasing frequency into the childs
+ * array.
+ */
+ int nodePtr = 2 * numLeafs;
+ for (int bits = maxLength; bits != 0; bits--)
+ {
+ int n = bl_counts[bits - 1];
+ while (n > 0)
+ {
+ int childPtr = 2 * childs[nodePtr++];
+ if (childs[childPtr + 1] == -1)
+ {
+ // We found another leaf
+ length[childs[childPtr]] = (byte)bits;
+ n--;
+ }
+ }
+ }
+ // if (DeflaterConstants.DEBUGGING) {
+ // //Console.WriteLine("*** After overflow elimination. ***");
+ // for (int i=0; i < numLeafs; i++) {
+ // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]]
+ // + " len: "+length[childs[2*i]]);
+ // }
+ // }
+ }
+ }
+
+ #region Instance Fields
+
+ ///
+ /// Pending buffer to use
+ ///
+ public DeflaterPending pending;
+
+ private Tree literalTree;
+ private Tree distTree;
+ private Tree blTree;
+
+ // Buffer for distances
+ private short[] d_buf;
+
+ private byte[] l_buf;
+ private int last_lit;
+ private int extra_bits;
+
+ #endregion Instance Fields
+
+ static DeflaterHuffman()
+ {
+ // See RFC 1951 3.2.6
+ // Literal codes
+ staticLCodes = new short[LITERAL_NUM];
+ staticLLength = new byte[LITERAL_NUM];
+
+ int i = 0;
+ while (i < 144)
+ {
+ staticLCodes[i] = BitReverse((0x030 + i) << 8);
+ staticLLength[i++] = 8;
+ }
+
+ while (i < 256)
+ {
+ staticLCodes[i] = BitReverse((0x190 - 144 + i) << 7);
+ staticLLength[i++] = 9;
+ }
+
+ while (i < 280)
+ {
+ staticLCodes[i] = BitReverse((0x000 - 256 + i) << 9);
+ staticLLength[i++] = 7;
+ }
+
+ while (i < LITERAL_NUM)
+ {
+ staticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8);
+ staticLLength[i++] = 8;
+ }
+
+ // Distance codes
+ staticDCodes = new short[DIST_NUM];
+ staticDLength = new byte[DIST_NUM];
+ for (i = 0; i < DIST_NUM; i++)
+ {
+ staticDCodes[i] = BitReverse(i << 11);
+ staticDLength[i] = 5;
+ }
+ }
+
+ ///
+ /// Construct instance with pending buffer
+ ///
+ /// Pending buffer to use
+ public DeflaterHuffman(DeflaterPending pending)
+ {
+ this.pending = pending;
+
+ literalTree = new Tree(this, LITERAL_NUM, 257, 15);
+ distTree = new Tree(this, DIST_NUM, 1, 15);
+ blTree = new Tree(this, BITLEN_NUM, 4, 7);
+
+ d_buf = new short[BUFSIZE];
+ l_buf = new byte[BUFSIZE];
+ }
+
+ ///
+ /// Reset internal state
+ ///
+ public void Reset()
+ {
+ last_lit = 0;
+ extra_bits = 0;
+ literalTree.Reset();
+ distTree.Reset();
+ blTree.Reset();
+ }
+
+ ///
+ /// Write all trees to pending buffer
+ ///
+ /// The number/rank of treecodes to send.
+ public void SendAllTrees(int blTreeCodes)
+ {
+ blTree.BuildCodes();
+ literalTree.BuildCodes();
+ distTree.BuildCodes();
+ pending.WriteBits(literalTree.numCodes - 257, 5);
+ pending.WriteBits(distTree.numCodes - 1, 5);
+ pending.WriteBits(blTreeCodes - 4, 4);
+ for (int rank = 0; rank < blTreeCodes; rank++)
+ {
+ pending.WriteBits(blTree.length[BL_ORDER[rank]], 3);
+ }
+ literalTree.WriteTree(blTree);
+ distTree.WriteTree(blTree);
+
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING) {
+ blTree.CheckEmpty();
+ }
+#endif
+ }
+
+ ///
+ /// Compress current buffer writing data to pending buffer
+ ///
+ public void CompressBlock()
+ {
+ for (int i = 0; i < last_lit; i++)
+ {
+ int litlen = l_buf[i] & 0xff;
+ int dist = d_buf[i];
+ if (dist-- != 0)
+ {
+ // if (DeflaterConstants.DEBUGGING) {
+ // Console.Write("["+(dist+1)+","+(litlen+3)+"]: ");
+ // }
+
+ int lc = Lcode(litlen);
+ literalTree.WriteSymbol(lc);
+
+ int bits = (lc - 261) / 4;
+ if (bits > 0 && bits <= 5)
+ {
+ pending.WriteBits(litlen & ((1 << bits) - 1), bits);
+ }
+
+ int dc = Dcode(dist);
+ distTree.WriteSymbol(dc);
+
+ bits = dc / 2 - 1;
+ if (bits > 0)
+ {
+ pending.WriteBits(dist & ((1 << bits) - 1), bits);
+ }
+ }
+ else
+ {
+ // if (DeflaterConstants.DEBUGGING) {
+ // if (litlen > 32 && litlen < 127) {
+ // Console.Write("("+(char)litlen+"): ");
+ // } else {
+ // Console.Write("{"+litlen+"}: ");
+ // }
+ // }
+ literalTree.WriteSymbol(litlen);
+ }
+ }
+
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING) {
+ Console.Write("EOF: ");
+ }
+#endif
+ literalTree.WriteSymbol(EOF_SYMBOL);
+
+#if DebugDeflation
+ if (DeflaterConstants.DEBUGGING) {
+ literalTree.CheckEmpty();
+ distTree.CheckEmpty();
+ }
+#endif
+ }
+
+ ///
+ /// Flush block to output with no compression
+ ///
+ /// Data to write
+ /// Index of first byte to write
+ /// Count of bytes to write
+ /// True if this is the last block
+ public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
+ {
+#if DebugDeflation
+ // if (DeflaterConstants.DEBUGGING) {
+ // //Console.WriteLine("Flushing stored block "+ storedLength);
+ // }
+#endif
+ pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3);
+ pending.AlignToByte();
+ pending.WriteShort(storedLength);
+ pending.WriteShort(~storedLength);
+ pending.WriteBlock(stored, storedOffset, storedLength);
+ Reset();
+ }
+
+ ///
+ /// Flush block to output with compression
+ ///
+ /// Data to flush
+ /// Index of first byte to flush
+ /// Count of bytes to flush
+ /// True if this is the last block
+ public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
+ {
+ literalTree.freqs[EOF_SYMBOL]++;
+
+ // Build trees
+ literalTree.BuildTree();
+ distTree.BuildTree();
+
+ // Calculate bitlen frequency
+ literalTree.CalcBLFreq(blTree);
+ distTree.CalcBLFreq(blTree);
+
+ // Build bitlen tree
+ blTree.BuildTree();
+
+ int blTreeCodes = 4;
+ for (int i = 18; i > blTreeCodes; i--)
+ {
+ if (blTree.length[BL_ORDER[i]] > 0)
+ {
+ blTreeCodes = i + 1;
+ }
+ }
+ int opt_len = 14 + blTreeCodes * 3 + blTree.GetEncodedLength() +
+ literalTree.GetEncodedLength() + distTree.GetEncodedLength() +
+ extra_bits;
+
+ int static_len = extra_bits;
+ for (int i = 0; i < LITERAL_NUM; i++)
+ {
+ static_len += literalTree.freqs[i] * staticLLength[i];
+ }
+ for (int i = 0; i < DIST_NUM; i++)
+ {
+ static_len += distTree.freqs[i] * staticDLength[i];
+ }
+ if (opt_len >= static_len)
+ {
+ // Force static trees
+ opt_len = static_len;
+ }
+
+ if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3)
+ {
+ // Store Block
+
+ // if (DeflaterConstants.DEBUGGING) {
+ // //Console.WriteLine("Storing, since " + storedLength + " < " + opt_len
+ // + " <= " + static_len);
+ // }
+ FlushStoredBlock(stored, storedOffset, storedLength, lastBlock);
+ }
+ else if (opt_len == static_len)
+ {
+ // Encode with static tree
+ pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3);
+ literalTree.SetStaticCodes(staticLCodes, staticLLength);
+ distTree.SetStaticCodes(staticDCodes, staticDLength);
+ CompressBlock();
+ Reset();
+ }
+ else
+ {
+ // Encode with dynamic tree
+ pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3);
+ SendAllTrees(blTreeCodes);
+ CompressBlock();
+ Reset();
+ }
+ }
+
+ ///
+ /// Get value indicating if internal buffer is full
+ ///
+ /// true if buffer is full
+ public bool IsFull()
+ {
+ return last_lit >= BUFSIZE;
+ }
+
+ ///
+ /// Add literal to buffer
+ ///
+ /// Literal value to add to buffer.
+ /// Value indicating internal buffer is full
+ public bool TallyLit(int literal)
+ {
+ // if (DeflaterConstants.DEBUGGING) {
+ // if (lit > 32 && lit < 127) {
+ // //Console.WriteLine("("+(char)lit+")");
+ // } else {
+ // //Console.WriteLine("{"+lit+"}");
+ // }
+ // }
+ d_buf[last_lit] = 0;
+ l_buf[last_lit++] = (byte)literal;
+ literalTree.freqs[literal]++;
+ return IsFull();
+ }
+
+ ///
+ /// Add distance code and length to literal and distance trees
+ ///
+ /// Distance code
+ /// Length
+ /// Value indicating if internal buffer is full
+ public bool TallyDist(int distance, int length)
+ {
+ // if (DeflaterConstants.DEBUGGING) {
+ // //Console.WriteLine("[" + distance + "," + length + "]");
+ // }
+
+ d_buf[last_lit] = (short)distance;
+ l_buf[last_lit++] = (byte)(length - 3);
+
+ int lc = Lcode(length - 3);
+ literalTree.freqs[lc]++;
+ if (lc >= 265 && lc < 285)
+ {
+ extra_bits += (lc - 261) / 4;
+ }
+
+ int dc = Dcode(distance - 1);
+ distTree.freqs[dc]++;
+ if (dc >= 4)
+ {
+ extra_bits += dc / 2 - 1;
+ }
+ return IsFull();
+ }
+
+ ///
+ /// Reverse the bits of a 16 bit value.
+ ///
+ /// Value to reverse bits
+ /// Value with bits reversed
+ 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]);
+ }
+
+ private static int Lcode(int length)
+ {
+ if (length == 255)
+ {
+ return 285;
+ }
+
+ int code = 257;
+ while (length >= 8)
+ {
+ code += 4;
+ length >>= 1;
+ }
+ return code + length;
+ }
+
+ private static int Dcode(int distance)
+ {
+ int code = 0;
+ while (distance >= 4)
+ {
+ code += 2;
+ distance >>= 1;
+ }
+ return code + distance;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs
new file mode 100644
index 0000000000..31db032aec
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs
@@ -0,0 +1,499 @@
+// 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;
+
+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
+ ///
+ 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.
+ ///
+ ///
+ /// the output stream where deflated output should be written.
+ ///
+ ///
+ /// the underlying deflater.
+ ///
+ public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater)
+ : this(baseOutputStream, deflater, 512)
+ {
+ }
+
+ ///
+ /// Creates a new DeflaterOutputStream with the given Deflater and
+ /// buffer size.
+ ///
+ ///
+ /// 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)
+ {
+ 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));
+ }
+
+ #endregion Constructors
+
+ #region Public API
+
+ ///
+ /// 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;
+ }
+
+ //if (cryptoTransform_ != null)
+ //{
+ // EncryptBlock(buffer_, 0, len);
+ //}
+
+ baseOutputStream_.Write(buffer_, 0, len);
+ }
+
+ if (!deflater_.IsFinished)
+ {
+ throw new ImageFormatException("Can't deflate all input?");
+ }
+
+ baseOutputStream_.Flush();
+
+ //if (cryptoTransform_ != null)
+ //{
+ // if (cryptoTransform_ is ZipAESTransform)
+ // {
+ // AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
+ // }
+ // cryptoTransform_.Dispose();
+ // cryptoTransform_ = null;
+ //}
+ }
+
+ ///
+ /// 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
+ {
+ return baseOutputStream_.CanSeek;
+ }
+ }
+
+ #endregion Public API
+
+ //#region Encryption
+
+ //private string password;
+
+ //private ICryptoTransform cryptoTransform_;
+
+ /////
+ ///// Returns the 10 byte AUTH CODE to be appended immediately following the AES data stream.
+ /////
+ //protected byte[] AESAuthCode;
+
+ /////
+ ///// 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;
+ // }
+ // }
+ //}
+
+ /////
+ ///// 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);
+ //}
+
+ /////
+ ///// 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
+
+ ///
+ /// Deflates everything in the input buffers. This will call
+ /// def.deflate() until all bytes from the input buffers
+ /// are processed.
+ ///
+ protected void Deflate()
+ {
+ Deflate(false);
+ }
+
+ private void Deflate(bool flushing)
+ {
+ while (flushing || !deflater_.IsNeedingInput)
+ {
+ int deflateCount = deflater_.Deflate(buffer_, 0, buffer_.Length);
+
+ if (deflateCount <= 0)
+ {
+ break;
+ }
+ //if (cryptoTransform_ != null)
+ //{
+ // EncryptBlock(buffer_, 0, deflateCount);
+ //}
+
+ baseOutputStream_.Write(buffer_, 0, deflateCount);
+ }
+
+ if (!deflater_.IsNeedingInput)
+ {
+ throw new ImageFormatException("DeflaterOutputStream can't deflate all input?");
+ }
+ }
+
+ #endregion Deflation Support
+
+ #region Stream Overrides
+
+ ///
+ /// Gets value indicating stream can be read from
+ ///
+ public override bool CanRead
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Gets a value indicating if seeking is supported for this stream
+ /// This property always returns false
+ ///
+ public override bool CanSeek
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Get value indicating if this stream supports writing
+ ///
+ public override bool CanWrite
+ {
+ get
+ {
+ return baseOutputStream_.CanWrite;
+ }
+ }
+
+ ///
+ /// Get current length of stream
+ ///
+ public override long Length
+ {
+ get
+ {
+ return baseOutputStream_.Length;
+ }
+ }
+
+ ///
+ /// Gets the current position within the stream.
+ ///
+ /// Any attempt to set position
+ public override long Position
+ {
+ get
+ {
+ return baseOutputStream_.Position;
+ }
+ set
+ {
+ throw new NotSupportedException("Position property not supported");
+ }
+ }
+
+ ///
+ /// 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_)
+ {
+ isClosed_ = true;
+
+ try
+ {
+ Finish();
+ //if (cryptoTransform_ != null)
+ //{
+ // GetAuthCodeIfAES();
+ // cryptoTransform_.Dispose();
+ // cryptoTransform_ = null;
+ //}
+ }
+ finally
+ {
+ if (IsStreamOwner)
+ {
+ baseOutputStream_.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();
+ }
+
+ #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/DeflaterPending.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterPending.cs
new file mode 100644
index 0000000000..cc421c5b79
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterPending.cs
@@ -0,0 +1,21 @@
+// 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/PendingBuffer.cs b/src/ImageSharp/Formats/Png/Zlib/PendingBuffer.cs
new file mode 100644
index 0000000000..ae02c5113d
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/PendingBuffer.cs
@@ -0,0 +1,276 @@
+// 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 8e0bac938f..ffe176368c 100644
--- a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
+++ b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -38,7 +38,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
///
/// The stream responsible for compressing the input stream.
///
- private System.IO.Compression.DeflateStream deflateStream;
+ // private DeflateStream deflateStream;
+ private DeflaterOutputStream deflateStream;
///
/// Initializes a new instance of the class.
@@ -89,18 +90,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
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.deflateStream = new System.IO.Compression.DeflateStream(this.rawStream, level, true);
+ // CompressionLevel level = CompressionLevel.Optimal;
+ //
+ // if (compressionLevel >= 1 && compressionLevel <= 5)
+ // {
+ // level = CompressionLevel.Fastest;
+ // }
+ // else if (compressionLevel == 0)
+ // {
+ // level = CompressionLevel.NoCompression;
+ // }
+ this.deflateStream = new DeflaterOutputStream(this.rawStream, new Deflater(compressionLevel, true)) { IsStreamOwner = false };
+
+ // this.deflateStream = new DeflateStream(this.rawStream, level, true);
}
///
diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs
index 157dadd2c1..7bd1b80447 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs
@@ -1,9 +1,10 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes;
+using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
@@ -56,8 +57,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
using (var memoryStream = new MemoryStream())
{
- this.bmpCore.SaveAsPng(memoryStream);
+ var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None };
+ this.bmpCore.SaveAsPng(memoryStream, encoder);
}
}
}
-}
\ No newline at end of file
+}