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; } } }