diff --git a/src/ImageProcessor/Common/Extensions/ByteExtensions.cs b/src/ImageProcessor/Common/Extensions/ByteExtensions.cs
new file mode 100644
index 000000000..2b180e77e
--- /dev/null
+++ b/src/ImageProcessor/Common/Extensions/ByteExtensions.cs
@@ -0,0 +1,51 @@
+namespace ImageProcessor
+{
+ using System;
+
+ internal static class ByteExtensions
+ {
+ ///
+ /// Converts a byte array to a new array where each value in the original array is represented
+ /// by a the specified number of bits.
+ ///
+ /// The bytes to convert from. Cannot be null.
+ /// The number of bits per value.
+ /// The resulting array. Is never null.
+ /// is null.
+ /// is less than or equals than zero.
+ public static byte[] ToArrayByBitsLength(this byte[] bytes, int bits)
+ {
+ Guard.NotNull(bytes, "bytes");
+ Guard.GreaterThan(bits, 0, "bits");
+
+ byte[] result;
+
+ if (bits < 8)
+ {
+ result = new byte[bytes.Length * 8 / bits];
+
+ int factor = (int)Math.Pow(2, bits) - 1;
+ int mask = (0xFF >> (8 - bits));
+ int resultOffset = 0;
+
+ foreach (byte b in bytes)
+ {
+ for (int shift = 0; shift < 8; shift += bits)
+ {
+ int colorIndex = (((b) >> (8 - bits - shift)) & mask) * (255 / factor);
+
+ result[resultOffset] = (byte)colorIndex;
+
+ resultOffset++;
+ }
+ }
+ }
+ else
+ {
+ result = bytes;
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/ImageProcessor/Common/Extensions/ComparableExtensions.cs b/src/ImageProcessor/Common/Extensions/ComparableExtensions.cs
new file mode 100644
index 000000000..cc6fd6052
--- /dev/null
+++ b/src/ImageProcessor/Common/Extensions/ComparableExtensions.cs
@@ -0,0 +1,36 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Extension methods for classes that implement .
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor
+{
+ using System;
+
+ ///
+ /// Extension methods for classes that implement .
+ ///
+ internal static class ComparableExtensions
+ {
+ ///
+ /// Returns value indicating whether the given number is with in the minimum and maximum
+ /// given range.
+ ///
+ /// The value to to check.
+ /// The minimum range value.
+ /// The maximum range value.
+ /// The to test.
+ ///
+ /// True if the value falls within the maximum and minimum; otherwise, false.
+ ///
+ public static bool IsBetween(this T value, T min, T max) where T : IComparable
+ {
+ return (value.CompareTo(min) > 0) && (value.CompareTo(max) < 0);
+ }
+ }
+}
diff --git a/src/ImageProcessor/Common/Helpers/Guard.cs b/src/ImageProcessor/Common/Helpers/Guard.cs
new file mode 100644
index 000000000..fcf85907d
--- /dev/null
+++ b/src/ImageProcessor/Common/Helpers/Guard.cs
@@ -0,0 +1,119 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Provides methods to protect against invalid parameters.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor
+{
+ using System;
+ using System.Globalization;
+
+ ///
+ /// Provides methods to protect against invalid parameters.
+ ///
+ internal static class Guard
+ {
+ ///
+ /// Verifies, that the method parameter with specified object value is not null
+ /// and throws an exception if it is found to be so.
+ ///
+ ///
+ /// The target object, which cannot be null.
+ ///
+ ///
+ /// The name of the parameter that is to be checked.
+ ///
+ ///
+ /// The error message, if any to add to the exception.
+ ///
+ ///
+ /// is null
+ ///
+ public static void NotNull(object target, string parameterName, string message = "")
+ {
+ if (target == null)
+ {
+ if (string.IsNullOrWhiteSpace(message))
+ {
+ throw new ArgumentNullException(parameterName, message);
+ }
+
+ throw new ArgumentNullException(parameterName);
+ }
+ }
+
+ ///
+ /// Verifies, that the string method parameter with specified object value and message
+ /// is not null, not empty and does not contain only blanks and throws an exception
+ /// if the object is null.
+ ///
+ /// The target string, which should be checked against being null or empty.
+ /// Name of the parameter.
+ ///
+ /// is null.
+ ///
+ ///
+ /// is
+ /// empty or contains only blanks.
+ ///
+ public static void NotNullOrEmpty(string target, string parameterName)
+ {
+ if (target == null)
+ {
+ throw new ArgumentNullException(parameterName);
+ }
+
+ if (string.IsNullOrWhiteSpace(target))
+ {
+ throw new ArgumentException("String parameter cannot be null or empty and cannot contain only blanks.", parameterName);
+ }
+ }
+
+ ///
+ /// Verifies that the specified value is less than a maximum value
+ /// and throws an exception if it is not.
+ ///
+ /// The target value, which should be validated.
+ /// The maximum value.
+ /// The name of the parameter that is to be checked.
+ /// The type of the value.
+ ///
+ /// is greater than the maximum value.
+ ///
+ public static void LessThan(TValue value, TValue max, string parameterName) where TValue : IComparable
+ {
+ if (value.CompareTo(max) >= 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ parameterName,
+ string.Format(CultureInfo.CurrentCulture, "Value must be less than {0}", max));
+ }
+ }
+
+ ///
+ /// Verifies that the specified value is greater than a minimum value
+ /// and throws an exception if it is not.
+ ///
+ /// The target value, which should be validated.
+ /// The minimum value.
+ /// The name of the parameter that is to be checked.
+ /// The type of the value.
+ ///
+ /// is less than the minimum value.
+ ///
+ public static void GreaterThan(TValue value, TValue min, string parameterName) where TValue : IComparable
+ {
+ if (value.CompareTo(min) <= 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ parameterName,
+ string.Format(CultureInfo.CurrentCulture, "Value must be greater than {0}", min));
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessor/Common/Helpers/Utils.cs b/src/ImageProcessor/Common/Helpers/Utils.cs
new file mode 100644
index 000000000..c56129d3e
--- /dev/null
+++ b/src/ImageProcessor/Common/Helpers/Utils.cs
@@ -0,0 +1,34 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// General utility methods.
+// TODO: I don't like having classes like this as they turn into a dumping ground. Investigate method usage.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor
+{
+ ///
+ /// General utility methods.
+ /// TODO: I don't like having classes like this as they turn into a dumping ground. Investigate method usage.
+ ///
+ internal static class Utils
+ {
+ ///
+ /// Swaps two references.
+ ///
+ /// The type of the references to swap.
+ /// The first reference.
+ /// The second reference.
+ public static void Swap(ref TRef lhs, ref TRef rhs) where TRef : class
+ {
+ TRef tmp = lhs;
+
+ lhs = rhs;
+ rhs = tmp;
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Gif/DisposalMethod.cs b/src/ImageProcessor/Formats/Gif/DisposalMethod.cs
new file mode 100644
index 000000000..2ce4a0523
--- /dev/null
+++ b/src/ImageProcessor/Formats/Gif/DisposalMethod.cs
@@ -0,0 +1,42 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Provides enumeration for instructing the decoder what to do with the last image
+// in an animation sequence.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// Provides enumeration for instructing the decoder what to do with the last image
+ /// in an animation sequence.
+ ///
+ public enum DisposalMethod
+ {
+ ///
+ /// No disposal specified. The decoder is not required to take any action.
+ ///
+ Unspecified = 0,
+
+ ///
+ /// Do not dispose. The graphic is to be left in place.
+ ///
+ NotDispose = 1,
+
+ ///
+ /// Restore to background color. The area used by the graphic must be restored to
+ /// the background color.
+ ///
+ RestoreToBackground = 2,
+
+ ///
+ /// Restore to previous. The decoder is required to restore the area overwritten by the
+ /// graphic with what was there prior to rendering the graphic.
+ ///
+ RestoreToPrevious = 3
+ }
+}
diff --git a/src/ImageProcessor/Formats/Gif/GifDecoder.cs b/src/ImageProcessor/Formats/Gif/GifDecoder.cs
new file mode 100644
index 000000000..056aee7e4
--- /dev/null
+++ b/src/ImageProcessor/Formats/Gif/GifDecoder.cs
@@ -0,0 +1,382 @@
+namespace ImageProcessor.Formats
+{
+ using System;
+ using System.IO;
+
+ public class GifDecoder : IImageDecoder
+ {
+ public static int MaxCommentLength = 1024 * 8;
+
+ public int HeaderSize
+ {
+ get
+ {
+ return 6;
+ }
+ }
+
+ public bool IsSupportedFileExtension(string extension)
+ {
+ if (extension.StartsWith(".")) extension = extension.Substring(1);
+ return extension.Equals("GIF", StringComparison.OrdinalIgnoreCase);
+ }
+
+ public bool IsSupportedFileFormat(byte[] header)
+ {
+ return header.Length >= 6 &&
+ header[0] == 0x47 && // G
+ header[1] == 0x49 && // I
+ header[2] == 0x46 && // F
+ header[3] == 0x38 && // 8
+ (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7
+ header[5] == 0x61; // a
+ }
+
+ public void Decode(Image image, Stream stream)
+ {
+ new GifDecoderCore().Decode(image, stream);
+ }
+
+ class GifDecoderCore
+ {
+ private const byte ExtensionIntroducer = 0x21;
+ private const byte Terminator = 0;
+ private const byte ImageLabel = 0x2C;
+ private const byte EndIntroducer = 0x3B;
+ private const byte ApplicationExtensionLabel = 0xFF;
+ private const byte CommentLabel = 0xFE;
+ private const byte ImageDescriptorLabel = 0x2C;
+ private const byte PlainTextLabel = 0x01;
+ private const byte GraphicControlLabel = 0xF9;
+
+ private Image _image;
+ private Stream _stream;
+ private GifLogicalScreenDescriptor _descriptor;
+ private byte[] _globalColorTable;
+ private byte[] _currentFrame;
+ private GifGraphicsControlExtension _graphicsControl;
+
+ public void Decode(Image image, Stream stream)
+ {
+ _image = image;
+
+ _stream = stream;
+ _stream.Seek(6, SeekOrigin.Current);
+
+ ReadLogicalScreenDescriptor();
+
+ if (_descriptor.GlobalColorTableFlag == true)
+ {
+ _globalColorTable = new byte[_descriptor.GlobalColorTableSize * 3];
+
+ // Read the global color table from the stream
+ stream.Read(_globalColorTable, 0, _globalColorTable.Length);
+ }
+
+ int nextFlag = stream.ReadByte();
+ while (nextFlag != 0)
+ {
+ if (nextFlag == ImageLabel)
+ {
+ ReadFrame();
+ }
+ else if (nextFlag == ExtensionIntroducer)
+ {
+ int gcl = stream.ReadByte();
+ switch (gcl)
+ {
+ case GraphicControlLabel:
+ ReadGraphicalControlExtension();
+ break;
+ case CommentLabel:
+ ReadComments();
+ break;
+ case ApplicationExtensionLabel:
+ Skip(12);
+ break;
+ case PlainTextLabel:
+ Skip(13);
+ break;
+ }
+ }
+ else if (nextFlag == EndIntroducer)
+ {
+ break;
+ }
+ nextFlag = stream.ReadByte();
+ }
+ }
+
+ private void ReadGraphicalControlExtension()
+ {
+ byte[] buffer = new byte[6];
+
+ _stream.Read(buffer, 0, buffer.Length);
+
+ byte packed = buffer[1];
+
+ _graphicsControl = new GifGraphicsControlExtension();
+ _graphicsControl.DelayTime = BitConverter.ToInt16(buffer, 2);
+ _graphicsControl.TransparencyIndex = buffer[4];
+ _graphicsControl.TransparencyFlag = (packed & 0x01) == 1;
+ _graphicsControl.DisposalMethod = (DisposalMethod)((packed & 0x1C) >> 2);
+ }
+
+ private GifImageDescriptor ReadImageDescriptor()
+ {
+ byte[] buffer = new byte[9];
+
+ _stream.Read(buffer, 0, buffer.Length);
+
+ byte packed = buffer[8];
+
+ GifImageDescriptor imageDescriptor = new GifImageDescriptor();
+ imageDescriptor.Left = BitConverter.ToInt16(buffer, 0);
+ imageDescriptor.Top = BitConverter.ToInt16(buffer, 2);
+ imageDescriptor.Width = BitConverter.ToInt16(buffer, 4);
+ imageDescriptor.Height = BitConverter.ToInt16(buffer, 6);
+ imageDescriptor.LocalColorTableFlag = ((packed & 0x80) >> 7) == 1;
+ imageDescriptor.LocalColorTableSize = 2 << (packed & 0x07);
+ imageDescriptor.InterlaceFlag = ((packed & 0x40) >> 6) == 1;
+
+ return imageDescriptor;
+ }
+
+ private void ReadLogicalScreenDescriptor()
+ {
+ byte[] buffer = new byte[7];
+
+ _stream.Read(buffer, 0, buffer.Length);
+
+ byte packed = buffer[4];
+
+ _descriptor = new GifLogicalScreenDescriptor();
+ _descriptor.Width = BitConverter.ToInt16(buffer, 0);
+ _descriptor.Height = BitConverter.ToInt16(buffer, 2);
+ _descriptor.Background = buffer[5];
+ _descriptor.GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1;
+ _descriptor.GlobalColorTableSize = 2 << (packed & 0x07);
+
+ if (_descriptor.GlobalColorTableSize > 255 * 4)
+ {
+ throw new ImageFormatException(string.Format("Invalid gif colormap size '{0}'", _descriptor.GlobalColorTableSize));
+ }
+
+ if (_descriptor.Width > ImageBase.MaxWidth || _descriptor.Height > ImageBase.MaxHeight)
+ {
+ throw new ArgumentOutOfRangeException(
+ string.Format("The input gif '{0}x{1}' is bigger then the max allowed size '{2}x{3}'",
+ _descriptor.Width,
+ _descriptor.Height,
+ ImageBase.MaxWidth,
+ ImageBase.MaxHeight));
+ }
+ }
+
+ private void Skip(int length)
+ {
+ _stream.Seek(length, SeekOrigin.Current);
+
+ int flag = 0;
+
+ while ((flag = _stream.ReadByte()) != 0)
+ {
+ _stream.Seek(flag, SeekOrigin.Current);
+ }
+ }
+
+ private void ReadComments()
+ {
+ int flag = 0;
+
+ while ((flag = _stream.ReadByte()) != 0)
+ {
+ if (flag > MaxCommentLength)
+ {
+ throw new ImageFormatException(string.Format("Gif comment length '{0}' exceeds max '{1}'", flag, MaxCommentLength));
+ }
+
+ byte[] buffer = new byte[flag];
+
+ _stream.Read(buffer, 0, flag);
+
+ _image.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(buffer)));
+ }
+ }
+
+ private void ReadFrame()
+ {
+ GifImageDescriptor imageDescriptor = ReadImageDescriptor();
+
+ byte[] localColorTable = ReadFrameLocalColorTable(imageDescriptor);
+
+ byte[] indices = ReadFrameIndices(imageDescriptor);
+
+ // Determine the color table for this frame. If there is a local one, use it
+ // otherwise use the global color table.
+ byte[] colorTable = localColorTable ?? this._globalColorTable;
+
+ ReadFrameColors(indices, colorTable, imageDescriptor);
+
+ Skip(0); // skip any remaining blocks
+ }
+
+ private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor)
+ {
+ int dataSize = _stream.ReadByte();
+
+ LzwDecoder lzwDecoder = new LzwDecoder(_stream);
+
+ byte[] indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize);
+ return indices;
+ }
+
+ private byte[] ReadFrameLocalColorTable(GifImageDescriptor imageDescriptor)
+ {
+ byte[] localColorTable = null;
+
+ if (imageDescriptor.LocalColorTableFlag)
+ {
+ localColorTable = new byte[imageDescriptor.LocalColorTableSize * 3];
+
+ _stream.Read(localColorTable, 0, localColorTable.Length);
+ }
+
+ return localColorTable;
+ }
+
+ private void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor)
+ {
+ int imageWidth = _descriptor.Width;
+ int imageHeight = _descriptor.Height;
+
+ if (_currentFrame == null)
+ {
+ _currentFrame = new byte[imageWidth * imageHeight * 4];
+ }
+
+ byte[] lastFrame = null;
+
+ if (_graphicsControl != null &&
+ _graphicsControl.DisposalMethod == DisposalMethod.RestoreToPrevious)
+ {
+ lastFrame = new byte[imageWidth * imageHeight * 4];
+
+ Array.Copy(_currentFrame, lastFrame, lastFrame.Length);
+ }
+
+ int offset = 0, i = 0, index = -1;
+
+ int iPass = 0; // the interlace pass
+ int iInc = 8; // the interlacing line increment
+ int iY = 0; // the current interlaced line
+ int writeY = 0; // the target y offset to write to
+
+ for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++)
+ {
+ // Check if this image is interlaced.
+ if (descriptor.InterlaceFlag)
+ {
+ // If so then we read lines at predetermined offsets.
+ // When an entire image height worth of offset lines has been read we consider this a pass.
+ // With each pass the number of offset lines changes and the starting line changes.
+ if (iY >= descriptor.Height)
+ {
+ iPass++;
+ switch (iPass)
+ {
+ case 1:
+ iY = 4;
+ break;
+ case 2:
+ iY = 2;
+ iInc = 4;
+ break;
+ case 3:
+ iY = 1;
+ iInc = 2;
+ break;
+ }
+ }
+
+ writeY = iY + descriptor.Top;
+
+ iY += iInc;
+ }
+ else
+ {
+ writeY = y;
+ }
+
+ for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
+ {
+ offset = writeY * imageWidth + x;
+
+ index = indices[i];
+
+ if (_graphicsControl == null ||
+ _graphicsControl.TransparencyFlag == false ||
+ _graphicsControl.TransparencyIndex != index)
+ {
+ _currentFrame[offset * 4 + 0] = colorTable[index * 3 + 2];
+ _currentFrame[offset * 4 + 1] = colorTable[index * 3 + 1];
+ _currentFrame[offset * 4 + 2] = colorTable[index * 3 + 0];
+ _currentFrame[offset * 4 + 3] = (byte)255;
+ }
+
+ i++;
+ }
+ }
+
+ byte[] pixels = new byte[imageWidth * imageHeight * 4];
+
+ Array.Copy(_currentFrame, pixels, pixels.Length);
+
+ ImageBase currentImage = null;
+
+ if (_image.Pixels == null)
+ {
+ currentImage = _image;
+ currentImage.SetPixels(imageWidth, imageHeight, pixels);
+
+ if (_graphicsControl != null && _graphicsControl.DelayTime > 0)
+ {
+ _image.DelayTime = _graphicsControl.DelayTime;
+ }
+ }
+ else
+ {
+ ImageFrame frame = new ImageFrame();
+
+ currentImage = frame;
+ currentImage.SetPixels(imageWidth, imageHeight, pixels);
+
+ _image.Frames.Add(frame);
+ }
+
+ if (_graphicsControl != null)
+ {
+ if (_graphicsControl.DisposalMethod == DisposalMethod.RestoreToBackground)
+ {
+ for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++)
+ {
+ for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
+ {
+ offset = y * imageWidth + x;
+
+ _currentFrame[offset * 4 + 0] = 0;
+ _currentFrame[offset * 4 + 1] = 0;
+ _currentFrame[offset * 4 + 2] = 0;
+ _currentFrame[offset * 4 + 3] = 0;
+ }
+ }
+ }
+ else if (_graphicsControl.DisposalMethod == DisposalMethod.RestoreToPrevious)
+ {
+ _currentFrame = lastFrame;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs b/src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs
new file mode 100644
index 000000000..000434716
--- /dev/null
+++ b/src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs
@@ -0,0 +1,49 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// The Graphic Control Extension contains parameters used when
+// processing a graphic rendering block.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// The Graphic Control Extension contains parameters used when
+ /// processing a graphic rendering block.
+ ///
+ internal sealed class GifGraphicsControlExtension
+ {
+ ///
+ /// Gets or sets the disposal method which indicates the way in which the
+ /// graphic is to be treated after being displayed.
+ ///
+ public DisposalMethod DisposalMethod { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether transparency flag is to be set.
+ /// This indicates whether a transparency index is given in the Transparent Index field.
+ /// (This field is the least significant bit of the byte.)
+ ///
+ public bool TransparencyFlag { get; set; }
+
+ ///
+ /// Gets or sets the transparency index.
+ /// The Transparency Index is such that when encountered, the corresponding pixel
+ /// of the display device is not modified and processing goes on to the next pixel.
+ ///
+ public int TransparencyIndex { get; set; }
+
+ ///
+ /// Gets or sets the delay time.
+ /// If not 0, this field specifies the number of hundredths (1/100) of a second to
+ /// wait before continuing with the processing of the Data Stream.
+ /// The clock starts ticking immediately after the graphic is rendered.
+ /// This field may be used in conjunction with the User Input Flag field.
+ ///
+ public int DelayTime { get; set; }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Gif/GifImageDescriptor.cs b/src/ImageProcessor/Formats/Gif/GifImageDescriptor.cs
new file mode 100644
index 000000000..cdd768250
--- /dev/null
+++ b/src/ImageProcessor/Formats/Gif/GifImageDescriptor.cs
@@ -0,0 +1,72 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Each image in the Data Stream is composed of an Image Descriptor,
+// an optional Local Color Table, and the image data.
+// Each image must fit within the boundaries of the
+// Logical Screen, as defined in the Logical Screen Descriptor.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// Each image in the Data Stream is composed of an Image Descriptor,
+ /// an optional Local Color Table, and the image data.
+ /// Each image must fit within the boundaries of the
+ /// Logical Screen, as defined in the Logical Screen Descriptor.
+ ///
+ internal sealed class GifImageDescriptor
+ {
+ ///
+ /// Gets or sets the column number, in pixels, of the left edge of the image,
+ /// with respect to the left edge of the Logical Screen.
+ /// Leftmost column of the Logical Screen is 0.
+ ///
+ public short Left { get; set; }
+
+ ///
+ /// Gets or sets the row number, in pixels, of the top edge of the image with
+ /// respect to the top edge of the Logical Screen.
+ /// Top row of the Logical Screen is 0.
+ ///
+ public short Top { get; set; }
+
+
+ ///
+ /// Gets or sets the width of the image in pixels.
+ ///
+ public short Width { get; set; }
+
+
+ ///
+ /// Gets or sets the height of the image in pixels.
+ ///
+ public short Height { get; set; }
+
+
+ ///
+ /// Gets or sets a value indicating whether the presence of a Local Color Table immediately
+ /// follows this Image Descriptor.
+ ///
+ public bool LocalColorTableFlag { get; set; }
+
+
+ ///
+ /// Gets or sets the local color table size.
+ /// If the Local Color Table Flag is set to 1, the value in this field
+ /// is used to calculate the number of bytes contained in the Local Color Table.
+ ///
+ public int LocalColorTableSize { get; set; }
+
+
+ ///
+ /// Gets or sets a value indicating whether the image is to be interlaced.
+ /// An image is interlaced in a four-pass interlace pattern.
+ ///
+ public bool InterlaceFlag { get; set; }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Gif/GifLogicalScreenDescriptor.cs b/src/ImageProcessor/Formats/Gif/GifLogicalScreenDescriptor.cs
new file mode 100644
index 000000000..f1dbf7ba0
--- /dev/null
+++ b/src/ImageProcessor/Formats/Gif/GifLogicalScreenDescriptor.cs
@@ -0,0 +1,57 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// The Logical Screen Descriptor contains the parameters
+// necessary to define the area of the display device
+// within which the images will be rendered
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// The Logical Screen Descriptor contains the parameters
+ /// necessary to define the area of the display device
+ /// within which the images will be rendered
+ ///
+ internal sealed class GifLogicalScreenDescriptor
+ {
+ ///
+ /// Gets or sets the width, in pixels, of the Logical Screen where the images will
+ /// be rendered in the displaying device.
+ ///
+ public short Width { get; set; }
+
+ ///
+ /// Gets or sets the height, in pixels, of the Logical Screen where the images will be
+ /// rendered in the displaying device.
+ ///
+ public short Height { get; set; }
+
+ ///
+ /// Gets or sets the index at the Global Color Table for the Background Color.
+ /// The Background Color is the color used for those
+ /// pixels on the screen that are not covered by an image.
+ ///
+ public byte Background { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether a flag denoting the presence of a Global Color Table
+ /// should be set.
+ /// If the flag is set, the Global Color Table will immediately
+ /// follow the Logical Screen Descriptor.
+ ///
+ public bool GlobalColorTableFlag { get; set; }
+
+ ///
+ /// Gets or sets the global color table size.
+ /// If the Global Color Table Flag is set to 1,
+ /// the value in this field is used to calculate the number of
+ /// bytes contained in the Global Color Table.
+ ///
+ public int GlobalColorTableSize { get; set; }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Gif/LzwDecoder.cs b/src/ImageProcessor/Formats/Gif/LzwDecoder.cs
new file mode 100644
index 000000000..8fa2e7880
--- /dev/null
+++ b/src/ImageProcessor/Formats/Gif/LzwDecoder.cs
@@ -0,0 +1,270 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Decompresses data using the LZW algorithms.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ using System;
+ using System.IO;
+
+ ///
+ /// Decompresses data using the LZW algorithms.
+ ///
+ internal sealed class LzwDecoder
+ {
+ ///
+ /// The stack size.
+ ///
+ private const int StackSize = 4096;
+
+ ///
+ /// The null code.
+ ///
+ private const int NullCode = -1;
+
+ ///
+ /// The stream.
+ ///
+ private readonly Stream stream;
+
+ ///
+ /// Initializes a new instance of the class
+ /// and sets the stream, where the compressed data should be read from.
+ ///
+ /// The stream. where to read from.
+ /// is null
+ /// (Nothing in Visual Basic).
+ public LzwDecoder(Stream stream)
+ {
+ Guard.NotNull(stream, "stream");
+
+ this.stream = stream;
+ }
+
+ ///
+ /// Decodes and decompresses all pixel indices from the stream.
+ ///
+ /// The width of the pixel index array.
+ /// The height of the pixel index array.
+ /// Size of the data.
+ /// The decoded and uncompressed array.
+ public byte[] DecodePixels(int width, int height, int dataSize)
+ {
+ Guard.LessThan(dataSize, int.MaxValue, "dataSize");
+
+ // The resulting index table.
+ byte[] pixels = new byte[width * height];
+
+ // Calculate the clear code. The value of the clear code is 2 ^ dataSize
+ int clearCode = 1 << dataSize;
+
+ int codeSize = dataSize + 1;
+
+ // Calculate the end code
+ int endCode = clearCode + 1;
+
+ // Calculate the available code.
+ int availableCode = clearCode + 2;
+
+ #region Jillzhangs Code (Not From Me) see: http://giflib.codeplex.com/
+ // TODO: It's imperative that this close is cleaned up and commented properly.
+ // TODO: Unfortunately I can't figure out the character encoding to translate from the original Chinese.
+ int code; // ÓÃÓÚ´æ´¢µ±Ç°µÄ±àÂëÖµ
+ int oldCode = NullCode; // ÓÃÓÚ´æ´¢ÉÏÒ»´ÎµÄ±àÂëÖµ
+ int codeMask = (1 << codeSize) - 1; // ±íʾ±àÂëµÄ×î´óÖµ£¬Èç¹ûcodeSize=5,Ôòcode_mask=31
+ int bits = 0; // ÔÚ±àÂëÁ÷ÖÐÊý¾ÝµÄ±£´æÐÎʽΪbyte£¬¶øÊµ¼Ê±àÂë¹ý³ÌÖÐÊÇÕÒʵ¼Ê±àÂëλÀ´´æ´¢µÄ£¬±ÈÈçµ±codeSize=5µÄʱºò£¬ÄÇôʵ¼ÊÉÏ5bitµÄÊý¾Ý¾ÍÓ¦¸Ã¿ÉÒÔ±íʾһ¸ö±àÂ룬ÕâÑùÈ¡³öÀ´µÄ1¸ö×ֽھ͸»ÓàÁË3¸öbit£¬Õâ3¸öbitÓÃÓں͵ڶþ¸ö×ֽڵĺóÁ½¸öbit½øÐÐ×éºÏ£¬ÔÙ´ÎÐγɱàÂëÖµ£¬Èç´ËÀàÍÆ
+
+
+ int[] prefix = new int[StackSize]; // ÓÃÓÚ±£´æÇ°×ºµÄ¼¯ºÏ
+ int[] suffix = new int[StackSize]; // ÓÃÓÚ±£´æºó׺
+ int[] pixelStatck = new int[StackSize + 1]; // ÓÃÓÚÁÙʱ±£´æÊý¾ÝÁ÷
+
+ int top = 0;
+ int count = 0; // ÔÚÏÂÃæµÄÑ»·ÖУ¬Ã¿´Î»á»ñȡһ¶¨Á¿µÄ±àÂëµÄ×Ö½ÚÊý×飬¶ø´¦ÀíÕâЩÊý×éµÄʱºòÐèÒª1¸ö¸ö×Ö½ÚÀ´´¦Àí£¬count¾ÍÊDZíʾ»¹Òª´¦ÀíµÄ×Ö½ÚÊýÄ¿
+ int bi = 0; // count±íʾ»¹Ê£¶àÉÙ×Ö½ÚÐèÒª´¦Àí£¬¶øbiÔò±íʾ±¾´ÎÒѾ´¦ÀíµÄ¸öÊý
+ int xyz = 0; // i´ú±íµ±Ç°´¦ÀíµÃµ½ÏñËØÊý
+
+ int data = 0; // ±íʾµ±Ç°´¦ÀíµÄÊý¾ÝµÄÖµ
+ int first = 0; // Ò»¸ö×Ö·û´®ÖصĵÚÒ»¸ö×Ö½Ú
+ int inCode; // ÔÚlzwÖУ¬Èç¹ûÈÏʶÁËÒ»¸ö±àÂëËù´ú±íµÄÊý¾Ýentry£¬Ôò½«±àÂë×÷ΪÏÂÒ»´ÎµÄprefix£¬´Ë´¦inCode´ú±í´«µÝ¸øÏÂÒ»´Î×÷Ϊǰ׺µÄ±àÂëÖµ
+
+ // ÏÈÉú³ÉÔªÊý¾ÝµÄǰ׺¼¯ºÏºÍºó׺¼¯ºÏ£¬ÔªÊý¾ÝµÄǰ׺¾ùΪ0£¬¶øºó׺ÓëÔªÊý¾ÝÏàµÈ£¬Í¬Ê±±àÂëÒ²ÓëÔªÊý¾ÝÏàµÈ
+ for (code = 0; code < clearCode; code++)
+ {
+ // ǰ׺³õʼΪ0
+ prefix[code] = 0;
+
+ // ºó׺=ÔªÊý¾Ý=±àÂë
+ suffix[code] = (byte)code;
+ }
+
+ byte[] buffer = null;
+ while (xyz < pixels.Length)
+ {
+ // ×î´óÏñËØÊýÒѾȷ¶¨ÎªpixelCount = width * width
+ if (top == 0)
+ {
+ if (bits < codeSize)
+ {
+ // Èç¹ûµ±Ç°µÄÒª´¦ÀíµÄbitÊýСÓÚ±àÂëλ´óС£¬ÔòÐèÒª¼ÓÔØÊý¾Ý
+ if (count == 0)
+ {
+ // Èç¹ûcountΪ0£¬±íʾҪ´Ó±àÂëÁ÷ÖжÁÒ»¸öÊý¾Ý¶ÎÀ´½øÐзÖÎö
+ buffer = this.ReadBlock();
+ count = buffer.Length;
+ if (count == 0)
+ {
+ // ÔÙ´ÎÏë¶ÁÈ¡Êý¾Ý¶Î£¬È´Ã»ÓжÁµ½Êý¾Ý£¬´Ëʱ¾Í±íÃ÷ÒѾ´¦ÀíÍêÁË
+ break;
+ }
+
+ // ÖØÐ¶Áȡһ¸öÊý¾Ý¶Îºó£¬Ó¦¸Ã½«ÒѾ´¦ÀíµÄ¸öÊýÖÃ0
+ bi = 0;
+ }
+
+ // »ñÈ¡±¾´ÎÒª´¦ÀíµÄÊý¾ÝµÄÖµ
+ data += buffer[bi] << bits; // ´Ë´¦ÎªºÎÒªÒÆÎ»ÄØ£¬±ÈÈçµÚÒ»´Î´¦ÀíÁË1¸ö×Ö½ÚΪ176£¬µÚÒ»´ÎÖ»Òª´¦Àí5bit¾Í¹»ÁË£¬Ê£ÏÂ3bitÁô¸øÏ¸ö×Ö½Ú½øÐÐ×éºÏ¡£Ò²¾ÍÊǵڶþ¸ö×ֽڵĺóÁ½Î»+µÚÒ»¸ö×Ö½ÚµÄǰÈýλ×é³ÉµÚ¶þ´ÎÊä³öÖµ
+ bits += 8; // ±¾´ÎÓÖ´¦ÀíÁËÒ»¸ö×Ö½Ú£¬ËùÒÔÐèÒª+8
+ bi++; // ½«´¦ÀíÏÂÒ»¸ö×Ö½Ú
+ count--; // ÒѾ´¦Àí¹ýµÄ×Ö½ÚÊý+1
+ continue;
+ }
+
+ // Èç¹ûÒѾÓÐ×ã¹»µÄbitÊý¿É¹©´¦Àí£¬ÏÂÃæ¾ÍÊÇ´¦Àí¹ý³Ì
+ // »ñÈ¡±àÂë
+ code = data & codeMask; // »ñÈ¡dataÊý¾ÝµÄ±àÂëλ´óСbitµÄÊý¾Ý
+ data >>= codeSize; // ½«±àÂëÊý¾Ý½ØÈ¡ºó£¬ÔÀ´µÄÊý¾Ý¾Íʣϼ¸¸öbitÁË£¬´Ëʱ½«ÕâЩbitÓÒÒÆ£¬ÎªÏ´Î×÷×¼±¸
+ bits -= codeSize; // ͬʱÐèÒª½«µ±Ç°Êý¾ÝµÄbitÊý¼õÈ¥±àÂë볤£¬ÒòΪÒѾµÃµ½ÁË´¦Àí¡£
+
+ // ÏÂÃæ¸ù¾Ý»ñÈ¡µÄcodeÖµÀ´½øÐд¦Àí
+ if (code > availableCode || code == endCode)
+ {
+ // µ±±àÂëÖµ´óÓÚ×î´ó±àÂëÖµ»òÕßΪ½áÊø±ê¼ÇµÄʱºò£¬Í£Ö¹´¦Àí
+ break;
+ }
+
+ if (code == clearCode)
+ {
+ // Èç¹ûµ±Ç°ÊÇÇå³ý±ê¼Ç£¬ÔòÖØÐ³õʼ»¯±äÁ¿£¬ºÃÖØÐÂÔÙÀ´
+ codeSize = dataSize + 1;
+
+ // ÖØÐ³õʼ»¯×î´ó±àÂëÖµ
+ codeMask = (1 << codeSize) - 1;
+
+ // ³õʼ»¯ÏÂÒ»²½Ó¦¸Ã´¦ÀíµÃ±àÂëÖµ
+ availableCode = clearCode + 2;
+
+ // ½«±£´æµ½old_codeÖеÄÖµÇå³ý£¬ÒÔ±ãÖØÍ·ÔÙÀ´
+ oldCode = NullCode;
+ continue;
+ }
+
+ // ÏÂÃæÊÇcodeÊôÓÚÄÜѹËõµÄ±àÂ뷶ΧÄڵĵĴ¦Àí¹ý³Ì
+ if (oldCode == NullCode)
+ {
+ // Èç¹ûµ±Ç°±àÂëֵΪ¿Õ,±íʾÊǵÚÒ»´Î»ñÈ¡±àÂë
+ pixelStatck[top++] = suffix[code]; // »ñÈ¡µ½1¸öÊý¾ÝÁ÷µÄÊý¾Ý
+
+ // ±¾´Î±àÂë´¦ÀíÍê³É£¬½«±àÂëÖµ±£´æµ½old_codeÖÐ
+ oldCode = code;
+
+ // µÚÒ»¸ö×Ö·ûΪµ±Ç°±àÂë
+ first = code;
+ continue;
+ }
+
+ inCode = code;
+ if (code == availableCode)
+ {
+ // Èç¹ûµ±Ç°±àÂëºÍ±¾´ÎÓ¦¸ÃÉú³ÉµÄ±àÂëÏàͬ
+ // ÄÇôÏÂÒ»¸öÊý¾Ý×ֽھ͵ÈÓÚµ±Ç°´¦Àí×Ö·û´®µÄµÚÒ»¸ö×Ö½Ú
+ pixelStatck[top++] = (byte)first;
+
+ code = oldCode; // »ØËݵ½ÉÏÒ»¸ö±àÂë
+ }
+
+ while (code > clearCode)
+ {
+ // Èç¹ûµ±Ç°±àÂë´óÓÚÇå³ý±ê¼Ç£¬±íʾ±àÂëÖµÊÇÄÜѹËõÊý¾ÝµÄ
+ pixelStatck[top++] = suffix[code];
+ code = prefix[code]; // »ØËݵ½ÉÏÒ»¸ö±àÂë
+ }
+
+ first = suffix[code];
+
+ // »ñÈ¡ÏÂÒ»¸öÊý¾Ý
+ pixelStatck[top++] = suffix[code];
+
+ // Fix for Gifs that have "deferred clear code" as per here : https://bugzilla.mozilla.org/show_bug.cgi?id=55918
+ if (availableCode < StackSize)
+ {
+ // ÉèÖõ±Ç°Ó¦¸Ã±àÂëλÖõÄǰ׺
+ prefix[availableCode] = oldCode;
+
+ // ÉèÖõ±Ç°Ó¦¸Ã±àÂëλÖõĺó׺
+ suffix[availableCode] = first;
+
+ // Ï´ÎÓ¦¸ÃµÃµ½µÄ±àÂëÖµ
+ availableCode++;
+ if (availableCode == codeMask + 1 && availableCode < StackSize)
+ {
+ // Ôö¼Ó±àÂëλÊý
+ codeSize++;
+
+ // ÖØÉè×î´ó±àÂëÖµ
+ codeMask = (1 << codeSize) - 1;
+ }
+ }
+
+ // »¹Ôold_code
+ oldCode = inCode;
+ }
+
+ // »ØËݵ½ÉÏÒ»¸ö´¦ÀíλÖÃ
+ top--;
+
+ // »ñȡԪÊý¾Ý
+ pixels[xyz++] = (byte)pixelStatck[top];
+ }
+
+ #endregion
+
+ return pixels;
+ }
+
+ ///
+ /// Reads the next data block from the stream. A data block begins with a byte,
+ /// which defines the size of the block, followed by the block itself.
+ ///
+ ///
+ /// The .
+ ///
+ private byte[] ReadBlock()
+ {
+ int blockSize = this.stream.ReadByte();
+ return this.ReadBytes(blockSize);
+ }
+
+ ///
+ /// Reads the specified number of bytes from the data stream.
+ ///
+ ///
+ /// The number of bytes to read.
+ ///
+ ///
+ /// The .
+ ///
+ private byte[] ReadBytes(int length)
+ {
+ byte[] buffer = new byte[length];
+ this.stream.Read(buffer, 0, length);
+ return buffer;
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/IImageDecoder.cs b/src/ImageProcessor/Formats/IImageDecoder.cs
new file mode 100644
index 000000000..4796f6cab
--- /dev/null
+++ b/src/ImageProcessor/Formats/IImageDecoder.cs
@@ -0,0 +1,53 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Encapsulates properties and methods required for decoding an image from a stream.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ using System.IO;
+
+ ///
+ /// Encapsulates properties and methods required for decoding an image from a stream.
+ ///
+ public interface IImageDecoder
+ {
+ ///
+ /// Gets the size of the header for this image type.
+ ///
+ /// The size of the header.
+ int HeaderSize { get; }
+
+ ///
+ /// Returns a value indicating whether the supports the specified
+ /// file header.
+ ///
+ /// The containing the file extension.
+ ///
+ /// True if the decoder supports the file extension; otherwise, false.
+ ///
+ bool IsSupportedFileExtension(string extension);
+
+ ///
+ /// Returns a value indicating whether the supports the specified
+ /// file header.
+ ///
+ /// The containing the file header.
+ ///
+ /// True if the decoder supports the file header; otherwise, false.
+ ///
+ bool IsSupportedFileFormat(byte[] header);
+
+ ///
+ /// Decodes the image from the specified stream to the .
+ ///
+ /// The to decode to.
+ /// The containing image data.
+ void Decode(Image image, Stream stream);
+ }
+}
diff --git a/src/ImageProcessor/Formats/IImageEncoder.cs b/src/ImageProcessor/Formats/IImageEncoder.cs
new file mode 100644
index 000000000..ab62f3566
--- /dev/null
+++ b/src/ImageProcessor/Formats/IImageEncoder.cs
@@ -0,0 +1,47 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Encapsulates properties and methods required for decoding an image to a stream.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ using System.IO;
+
+ ///
+ /// Encapsulates properties and methods required for encoding an image to a stream.
+ ///
+ public interface IImageEncoder
+ {
+ ///
+ /// Gets or sets the quality of output for images.
+ ///
+ int Quality { get; set; }
+
+ ///
+ /// Gets the default file extension for this encoder.
+ ///
+ string Extension { get; }
+
+ ///
+ /// Returns a value indicating whether the supports the specified
+ /// file header.
+ ///
+ /// The containing the file extension.
+ ///
+ /// True if the decoder supports the file extension; otherwise, false.
+ ///
+ bool IsSupportedFileExtension(string extension);
+
+ ///
+ /// Encodes the image to the specified stream from the .
+ ///
+ /// The to encode from.
+ /// The to encode the image data to.
+ void Encode(ImageBase image, Stream stream);
+ }
+}
diff --git a/src/ImageProcessor/Formats/IImageFormat.cs b/src/ImageProcessor/Formats/IImageFormat.cs
new file mode 100644
index 000000000..596805877
--- /dev/null
+++ b/src/ImageProcessor/Formats/IImageFormat.cs
@@ -0,0 +1,9 @@
+namespace ImageProcessor.Encoders
+{
+ public interface IImageFormat
+ {
+ IImageEncoder Encoder { get; }
+
+ IImageDecoder Decoder { get; }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/GrayscaleReader.cs b/src/ImageProcessor/Formats/Png/GrayscaleReader.cs
new file mode 100644
index 000000000..0069b8a0d
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/GrayscaleReader.cs
@@ -0,0 +1,83 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Color reader for reading grayscale colors from a png file.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// Color reader for reading grayscale colors from a png file.
+ ///
+ public sealed class GrayscaleReader : IColorReader
+ {
+ ///
+ /// Whether t also read the alpha channel.
+ ///
+ private readonly bool useAlpha;
+
+ ///
+ /// The current row.
+ ///
+ private int row;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// If set to true the color reader will also read the
+ /// alpha channel from the scanline.
+ ///
+ public GrayscaleReader(bool useAlpha)
+ {
+ this.useAlpha = useAlpha;
+ }
+
+ ///
+ /// Reads the specified scanline.
+ ///
+ /// The scanline.
+ /// The pixels, where the colors should be stored in RGBA format.
+ ///
+ /// The header, which contains information about the png file, like
+ /// the width of the image and the height.
+ ///
+ public void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header)
+ {
+ int offset;
+
+ byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth);
+
+ if (this.useAlpha)
+ {
+ for (int x = 0; x < header.Width / 2; x++)
+ {
+ offset = ((this.row * header.Width) + x) * 4;
+
+ pixels[offset + 0] = newScanline[x * 2];
+ pixels[offset + 1] = newScanline[x * 2];
+ pixels[offset + 2] = newScanline[x * 2];
+ pixels[offset + 3] = newScanline[(x * 2) + 1];
+ }
+ }
+ else
+ {
+ for (int x = 0; x < header.Width; x++)
+ {
+ offset = ((this.row * header.Width) + x) * 4;
+
+ pixels[offset + 0] = newScanline[x];
+ pixels[offset + 1] = newScanline[x];
+ pixels[offset + 2] = newScanline[x];
+ pixels[offset + 3] = 255;
+ }
+ }
+
+ this.row++;
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/IColorReader.cs b/src/ImageProcessor/Formats/Png/IColorReader.cs
new file mode 100644
index 000000000..b2a7e4460
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/IColorReader.cs
@@ -0,0 +1,31 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Encapsulates methods for color readers, which are responsible for reading
+// different color formats from a png file.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// Encapsulates methods for color readers, which are responsible for reading
+ /// different color formats from a png file.
+ ///
+ public interface IColorReader
+ {
+ ///
+ /// Reads the specified scanline.
+ ///
+ /// The scanline.
+ /// The pixels, where the colors should be stored in RGBA format.
+ ///
+ /// The header, which contains information about the png file, like
+ /// the width of the image and the height.
+ ///
+ void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header);
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/PaletteIndexReader.cs b/src/ImageProcessor/Formats/Png/PaletteIndexReader.cs
new file mode 100644
index 000000000..140da5e5d
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/PaletteIndexReader.cs
@@ -0,0 +1,95 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// A color reader for reading palette indices from the png file.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// A color reader for reading palette indices from the png file.
+ ///
+ public sealed class PaletteIndexReader : IColorReader
+ {
+ ///
+ /// The palette.
+ ///
+ private readonly byte[] palette;
+
+ ///
+ /// The alpha palette.
+ ///
+ private readonly byte[] paletteAlpha;
+
+ ///
+ /// The current row.
+ ///
+ private int row;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The palette as simple byte array. It will contains 3 values for each
+ /// color, which represents the red-, the green- and the blue channel.
+ /// The alpha palette. Can be null, if the image does not have an
+ /// alpha channel and can contain less entries than the number of colors in the palette.
+ public PaletteIndexReader(byte[] palette, byte[] paletteAlpha)
+ {
+ this.palette = palette;
+ this.paletteAlpha = paletteAlpha;
+ }
+
+ ///
+ /// Reads the specified scanline.
+ ///
+ /// The scanline.
+ /// The pixels, where the colors should be stored in RGBA format.
+ /// The header, which contains information about the png file, like
+ /// the width of the image and the height.
+ public void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header)
+ {
+ byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth);
+ int offset, index;
+
+ if (this.paletteAlpha != null && this.paletteAlpha.Length > 0)
+ {
+ // If the alpha palette is not null and does one or
+ // more entries, this means, that the image contains and alpha
+ // channel and we should try to read it.
+ for (int i = 0; i < header.Width; i++)
+ {
+ index = newScanline[i];
+
+ offset = ((this.row * header.Width) + i) * 4;
+
+ pixels[offset + 0] = this.palette[(index * 3) + 2];
+ pixels[offset + 1] = this.palette[(index * 3) + 1];
+ pixels[offset + 2] = this.palette[(index * 3) + 0];
+ pixels[offset + 3] = this.paletteAlpha.Length > index
+ ? this.paletteAlpha[index]
+ : (byte)255;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < header.Width; i++)
+ {
+ index = newScanline[i];
+
+ offset = ((this.row * header.Width) + i) * 4;
+
+ pixels[offset + 0] = this.palette[(index * 3) + 2];
+ pixels[offset + 1] = this.palette[(index * 3) + 1];
+ pixels[offset + 2] = this.palette[(index * 3) + 0];
+ pixels[offset + 3] = 255;
+ }
+ }
+
+ this.row++;
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/PngChunk.cs b/src/ImageProcessor/Formats/Png/PngChunk.cs
new file mode 100644
index 000000000..554ccbca2
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/PngChunk.cs
@@ -0,0 +1,44 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Stores header information about a chunk.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// Stores header information about a chunk.
+ ///
+ internal sealed class PngChunk
+ {
+ ///
+ /// Gets or sets the length.
+ /// An unsigned integer giving the number of bytes in the chunk's
+ /// data field. The length counts only the data field, not itself,
+ /// the chunk type code, or the CRC. Zero is a valid length
+ ///
+ public int Length { get; set; }
+
+ ///
+ /// Gets or sets the chunk type as string with 4 chars.
+ ///
+ public string Type { get; set; }
+
+ ///
+ /// Gets or sets the data bytes appropriate to the chunk type, if any.
+ /// This field can be of zero length.
+ ///
+ public byte[] Data { get; set; }
+
+ ///
+ /// Gets or sets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk,
+ /// including the chunk type code and chunk data fields, but not including the length field.
+ /// The CRC is always present, even for chunks containing no data
+ ///
+ public uint Crc { get; set; }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/PngChunkTypes.cs b/src/ImageProcessor/Formats/Png/PngChunkTypes.cs
new file mode 100644
index 000000000..cc908e48e
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/PngChunkTypes.cs
@@ -0,0 +1,67 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Contains a list of possible chunk type identifiers.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// Contains a list of possible chunk type identifiers.
+ ///
+ internal static class PngChunkTypes
+ {
+ ///
+ /// The first chunk in a png file. Can only exists once. Contains
+ /// common information like the width and the height of the image or
+ /// the used compression method.
+ ///
+ public const string Header = "IHDR";
+
+ ///
+ /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
+ /// series in the RGB format.
+ ///
+ public const string Palette = "PLTE";
+
+ ///
+ /// The IDAT chunk contains the actual image data. The image can contains more
+ /// than one chunk of this type. All chunks together are the whole image.
+ ///
+ public const string Data = "IDAT";
+
+ ///
+ /// This chunk must appear last. It marks the end of the PNG data stream.
+ /// The chunk's data field is empty.
+ ///
+ public const string End = "IEND";
+
+ ///
+ /// This chunk specifies that the image uses simple transparency:
+ /// either alpha values associated with palette entries (for indexed-color images)
+ /// or a single transparent color (for grayscale and true color images).
+ ///
+ public const string PaletteAlpha = "tRNS";
+
+ ///
+ /// Textual information that the encoder wishes to record with the image can be stored in
+ /// tEXt chunks. Each tEXt chunk contains a keyword and a text string.
+ ///
+ public const string Text = "tEXt";
+
+ ///
+ /// This chunk specifies the relationship between the image samples and the desired
+ /// display output intensity.
+ ///
+ public const string Gamma = "gAMA";
+
+ ///
+ /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
+ ///
+ public const string Physical = "pHYs";
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/PngColorTypeInformation.cs b/src/ImageProcessor/Formats/Png/PngColorTypeInformation.cs
new file mode 100644
index 000000000..15b3fb7ff
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/PngColorTypeInformation.cs
@@ -0,0 +1,66 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Contains information that are required when loading a png with a specific color type.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ using System;
+
+ ///
+ /// Contains information that are required when loading a png with a specific color type.
+ ///
+ internal sealed class PngColorTypeInformation
+ {
+ ///
+ /// Initializes a new instance of the class with
+ /// the scanline factory, the function to create the color reader and the supported bit depths.
+ ///
+ /// The scanline factor.
+ /// The supported bit depths.
+ /// The factory to create the color reader.
+ public PngColorTypeInformation(int scanlineFactor, int[] supportedBitDepths, Func scanlineReaderFactory)
+ {
+ this.ChannelsPerColor = scanlineFactor;
+ this.ScanlineReaderFactory = scanlineReaderFactory;
+ this.SupportedBitDepths = supportedBitDepths;
+ }
+
+ ///
+ /// Gets an array with the bit depths that are supported for the color type
+ /// where this object is created for.
+ ///
+ /// The supported bit depths that can be used in combination with this color type.
+ public int[] SupportedBitDepths { get; private set; }
+
+ ///
+ /// Gets a function that is used the create the color reader for the color type where
+ /// this object is created for.
+ ///
+ /// The factory function to create the color type.
+ public Func ScanlineReaderFactory { get; private set; }
+
+ ///
+ /// Gets a factor that is used when iterating through the scan lines.
+ ///
+ /// The scanline factor.
+ public int ChannelsPerColor { get; private set; }
+
+ ///
+ /// Creates the color reader for the color type where this object is create for.
+ ///
+ /// The palette of the image. Can be null when no palette is used.
+ /// The alpha palette of the image. Can be null when
+ /// no palette is used for the image or when the image has no alpha.
+ /// The color reader for the image.
+ public IColorReader CreateColorReader(byte[] palette, byte[] paletteAlpha)
+ {
+ return this.ScanlineReaderFactory(palette, paletteAlpha);
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/PngDecoder.cs b/src/ImageProcessor/Formats/Png/PngDecoder.cs
new file mode 100644
index 000000000..8df7d0106
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/PngDecoder.cs
@@ -0,0 +1,98 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Encoder for generating a image out of a png stream.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ using System;
+ using System.IO;
+
+ ///
+ /// Encoder for generating a image out of a png stream.
+ ///
+ ///
+ /// At the moment the following features are supported:
+ ///
+ /// Filters: all filters are supported.
+ ///
+ ///
+ /// Pixel formats:
+ ///
+ /// - RGBA (True color) with alpha (8 bit).
+ /// - RGB (True color) without alpha (8 bit).
+ /// - Greyscale with alpha (8 bit).
+ /// - Greyscale without alpha (8 bit).
+ /// - Palette Index with alpha (8 bit).
+ /// - Palette Index without alpha (8 bit).
+ ///
+ ///
+ ///
+ public class PngDecoder : IImageDecoder
+ {
+ ///
+ /// Gets the size of the header for this image type.
+ ///
+ /// The size of the header.
+ public int HeaderSize
+ {
+ get
+ {
+ return 8;
+ }
+ }
+
+ ///
+ /// Returns a value indicating whether the supports the specified
+ /// file header.
+ ///
+ /// The containing the file extension.
+ ///
+ /// True if the decoder supports the file extension; otherwise, false.
+ ///
+ public bool IsSupportedFileExtension(string extension)
+ {
+ Guard.NotNullOrEmpty(extension, "extension");
+
+ extension = extension.StartsWith(".") ? extension.Substring(1) : extension;
+
+ return extension.Equals("PNG", StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// Returns a value indicating whether the supports the specified
+ /// file header.
+ ///
+ /// The containing the file header.
+ ///
+ /// True if the decoder supports the file header; otherwise, false.
+ ///
+ public bool IsSupportedFileFormat(byte[] header)
+ {
+ return header.Length >= 8 &&
+ header[0] == 0x89 &&
+ header[1] == 0x50 && // P
+ header[2] == 0x4E && // N
+ header[3] == 0x47 && // G
+ header[4] == 0x0D && // CR
+ header[5] == 0x0A && // LF
+ header[6] == 0x1A && // EOF
+ header[7] == 0x0A; // LF
+ }
+
+ ///
+ /// Decodes the image from the specified stream to the .
+ ///
+ /// The to decode to.
+ /// The containing image data.
+ public void Decode(Image image, Stream stream)
+ {
+ new PngDecoderCore().Decode(image, stream);
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/PngDecoderCore.cs b/src/ImageProcessor/Formats/Png/PngDecoderCore.cs
new file mode 100644
index 000000000..f0fec8d04
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/PngDecoderCore.cs
@@ -0,0 +1,554 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Performs the png decoding operation.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+
+ using ICSharpCode.SharpZipLib.Checksums;
+ using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
+
+ ///
+ /// Performs the png decoding operation.
+ ///
+ internal class PngDecoderCore
+ {
+ ///
+ /// The maximum chunk size.
+ ///
+ private const int MaxChunkSize = 1024 * 1024;
+
+ ///
+ /// The dictionary of available color types.
+ ///
+ private static readonly Dictionary ColorTypes
+ = new Dictionary();
+
+ ///
+ /// The image to decode.
+ ///
+ private Image currentImage;
+
+ ///
+ /// The stream to decode to.
+ ///
+ private Stream currentStream;
+
+ ///
+ /// The png header.
+ ///
+ private PngHeader header;
+
+ ///
+ /// Initializes static members of the class.
+ ///
+ static PngDecoderCore()
+ {
+ ColorTypes.Add(
+ 0,
+ new PngColorTypeInformation(
+ 1,
+ new[] { 1, 2, 4, 8 },
+ (p, a) => new GrayscaleReader(false)));
+
+ ColorTypes.Add(
+ 2,
+ new PngColorTypeInformation(
+ 3,
+ new[] { 8 },
+ (p, a) => new TrueColorReader(false)));
+
+ ColorTypes.Add(
+ 3,
+ new PngColorTypeInformation(
+ 1,
+ new[] { 1, 2, 4, 8 },
+ (p, a) => new PaletteIndexReader(p, a)));
+
+ ColorTypes.Add(
+ 4,
+ new PngColorTypeInformation(
+ 2,
+ new[] { 8 },
+ (p, a) => new GrayscaleReader(true)));
+
+ ColorTypes.Add(
+ 6,
+ new PngColorTypeInformation(
+ 4,
+ new[] { 8 },
+ (p, a) => new TrueColorReader(true)));
+ }
+
+ ///
+ /// Decodes the stream to the image.
+ ///
+ /// The image to decode to.
+ /// The stream containing image data.
+ ///
+ /// Thrown if the stream does not contain and end chunk.
+ ///
+ ///
+ /// Thrown if the image is larger than the maximum allowable size.
+ ///
+ public void Decode(Image image, Stream stream)
+ {
+ this.currentImage = image;
+ this.currentStream = stream;
+ this.currentStream.Seek(8, SeekOrigin.Current);
+
+ bool isEndChunkReached = false;
+
+ byte[] palette = null;
+ byte[] paletteAlpha = null;
+
+ using (MemoryStream dataStream = new MemoryStream())
+ {
+ PngChunk currentChunk;
+ while ((currentChunk = this.ReadChunk()) != null)
+ {
+ if (isEndChunkReached)
+ {
+ throw new ImageFormatException("Image does not end with end chunk.");
+ }
+
+ if (currentChunk.Type == PngChunkTypes.Header)
+ {
+ this.ReadHeaderChunk(currentChunk.Data);
+ this.ValidateHeader();
+ }
+ else if (currentChunk.Type == PngChunkTypes.Physical)
+ {
+ this.ReadPhysicalChunk(currentChunk.Data);
+ }
+ else if (currentChunk.Type == PngChunkTypes.Data)
+ {
+ dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length);
+ }
+ else if (currentChunk.Type == PngChunkTypes.Palette)
+ {
+ palette = currentChunk.Data;
+ }
+ else if (currentChunk.Type == PngChunkTypes.PaletteAlpha)
+ {
+ paletteAlpha = currentChunk.Data;
+ }
+ else if (currentChunk.Type == PngChunkTypes.Text)
+ {
+ this.ReadTextChunk(currentChunk.Data);
+ }
+ else if (currentChunk.Type == PngChunkTypes.End)
+ {
+ isEndChunkReached = true;
+ }
+ }
+
+ if (this.header.Width > ImageBase.MaxWidth || this.header.Height > ImageBase.MaxHeight)
+ {
+ throw new ArgumentOutOfRangeException(
+ string.Format(
+ "The input png '{0}x{1}' is bigger then the max allowed size '{2}x{3}'",
+ this.header.Width,
+ this.header.Height,
+ ImageBase.MaxWidth,
+ ImageBase.MaxHeight));
+ }
+
+ byte[] pixels = new byte[this.header.Width * this.header.Height * 4];
+
+ PngColorTypeInformation colorTypeInformation = ColorTypes[this.header.ColorType];
+
+ if (colorTypeInformation != null)
+ {
+ IColorReader colorReader = colorTypeInformation.CreateColorReader(palette, paletteAlpha);
+
+ this.ReadScanlines(dataStream, pixels, colorReader, colorTypeInformation);
+ }
+
+ image.SetPixels(this.header.Width, this.header.Height, pixels);
+ }
+ }
+
+ ///
+ /// Reads the data chunk containing physical dimension data.
+ ///
+ /// The data containing physical data.
+ private void ReadPhysicalChunk(byte[] data)
+ {
+ Array.Reverse(data, 0, 4);
+ Array.Reverse(data, 4, 4);
+
+ this.currentImage.DensityX = BitConverter.ToInt32(data, 0) / 39.3700787d;
+ this.currentImage.DensityY = BitConverter.ToInt32(data, 4) / 39.3700787d;
+ }
+
+ ///
+ /// Calculates the scanline length.
+ ///
+ /// The color type information.
+ /// The representing the length.
+ private int CalculateScanlineLength(PngColorTypeInformation colorTypeInformation)
+ {
+ int scanlineLength = this.header.Width * this.header.BitDepth * colorTypeInformation.ChannelsPerColor;
+
+ int amount = scanlineLength % 8;
+ if (amount != 0)
+ {
+ scanlineLength += 8 - amount;
+ }
+
+ return scanlineLength / 8;
+ }
+
+ ///
+ /// Calculates a scanline step.
+ ///
+ /// The color type information.
+ /// The representing the length of each step.
+ private int CalculateScanlineStep(PngColorTypeInformation colorTypeInformation)
+ {
+ int scanlineStep = 1;
+
+ if (this.header.BitDepth >= 8)
+ {
+ scanlineStep = (colorTypeInformation.ChannelsPerColor * this.header.BitDepth) / 8;
+ }
+
+ return scanlineStep;
+ }
+
+ ///
+ /// Reads the scanlines within the image.
+ ///
+ /// The containing data.
+ ///
+ /// The containing pixel data.
+ /// The color reader.
+ /// The color type information.
+ private void ReadScanlines(MemoryStream dataStream, byte[] pixels, IColorReader colorReader, PngColorTypeInformation colorTypeInformation)
+ {
+ dataStream.Position = 0;
+
+ int scanlineLength = this.CalculateScanlineLength(colorTypeInformation);
+ int scanlineStep = this.CalculateScanlineStep(colorTypeInformation);
+
+ byte[] lastScanline = new byte[scanlineLength];
+ byte[] currentScanline = new byte[scanlineLength];
+ int filter = 0, column = -1;
+
+ using (InflaterInputStream compressedStream = new InflaterInputStream(dataStream))
+ {
+ int readByte;
+ while ((readByte = compressedStream.ReadByte()) >= 0)
+ {
+ if (column == -1)
+ {
+ filter = readByte;
+
+ column++;
+ }
+ else
+ {
+ currentScanline[column] = (byte)readByte;
+
+ byte a;
+ byte b;
+ byte c;
+
+ if (column >= scanlineStep)
+ {
+ a = currentScanline[column - scanlineStep];
+ c = lastScanline[column - scanlineStep];
+ }
+ else
+ {
+ a = 0;
+ c = 0;
+ }
+
+ b = lastScanline[column];
+
+ if (filter == 1)
+ {
+ currentScanline[column] = (byte)(currentScanline[column] + a);
+ }
+ else if (filter == 2)
+ {
+ currentScanline[column] = (byte)(currentScanline[column] + b);
+ }
+ else if (filter == 3)
+ {
+ currentScanline[column] = (byte)(currentScanline[column] + (byte)((a + b) / 2));
+ }
+ else if (filter == 4)
+ {
+ currentScanline[column] = (byte)(currentScanline[column] + PathPredicator(a, b, c));
+ }
+
+ column++;
+
+ if (column == scanlineLength)
+ {
+ colorReader.ReadScanline(currentScanline, pixels, this.header);
+ column = -1;
+
+ Utils.Swap(ref currentScanline, ref lastScanline);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// TODO: Document this
+ ///
+ /// The a.
+ /// The b.
+ /// The c.
+ ///
+ /// The .
+ ///
+ private static byte PathPredicator(byte a, byte b, byte c)
+ {
+ byte predicator;
+
+ int p = a + b - c;
+ int pa = Math.Abs(p - a);
+ int pb = Math.Abs(p - b);
+ int pc = Math.Abs(p - c);
+
+ if (pa <= pb && pa <= pc)
+ {
+ predicator = a;
+ }
+ else if (pb <= pc)
+ {
+ predicator = b;
+ }
+ else
+ {
+ predicator = c;
+ }
+
+ return predicator;
+ }
+
+ ///
+ /// Reads a text chunk containing image properties from the data.
+ ///
+ /// The containing data.
+ private void ReadTextChunk(byte[] data)
+ {
+ int zeroIndex = 0;
+
+ for (int i = 0; i < data.Length; i++)
+ {
+ if (data[i] == 0)
+ {
+ zeroIndex = i;
+ break;
+ }
+ }
+
+ string name = System.Text.Encoding.Unicode.GetString(data, 0, zeroIndex);
+ string value = System.Text.Encoding.Unicode.GetString(data, zeroIndex + 1, data.Length - zeroIndex - 1);
+
+ this.currentImage.Properties.Add(new ImageProperty(name, value));
+ }
+
+ ///
+ /// Reads a header chunk from the data.
+ ///
+ /// The containing data.
+ private void ReadHeaderChunk(byte[] data)
+ {
+ this.header = new PngHeader();
+
+ Array.Reverse(data, 0, 4);
+ Array.Reverse(data, 4, 4);
+
+ this.header.Width = BitConverter.ToInt32(data, 0);
+ this.header.Height = BitConverter.ToInt32(data, 4);
+
+ this.header.BitDepth = data[8];
+ this.header.ColorType = data[9];
+ this.header.FilterMethod = data[11];
+ this.header.InterlaceMethod = data[12];
+ this.header.CompressionMethod = data[10];
+ }
+
+ ///
+ /// Validates the png header.
+ ///
+ ///
+ /// Thrown if the image does pass validation.
+ ///
+ private void ValidateHeader()
+ {
+ if (!ColorTypes.ContainsKey(this.header.ColorType))
+ {
+ throw new ImageFormatException("Color type is not supported or not valid.");
+ }
+
+ if (!ColorTypes[this.header.ColorType].SupportedBitDepths.Contains(this.header.BitDepth))
+ {
+ throw new ImageFormatException("Bit depth is not supported or not valid.");
+ }
+
+ if (this.header.FilterMethod != 0)
+ {
+ throw new ImageFormatException("The png specification only defines 0 as filter method.");
+ }
+
+ if (this.header.InterlaceMethod != 0)
+ {
+ throw new ImageFormatException("Interlacing is not supported.");
+ }
+ }
+
+ ///
+ /// Reads a chunk from the stream.
+ ///
+ ///
+ /// The .
+ ///
+ private PngChunk ReadChunk()
+ {
+ PngChunk chunk = new PngChunk();
+
+ if (this.ReadChunkLength(chunk) == 0)
+ {
+ return null;
+ }
+
+ byte[] typeBuffer = this.ReadChunkType(chunk);
+
+ this.ReadChunkData(chunk);
+ this.ReadChunkCrc(chunk, typeBuffer);
+
+ return chunk;
+ }
+
+ ///
+ /// Reads the cycle redundancy chunk from the data.
+ ///
+ /// The chunk.
+ /// The type buffer.
+ ///
+ /// Thrown if the input stream is not valid or corrupt.
+ ///
+ private void ReadChunkCrc(PngChunk chunk, byte[] typeBuffer)
+ {
+ byte[] crcBuffer = new byte[4];
+
+ int numBytes = this.currentStream.Read(crcBuffer, 0, 4);
+ if (numBytes.IsBetween(1, 3))
+ {
+ throw new ImageFormatException("Image stream is not valid!");
+ }
+
+ Array.Reverse(crcBuffer);
+
+ chunk.Crc = BitConverter.ToUInt32(crcBuffer, 0);
+
+ Crc32 crc = new Crc32();
+ crc.Update(typeBuffer);
+ crc.Update(chunk.Data);
+
+ if (crc.Value != chunk.Crc)
+ {
+ throw new ImageFormatException("CRC Error. PNG Image chunk is corrupt!");
+ }
+ }
+
+ ///
+ /// Reads the chunk data from the stream.
+ ///
+ /// The chunk.
+ ///
+ /// Thrown if the chunk length exceeds the maximum allowable size.
+ ///
+ private void ReadChunkData(PngChunk chunk)
+ {
+ if (chunk.Length > MaxChunkSize)
+ {
+ throw new ArgumentOutOfRangeException(
+ string.Format(
+ "Png chunk size '{0}' exceeds the '{1}'",
+ chunk.Length,
+ MaxChunkSize));
+ }
+
+ chunk.Data = new byte[chunk.Length];
+ this.currentStream.Read(chunk.Data, 0, chunk.Length);
+ }
+
+ ///
+ /// Identifies the chunk type from the chunk.
+ ///
+ /// The chunk.
+ ///
+ /// The containing identifying information.
+ ///
+ ///
+ /// Thrown if the input stream is not valid.
+ ///
+ private byte[] ReadChunkType(PngChunk chunk)
+ {
+ byte[] typeBuffer = new byte[4];
+
+ int numBytes = this.currentStream.Read(typeBuffer, 0, 4);
+ if (numBytes.IsBetween(1, 3))
+ {
+ throw new ImageFormatException("Image stream is not valid!");
+ }
+
+ char[] chars = new char[4];
+ chars[0] = (char)typeBuffer[0];
+ chars[1] = (char)typeBuffer[1];
+ chars[2] = (char)typeBuffer[2];
+ chars[3] = (char)typeBuffer[3];
+
+ chunk.Type = new string(chars);
+
+ return typeBuffer;
+ }
+
+ ///
+ /// Calculates the length of the given chunk.
+ ///
+ /// he chunk.
+ ///
+ /// The representing the chunk length.
+ ///
+ ///
+ /// Thrown if the input stream is not valid.
+ ///
+ private int ReadChunkLength(PngChunk chunk)
+ {
+ byte[] lengthBuffer = new byte[4];
+
+ int numBytes = this.currentStream.Read(lengthBuffer, 0, 4);
+ if (numBytes.IsBetween(1, 3))
+ {
+ throw new ImageFormatException("Image stream is not valid!");
+ }
+
+ Array.Reverse(lengthBuffer);
+
+ chunk.Length = BitConverter.ToInt32(lengthBuffer, 0);
+
+ return numBytes;
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/PngEncoder.cs b/src/ImageProcessor/Formats/Png/PngEncoder.cs
new file mode 100644
index 000000000..a4e64d6f5
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/PngEncoder.cs
@@ -0,0 +1,488 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Image encoder for writing image data to a stream in png format.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ using System;
+ using System.IO;
+
+ using ICSharpCode.SharpZipLib.Checksums;
+ using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
+
+ ///
+ /// Image encoder for writing image data to a stream in png format.
+ ///
+ public class PngEncoder : IImageEncoder
+ {
+ ///
+ /// The maximum block size.
+ ///
+ private const int MaxBlockSize = 0xFFFF;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PngEncoder()
+ {
+ this.Gamma = 2.2f;
+ }
+
+ ///
+ /// Gets or sets the quality of output for images.
+ ///
+ /// Png is a lossless format so this is not used in this encoder.
+ public int Quality { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this encoder
+ /// will write the image uncompressed the stream.
+ ///
+ ///
+ /// true if the image should be written uncompressed to
+ /// the stream; otherwise, false.
+ ///
+ public bool IsWritingUncompressed { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this instance is writing
+ /// gamma information to the stream. The default value is false.
+ ///
+ ///
+ /// true if this instance is writing gamma
+ /// information to the stream.; otherwise, false.
+ ///
+ public bool IsWritingGamma { get; set; }
+
+ ///
+ /// Gets or sets the gamma value, that will be written
+ /// the the stream, when the property
+ /// is set to true. The default value is 2.2f.
+ ///
+ /// The gamma value of the image.
+ public double Gamma { get; set; }
+
+ ///
+ /// Gets the default file extension for this encoder.
+ ///
+ /// The default file extension for this encoder.
+ public string Extension
+ {
+ get { return "PNG"; }
+ }
+
+ ///
+ /// Indicates if the image encoder supports the specified
+ /// file extension.
+ ///
+ /// The file extension.
+ /// true, if the encoder supports the specified
+ /// extensions; otherwise false.
+ ///
+ ///
+ /// is null (Nothing in Visual Basic).
+ /// is a string
+ /// of length zero or contains only blanks.
+ public bool IsSupportedFileExtension(string extension)
+ {
+ Guard.NotNullOrEmpty(extension, "extension");
+
+ extension = extension.StartsWith(".") ? extension.Substring(1) : extension;
+
+ return extension.Equals("PNG", StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// Encodes the data of the specified image and writes the result to
+ /// the specified stream.
+ ///
+ /// The image, where the data should be get from.
+ /// Cannot be null (Nothing in Visual Basic).
+ /// The stream, where the image data should be written to.
+ /// Cannot be null (Nothing in Visual Basic).
+ ///
+ /// is null (Nothing in Visual Basic).
+ /// - or -
+ /// is null (Nothing in Visual Basic).
+ ///
+ public void Encode(ImageBase image, Stream stream)
+ {
+ Guard.NotNull(image, "image");
+ Guard.NotNull(stream, "stream");
+
+ // Write the png header.
+ stream.Write(
+ new byte[]
+ {
+ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
+ },
+ 0,
+ 8);
+
+ PngHeader header = new PngHeader
+ {
+ Width = image.Width,
+ Height = image.Height,
+ ColorType = 6,
+ BitDepth = 8,
+ FilterMethod = 0,
+ CompressionMethod = 0,
+ InterlaceMethod = 0
+ };
+
+ this.WriteHeaderChunk(stream, header);
+ this.WritePhysicalChunk(stream, image);
+ this.WriteGammaChunk(stream);
+
+ if (this.IsWritingUncompressed)
+ {
+ this.WriteDataChunksFast(stream, image);
+ }
+ else
+ {
+ this.WriteDataChunks(stream, image);
+ }
+
+ this.WriteEndChunk(stream);
+ stream.Flush();
+ }
+
+ ///
+ /// Writes an integer to the byte array.
+ ///
+ /// The containing image data.
+ /// The amount to offset by.
+ /// The value to write.
+ private static void WriteInteger(byte[] data, int offset, int value)
+ {
+ byte[] buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+ Array.Copy(buffer, 0, data, offset, 4);
+ }
+
+ ///
+ /// Writes an integer to the stream.
+ ///
+ /// The containing image data.
+ /// The value to write.
+ private static void WriteInteger(Stream stream, int value)
+ {
+ byte[] buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+
+ stream.Write(buffer, 0, 4);
+ }
+
+ ///
+ /// Writes an unsigned integer to the stream.
+ ///
+ /// The containing image data.
+ /// The value to write.
+ private static void WriteInteger(Stream stream, uint value)
+ {
+ byte[] buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+
+ stream.Write(buffer, 0, 4);
+ }
+
+ ///
+ /// Writes the physical dimension information to the stream.
+ ///
+ /// The containing image data.
+ /// The image base.
+ private void WritePhysicalChunk(Stream stream, ImageBase imageBase)
+ {
+ Image image = imageBase as Image;
+ if (image != null && image.DensityX > 0 && image.DensityY > 0)
+ {
+ int dpmX = (int)Math.Round(image.DensityX * 39.3700787d);
+ int dpmY = (int)Math.Round(image.DensityY * 39.3700787d);
+
+ byte[] chunkData = new byte[9];
+
+ WriteInteger(chunkData, 0, dpmX);
+ WriteInteger(chunkData, 4, dpmY);
+
+ chunkData[8] = 1;
+
+ this.WriteChunk(stream, PngChunkTypes.Physical, chunkData);
+ }
+ }
+
+ ///
+ /// Writes the gamma information to the stream.
+ ///
+ /// The containing image data.
+ private void WriteGammaChunk(Stream stream)
+ {
+ if (this.IsWritingGamma)
+ {
+ int gammaValue = (int)(this.Gamma * 100000f);
+
+ byte[] fourByteData = new byte[4];
+
+ byte[] size = BitConverter.GetBytes(gammaValue);
+
+ fourByteData[0] = size[3];
+ fourByteData[1] = size[2];
+ fourByteData[2] = size[1];
+ fourByteData[3] = size[0];
+
+ this.WriteChunk(stream, PngChunkTypes.Gamma, fourByteData);
+ }
+ }
+
+ ///
+ /// Writes the pixel information to the stream.
+ ///
+ /// The containing image data.
+ /// The image base.
+ private void WriteDataChunksFast(Stream stream, ImageBase imageBase)
+ {
+ byte[] pixels = imageBase.Pixels;
+
+ // Convert the pixel array to a new array for adding
+ // the filter byte.
+ byte[] data = new byte[(imageBase.Width * imageBase.Height * 4) + imageBase.Height];
+
+ int rowLength = (imageBase.Width * 4) + 1;
+
+ for (int y = 0; y < imageBase.Height; y++)
+ {
+ data[y * rowLength] = 0;
+ Array.Copy(pixels, y * imageBase.Width * 4, data, (y * rowLength) + 1, imageBase.Width * 4);
+ }
+
+ Adler32 adler32 = new Adler32();
+ adler32.Update(data);
+
+ using (MemoryStream tempStream = new MemoryStream())
+ {
+ int remainder = data.Length;
+
+ int blockCount;
+ if ((data.Length % MaxBlockSize) == 0)
+ {
+ blockCount = data.Length / MaxBlockSize;
+ }
+ else
+ {
+ blockCount = (data.Length / MaxBlockSize) + 1;
+ }
+
+ // Write headers
+ tempStream.WriteByte(0x78);
+ tempStream.WriteByte(0xDA);
+
+ for (int i = 0; i < blockCount; i++)
+ {
+ // Write the length
+ ushort length = (ushort)((remainder < MaxBlockSize) ? remainder : MaxBlockSize);
+
+ tempStream.WriteByte(length == remainder ? (byte)0x01 : (byte)0x00);
+
+ tempStream.Write(BitConverter.GetBytes(length), 0, 2);
+
+ // Write one's compliment of length
+ tempStream.Write(BitConverter.GetBytes((ushort)~length), 0, 2);
+
+ // Write blocks
+ tempStream.Write(data, i * MaxBlockSize, length);
+
+ // Next block
+ remainder -= MaxBlockSize;
+ }
+
+ WriteInteger(tempStream, (int)adler32.Value);
+
+ tempStream.Seek(0, SeekOrigin.Begin);
+
+ byte[] zipData = new byte[tempStream.Length];
+ tempStream.Read(zipData, 0, (int)tempStream.Length);
+
+ this.WriteChunk(stream, PngChunkTypes.Data, zipData);
+ }
+ }
+
+ ///
+ /// Writes the pixel information to the stream.
+ ///
+ /// The containing image data.
+ /// The image base.
+ private void WriteDataChunks(Stream stream, ImageBase imageBase)
+ {
+ byte[] pixels = imageBase.Pixels;
+
+ byte[] data = new byte[(imageBase.Width * imageBase.Height * 4) + imageBase.Height];
+
+ int rowLength = (imageBase.Width * 4) + 1;
+
+ for (int y = 0; y < imageBase.Height; y++)
+ {
+ byte compression = 0;
+ if (y > 0)
+ {
+ compression = 2;
+ }
+
+ data[y * rowLength] = compression;
+
+ for (int x = 0; x < imageBase.Width; x++)
+ {
+ // Calculate the offset for the new array.
+ int dataOffset = (y * rowLength) + (x * 4) + 1;
+
+ // Calculate the offset for the original pixel array.
+ int pixelOffset = ((y * imageBase.Width) + x) * 4;
+
+ data[dataOffset + 0] = pixels[pixelOffset + 2];
+ data[dataOffset + 1] = pixels[pixelOffset + 1];
+ data[dataOffset + 2] = pixels[pixelOffset + 0];
+ data[dataOffset + 3] = pixels[pixelOffset + 3];
+
+ if (y > 0)
+ {
+ int lastOffset = (((y - 1) * imageBase.Width) + x) * 4;
+
+ data[dataOffset + 0] -= pixels[lastOffset + 2];
+ data[dataOffset + 1] -= pixels[lastOffset + 1];
+ data[dataOffset + 2] -= pixels[lastOffset + 0];
+ data[dataOffset + 3] -= pixels[lastOffset + 3];
+ }
+ }
+ }
+
+ byte[] buffer;
+ int bufferLength;
+
+ MemoryStream memoryStream = null;
+ try
+ {
+ memoryStream = new MemoryStream();
+
+ using (DeflaterOutputStream outputStream = new DeflaterOutputStream(memoryStream))
+ {
+ outputStream.Write(data, 0, data.Length);
+ outputStream.Flush();
+ outputStream.Finish();
+
+ bufferLength = (int)memoryStream.Length;
+ buffer = memoryStream.ToArray();
+ }
+ }
+ finally
+ {
+ if (memoryStream != null)
+ {
+ memoryStream.Dispose();
+ }
+ }
+
+ int numChunks = bufferLength / MaxBlockSize;
+
+ if (bufferLength % MaxBlockSize != 0)
+ {
+ numChunks++;
+ }
+
+ for (int i = 0; i < numChunks; i++)
+ {
+ int length = bufferLength - (i * MaxBlockSize);
+
+ if (length > MaxBlockSize)
+ {
+ length = MaxBlockSize;
+ }
+
+ this.WriteChunk(stream, PngChunkTypes.Data, buffer, i * MaxBlockSize, length);
+ }
+ }
+
+ ///
+ /// Writes the chunk end to the stream.
+ ///
+ /// The containing image data.
+ private void WriteEndChunk(Stream stream)
+ {
+ this.WriteChunk(stream, PngChunkTypes.End, null);
+ }
+
+ ///
+ /// Writes the header chunk to the stream.
+ ///
+ /// The containing image data.
+ /// The .
+ private void WriteHeaderChunk(Stream stream, PngHeader header)
+ {
+ byte[] chunkData = new byte[13];
+
+ WriteInteger(chunkData, 0, header.Width);
+ WriteInteger(chunkData, 4, header.Height);
+
+ chunkData[8] = header.BitDepth;
+ chunkData[9] = header.ColorType;
+ chunkData[10] = header.CompressionMethod;
+ chunkData[11] = header.FilterMethod;
+ chunkData[12] = header.InterlaceMethod;
+
+ this.WriteChunk(stream, PngChunkTypes.Header, chunkData);
+ }
+
+ ///
+ /// Writes a chunk to the stream.
+ ///
+ /// The to write to.
+ /// The type of chunk to write.
+ /// The containing data.
+ private void WriteChunk(Stream stream, string type, byte[] data)
+ {
+ this.WriteChunk(stream, type, data, 0, data != null ? data.Length : 0);
+ }
+
+ ///
+ /// Writes a chunk of a specified length to the stream at the given offset.
+ ///
+ /// The to write to.
+ /// The type of chunk to write.
+ /// The containing data.
+ /// The position to offset the data at.
+ /// The of the data to write.
+ private void WriteChunk(Stream stream, string type, byte[] data, int offset, int length)
+ {
+ WriteInteger(stream, length);
+
+ byte[] typeArray = new byte[4];
+ typeArray[0] = (byte)type[0];
+ typeArray[1] = (byte)type[1];
+ typeArray[2] = (byte)type[2];
+ typeArray[3] = (byte)type[3];
+
+ stream.Write(typeArray, 0, 4);
+
+ if (data != null)
+ {
+ stream.Write(data, offset, length);
+ }
+
+ Crc32 crc32 = new Crc32();
+ crc32.Update(typeArray);
+
+ if (data != null)
+ {
+ crc32.Update(data, offset, length);
+ }
+
+ WriteInteger(stream, (uint)crc32.Value);
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/PngHeader.cs b/src/ImageProcessor/Formats/Png/PngHeader.cs
new file mode 100644
index 000000000..4c2ac2d56
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/PngHeader.cs
@@ -0,0 +1,67 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Represents the png header chunk.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// Represents the png header chunk.
+ ///
+ public sealed class PngHeader
+ {
+ ///
+ /// Gets or sets the dimension in x-direction of the image in pixels.
+ ///
+ public int Width { get; set; }
+
+ ///
+ /// Gets or sets the dimension in y-direction of the image in pixels.
+ ///
+ public int Height { get; set; }
+
+ ///
+ /// Gets or sets the bit depth.
+ /// Bit depth is a single-byte integer giving the number of bits per sample
+ /// or per palette index (not per pixel). Valid values are 1, 2, 4, 8, and 16,
+ /// although not all values are allowed for all color types.
+ ///
+ public byte BitDepth { get; set; }
+
+ ///
+ /// Gets or sets the color type.
+ /// Color type is a integer that describes the interpretation of the
+ /// image data. Color type codes represent sums of the following values:
+ /// 1 (palette used), 2 (color used), and 4 (alpha channel used).
+ ///
+ public byte ColorType { get; set; }
+
+ ///
+ /// Gets or sets the compression method.
+ /// Indicates the method used to compress the image data. At present,
+ /// only compression method 0 (deflate/inflate compression with a sliding
+ /// window of at most 32768 bytes) is defined.
+ ///
+ public byte CompressionMethod { get; set; }
+
+ ///
+ /// Gets or sets the preprocessing method.
+ /// Indicates the preprocessing method applied to the image
+ /// data before compression. At present, only filter method 0
+ /// (adaptive filtering with five basic filter types) is defined.
+ ///
+ public byte FilterMethod { get; set; }
+
+ ///
+ /// Gets or sets the transmission order.
+ /// Indicates the transmission order of the image data.
+ /// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace).
+ ///
+ public byte InterlaceMethod { get; set; }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Png/TrueColorReader.cs b/src/ImageProcessor/Formats/Png/TrueColorReader.cs
new file mode 100644
index 000000000..bb78ecf1f
--- /dev/null
+++ b/src/ImageProcessor/Formats/Png/TrueColorReader.cs
@@ -0,0 +1,81 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Color reader for reading true colors from a png file. Only colors
+// with 24 or 32 bit (3 or 4 bytes) per pixel are supported at the moment.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// Color reader for reading true colors from a png file. Only colors
+ /// with 24 or 32 bit (3 or 4 bytes) per pixel are supported at the moment.
+ ///
+ public sealed class TrueColorReader : IColorReader
+ {
+ ///
+ /// Whether t also read the alpha channel.
+ ///
+ private readonly bool useAlpha;
+
+ ///
+ /// The current row.
+ ///
+ private int row;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// if set to true the color reader will also read the
+ /// alpha channel from the scanline.
+ public TrueColorReader(bool useAlpha)
+ {
+ this.useAlpha = useAlpha;
+ }
+
+ ///
+ /// Reads the specified scanline.
+ ///
+ /// The scanline.
+ /// The pixels, where the colors should be stored in RGBA format.
+ /// The header, which contains information about the png file, like
+ /// the width of the image and the height.
+ public void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header)
+ {
+ int offset;
+
+ byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth);
+
+ if (this.useAlpha)
+ {
+ for (int x = 0; x < newScanline.Length; x += 4)
+ {
+ offset = ((this.row * header.Width) + (x >> 2)) * 4;
+
+ pixels[offset + 0] = newScanline[x + 2];
+ pixels[offset + 1] = newScanline[x + 1];
+ pixels[offset + 2] = newScanline[x + 0];
+ pixels[offset + 3] = newScanline[x + 3];
+ }
+ }
+ else
+ {
+ for (int x = 0; x < newScanline.Length / 3; x++)
+ {
+ offset = ((this.row * header.Width) + x) * 4;
+
+ pixels[offset + 0] = newScanline[(x * 3) + 2];
+ pixels[offset + 1] = newScanline[(x * 3) + 1];
+ pixels[offset + 2] = newScanline[(x * 3) + 0];
+ pixels[offset + 3] = 255;
+ }
+ }
+
+ this.row++;
+ }
+ }
+}
diff --git a/src/ImageProcessor/IImageBase.cs b/src/ImageProcessor/IImageBase.cs
deleted file mode 100644
index 15a37b68e..000000000
--- a/src/ImageProcessor/IImageBase.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-// --------------------------------------------------------------------------------------------------------------------
-//
-// Copyright © James South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-//
-// Encapsulates all the basic properties and methods required to manipulate images.
-//
-// --------------------------------------------------------------------------------------------------------------------
-
-namespace ImageProcessor
-{
- ///
- /// Encapsulates all the basic properties and methods required to manipulate images.
- ///
- public interface IImageBase
- {
- ///
- /// Gets the image pixels as byte array.
- ///
- ///
- /// The returned array has a length of Width * Height * 4 bytes
- /// and stores the blue, the green, the red and the alpha value for
- /// each pixel in this order.
- ///
- byte[] Pixels { get; }
-
- ///
- /// Gets the width in pixels.
- ///
- int Width { get; }
-
- ///
- /// Gets the height in pixels.
- ///
- int Height { get; }
-
- ///
- /// Gets the pixel ratio made up of the width and height.
- ///
- double PixelRatio { get; }
-
- ///
- /// Gets the representing the bounds of the image.
- ///
- Rectangle Bounds { get; }
-
- ///
- /// Gets or sets the color of a pixel at the specified position.
- ///
- ///
- /// The x-coordinate of the pixel. Must be greater
- /// than zero and smaller than the width of the pixel.
- ///
- ///
- /// The y-coordinate of the pixel. Must be greater
- /// than zero and smaller than the width of the pixel.
- ///
- /// The at the specified position.
- Color this[int x, int y]
- {
- get;
- set;
- }
-
- ///
- /// Sets the pixel array of the image.
- ///
- ///
- /// The new width of the image. Must be greater than zero.
- /// The new height of the image. Must be greater than zero.
- ///
- /// The array with colors. Must be a multiple
- /// of four, width and height.
- ///
- void SetPixels(int width, int height, byte[] pixels);
- }
-}
diff --git a/src/ImageProcessor/Image.cs b/src/ImageProcessor/Image.cs
new file mode 100644
index 000000000..6e8d116ab
--- /dev/null
+++ b/src/ImageProcessor/Image.cs
@@ -0,0 +1,299 @@
+namespace ImageProcessor
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Linq;
+ using System.Text;
+
+ using ImageProcessor.Formats;
+
+ ///
+ /// Image class with stores the pixels and provides common functionality
+ /// such as loading images from files and streams or operation like resizing or cutting.
+ ///
+ /// The image data is alway stored in RGBA format, where the red, the blue, the
+ /// alpha values are simple bytes.
+ [DebuggerDisplay("Image: {PixelWidth}x{PixelHeight}")]
+ public class Image : ImageBase
+ {
+ #region Constants
+
+ ///
+ /// The default density value (dots per inch) in x direction. The default value is 75 dots per inch.
+ ///
+ public const double DefaultDensityX = 75;
+ ///
+ /// The default density value (dots per inch) in y direction. The default value is 75 dots per inch.
+ ///
+ public const double DefaultDensityY = 75;
+
+ private static readonly Lazy> defaultDecoders = new Lazy>(() => new List
+ {
+ //new BmpDecoder(),
+ //new JpegDecoder(),
+ //new PngDecoder(),
+ new GifDecoder(),
+ });
+
+ private static readonly Lazy> defaultEncoders = new Lazy>(() => new List
+ {
+ //new BmpEncoder(),
+ //new JpegEncoder(),
+ //new PngEncoder(),
+ });
+
+ ///
+ /// Gets a list of default decoders.
+ ///
+ public static IList Decoders
+ {
+ get { return defaultDecoders.Value; }
+ }
+
+ ///
+ /// Gets a list of default encoders.
+ ///
+ public static IList Encoders
+ {
+ get { return defaultEncoders.Value; }
+ }
+
+ #endregion
+
+ #region Fields
+
+ private readonly object _lockObject = new object();
+
+ #endregion
+
+ ///
+ /// If not 0, this field specifies the number of hundredths (1/100) of a second to
+ /// wait before continuing with the processing of the Data Stream.
+ /// The clock starts ticking immediately after the graphic is rendered.
+ /// This field may be used in conjunction with the User Input Flag field.
+ ///
+ public int? DelayTime { get; set; }
+
+ #region Properties
+
+ ///
+ /// Gets or sets the resolution of the image in x direction. It is defined as
+ /// number of dots per inch and should be an positive value.
+ ///
+ /// The density of the image in x direction.
+ public double DensityX { get; set; }
+
+ ///
+ /// Gets or sets the resolution of the image in y direction. It is defined as
+ /// number of dots per inch and should be an positive value.
+ ///
+ /// The density of the image in y direction.
+ public double DensityY { get; set; }
+
+ ///
+ /// Gets the width of the image in inches. It is calculated as the width of the image
+ /// in pixels multiplied with the density. When the density is equals or less than zero
+ /// the default value is used.
+ ///
+ /// The width of the image in inches.
+ public double InchWidth
+ {
+ get
+ {
+ double densityX = DensityX;
+
+ if (densityX <= 0)
+ {
+ densityX = DefaultDensityX;
+ }
+
+ return Width / densityX;
+ }
+ }
+
+ ///
+ /// Gets the height of the image in inches. It is calculated as the height of the image
+ /// in pixels multiplied with the density. When the density is equals or less than zero
+ /// the default value is used.
+ ///
+ /// The height of the image in inches.
+ public double InchHeight
+ {
+ get
+ {
+ double densityY = DensityY;
+
+ if (densityY <= 0)
+ {
+ densityY = DefaultDensityY;
+ }
+
+ return Height / densityY;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this image is animated.
+ ///
+ ///
+ /// true if this image is animated; otherwise, false.
+ ///
+ public bool IsAnimated
+ {
+ get { return _frames.Count > 0; }
+ }
+
+ private IList _frames = new List();
+ ///
+ /// Get the other frames for the animation.
+ ///
+ /// The list of frame images.
+ public IList Frames
+ {
+ get { return _frames; }
+ }
+
+ private IList _properties = new List();
+ ///
+ /// Gets the list of properties for storing meta information about this image.
+ ///
+ /// A list of image properties.
+ public IList Properties
+ {
+ get { return _properties; }
+ }
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the height and the width of the image.
+ ///
+ /// The width of the image in pixels.
+ /// The height of the image in pixels.
+ public Image(int width, int height)
+ : base(width, height)
+ {
+ DensityX = DefaultDensityX;
+ DensityY = DefaultDensityY;
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// by making a copy from another image.
+ ///
+ /// The other image, where the clone should be made from.
+ /// is null
+ /// (Nothing in Visual Basic).
+ public Image(Image other)
+ : base(other)
+ {
+ if (other == null) throw new ArgumentNullException("Other image cannot be null.");
+
+ foreach (ImageFrame frame in other.Frames)
+ {
+ if (frame != null)
+ {
+ Frames.Add(new ImageFrame(frame));
+ }
+ }
+
+ DensityX = DefaultDensityX;
+ DensityY = DefaultDensityY;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Image()
+ {
+ DensityX = DefaultDensityX;
+ DensityY = DefaultDensityY;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Image(Stream stream)
+ {
+ if (stream == null)
+ {
+ throw new ArgumentNullException("stream");
+ }
+
+ Load(stream, Decoders);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Image(Stream stream, params IImageDecoder[] decoders)
+ {
+ if (stream == null)
+ {
+ throw new ArgumentNullException("stream");
+ }
+
+ Load(stream, decoders);
+ }
+
+ #endregion Constructors
+
+ #region Methods
+
+ private void Load(Stream stream, IList decoders)
+ {
+ try
+ {
+ if (!stream.CanRead)
+ {
+ throw new NotSupportedException("Cannot read from the stream.");
+ }
+
+ if (!stream.CanSeek)
+ {
+ throw new NotSupportedException("The stream does not support seeking.");
+ }
+
+ if (decoders.Count > 0)
+ {
+ int maxHeaderSize = decoders.Max(x => x.HeaderSize);
+ if (maxHeaderSize > 0)
+ {
+ byte[] header = new byte[maxHeaderSize];
+
+ stream.Read(header, 0, maxHeaderSize);
+ stream.Position = 0;
+
+ var decoder = decoders.FirstOrDefault(x => x.IsSupportedFileFormat(header));
+ if (decoder != null)
+ {
+ decoder.Decode(this, stream);
+ return;
+ }
+ }
+ }
+
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.AppendLine("Image cannot be loaded. Available decoders:");
+
+ foreach (IImageDecoder decoder in decoders)
+ {
+ stringBuilder.AppendLine("-" + decoder);
+ }
+
+ throw new NotSupportedException(stringBuilder.ToString());
+ }
+ finally
+ {
+ stream.Dispose();
+ }
+ }
+
+ #endregion Methods
+ }
+}
diff --git a/src/ImageProcessor/ImageBase.cs b/src/ImageProcessor/ImageBase.cs
index 616f879fe..951a6af9d 100644
--- a/src/ImageProcessor/ImageBase.cs
+++ b/src/ImageProcessor/ImageBase.cs
@@ -14,11 +14,28 @@ namespace ImageProcessor
using System;
///
- /// The base class of all images. Encapsulates all the properties and methods
+ /// The base class of all images. Encapsulates the basic properties and methods
/// required to manipulate images.
///
- public abstract class ImageBase : IImageBase
+ public abstract class ImageBase
{
+ ///
+ /// The maximum allowable width in pixels.
+ ///
+ private static int maxWidth = int.MaxValue;
+
+ ///
+ /// The maximum allowable height in pixels.
+ ///
+ private static int maxHeight = int.MaxValue;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ImageBase()
+ {
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -73,6 +90,38 @@ namespace ImageProcessor
Array.Copy(pixels, this.Pixels, pixels.Length);
}
+ ///
+ /// Gets or sets the maximum allowable width in pixels.
+ ///
+ public static int MaxWidth
+ {
+ get
+ {
+ return maxWidth;
+ }
+
+ set
+ {
+ maxWidth = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the maximum allowable height in pixels.
+ ///
+ public static int MaxHeight
+ {
+ get
+ {
+ return maxHeight;
+ }
+
+ set
+ {
+ maxHeight = value;
+ }
+ }
+
///
/// Gets the image pixels as byte array.
///
diff --git a/src/ImageProcessor/ImageFrame.cs b/src/ImageProcessor/ImageFrame.cs
new file mode 100644
index 000000000..b2d4f5235
--- /dev/null
+++ b/src/ImageProcessor/ImageFrame.cs
@@ -0,0 +1,41 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Represents a single frame in a animation.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor
+{
+ using System;
+
+ ///
+ /// Represents a single frame in a animation.
+ ///
+ public class ImageFrame : ImageBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ImageFrame()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The other to create this instance from.
+ ///
+ ///
+ /// Thrown if the given is null.
+ ///
+ public ImageFrame(ImageFrame other)
+ : base(other)
+ {
+ }
+ }
+}
diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj
index 02f70a50e..1553528c3 100644
--- a/src/ImageProcessor/ImageProcessor.csproj
+++ b/src/ImageProcessor/ImageProcessor.csproj
@@ -35,21 +35,52 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\..\packages\SharpZipLib.Portable.0.86.0.0002\lib\portable-net45+netcore45+wp8+win8+wpa81+MonoTouch+MonoAndroid\ICSharpCode.SharpZipLib.Portable.dll
+ True
+
+
+
+
+