Browse Source

!st pass cleanup of Deflater

af/merge-core
James Jackson-South 7 years ago
parent
commit
1bab6db93e
  1. 573
      src/ImageSharp/Formats/Png/Zlib/Deflater.cs
  2. 17
      src/ImageSharp/Formats/Png/Zlib/DeflaterThrowHelper.cs
  3. 9
      src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs

573
src/ImageSharp/Formats/Png/Zlib/Deflater.cs

@ -1,97 +1,92 @@
// 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;
using System.Runtime.CompilerServices;
using SixLabors.Memory;
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
/// This class compresses input with the deflate algorithm described in RFC 1951.
/// It has several compression levels and three different strategies described below.
/// </summary>
public sealed class Deflater : IDisposable
{
#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;
public const int BestCompression = 9;
/// <summary>
/// The worst but fastest compression level.
/// </summary>
public const int BEST_SPEED = 1;
public const int BestSpeed = 1;
/// <summary>
/// The default compression level.
/// </summary>
public const int DEFAULT_COMPRESSION = -1;
public const int DefaultCompression = -1;
/// <summary>
/// This level won't compress at all but output uncompressed blocks.
/// </summary>
public const int NO_COMPRESSION = 0;
public const int NoCompression = 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;
public const int Deflated = 8;
#endregion Public Constants
/// <summary>
/// Compression level.
/// </summary>
private int level;
/// <summary>
/// The current state.
/// </summary>
private int state;
private DeflaterPendingBuffer pending;
private DeflaterEngine engine;
private bool isDisposed;
#region Public Enum
private const int IsSetDict = 0x01;
private const int IsFlushing = 0x04;
private const int IsFinishing = 0x08;
private const int BusyState = 0x10;
private const int FlushingState = 0x14;
private const int FinishingState = 0x1c;
private const int FinishedState = 0x1e;
private const int ClosedState = 0x7f;
/// <summary>
/// Initializes a new instance of the <see cref="Deflater"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
/// <param name="level">The compression level, a value between NoCompression and BestCompression.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">if level is out of range.</exception>
public Deflater(MemoryAllocator memoryAllocator, int level)
{
if (level == DefaultCompression)
{
level = 6;
}
else if (level < NoCompression || level > BestCompression)
{
throw new ArgumentOutOfRangeException(nameof(level));
}
this.pending = new DeflaterPendingBuffer(memoryAllocator);
this.engine = new DeflaterEngine(this.pending, true);
this.engine.Strategy = DeflateStrategy.Default;
this.SetLevel(level);
this.Reset();
}
/// <summary>
/// Compression Level as an enum for safer use
@ -102,202 +97,72 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// The best and slowest compression level. This tries to find very
/// long and distant string repetitions.
/// </summary>
BEST_COMPRESSION = Deflater.BEST_COMPRESSION,
BestCompression = Deflater.BestCompression,
/// <summary>
/// The worst but fastest compression level.
/// </summary>
BEST_SPEED = Deflater.BEST_SPEED,
BestSpeed = Deflater.BestSpeed,
/// <summary>
/// The default compression level.
/// </summary>
DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION,
DefaultCompression = Deflater.DefaultCompression,
/// <summary>
/// This level won't compress at all but output uncompressed blocks.
/// </summary>
NO_COMPRESSION = Deflater.NO_COMPRESSION,
NoCompression = Deflater.NoCompression,
/// <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
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 given compression level.
/// Gets a value indicating whetherthe stream was finished and no more output bytes
/// are available.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
/// <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(MemoryAllocator memoryAllocator, 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 DeflaterPendingBuffer(memoryAllocator);
engine = new DeflaterEngine(pending, noZlibHeaderOrFooter);
this.noZlibHeaderOrFooter = noZlibHeaderOrFooter;
SetStrategy(DeflateStrategy.Default);
SetLevel(level);
Reset();
}
public bool IsFinished => (this.state == FinishedState) && this.pending.IsFlushed;
#endregion Constructors
/// <summary>
/// Gets a value indicating whether 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 => this.engine.NeedsInput();
/// <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>
[MethodImpl(InliningOptions.ShortMethod)]
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;
}
this.state = BusyState;
this.pending.Reset();
this.engine.Reset();
}
/// <summary>
/// Flushes the current input block. Further calls to deflate() will
/// 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().
/// block. It is used by DeflaterOutputStream to implement Flush().
/// </summary>
public void Flush()
{
state |= IS_FLUSHING;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Flush() => this.state |= IsFlushing;
/// <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
/// 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);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Finish() => this.state |= IsFlushing | IsFinishing;
/// <summary>
/// Sets the data which should be compressed next. This should be
@ -305,25 +170,21 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// 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.
/// <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="InvalidOperationException">
/// if the buffer was finished or if previous input is still pending.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
public void SetInput(byte[] input, int offset, int count)
{
if ((state & IS_FINISHING) != 0)
if ((this.state & IsFinishing) != 0)
{
throw new InvalidOperationException("Finish() already called");
DeflaterThrowHelper.ThrowAlreadyFinished();
}
engine.SetInput(input, offset, count);
this.engine.SetInput(input, offset, count);
}
/// <summary>
@ -337,11 +198,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// </param>
public void SetLevel(int level)
{
if (level == DEFAULT_COMPRESSION)
if (level == DefaultCompression)
{
level = 6;
}
else if (level < NO_COMPRESSION || level > BEST_COMPRESSION)
else if (level < NoCompression || level > BestCompression)
{
throw new ArgumentOutOfRangeException(nameof(level));
}
@ -349,273 +210,127 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (this.level != level)
{
this.level = level;
engine.SetLevel(level);
this.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>
/// <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.
/// <see cref="IsNeedingInput"/> or <see cref="IsFinished"/> 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)
if (this.state == ClosedState)
{
throw new InvalidOperationException("Deflater closed");
DeflaterThrowHelper.ThrowAlreadyClosed();
}
if (state < BUSY_STATE)
if (this.state < BusyState)
{
// output header
int header = (DEFLATED +
((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8;
int level_flags = (level - 1) >> 1;
if (level_flags < 0 || level_flags > 3)
// Output header
int header = (Deflated + ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8;
int levelFlags = (this.level - 1) >> 1;
if (levelFlags < 0 || levelFlags > 3)
{
level_flags = 3;
levelFlags = 3;
}
header |= level_flags << 6;
if ((state & IS_SETDICT) != 0)
header |= levelFlags << 6;
if ((this.state & IsSetDict) != 0)
{
// Dictionary was set
header |= DeflaterConstants.PRESET_DICT;
}
header += 31 - (header % 31);
pending.WriteShortMSB(header);
if ((state & IS_SETDICT) != 0)
this.pending.WriteShortMSB(header);
if ((this.state & IsSetDict) != 0)
{
int chksum = engine.Adler;
engine.ResetAdler();
pending.WriteShortMSB(chksum >> 16);
pending.WriteShortMSB(chksum & 0xffff);
int chksum = this.engine.Adler;
this.engine.ResetAdler();
this.pending.WriteShortMSB(chksum >> 16);
this.pending.WriteShortMSB(chksum & 0xffff);
}
state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING));
this.state = BusyState | (this.state & (IsFlushing | IsFinishing));
}
for (; ; )
while (true)
{
int count = pending.Flush(output, offset, length);
int count = this.pending.Flush(output, offset, length);
offset += count;
totalOut += count;
length -= count;
if (length == 0 || state == FINISHED_STATE)
if (length == 0 || this.state == FinishedState)
{
break;
}
if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0))
if (!this.engine.Deflate((this.state & IsFlushing) != 0, (this.state & IsFinishing) != 0))
{
switch (state)
switch (this.state)
{
case BUSY_STATE:
case BusyState:
// We need more input now
return origLength - length;
case FLUSHING_STATE:
if (level != NO_COMPRESSION)
case FlushingState:
if (this.level != NoCompression)
{
/* 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);
// 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 + ((-this.pending.BitCount) & 7);
while (neededbits > 0)
{
/* write a static tree block consisting solely of
* an EOF:
*/
pending.WriteBits(2, 10);
// Write a static tree block consisting solely of an EOF:
this.pending.WriteBits(2, 10);
neededbits -= 10;
}
}
state = BUSY_STATE;
break;
case FINISHING_STATE:
pending.AlignToByte();
this.state = BusyState;
break;
// 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;
case FinishingState:
this.pending.AlignToByte();
this.state = FinishedState;
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);
return origLength - 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)
/// <inheritdoc/>
public void Dispose()
{
if (state != INIT_STATE)
{
throw new InvalidOperationException();
}
state = SETDICT_STATE;
engine.SetDictionary(dictionary, index, count);
this.Dispose(true);
GC.SuppressFinalize(this);
}
#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 DeflaterPendingBuffer pending;
/// <summary>
/// The deflater engine.
/// </summary>
private DeflaterEngine engine;
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
void Dispose(bool disposing)
private void Dispose(bool disposing)
{
if (!disposedValue)
if (!this.isDisposed)
{
if (disposing)
{
this.pending.Dispose();
// TODO: dispose managed state (managed objects).
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
this.pending = null;
disposedValue = true;
this.isDisposed = true;
}
}
/// <inheritdoc/>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#endregion Instance Fields
}
}

17
src/ImageSharp/Formats/Png/Zlib/DeflaterThrowHelper.cs

@ -0,0 +1,17 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
internal static class DeflaterThrowHelper
{
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowAlreadyFinished() => throw new InvalidOperationException("Finish() already called.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowAlreadyClosed() => throw new InvalidOperationException("Deflator already closed.");
}
}

9
src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs

@ -3,7 +3,6 @@
using System;
using System.IO;
using System.IO.Compression;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
@ -65,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
// +---+---+
// |CMF|FLG|
// +---+---+
int cmf = 0x78;
const int Cmf = 0x78;
int flg = 218;
// http://stackoverflow.com/a/2331025/277304
@ -83,14 +82,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
}
// Just in case
flg -= ((cmf * 256) + flg) % 31;
flg -= ((Cmf * 256) + flg) % 31;
if (flg < 0)
{
flg += 31;
}
this.rawStream.WriteByte((byte)cmf);
this.rawStream.WriteByte(Cmf);
this.rawStream.WriteByte((byte)flg);
// Initialize the deflate Stream.
@ -104,7 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
// {
// level = CompressionLevel.NoCompression;
// }
this.deflater = new Deflater(memoryAllocator, compressionLevel, true);
this.deflater = new Deflater(memoryAllocator, compressionLevel);
this.deflateStream = new DeflaterOutputStream(this.rawStream, this.deflater) { IsStreamOwner = false };
// this.deflateStream = new DeflateStream(this.rawStream, level, true);

Loading…
Cancel
Save