namespace ImageProcessor.Formats
{
using System;
using System.IO;
using System.IO.Compression;
internal class ZlibOutputStream : Stream
{
///
/// The raw stream containing the uncompressed image data.
///
private readonly Stream rawStream;
///
/// Computes the checksum for the data stream.
///
private readonly Adler32 adler32 = new Adler32();
///
/// A value indicating whether this instance of the given entity has been disposed.
///
/// if this instance has been disposed; otherwise, .
///
/// If the entity is disposed, it must not be disposed a second
/// time. The isDisposed field is set the first time the entity
/// is disposed. If the isDisposed field is true, then the Dispose()
/// method will not dispose again. This help not to prolong the entity's
/// life in the Garbage Collector.
///
private bool isDisposed;
// The stream responsible for decompressing the input stream.
private DeflateStream deflateStream;
///
/// Initializes a new instance of
///
/// The stream to compress.
/// The compression level.
public ZlibOutputStream(Stream stream, int compressionLevel)
{
this.rawStream = stream;
// Write the zlib header : http://tools.ietf.org/html/rfc1950
// CMF(Compression Method and flags)
// This byte is divided into a 4 - bit compression method and a
// 4-bit information field depending on the compression method.
// bits 0 to 3 CM Compression method
// bits 4 to 7 CINFO Compression info
// 0 1
//
// +---+---+
// |CMF|FLG|
// +---+---+
int cmf = 0x78;
int flg = 218;
// http://stackoverflow.com/a/2331025/277304
if (compressionLevel >= 5 && compressionLevel <= 6)
{
flg = 156;
}
else if (compressionLevel >= 3 && compressionLevel <= 4)
{
flg = 94;
}
else if (compressionLevel <= 2)
{
flg = 1;
}
// Just in case
flg -= (cmf * 256 + flg) % 31;
if (flg < 0)
{
flg += 31;
}
this.rawStream.WriteByte((byte)cmf);
this.rawStream.WriteByte((byte)flg);
// Initialize the deflate Stream.
CompressionLevel level = CompressionLevel.Optimal;
if (compressionLevel >= 1 && compressionLevel <= 5)
{
level = CompressionLevel.Fastest;
}
else if (compressionLevel == 0)
{
level = CompressionLevel.NoCompression;
}
// I must create with leaveopen=true always and do the closing myself, because MS implementation
// of DeflateStream: I cant force a flush of the underlying stream without closing.
this.deflateStream = new DeflateStream(this.rawStream, level, true);
}
///
public override bool CanRead => false;
///
public override bool CanSeek => false;
///
public override bool CanWrite => true;
///
public override long Length
{
get
{
throw new NotImplementedException();
}
}
///
public override long Position
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
///
public override void Flush()
{
this.deflateStream?.Flush();
}
///
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
///
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
///
public override void SetLength(long value)
{
throw new NotImplementedException();
}
///
public override void Write(byte[] buffer, int offset, int count)
{
this.deflateStream.Write(buffer, offset, count);
this.adler32.Update(buffer, offset, count);
}
///
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
// dispose managed resources
if (this.deflateStream != null)
{
this.deflateStream.Dispose();
this.deflateStream = null;
}
else {
// Second hack: empty input?
this.rawStream.WriteByte(3);
this.rawStream.WriteByte(0);
}
// Add the crc
uint crc = (uint)this.adler32.Value;
this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF));
this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF));
this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF));
this.rawStream.WriteByte((byte)((crc) & 0xFF));
}
base.Dispose(disposing);
// Call the appropriate methods to clean up
// unmanaged resources here.
// Note disposing is done.
this.isDisposed = true;
}
}
}