diff --git a/src/ImageProcessor/Formats/Png/PngDecoderCore.cs b/src/ImageProcessor/Formats/Png/PngDecoderCore.cs
index 85ed12c12f..489d69eebe 100644
--- a/src/ImageProcessor/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageProcessor/Formats/Png/PngDecoderCore.cs
@@ -138,7 +138,7 @@ namespace ImageProcessor.Formats
if (this.header.Width > ImageBase.MaxWidth || this.header.Height > ImageBase.MaxHeight)
{
throw new ArgumentOutOfRangeException(
- $"The input png '{this.header.Width}x{this.header.Height}' is bigger thean the "
+ $"The input png '{this.header.Width}x{this.header.Height}' is bigger than the "
+ $"max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'");
}
@@ -260,7 +260,8 @@ namespace ImageProcessor.Formats
byte[] currentScanline = new byte[scanlineLength];
int filter = 0, column = -1;
- using (InflaterInputStream compressedStream = new InflaterInputStream(dataStream))
+ using (ZlibInputStream compressedStream = new ZlibInputStream(dataStream))
+ //using (InflaterInputStream compressedStream = new InflaterInputStream(dataStream))
{
int readByte;
while ((readByte = compressedStream.ReadByte()) >= 0)
diff --git a/src/ImageProcessor/Formats/Png/PngEncoder.cs b/src/ImageProcessor/Formats/Png/PngEncoder.cs
index e4501a9560..ba404518d7 100644
--- a/src/ImageProcessor/Formats/Png/PngEncoder.cs
+++ b/src/ImageProcessor/Formats/Png/PngEncoder.cs
@@ -38,6 +38,13 @@ namespace ImageProcessor.Formats
///
public string Extension => "png";
+ ///
+ /// The compression level 1-9.
+ /// TODO: Get other compression levels to work. Something is cutting of image content.
+ /// Defaults to 6.
+ ///
+ public int CompressionLevel { get; set; } = 6;
+
///
/// Gets or sets a value indicating whether this encoder
/// will write the image uncompressed the stream.
@@ -56,7 +63,7 @@ namespace ImageProcessor.Formats
///
///
/// True if this instance is writing gamma
- /// information to the stream.; otherwise, false.
+ /// information to the stream; otherwise, false.
///
public bool IsWritingGamma { get; set; }
@@ -95,7 +102,7 @@ namespace ImageProcessor.Formats
0x0D, // Line ending CRLF
0x0A, // Line ending CRLF
0x1A, // EOF
- 0x0A // LF
+ 0x0A // LF
},
0,
8);
@@ -360,6 +367,8 @@ namespace ImageProcessor.Formats
{
memoryStream = new MemoryStream();
+ // TODO: Get this working!
+ //using (ZlibOutputStream outputStream = new ZlibOutputStream(memoryStream, this.CompressionLevel))
using (DeflaterOutputStream outputStream = new DeflaterOutputStream(memoryStream))
{
outputStream.Write(data, 0, data.Length);
diff --git a/src/ImageProcessor/Formats/Png/Zlib/Crc32.cs b/src/ImageProcessor/Formats/Png/Zlib/Crc32.cs
index 3828260b40..70cceb0a2f 100644
--- a/src/ImageProcessor/Formats/Png/Zlib/Crc32.cs
+++ b/src/ImageProcessor/Formats/Png/Zlib/Crc32.cs
@@ -104,7 +104,7 @@ namespace ImageProcessor.Formats
private uint crc;
///
- /// Returns the CRC32 data checksum computed so far.
+ /// Gets or sets the CRC32 data checksum computed so far.
///
public long Value
{
diff --git a/src/ImageProcessor/Formats/Png/Zlib/IChecksum.cs b/src/ImageProcessor/Formats/Png/Zlib/IChecksum.cs
index 18dbe5edff..7e6d65be1c 100644
--- a/src/ImageProcessor/Formats/Png/Zlib/IChecksum.cs
+++ b/src/ImageProcessor/Formats/Png/Zlib/IChecksum.cs
@@ -15,7 +15,7 @@ namespace ImageProcessor.Formats
public interface IChecksum
{
///
- /// Returns the data checksum computed so far.
+ /// Gets the data checksum computed so far.
///
long Value
{
@@ -31,7 +31,7 @@ namespace ImageProcessor.Formats
/// Adds one byte to the data checksum.
///
///
- /// the data value to add. The high byte of the int is ignored.
+ /// The data value to add. The high byte of the integer is ignored.
///
void Update(int value);
diff --git a/src/ImageProcessor/Formats/Png/Zlib/NewFolder/ZlibInputStream.cs b/src/ImageProcessor/Formats/Png/Zlib/NewFolder/ZlibInputStream.cs
new file mode 100644
index 0000000000..129042bda1
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/Zlib/NewFolder/ZlibInputStream.cs
@@ -0,0 +1,210 @@
+
+namespace ImageProcessor.Formats
+{
+ using System;
+ using System.IO;
+ using System.IO.Compression;
+
+ internal class ZlibInputStream : Stream
+ {
+ ///
+ /// 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 raw stream containing the uncompressed image data.
+ ///
+ private readonly Stream rawStream;
+
+ ///
+ /// The preset dictionary.
+ /// Merely informational, not used.
+ ///
+ private bool fdict;
+
+ ///
+ /// The DICT dictionary identifier identifying the used dictionary.
+ /// Merely informational, not used.
+ ///
+ private byte[] dictId;
+
+ ///
+ /// CINFO is the base-2 logarithm of the LZ77 window size, minus eight.
+ /// Merely informational, not used.
+ ///
+ private int cinfo;
+
+ ///
+ /// The read crc data.
+ ///
+ private byte[] crcread;
+
+ // The stream responsible for decompressing the input stream.
+ private DeflateStream deflateStream;
+
+ public ZlibInputStream(Stream stream)
+ {
+ this.rawStream = stream;
+
+ // Read 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 = this.rawStream.ReadByte();
+ int flag = this.rawStream.ReadByte();
+ if (cmf == -1 || flag == -1)
+ {
+ return;
+ }
+
+ if ((cmf & 0x0f) != 8)
+ {
+ throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}");
+ }
+
+ this.cinfo = ((cmf & (0xf0)) >> 8);
+ this.fdict = (flag & 32) != 0;
+
+ if (this.fdict)
+ {
+ this.dictId = new byte[4];
+
+ for (int i = 0; i < 4; i++)
+ {
+ // We consume but don't use this.
+ this.dictId[i] = (byte)this.rawStream.ReadByte();
+ }
+ }
+
+ // Initialize the deflate Stream.
+ this.deflateStream = new DeflateStream(this.rawStream, CompressionMode.Decompress, true);
+ }
+
+ ///
+ public override bool CanRead => true;
+
+ ///
+ public override bool CanSeek => false;
+
+ ///
+ public override bool CanWrite => false;
+
+ ///
+ 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)
+ {
+ // We dont't check CRC on reading
+ int read = this.deflateStream.Read(buffer, offset, count);
+ if (read < 1 && this.crcread == null)
+ {
+ // The deflater has ended. We try to read the next 4 bytes from raw stream (crc)
+ this.crcread = new byte[4];
+ for (int i = 0; i < 4; i++)
+ {
+ // we dont really check/use this
+ this.crcread[i] = (byte)rawStream.ReadByte();
+ }
+ }
+
+ return read;
+ }
+
+ ///
+ 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)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ 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;
+
+ if (this.crcread == null)
+ {
+ // Consume the trailing 4 bytes
+ this.crcread = new byte[4];
+ for (int i = 0; i < 4; i++)
+ {
+ this.crcread[i] = (byte)this.rawStream.ReadByte();
+ }
+ }
+ }
+ }
+
+ base.Dispose(disposing);
+
+ // Call the appropriate methods to clean up
+ // unmanaged resources here.
+ // Note disposing is done.
+ this.isDisposed = true;
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/Zlib/NewFolder/ZlibOutputStream.cs b/src/ImageProcessor/Formats/Png/Zlib/NewFolder/ZlibOutputStream.cs
new file mode 100644
index 0000000000..258c13f0e0
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/Zlib/NewFolder/ZlibOutputStream.cs
@@ -0,0 +1,205 @@
+
+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;
+ }
+ }
+}