mirror of https://github.com/SixLabors/ImageSharp
10 changed files with 3498 additions and 21 deletions
@ -0,0 +1,610 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// <auto-generated/>
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Png.Zlib |
|||
{ |
|||
/// <summary>
|
|||
/// 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 <i>not</i> thread safe. This is inherent in the API, due
|
|||
/// to the split of deflate and setInput.
|
|||
///
|
|||
/// author of the original java version : Jochen Hoenicke
|
|||
/// </summary>
|
|||
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
|
|||
|
|||
/// <summary>
|
|||
/// The best and slowest compression level. This tries to find very
|
|||
/// long and distant string repetitions.
|
|||
/// </summary>
|
|||
public const int BEST_COMPRESSION = 9; |
|||
|
|||
/// <summary>
|
|||
/// The worst but fastest compression level.
|
|||
/// </summary>
|
|||
public const int BEST_SPEED = 1; |
|||
|
|||
/// <summary>
|
|||
/// The default compression level.
|
|||
/// </summary>
|
|||
public const int DEFAULT_COMPRESSION = -1; |
|||
|
|||
/// <summary>
|
|||
/// This level won't compress at all but output uncompressed blocks.
|
|||
/// </summary>
|
|||
public const int NO_COMPRESSION = 0; |
|||
|
|||
/// <summary>
|
|||
/// The compression method. This is the only method supported so far.
|
|||
/// There is no need to use this constant at all.
|
|||
/// </summary>
|
|||
public const int DEFLATED = 8; |
|||
|
|||
#endregion Public Constants
|
|||
|
|||
#region Public Enum
|
|||
|
|||
/// <summary>
|
|||
/// Compression Level as an enum for safer use
|
|||
/// </summary>
|
|||
public enum CompressionLevel |
|||
{ |
|||
/// <summary>
|
|||
/// The best and slowest compression level. This tries to find very
|
|||
/// long and distant string repetitions.
|
|||
/// </summary>
|
|||
BEST_COMPRESSION = Deflater.BEST_COMPRESSION, |
|||
|
|||
/// <summary>
|
|||
/// The worst but fastest compression level.
|
|||
/// </summary>
|
|||
BEST_SPEED = Deflater.BEST_SPEED, |
|||
|
|||
/// <summary>
|
|||
/// The default compression level.
|
|||
/// </summary>
|
|||
DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION, |
|||
|
|||
/// <summary>
|
|||
/// This level won't compress at all but output uncompressed blocks.
|
|||
/// </summary>
|
|||
NO_COMPRESSION = Deflater.NO_COMPRESSION, |
|||
|
|||
/// <summary>
|
|||
/// The compression method. This is the only method supported so far.
|
|||
/// There is no need to use this constant at all.
|
|||
/// </summary>
|
|||
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
|
|||
|
|||
/// <summary>
|
|||
/// Creates a new deflater with default compression level.
|
|||
/// </summary>
|
|||
public Deflater() : this(DEFAULT_COMPRESSION, false) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new deflater with given compression level.
|
|||
/// </summary>
|
|||
/// <param name="level">
|
|||
/// the compression level, a value between NO_COMPRESSION
|
|||
/// and BEST_COMPRESSION, or DEFAULT_COMPRESSION.
|
|||
/// </param>
|
|||
/// <exception cref="System.ArgumentOutOfRangeException">if lvl is out of range.</exception>
|
|||
public Deflater(int level) : this(level, false) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new deflater with given compression level.
|
|||
/// </summary>
|
|||
/// <param name="level">
|
|||
/// the compression level, a value between NO_COMPRESSION
|
|||
/// and BEST_COMPRESSION.
|
|||
/// </param>
|
|||
/// <param name="noZlibHeaderOrFooter">
|
|||
/// 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.
|
|||
/// </param>
|
|||
/// <exception cref="System.ArgumentOutOfRangeException">if lvl is out of range.</exception>
|
|||
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
|
|||
|
|||
/// <summary>
|
|||
/// Resets the deflater. The deflater acts afterwards as if it was
|
|||
/// just created with the same compression level and strategy as it
|
|||
/// had before.
|
|||
/// </summary>
|
|||
public void Reset() |
|||
{ |
|||
state = (noZlibHeaderOrFooter ? BUSY_STATE : INIT_STATE); |
|||
totalOut = 0; |
|||
pending.Reset(); |
|||
engine.Reset(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current adler checksum of the data that was processed so far.
|
|||
/// </summary>
|
|||
public int Adler |
|||
{ |
|||
get |
|||
{ |
|||
return engine.Adler; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of input bytes processed so far.
|
|||
/// </summary>
|
|||
public long TotalIn |
|||
{ |
|||
get |
|||
{ |
|||
return engine.TotalIn; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of output bytes so far.
|
|||
/// </summary>
|
|||
public long TotalOut |
|||
{ |
|||
get |
|||
{ |
|||
return totalOut; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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().
|
|||
/// </summary>
|
|||
public void Flush() |
|||
{ |
|||
state |= IS_FLUSHING; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
public void Finish() |
|||
{ |
|||
state |= (IS_FLUSHING | IS_FINISHING); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns true if the stream was finished and no more output bytes
|
|||
/// are available.
|
|||
/// </summary>
|
|||
public bool IsFinished |
|||
{ |
|||
get |
|||
{ |
|||
return (state == FINISHED_STATE) && pending.IsFlushed; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
public bool IsNeedingInput |
|||
{ |
|||
get |
|||
{ |
|||
return engine.NeedsInput(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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 <code>setInput(input, 0, input.length)</code>.
|
|||
/// </summary>
|
|||
/// <param name="input">
|
|||
/// the buffer containing the input data.
|
|||
/// </param>
|
|||
/// <exception cref="System.InvalidOperationException">
|
|||
/// if the buffer was finished() or ended().
|
|||
/// </exception>
|
|||
public void SetInput(byte[] input) |
|||
{ |
|||
SetInput(input, 0, input.Length); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
/// <param name="input">
|
|||
/// the buffer containing the input data.
|
|||
/// </param>
|
|||
/// <param name="offset">
|
|||
/// the start of the data.
|
|||
/// </param>
|
|||
/// <param name="count">
|
|||
/// the number of data bytes of input.
|
|||
/// </param>
|
|||
/// <exception cref="System.InvalidOperationException">
|
|||
/// if the buffer was Finish()ed or if previous input is still pending.
|
|||
/// </exception>
|
|||
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); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
/// <param name="level">
|
|||
/// the new compression level.
|
|||
/// </param>
|
|||
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); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get current compression level
|
|||
/// </summary>
|
|||
/// <returns>Returns the current compression level</returns>
|
|||
public int GetLevel() |
|||
{ |
|||
return level; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
/// <param name="strategy">
|
|||
/// The new compression strategy.
|
|||
/// </param>
|
|||
public void SetStrategy(DeflateStrategy strategy) |
|||
{ |
|||
engine.Strategy = strategy; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Deflates the current input block with to the given array.
|
|||
/// </summary>
|
|||
/// <param name="output">
|
|||
/// The buffer where compressed data is stored
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The number of compressed bytes added to the output, or 0 if either
|
|||
/// IsNeedingInput() or IsFinished returns true or length is zero.
|
|||
/// </returns>
|
|||
public int Deflate(byte[] output) |
|||
{ |
|||
return Deflate(output, 0, output.Length); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Deflates the current input block to the given array.
|
|||
/// </summary>
|
|||
/// <param name="output">
|
|||
/// Buffer to store the compressed data.
|
|||
/// </param>
|
|||
/// <param name="offset">
|
|||
/// Offset into the output array.
|
|||
/// </param>
|
|||
/// <param name="length">
|
|||
/// The maximum number of bytes that may be stored.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The number of compressed bytes added to the output, or 0 if either
|
|||
/// needsInput() or finished() returns true or length is zero.
|
|||
/// </returns>
|
|||
/// <exception cref="System.InvalidOperationException">
|
|||
/// If Finish() was previously called.
|
|||
/// </exception>
|
|||
/// <exception cref="System.ArgumentOutOfRangeException">
|
|||
/// If offset or length don't match the array length.
|
|||
/// </exception>
|
|||
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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the dictionary which should be used in the deflate process.
|
|||
/// This call is equivalent to <code>setDictionary(dict, 0, dict.Length)</code>.
|
|||
/// </summary>
|
|||
/// <param name="dictionary">
|
|||
/// the dictionary.
|
|||
/// </param>
|
|||
/// <exception cref="System.InvalidOperationException">
|
|||
/// if SetInput () or Deflate () were already called or another dictionary was already set.
|
|||
/// </exception>
|
|||
public void SetDictionary(byte[] dictionary) |
|||
{ |
|||
SetDictionary(dictionary, 0, dictionary.Length); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
/// <param name="dictionary">
|
|||
/// The dictionary data
|
|||
/// </param>
|
|||
/// <param name="index">
|
|||
/// The index where dictionary information commences.
|
|||
/// </param>
|
|||
/// <param name="count">
|
|||
/// The number of bytes in the dictionary.
|
|||
/// </param>
|
|||
/// <exception cref="System.InvalidOperationException">
|
|||
/// If SetInput () or Deflate() were already called or another dictionary was already set.
|
|||
/// </exception>
|
|||
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
|
|||
|
|||
/// <summary>
|
|||
/// Compression level.
|
|||
/// </summary>
|
|||
private int level; |
|||
|
|||
/// <summary>
|
|||
/// If true no Zlib/RFC1950 headers or footers are generated
|
|||
/// </summary>
|
|||
private bool noZlibHeaderOrFooter; |
|||
|
|||
/// <summary>
|
|||
/// The current state.
|
|||
/// </summary>
|
|||
private int state; |
|||
|
|||
/// <summary>
|
|||
/// The total bytes of output written.
|
|||
/// </summary>
|
|||
private long totalOut; |
|||
|
|||
/// <summary>
|
|||
/// The pending output.
|
|||
/// </summary>
|
|||
private DeflaterPending pending; |
|||
|
|||
/// <summary>
|
|||
/// The deflater engine.
|
|||
/// </summary>
|
|||
private DeflaterEngine engine; |
|||
|
|||
#endregion Instance Fields
|
|||
} |
|||
} |
|||
@ -0,0 +1,151 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// <auto-generated/>
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Png.Zlib |
|||
{ |
|||
/// <summary>
|
|||
/// This class contains constants used for deflation.
|
|||
/// </summary>
|
|||
public static class DeflaterConstants |
|||
{ |
|||
/// <summary>
|
|||
/// Set to true to enable debugging
|
|||
/// </summary>
|
|||
public const bool DEBUGGING = false; |
|||
|
|||
/// <summary>
|
|||
/// Written to Zip file to identify a stored block
|
|||
/// </summary>
|
|||
public const int STORED_BLOCK = 0; |
|||
|
|||
/// <summary>
|
|||
/// Identifies static tree in Zip file
|
|||
/// </summary>
|
|||
public const int STATIC_TREES = 1; |
|||
|
|||
/// <summary>
|
|||
/// Identifies dynamic tree in Zip file
|
|||
/// </summary>
|
|||
public const int DYN_TREES = 2; |
|||
|
|||
/// <summary>
|
|||
/// Header flag indicating a preset dictionary for deflation
|
|||
/// </summary>
|
|||
public const int PRESET_DICT = 0x20; |
|||
|
|||
/// <summary>
|
|||
/// Sets internal buffer sizes for Huffman encoding
|
|||
/// </summary>
|
|||
public const int DEFAULT_MEM_LEVEL = 8; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int MAX_MATCH = 258; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int MIN_MATCH = 3; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int MAX_WBITS = 15; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int WSIZE = 1 << MAX_WBITS; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int WMASK = WSIZE - 1; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int HASH_SIZE = 1 << HASH_BITS; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int HASH_MASK = HASH_SIZE - 1; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8); |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5); |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int DEFLATE_STORED = 0; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int DEFLATE_FAST = 1; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public const int DEFLATE_SLOW = 2; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 }; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 }; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 }; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 }; |
|||
|
|||
/// <summary>
|
|||
/// Internal compression engine constant
|
|||
/// </summary>
|
|||
public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 }; |
|||
} |
|||
} |
|||
@ -0,0 +1,951 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// <auto-generated/>
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Png.Zlib |
|||
{ |
|||
/// <summary>
|
|||
/// Strategies for deflater
|
|||
/// </summary>
|
|||
public enum DeflateStrategy |
|||
{ |
|||
/// <summary>
|
|||
/// The default strategy
|
|||
/// </summary>
|
|||
Default = 0, |
|||
|
|||
/// <summary>
|
|||
/// This strategy will only allow longer string repetitions. It is
|
|||
/// useful for random data with a small character set.
|
|||
/// </summary>
|
|||
Filtered = 1, |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
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.
|
|||
//
|
|||
|
|||
/// <summary>
|
|||
/// Low level compression engine for deflate algorithm which uses a 32K sliding window
|
|||
/// with secondary compression from Huffman/Shannon-Fano codes.
|
|||
/// </summary>
|
|||
public class DeflaterEngine |
|||
{ |
|||
#region Constants
|
|||
|
|||
private const int TooFar = 4096; |
|||
|
|||
#endregion Constants
|
|||
|
|||
#region Constructors
|
|||
|
|||
/// <summary>
|
|||
/// Construct instance with pending buffer
|
|||
/// Adler calculation will be peformed
|
|||
/// </summary>
|
|||
/// <param name="pending">
|
|||
/// Pending buffer to use
|
|||
/// </param>
|
|||
public DeflaterEngine(DeflaterPending pending) |
|||
: this(pending, false) |
|||
{ |
|||
} |
|||
|
|||
|
|||
|
|||
/// <summary>
|
|||
/// Construct instance with pending buffer
|
|||
/// </summary>
|
|||
/// <param name="pending">
|
|||
/// Pending buffer to use
|
|||
/// </param>
|
|||
/// <param name="noAdlerCalculation">
|
|||
/// If no adler calculation should be performed
|
|||
/// </param>
|
|||
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
|
|||
|
|||
/// <summary>
|
|||
/// Deflate drives actual compression of data
|
|||
/// </summary>
|
|||
/// <param name="flush">True to flush input buffers</param>
|
|||
/// <param name="finish">Finish deflation with the current input.</param>
|
|||
/// <returns>Returns true if progress has been made.</returns>
|
|||
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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets input data to be deflated. Should only be called when <code>NeedsInput()</code>
|
|||
/// returns true
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer containing input data.</param>
|
|||
/// <param name="offset">The offset of the first byte of data.</param>
|
|||
/// <param name="count">The number of bytes of data to use as input.</param>
|
|||
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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines if more <see cref="SetInput">input</see> is needed.
|
|||
/// </summary>
|
|||
/// <returns>Return true if input is needed via <see cref="SetInput">SetInput</see></returns>
|
|||
public bool NeedsInput() |
|||
{ |
|||
return (inputEnd == inputOff); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Set compression dictionary
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer containing the dictionary data</param>
|
|||
/// <param name="offset">The offset in the buffer for the first byte of data</param>
|
|||
/// <param name="length">The length of the dictionary data.</param>
|
|||
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<byte>(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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reset internal state
|
|||
/// </summary>
|
|||
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; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reset Adler checksum
|
|||
/// </summary>
|
|||
public void ResetAdler() |
|||
{ |
|||
adler?.Reset(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get current value of Adler checksum
|
|||
/// </summary>
|
|||
public int Adler |
|||
{ |
|||
get |
|||
{ |
|||
return (adler != null) ? unchecked((int)adler.Value) : 0; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Total data processed
|
|||
/// </summary>
|
|||
public long TotalIn |
|||
{ |
|||
get |
|||
{ |
|||
return totalIn; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get/set the <see cref="DeflateStrategy">deflate strategy</see>
|
|||
/// </summary>
|
|||
public DeflateStrategy Strategy |
|||
{ |
|||
get |
|||
{ |
|||
return strategy; |
|||
} |
|||
set |
|||
{ |
|||
strategy = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Set the deflate level (0-9)
|
|||
/// </summary>
|
|||
/// <param name="level">The value to set the level to.</param>
|
|||
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]; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fill the window
|
|||
/// </summary>
|
|||
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<byte>(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]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inserts the current string in the head hash and returns the previous
|
|||
/// value for this hash.
|
|||
/// </summary>
|
|||
/// <returns>The previous hash value</returns>
|
|||
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); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Find the best (longest) string in the window matching the
|
|||
/// string starting at strstart.
|
|||
///
|
|||
/// Preconditions:
|
|||
/// <code>
|
|||
/// strstart + DeflaterConstants.MAX_MATCH <= window.length.</code>
|
|||
/// </summary>
|
|||
/// <param name="curMatch"></param>
|
|||
/// <returns>True if a match greater than the minimum length is found</returns>
|
|||
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; |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
private short[] head; |
|||
|
|||
/// <summary>
|
|||
/// <code>prev[index & WMASK]</code> 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.
|
|||
/// </summary>
|
|||
private short[] prev; |
|||
|
|||
private int matchStart; |
|||
|
|||
// Length of best match
|
|||
private int matchLen; |
|||
|
|||
// Set if previous match exists
|
|||
private bool prevAvailable; |
|||
|
|||
private int blockStart; |
|||
|
|||
/// <summary>
|
|||
/// Points to the current character in the window.
|
|||
/// </summary>
|
|||
private int strstart; |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
private int lookahead; |
|||
|
|||
/// <summary>
|
|||
/// This array contains the part of the uncompressed stream that
|
|||
/// is of relevance. The current character is indexed by strstart.
|
|||
/// </summary>
|
|||
private byte[] window; |
|||
|
|||
private DeflateStrategy strategy; |
|||
private int max_chain, max_lazy, niceLength, goodLength; |
|||
|
|||
/// <summary>
|
|||
/// The current compression function.
|
|||
/// </summary>
|
|||
private int compressionFunction; |
|||
|
|||
/// <summary>
|
|||
/// The input data for compression.
|
|||
/// </summary>
|
|||
private byte[] inputBuf; |
|||
|
|||
/// <summary>
|
|||
/// The total bytes of input read.
|
|||
/// </summary>
|
|||
private long totalIn; |
|||
|
|||
/// <summary>
|
|||
/// The offset into inputBuf, where input data starts.
|
|||
/// </summary>
|
|||
private int inputOff; |
|||
|
|||
/// <summary>
|
|||
/// The end offset of the input data.
|
|||
/// </summary>
|
|||
private int inputEnd; |
|||
|
|||
private DeflaterPending pending; |
|||
private DeflaterHuffman huffman; |
|||
|
|||
/// <summary>
|
|||
/// The adler checksum
|
|||
/// </summary>
|
|||
private Adler32 adler; |
|||
|
|||
#endregion Instance Fields
|
|||
} |
|||
} |
|||
@ -0,0 +1,965 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// <auto-generated/>
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Png.Zlib |
|||
{ |
|||
/// <summary>
|
|||
/// This is the DeflaterHuffman class.
|
|||
///
|
|||
/// This class is <i>not</i> thread safe. This is inherent in the API, due
|
|||
/// to the split of Deflate and SetInput.
|
|||
///
|
|||
/// author of the original java version : Jochen Hoenicke
|
|||
/// </summary>
|
|||
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
|
|||
|
|||
/// <summary>
|
|||
/// Resets the internal state of the tree
|
|||
/// </summary>
|
|||
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]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Check that all frequencies are zero
|
|||
/// </summary>
|
|||
/// <exception cref="ImageFormatException">
|
|||
/// At least one frequency is non-zero
|
|||
/// </exception>
|
|||
public void CheckEmpty() |
|||
{ |
|||
bool empty = true; |
|||
for (int i = 0; i < freqs.Length; i++) |
|||
{ |
|||
empty &= freqs[i] == 0; |
|||
} |
|||
|
|||
if (!empty) |
|||
{ |
|||
throw new ImageFormatException("!Empty"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Set static codes and length
|
|||
/// </summary>
|
|||
/// <param name="staticCodes">new codes</param>
|
|||
/// <param name="staticLengths">length for new codes</param>
|
|||
public void SetStaticCodes(short[] staticCodes, byte[] staticLengths) |
|||
{ |
|||
codes = staticCodes; |
|||
length = staticLengths; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Build dynamic codes and lengths
|
|||
/// </summary>
|
|||
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); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get encoded length
|
|||
/// </summary>
|
|||
/// <returns>Encoded length, the sum of frequencies * lengths</returns>
|
|||
public int GetEncodedLength() |
|||
{ |
|||
int len = 0; |
|||
for (int i = 0; i < freqs.Length; i++) |
|||
{ |
|||
len += freqs[i] * length[i]; |
|||
} |
|||
return len; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scan a literal or distance tree to determine the frequencies of the codes
|
|||
/// in the bit length tree.
|
|||
/// </summary>
|
|||
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]++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Write tree values
|
|||
/// </summary>
|
|||
/// <param name="blTree">Tree to write</param>
|
|||
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
|
|||
|
|||
/// <summary>
|
|||
/// Pending buffer to use
|
|||
/// </summary>
|
|||
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; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Construct instance with pending buffer
|
|||
/// </summary>
|
|||
/// <param name="pending">Pending buffer to use</param>
|
|||
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]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reset internal state
|
|||
/// </summary>
|
|||
public void Reset() |
|||
{ |
|||
last_lit = 0; |
|||
extra_bits = 0; |
|||
literalTree.Reset(); |
|||
distTree.Reset(); |
|||
blTree.Reset(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Write all trees to pending buffer
|
|||
/// </summary>
|
|||
/// <param name="blTreeCodes">The number/rank of treecodes to send.</param>
|
|||
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
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compress current buffer writing data to pending buffer
|
|||
/// </summary>
|
|||
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
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Flush block to output with no compression
|
|||
/// </summary>
|
|||
/// <param name="stored">Data to write</param>
|
|||
/// <param name="storedOffset">Index of first byte to write</param>
|
|||
/// <param name="storedLength">Count of bytes to write</param>
|
|||
/// <param name="lastBlock">True if this is the last block</param>
|
|||
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(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Flush block to output with compression
|
|||
/// </summary>
|
|||
/// <param name="stored">Data to flush</param>
|
|||
/// <param name="storedOffset">Index of first byte to flush</param>
|
|||
/// <param name="storedLength">Count of bytes to flush</param>
|
|||
/// <param name="lastBlock">True if this is the last block</param>
|
|||
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(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get value indicating if internal buffer is full
|
|||
/// </summary>
|
|||
/// <returns>true if buffer is full</returns>
|
|||
public bool IsFull() |
|||
{ |
|||
return last_lit >= BUFSIZE; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Add literal to buffer
|
|||
/// </summary>
|
|||
/// <param name="literal">Literal value to add to buffer.</param>
|
|||
/// <returns>Value indicating internal buffer is full</returns>
|
|||
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(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Add distance code and length to literal and distance trees
|
|||
/// </summary>
|
|||
/// <param name="distance">Distance code</param>
|
|||
/// <param name="length">Length</param>
|
|||
/// <returns>Value indicating if internal buffer is full</returns>
|
|||
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(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reverse the bits of a 16 bit value.
|
|||
/// </summary>
|
|||
/// <param name="toReverse">Value to reverse bits</param>
|
|||
/// <returns>Value with bits reversed</returns>
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,499 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// <auto-generated/>
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Text; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Png.Zlib |
|||
{ |
|||
/// <summary>
|
|||
/// A special stream deflating or compressing the bytes that are
|
|||
/// written to it. It uses a Deflater to perform actual deflating.<br/>
|
|||
/// Authors of the original java version : Tom Tromey, Jochen Hoenicke
|
|||
/// </summary>
|
|||
public class DeflaterOutputStream : Stream |
|||
{ |
|||
#region Constructors
|
|||
|
|||
/// <summary>
|
|||
/// Creates a new DeflaterOutputStream with a default Deflater and default buffer size.
|
|||
/// </summary>
|
|||
/// <param name="baseOutputStream">
|
|||
/// the output stream where deflated output should be written.
|
|||
/// </param>
|
|||
public DeflaterOutputStream(Stream baseOutputStream) |
|||
: this(baseOutputStream, new Deflater(), 512) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new DeflaterOutputStream with the given Deflater and
|
|||
/// default buffer size.
|
|||
/// </summary>
|
|||
/// <param name="baseOutputStream">
|
|||
/// the output stream where deflated output should be written.
|
|||
/// </param>
|
|||
/// <param name="deflater">
|
|||
/// the underlying deflater.
|
|||
/// </param>
|
|||
public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater) |
|||
: this(baseOutputStream, deflater, 512) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new DeflaterOutputStream with the given Deflater and
|
|||
/// buffer size.
|
|||
/// </summary>
|
|||
/// <param name="baseOutputStream">
|
|||
/// The output stream where deflated output is written.
|
|||
/// </param>
|
|||
/// <param name="deflater">
|
|||
/// The underlying deflater to use
|
|||
/// </param>
|
|||
/// <param name="bufferSize">
|
|||
/// The buffer size in bytes to use when deflating (minimum value 512)
|
|||
/// </param>
|
|||
/// <exception cref="ArgumentOutOfRangeException">
|
|||
/// bufsize is less than or equal to zero.
|
|||
/// </exception>
|
|||
/// <exception cref="ArgumentException">
|
|||
/// baseOutputStream does not support writing
|
|||
/// </exception>
|
|||
/// <exception cref="ArgumentNullException">
|
|||
/// deflater instance is null
|
|||
/// </exception>
|
|||
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
|
|||
|
|||
/// <summary>
|
|||
/// Finishes the stream by calling finish() on the deflater.
|
|||
/// </summary>
|
|||
/// <exception cref="ImageFormatException">
|
|||
/// Not all input is deflated
|
|||
/// </exception>
|
|||
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;
|
|||
//}
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a flag indicating ownership of underlying stream.
|
|||
/// When the flag is true <see cref="Stream.Dispose()" /> will close the underlying stream also.
|
|||
/// </summary>
|
|||
/// <remarks>The default value is true.</remarks>
|
|||
public bool IsStreamOwner { get; set; } = true; |
|||
|
|||
/// <summary>
|
|||
/// Allows client to determine if an entry can be patched after its added
|
|||
/// </summary>
|
|||
public bool CanPatchEntries |
|||
{ |
|||
get |
|||
{ |
|||
return baseOutputStream_.CanSeek; |
|||
} |
|||
} |
|||
|
|||
#endregion Public API
|
|||
|
|||
//#region Encryption
|
|||
|
|||
//private string password;
|
|||
|
|||
//private ICryptoTransform cryptoTransform_;
|
|||
|
|||
///// <summary>
|
|||
///// Returns the 10 byte AUTH CODE to be appended immediately following the AES data stream.
|
|||
///// </summary>
|
|||
//protected byte[] AESAuthCode;
|
|||
|
|||
///// <summary>
|
|||
///// Get/set the password used for encryption.
|
|||
///// </summary>
|
|||
///// <remarks>When set to null or if the password is empty no encryption is performed</remarks>
|
|||
//public string Password
|
|||
//{
|
|||
// get
|
|||
// {
|
|||
// return password;
|
|||
// }
|
|||
// set
|
|||
// {
|
|||
// if ((value != null) && (value.Length == 0))
|
|||
// {
|
|||
// password = null;
|
|||
// }
|
|||
// else
|
|||
// {
|
|||
// password = value;
|
|||
// }
|
|||
// }
|
|||
//}
|
|||
|
|||
///// <summary>
|
|||
///// Encrypt a block of data
|
|||
///// </summary>
|
|||
///// <param name="buffer">
|
|||
///// Data to encrypt. NOTE the original contents of the buffer are lost
|
|||
///// </param>
|
|||
///// <param name="offset">
|
|||
///// Offset of first byte in buffer to encrypt
|
|||
///// </param>
|
|||
///// <param name="length">
|
|||
///// Number of bytes in buffer to encrypt
|
|||
///// </param>
|
|||
//protected void EncryptBlock(byte[] buffer, int offset, int length)
|
|||
//{
|
|||
// cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0);
|
|||
//}
|
|||
|
|||
///// <summary>
|
|||
///// Initializes encryption keys based on given <paramref name="password"/>.
|
|||
///// </summary>
|
|||
///// <param name="password">The password.</param>
|
|||
//protected void InitializePassword(string password)
|
|||
//{
|
|||
// var pkManaged = new PkzipClassicManaged();
|
|||
// byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password));
|
|||
// cryptoTransform_ = pkManaged.CreateEncryptor(key, null);
|
|||
//}
|
|||
|
|||
///// <summary>
|
|||
///// Initializes encryption keys based on given password.
|
|||
///// </summary>
|
|||
//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
|
|||
|
|||
/// <summary>
|
|||
/// Deflates everything in the input buffers. This will call
|
|||
/// <code>def.deflate()</code> until all bytes from the input buffers
|
|||
/// are processed.
|
|||
/// </summary>
|
|||
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
|
|||
|
|||
/// <summary>
|
|||
/// Gets value indicating stream can be read from
|
|||
/// </summary>
|
|||
public override bool CanRead |
|||
{ |
|||
get |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating if seeking is supported for this stream
|
|||
/// This property always returns false
|
|||
/// </summary>
|
|||
public override bool CanSeek |
|||
{ |
|||
get |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get value indicating if this stream supports writing
|
|||
/// </summary>
|
|||
public override bool CanWrite |
|||
{ |
|||
get |
|||
{ |
|||
return baseOutputStream_.CanWrite; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get current length of stream
|
|||
/// </summary>
|
|||
public override long Length |
|||
{ |
|||
get |
|||
{ |
|||
return baseOutputStream_.Length; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current position within the stream.
|
|||
/// </summary>
|
|||
/// <exception cref="NotSupportedException">Any attempt to set position</exception>
|
|||
public override long Position |
|||
{ |
|||
get |
|||
{ |
|||
return baseOutputStream_.Position; |
|||
} |
|||
set |
|||
{ |
|||
throw new NotSupportedException("Position property not supported"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the current position of this stream to the given value. Not supported by this class!
|
|||
/// </summary>
|
|||
/// <param name="offset">The offset relative to the <paramref name="origin"/> to seek.</param>
|
|||
/// <param name="origin">The <see cref="SeekOrigin"/> to seek from.</param>
|
|||
/// <returns>The new position in the stream.</returns>
|
|||
/// <exception cref="NotSupportedException">Any access</exception>
|
|||
public override long Seek(long offset, SeekOrigin origin) |
|||
{ |
|||
throw new NotSupportedException("DeflaterOutputStream Seek not supported"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the length of this stream to the given value. Not supported by this class!
|
|||
/// </summary>
|
|||
/// <param name="value">The new stream length.</param>
|
|||
/// <exception cref="NotSupportedException">Any access</exception>
|
|||
public override void SetLength(long value) |
|||
{ |
|||
throw new NotSupportedException("DeflaterOutputStream SetLength not supported"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Read a byte from stream advancing position by one
|
|||
/// </summary>
|
|||
/// <returns>The byte read cast to an int. THe value is -1 if at the end of the stream.</returns>
|
|||
/// <exception cref="NotSupportedException">Any access</exception>
|
|||
public override int ReadByte() |
|||
{ |
|||
throw new NotSupportedException("DeflaterOutputStream ReadByte not supported"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Read a block of bytes from stream
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to store read data in.</param>
|
|||
/// <param name="offset">The offset to start storing at.</param>
|
|||
/// <param name="count">The maximum number of bytes to read.</param>
|
|||
/// <returns>The actual number of bytes read. Zero if end of stream is detected.</returns>
|
|||
/// <exception cref="NotSupportedException">Any access</exception>
|
|||
public override int Read(byte[] buffer, int offset, int count) |
|||
{ |
|||
throw new NotSupportedException("DeflaterOutputStream Read not supported"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Flushes the stream by calling <see cref="DeflaterOutputStream.Flush">Flush</see> on the deflater and then
|
|||
/// on the underlying stream. This ensures that all bytes are flushed.
|
|||
/// </summary>
|
|||
public override void Flush() |
|||
{ |
|||
deflater_.Flush(); |
|||
Deflate(true); |
|||
baseOutputStream_.Flush(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calls <see cref="Finish"/> and closes the underlying
|
|||
/// stream when <see cref="IsStreamOwner"></see> is true.
|
|||
/// </summary>
|
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
///// <summary>
|
|||
///// Get the Auth code for AES encrypted entries
|
|||
///// </summary>
|
|||
//protected void GetAuthCodeIfAES()
|
|||
//{
|
|||
// if (cryptoTransform_ is ZipAESTransform)
|
|||
// {
|
|||
// AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
|
|||
// }
|
|||
//}
|
|||
|
|||
/// <summary>
|
|||
/// Writes a single byte to the compressed output stream.
|
|||
/// </summary>
|
|||
/// <param name="value">
|
|||
/// The byte value.
|
|||
/// </param>
|
|||
public override void WriteByte(byte value) |
|||
{ |
|||
byte[] b = new byte[1]; |
|||
b[0] = value; |
|||
Write(b, 0, 1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes bytes from an array to the compressed stream.
|
|||
/// </summary>
|
|||
/// <param name="buffer">
|
|||
/// The byte array
|
|||
/// </param>
|
|||
/// <param name="offset">
|
|||
/// The offset into the byte array where to start.
|
|||
/// </param>
|
|||
/// <param name="count">
|
|||
/// The number of bytes to write.
|
|||
/// </param>
|
|||
public override void Write(byte[] buffer, int offset, int count) |
|||
{ |
|||
deflater_.SetInput(buffer, offset, count); |
|||
Deflate(); |
|||
} |
|||
|
|||
#endregion Stream Overrides
|
|||
|
|||
#region Instance Fields
|
|||
|
|||
/// <summary>
|
|||
/// This buffer is used temporarily to retrieve the bytes from the
|
|||
/// deflater and write them to the underlying output stream.
|
|||
/// </summary>
|
|||
private byte[] buffer_; |
|||
|
|||
/// <summary>
|
|||
/// The deflater which is used to deflate the stream.
|
|||
/// </summary>
|
|||
protected Deflater deflater_; |
|||
|
|||
/// <summary>
|
|||
/// Base stream the deflater depends on.
|
|||
/// </summary>
|
|||
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
|
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// <auto-generated/>
|
|||
namespace SixLabors.ImageSharp.Formats.Png.Zlib |
|||
{ |
|||
/// <summary>
|
|||
/// This class stores the pending output of the Deflater.
|
|||
///
|
|||
/// author of the original java version : Jochen Hoenicke
|
|||
/// </summary>
|
|||
public class DeflaterPending : PendingBuffer |
|||
{ |
|||
/// <summary>
|
|||
/// Construct instance with default buffer size
|
|||
/// </summary>
|
|||
public DeflaterPending() : base(DeflaterConstants.PENDING_BUF_SIZE) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,276 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// <auto-generated/>
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Png.Zlib |
|||
{ |
|||
/// <summary>
|
|||
/// 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
|
|||
/// </summary>
|
|||
public class PendingBuffer |
|||
{ |
|||
#region Instance Fields
|
|||
|
|||
/// <summary>
|
|||
/// Internal work buffer
|
|||
/// </summary>
|
|||
private readonly byte[] buffer; |
|||
|
|||
private int start; |
|||
private int end; |
|||
|
|||
private uint bits; |
|||
private int bitCount; |
|||
|
|||
#endregion Instance Fields
|
|||
|
|||
#region Constructors
|
|||
|
|||
/// <summary>
|
|||
/// construct instance using default buffer size of 4096
|
|||
/// </summary>
|
|||
public PendingBuffer() : this(4096) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// construct instance using specified buffer size
|
|||
/// </summary>
|
|||
/// <param name="bufferSize">
|
|||
/// size to use for internal buffer
|
|||
/// </param>
|
|||
public PendingBuffer(int bufferSize) |
|||
{ |
|||
buffer = new byte[bufferSize]; |
|||
} |
|||
|
|||
#endregion Constructors
|
|||
|
|||
/// <summary>
|
|||
/// Clear internal state/buffers
|
|||
/// </summary>
|
|||
public void Reset() |
|||
{ |
|||
start = end = bitCount = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Write a byte to buffer
|
|||
/// </summary>
|
|||
/// <param name="value">
|
|||
/// The value to write
|
|||
/// </param>
|
|||
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); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Write a short value to buffer LSB first
|
|||
/// </summary>
|
|||
/// <param name="value">
|
|||
/// The value to write.
|
|||
/// </param>
|
|||
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)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// write an integer LSB first
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write.</param>
|
|||
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)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Write a block of data to buffer
|
|||
/// </summary>
|
|||
/// <param name="block">data to write</param>
|
|||
/// <param name="offset">offset of first byte to write</param>
|
|||
/// <param name="length">number of bytes to write</param>
|
|||
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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The number of bits written to the buffer
|
|||
/// </summary>
|
|||
public int BitCount |
|||
{ |
|||
get |
|||
{ |
|||
return bitCount; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Align internal buffer on a byte boundary
|
|||
/// </summary>
|
|||
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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Write bits to internal buffer
|
|||
/// </summary>
|
|||
/// <param name="b">source of bits</param>
|
|||
/// <param name="count">number of bits to write</param>
|
|||
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; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Write a short value to internal buffer most significant byte first
|
|||
/// </summary>
|
|||
/// <param name="s">value to write</param>
|
|||
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); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Indicates if buffer has been flushed
|
|||
/// </summary>
|
|||
public bool IsFlushed |
|||
{ |
|||
get |
|||
{ |
|||
return end == 0; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Flushes the pending buffer into the given output array. If the
|
|||
/// output array is to small, only a partial flush is done.
|
|||
/// </summary>
|
|||
/// <param name="output">The output array.</param>
|
|||
/// <param name="offset">The offset into output array.</param>
|
|||
/// <param name="length">The maximum number of bytes to store.</param>
|
|||
/// <returns>The number of bytes flushed.</returns>
|
|||
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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert internal buffer to byte array.
|
|||
/// Buffer is empty on completion
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The internal buffer contents converted to a byte array.
|
|||
/// </returns>
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue