Browse Source

Import png encoder and begin gif

Former-commit-id: da82a46559b9964bf02563bd0dd102bf68f8ff10
Former-commit-id: a71fb9608d926ad5fc48b6dc780a100641c00763
Former-commit-id: 4dabf320c3ef95ae3c27f437f7551c2a737c87cc
af/merge-core
James South 11 years ago
parent
commit
85dabe1260
  1. 51
      src/ImageProcessor/Common/Extensions/ByteExtensions.cs
  2. 36
      src/ImageProcessor/Common/Extensions/ComparableExtensions.cs
  3. 119
      src/ImageProcessor/Common/Helpers/Guard.cs
  4. 34
      src/ImageProcessor/Common/Helpers/Utils.cs
  5. 42
      src/ImageProcessor/Formats/Gif/DisposalMethod.cs
  6. 382
      src/ImageProcessor/Formats/Gif/GifDecoder.cs
  7. 49
      src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs
  8. 72
      src/ImageProcessor/Formats/Gif/GifImageDescriptor.cs
  9. 57
      src/ImageProcessor/Formats/Gif/GifLogicalScreenDescriptor.cs
  10. 270
      src/ImageProcessor/Formats/Gif/LzwDecoder.cs
  11. 53
      src/ImageProcessor/Formats/IImageDecoder.cs
  12. 47
      src/ImageProcessor/Formats/IImageEncoder.cs
  13. 9
      src/ImageProcessor/Formats/IImageFormat.cs
  14. 83
      src/ImageProcessor/Formats/Png/GrayscaleReader.cs
  15. 31
      src/ImageProcessor/Formats/Png/IColorReader.cs
  16. 95
      src/ImageProcessor/Formats/Png/PaletteIndexReader.cs
  17. 44
      src/ImageProcessor/Formats/Png/PngChunk.cs
  18. 67
      src/ImageProcessor/Formats/Png/PngChunkTypes.cs
  19. 66
      src/ImageProcessor/Formats/Png/PngColorTypeInformation.cs
  20. 98
      src/ImageProcessor/Formats/Png/PngDecoder.cs
  21. 554
      src/ImageProcessor/Formats/Png/PngDecoderCore.cs
  22. 488
      src/ImageProcessor/Formats/Png/PngEncoder.cs
  23. 67
      src/ImageProcessor/Formats/Png/PngHeader.cs
  24. 81
      src/ImageProcessor/Formats/Png/TrueColorReader.cs
  25. 78
      src/ImageProcessor/IImageBase.cs
  26. 299
      src/ImageProcessor/Image.cs
  27. 53
      src/ImageProcessor/ImageBase.cs
  28. 41
      src/ImageProcessor/ImageFrame.cs
  29. 39
      src/ImageProcessor/ImageProcessor.csproj
  30. 3
      src/ImageProcessor/ImageProcessor.csproj.DotSettings
  31. 167
      src/ImageProcessor/ImageProperty.cs
  32. 12
      src/ImageProcessor/Settings.StyleCop
  33. 4
      src/ImageProcessor/packages.config
  34. 102
      tests/ImageProcessor.Tests/Colors/ColorTests.cs
  35. 1
      tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj
  36. 1
      tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj.DotSettings

51
src/ImageProcessor/Common/Extensions/ByteExtensions.cs

@ -0,0 +1,51 @@
namespace ImageProcessor
{
using System;
internal static class ByteExtensions
{
/// <summary>
/// Converts a byte array to a new array where each value in the original array is represented
/// by a the specified number of bits.
/// </summary>
/// <param name="bytes">The bytes to convert from. Cannot be null.</param>
/// <param name="bits">The number of bits per value.</param>
/// <returns>The resulting <see cref="T:byte[]"/> array. Is never null.</returns>
/// <exception cref="ArgumentNullException"><paramref name="bytes"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="bits"/> is less than or equals than zero.</exception>
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;
}
}
}

36
src/ImageProcessor/Common/Extensions/ComparableExtensions.cs

@ -0,0 +1,36 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ComparableExtensions.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Extension methods for classes that implement <see cref="IComparable{T}" />.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor
{
using System;
/// <summary>
/// Extension methods for classes that implement <see cref="IComparable{T}"/>.
/// </summary>
internal static class ComparableExtensions
{
/// <summary>
/// Returns value indicating whether the given number is with in the minimum and maximum
/// given range.
/// </summary>
/// <param name="value">The value to to check. </param>
/// <param name="min">The minimum range value.</param>
/// <param name="max">The maximum range value.</param>
/// <typeparam name="T">The <see cref="System.Type"/> to test.</typeparam>
/// <returns>
/// True if the value falls within the maximum and minimum; otherwise, false.
/// </returns>
public static bool IsBetween<T>(this T value, T min, T max) where T : IComparable<T>
{
return (value.CompareTo(min) > 0) && (value.CompareTo(max) < 0);
}
}
}

119
src/ImageProcessor/Common/Helpers/Guard.cs

@ -0,0 +1,119 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Guard.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Provides methods to protect against invalid parameters.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor
{
using System;
using System.Globalization;
/// <summary>
/// Provides methods to protect against invalid parameters.
/// </summary>
internal static class Guard
{
/// <summary>
/// Verifies, that the method parameter with specified object value is not null
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="target">
/// The target object, which cannot be null.
/// </param>
/// <param name="parameterName">
/// The name of the parameter that is to be checked.
/// </param>
/// <param name="message">
/// The error message, if any to add to the exception.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="target"/> is null
/// </exception>
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);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="target">The target string, which should be checked against being null or empty.</param>
/// <param name="parameterName">Name of the parameter.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="target"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is
/// empty or contains only blanks.
/// </exception>
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);
}
}
/// <summary>
/// Verifies that the specified value is less than a maximum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
public static void LessThan<TValue>(TValue value, TValue max, string parameterName) where TValue : IComparable<TValue>
{
if (value.CompareTo(max) >= 0)
{
throw new ArgumentOutOfRangeException(
parameterName,
string.Format(CultureInfo.CurrentCulture, "Value must be less than {0}", max));
}
}
/// <summary>
/// Verifies that the specified value is greater than a minimum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
public static void GreaterThan<TValue>(TValue value, TValue min, string parameterName) where TValue : IComparable<TValue>
{
if (value.CompareTo(min) <= 0)
{
throw new ArgumentOutOfRangeException(
parameterName,
string.Format(CultureInfo.CurrentCulture, "Value must be greater than {0}", min));
}
}
}
}

34
src/ImageProcessor/Common/Helpers/Utils.cs

@ -0,0 +1,34 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Utils.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// General utility methods.
// TODO: I don't like having classes like this as they turn into a dumping ground. Investigate method usage.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor
{
/// <summary>
/// General utility methods.
/// TODO: I don't like having classes like this as they turn into a dumping ground. Investigate method usage.
/// </summary>
internal static class Utils
{
/// <summary>
/// Swaps two references.
/// </summary>
/// <typeparam name="TRef">The type of the references to swap.</typeparam>
/// <param name="lhs">The first reference.</param>
/// <param name="rhs">The second reference.</param>
public static void Swap<TRef>(ref TRef lhs, ref TRef rhs) where TRef : class
{
TRef tmp = lhs;
lhs = rhs;
rhs = tmp;
}
}
}

42
src/ImageProcessor/Formats/Gif/DisposalMethod.cs

@ -0,0 +1,42 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DisposalMethod.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Provides enumeration for instructing the decoder what to do with the last image
// in an animation sequence.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// Provides enumeration for instructing the decoder what to do with the last image
/// in an animation sequence.
/// </summary>
public enum DisposalMethod
{
/// <summary>
/// No disposal specified. The decoder is not required to take any action.
/// </summary>
Unspecified = 0,
/// <summary>
/// Do not dispose. The graphic is to be left in place.
/// </summary>
NotDispose = 1,
/// <summary>
/// Restore to background color. The area used by the graphic must be restored to
/// the background color.
/// </summary>
RestoreToBackground = 2,
/// <summary>
/// Restore to previous. The decoder is required to restore the area overwritten by the
/// graphic with what was there prior to rendering the graphic.
/// </summary>
RestoreToPrevious = 3
}
}

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

49
src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs

@ -0,0 +1,49 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GifGraphicsControlExtension.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The Graphic Control Extension contains parameters used when
// processing a graphic rendering block.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// The Graphic Control Extension contains parameters used when
/// processing a graphic rendering block.
/// </summary>
internal sealed class GifGraphicsControlExtension
{
/// <summary>
/// Gets or sets the disposal method which indicates the way in which the
/// graphic is to be treated after being displayed.
/// </summary>
public DisposalMethod DisposalMethod { get; set; }
/// <summary>
/// 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.)
/// </summary>
public bool TransparencyFlag { get; set; }
/// <summary>
/// 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.
/// </summary>
public int TransparencyIndex { get; set; }
/// <summary>
/// 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.
/// </summary>
public int DelayTime { get; set; }
}
}

72
src/ImageProcessor/Formats/Gif/GifImageDescriptor.cs

@ -0,0 +1,72 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GifImageDescriptor.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// 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.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// 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.
/// </summary>
internal sealed class GifImageDescriptor
{
/// <summary>
/// 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.
/// </summary>
public short Left { get; set; }
/// <summary>
/// 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.
/// </summary>
public short Top { get; set; }
/// <summary>
/// Gets or sets the width of the image in pixels.
/// </summary>
public short Width { get; set; }
/// <summary>
/// Gets or sets the height of the image in pixels.
/// </summary>
public short Height { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the presence of a Local Color Table immediately
/// follows this Image Descriptor.
/// </summary>
public bool LocalColorTableFlag { get; set; }
/// <summary>
/// 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.
/// </summary>
public int LocalColorTableSize { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the image is to be interlaced.
/// An image is interlaced in a four-pass interlace pattern.
/// </summary>
public bool InterlaceFlag { get; set; }
}
}

57
src/ImageProcessor/Formats/Gif/GifLogicalScreenDescriptor.cs

@ -0,0 +1,57 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GifLogicalScreenDescriptor.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The Logical Screen Descriptor contains the parameters
// necessary to define the area of the display device
// within which the images will be rendered
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// The Logical Screen Descriptor contains the parameters
/// necessary to define the area of the display device
/// within which the images will be rendered
/// </summary>
internal sealed class GifLogicalScreenDescriptor
{
/// <summary>
/// Gets or sets the width, in pixels, of the Logical Screen where the images will
/// be rendered in the displaying device.
/// </summary>
public short Width { get; set; }
/// <summary>
/// Gets or sets the height, in pixels, of the Logical Screen where the images will be
/// rendered in the displaying device.
/// </summary>
public short Height { get; set; }
/// <summary>
/// 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.
/// </summary>
public byte Background { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool GlobalColorTableFlag { get; set; }
/// <summary>
/// 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.
/// </summary>
public int GlobalColorTableSize { get; set; }
}
}

270
src/ImageProcessor/Formats/Gif/LzwDecoder.cs

@ -0,0 +1,270 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LzwDecoder.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Decompresses data using the LZW algorithms.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
using System;
using System.IO;
/// <summary>
/// Decompresses data using the LZW algorithms.
/// </summary>
internal sealed class LzwDecoder
{
/// <summary>
/// The stack size.
/// </summary>
private const int StackSize = 4096;
/// <summary>
/// The null code.
/// </summary>
private const int NullCode = -1;
/// <summary>
/// The stream.
/// </summary>
private readonly Stream stream;
/// <summary>
/// Initializes a new instance of the <see cref="LzwDecoder"/> class
/// and sets the stream, where the compressed data should be read from.
/// </summary>
/// <param name="stream">The stream. where to read from.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null
/// (Nothing in Visual Basic).</exception>
public LzwDecoder(Stream stream)
{
Guard.NotNull(stream, "stream");
this.stream = stream;
}
/// <summary>
/// Decodes and decompresses all pixel indices from the stream.
/// </summary>
/// <param name="width">The width of the pixel index array.</param>
/// <param name="height">The height of the pixel index array.</param>
/// <param name="dataSize">Size of the data.</param>
/// <returns>The decoded and uncompressed array.</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <returns>
/// The <see cref="T:byte[]"/>.
/// </returns>
private byte[] ReadBlock()
{
int blockSize = this.stream.ReadByte();
return this.ReadBytes(blockSize);
}
/// <summary>
/// Reads the specified number of bytes from the data stream.
/// </summary>
/// <param name="length">
/// The number of bytes to read.
/// </param>
/// <returns>
/// The <see cref="T:byte[]"/>.
/// </returns>
private byte[] ReadBytes(int length)
{
byte[] buffer = new byte[length];
this.stream.Read(buffer, 0, length);
return buffer;
}
}
}

53
src/ImageProcessor/Formats/IImageDecoder.cs

@ -0,0 +1,53 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IImageDecoder.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates properties and methods required for decoding an image from a stream.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
using System.IO;
/// <summary>
/// Encapsulates properties and methods required for decoding an image from a stream.
/// </summary>
public interface IImageDecoder
{
/// <summary>
/// Gets the size of the header for this image type.
/// </summary>
/// <value>The size of the header.</value>
int HeaderSize { get; }
/// <summary>
/// Returns a value indicating whether the <see cref="IImageDecoder"/> supports the specified
/// file header.
/// </summary>
/// <param name="extension">The <see cref="string"/> containing the file extension.</param>
/// <returns>
/// True if the decoder supports the file extension; otherwise, false.
/// </returns>
bool IsSupportedFileExtension(string extension);
/// <summary>
/// Returns a value indicating whether the <see cref="IImageDecoder"/> supports the specified
/// file header.
/// </summary>
/// <param name="header">The <see cref="T:byte[]"/> containing the file header.</param>
/// <returns>
/// True if the decoder supports the file header; otherwise, false.
/// </returns>
bool IsSupportedFileFormat(byte[] header);
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase"/>.
/// </summary>
/// <param name="image">The <see cref="ImageBase"/> to decode to.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
void Decode(Image image, Stream stream);
}
}

47
src/ImageProcessor/Formats/IImageEncoder.cs

@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IImageEncoder.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates properties and methods required for decoding an image to a stream.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
using System.IO;
/// <summary>
/// Encapsulates properties and methods required for encoding an image to a stream.
/// </summary>
public interface IImageEncoder
{
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
int Quality { get; set; }
/// <summary>
/// Gets the default file extension for this encoder.
/// </summary>
string Extension { get; }
/// <summary>
/// Returns a value indicating whether the <see cref="IImageEncoder"/> supports the specified
/// file header.
/// </summary>
/// <param name="extension">The <see cref="string"/> containing the file extension.</param>
/// <returns>
/// True if the decoder supports the file extension; otherwise, false.
/// </returns>
bool IsSupportedFileExtension(string extension);
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageBase"/>.
/// </summary>
/// <param name="image">The <see cref="ImageBase"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
void Encode(ImageBase image, Stream stream);
}
}

9
src/ImageProcessor/Formats/IImageFormat.cs

@ -0,0 +1,9 @@
namespace ImageProcessor.Encoders
{
public interface IImageFormat
{
IImageEncoder Encoder { get; }
IImageDecoder Decoder { get; }
}
}

83
src/ImageProcessor/Formats/Png/GrayscaleReader.cs

@ -0,0 +1,83 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GrayscaleReader.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Color reader for reading grayscale colors from a png file.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// Color reader for reading grayscale colors from a png file.
/// </summary>
public sealed class GrayscaleReader : IColorReader
{
/// <summary>
/// Whether t also read the alpha channel.
/// </summary>
private readonly bool useAlpha;
/// <summary>
/// The current row.
/// </summary>
private int row;
/// <summary>
/// Initializes a new instance of the <see cref="GrayscaleReader"/> class.
/// </summary>
/// <param name="useAlpha">
/// If set to <c>true</c> the color reader will also read the
/// alpha channel from the scanline.
/// </param>
public GrayscaleReader(bool useAlpha)
{
this.useAlpha = useAlpha;
}
/// <summary>
/// Reads the specified scanline.
/// </summary>
/// <param name="scanline">The scanline.</param>
/// <param name="pixels">The pixels, where the colors should be stored in RGBA format.</param>
/// <param name="header">
/// The header, which contains information about the png file, like
/// the width of the image and the height.
/// </param>
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++;
}
}
}

31
src/ImageProcessor/Formats/Png/IColorReader.cs

@ -0,0 +1,31 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IColorReader.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates methods for color readers, which are responsible for reading
// different color formats from a png file.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// Encapsulates methods for color readers, which are responsible for reading
/// different color formats from a png file.
/// </summary>
public interface IColorReader
{
/// <summary>
/// Reads the specified scanline.
/// </summary>
/// <param name="scanline">The scanline.</param>
/// <param name="pixels">The pixels, where the colors should be stored in RGBA format.</param>
/// <param name="header">
/// The header, which contains information about the png file, like
/// the width of the image and the height.
/// </param>
void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header);
}
}

95
src/ImageProcessor/Formats/Png/PaletteIndexReader.cs

@ -0,0 +1,95 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PaletteIndexReader.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// A color reader for reading palette indices from the png file.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// A color reader for reading palette indices from the png file.
/// </summary>
public sealed class PaletteIndexReader : IColorReader
{
/// <summary>
/// The palette.
/// </summary>
private readonly byte[] palette;
/// <summary>
/// The alpha palette.
/// </summary>
private readonly byte[] paletteAlpha;
/// <summary>
/// The current row.
/// </summary>
private int row;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteIndexReader"/> class.
/// </summary>
/// <param name="palette">The palette as simple byte array. It will contains 3 values for each
/// color, which represents the red-, the green- and the blue channel.</param>
/// <param name="paletteAlpha">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.</param>
public PaletteIndexReader(byte[] palette, byte[] paletteAlpha)
{
this.palette = palette;
this.paletteAlpha = paletteAlpha;
}
/// <summary>
/// Reads the specified scanline.
/// </summary>
/// <param name="scanline">The scanline.</param>
/// <param name="pixels">The pixels, where the colors should be stored in RGBA format.</param>
/// <param name="header">The header, which contains information about the png file, like
/// the width of the image and the height.</param>
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++;
}
}
}

44
src/ImageProcessor/Formats/Png/PngChunk.cs

@ -0,0 +1,44 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PngChunk.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Stores header information about a chunk.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// Stores header information about a chunk.
/// </summary>
internal sealed class PngChunk
{
/// <summary>
/// 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
/// </summary>
public int Length { get; set; }
/// <summary>
/// Gets or sets the chunk type as string with 4 chars.
/// </summary>
public string Type { get; set; }
/// <summary>
/// Gets or sets the data bytes appropriate to the chunk type, if any.
/// This field can be of zero length.
/// </summary>
public byte[] Data { get; set; }
/// <summary>
/// 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
/// </summary>
public uint Crc { get; set; }
}
}

67
src/ImageProcessor/Formats/Png/PngChunkTypes.cs

@ -0,0 +1,67 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PngChunkTypes.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Contains a list of possible chunk type identifiers.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// Contains a list of possible chunk type identifiers.
/// </summary>
internal static class PngChunkTypes
{
/// <summary>
/// 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.
/// </summary>
public const string Header = "IHDR";
/// <summary>
/// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
/// series in the RGB format.
/// </summary>
public const string Palette = "PLTE";
/// <summary>
/// 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.
/// </summary>
public const string Data = "IDAT";
/// <summary>
/// This chunk must appear last. It marks the end of the PNG data stream.
/// The chunk's data field is empty.
/// </summary>
public const string End = "IEND";
/// <summary>
/// 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).
/// </summary>
public const string PaletteAlpha = "tRNS";
/// <summary>
/// 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.
/// </summary>
public const string Text = "tEXt";
/// <summary>
/// This chunk specifies the relationship between the image samples and the desired
/// display output intensity.
/// </summary>
public const string Gamma = "gAMA";
/// <summary>
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
/// </summary>
public const string Physical = "pHYs";
}
}

66
src/ImageProcessor/Formats/Png/PngColorTypeInformation.cs

@ -0,0 +1,66 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PngColorTypeInformation.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Contains information that are required when loading a png with a specific color type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
using System;
/// <summary>
/// Contains information that are required when loading a png with a specific color type.
/// </summary>
internal sealed class PngColorTypeInformation
{
/// <summary>
/// Initializes a new instance of the <see cref="PngColorTypeInformation"/> class with
/// the scanline factory, the function to create the color reader and the supported bit depths.
/// </summary>
/// <param name="scanlineFactor">The scanline factor.</param>
/// <param name="supportedBitDepths">The supported bit depths.</param>
/// <param name="scanlineReaderFactory">The factory to create the color reader.</param>
public PngColorTypeInformation(int scanlineFactor, int[] supportedBitDepths, Func<byte[], byte[], IColorReader> scanlineReaderFactory)
{
this.ChannelsPerColor = scanlineFactor;
this.ScanlineReaderFactory = scanlineReaderFactory;
this.SupportedBitDepths = supportedBitDepths;
}
/// <summary>
/// Gets an array with the bit depths that are supported for the color type
/// where this object is created for.
/// </summary>
/// <value>The supported bit depths that can be used in combination with this color type.</value>
public int[] SupportedBitDepths { get; private set; }
/// <summary>
/// Gets a function that is used the create the color reader for the color type where
/// this object is created for.
/// </summary>
/// <value>The factory function to create the color type.</value>
public Func<byte[], byte[], IColorReader> ScanlineReaderFactory { get; private set; }
/// <summary>
/// Gets a factor that is used when iterating through the scan lines.
/// </summary>
/// <value>The scanline factor.</value>
public int ChannelsPerColor { get; private set; }
/// <summary>
/// Creates the color reader for the color type where this object is create for.
/// </summary>
/// <param name="palette">The palette of the image. Can be null when no palette is used.</param>
/// <param name="paletteAlpha">The alpha palette of the image. Can be null when
/// no palette is used for the image or when the image has no alpha.</param>
/// <returns>The color reader for the image.</returns>
public IColorReader CreateColorReader(byte[] palette, byte[] paletteAlpha)
{
return this.ScanlineReaderFactory(palette, paletteAlpha);
}
}
}

98
src/ImageProcessor/Formats/Png/PngDecoder.cs

@ -0,0 +1,98 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PngDecoder.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encoder for generating a image out of a png stream.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
using System;
using System.IO;
/// <summary>
/// Encoder for generating a image out of a png stream.
/// </summary>
/// <remarks>
/// At the moment the following features are supported:
/// <para>
/// <b>Filters:</b> all filters are supported.
/// </para>
/// <para>
/// <b>Pixel formats:</b>
/// <list type="bullet">
/// <item>RGBA (True color) with alpha (8 bit).</item>
/// <item>RGB (True color) without alpha (8 bit).</item>
/// <item>Greyscale with alpha (8 bit).</item>
/// <item>Greyscale without alpha (8 bit).</item>
/// <item>Palette Index with alpha (8 bit).</item>
/// <item>Palette Index without alpha (8 bit).</item>
/// </list>
/// </para>
/// </remarks>
public class PngDecoder : IImageDecoder
{
/// <summary>
/// Gets the size of the header for this image type.
/// </summary>
/// <value>The size of the header.</value>
public int HeaderSize
{
get
{
return 8;
}
}
/// <summary>
/// Returns a value indicating whether the <see cref="IImageDecoder"/> supports the specified
/// file header.
/// </summary>
/// <param name="extension">The <see cref="string"/> containing the file extension.</param>
/// <returns>
/// True if the decoder supports the file extension; otherwise, false.
/// </returns>
public bool IsSupportedFileExtension(string extension)
{
Guard.NotNullOrEmpty(extension, "extension");
extension = extension.StartsWith(".") ? extension.Substring(1) : extension;
return extension.Equals("PNG", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Returns a value indicating whether the <see cref="IImageDecoder"/> supports the specified
/// file header.
/// </summary>
/// <param name="header">The <see cref="T:byte[]"/> containing the file header.</param>
/// <returns>
/// True if the decoder supports the file header; otherwise, false.
/// </returns>
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
}
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase"/>.
/// </summary>
/// <param name="image">The <see cref="ImageBase"/> to decode to.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public void Decode(Image image, Stream stream)
{
new PngDecoderCore().Decode(image, stream);
}
}
}

554
src/ImageProcessor/Formats/Png/PngDecoderCore.cs

@ -0,0 +1,554 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PngDecoderCore.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Performs the png decoding operation.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
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;
/// <summary>
/// Performs the png decoding operation.
/// </summary>
internal class PngDecoderCore
{
/// <summary>
/// The maximum chunk size.
/// </summary>
private const int MaxChunkSize = 1024 * 1024;
/// <summary>
/// The dictionary of available color types.
/// </summary>
private static readonly Dictionary<int, PngColorTypeInformation> ColorTypes
= new Dictionary<int, PngColorTypeInformation>();
/// <summary>
/// The image to decode.
/// </summary>
private Image currentImage;
/// <summary>
/// The stream to decode to.
/// </summary>
private Stream currentStream;
/// <summary>
/// The png header.
/// </summary>
private PngHeader header;
/// <summary>
/// Initializes static members of the <see cref="PngDecoderCore"/> class.
/// </summary>
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)));
}
/// <summary>
/// Decodes the stream to the image.
/// </summary>
/// <param name="image">The image to decode to.</param>
/// <param name="stream">The stream containing image data. </param>
/// <exception cref="ImageFormatException">
/// Thrown if the stream does not contain and end chunk.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the image is larger than the maximum allowable size.
/// </exception>
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);
}
}
/// <summary>
/// Reads the data chunk containing physical dimension data.
/// </summary>
/// <param name="data">The data containing physical data.</param>
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;
}
/// <summary>
/// Calculates the scanline length.
/// </summary>
/// <param name="colorTypeInformation">The color type information.</param>
/// <returns>The <see cref="int"/> representing the length.</returns>
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;
}
/// <summary>
/// Calculates a scanline step.
/// </summary>
/// <param name="colorTypeInformation">The color type information.</param>
/// <returns>The <see cref="int"/> representing the length of each step.</returns>
private int CalculateScanlineStep(PngColorTypeInformation colorTypeInformation)
{
int scanlineStep = 1;
if (this.header.BitDepth >= 8)
{
scanlineStep = (colorTypeInformation.ChannelsPerColor * this.header.BitDepth) / 8;
}
return scanlineStep;
}
/// <summary>
/// Reads the scanlines within the image.
/// </summary>
/// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param>
/// <param name="pixels">
/// The <see cref="T:byte[]"/> containing pixel data.</param>
/// <param name="colorReader">The color reader.</param>
/// <param name="colorTypeInformation">The color type information.</param>
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);
}
}
}
}
}
/// <summary>
/// TODO: Document this
/// </summary>
/// <param name="a">The a.</param>
/// <param name="b">The b.</param>
/// <param name="c">The c.</param>
/// <returns>
/// The <see cref="byte"/>.
/// </returns>
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;
}
/// <summary>
/// Reads a text chunk containing image properties from the data.
/// </summary>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
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));
}
/// <summary>
/// Reads a header chunk from the data.
/// </summary>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
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];
}
/// <summary>
/// Validates the png header.
/// </summary>
/// <exception cref="ImageFormatException">
/// Thrown if the image does pass validation.
/// </exception>
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.");
}
}
/// <summary>
/// Reads a chunk from the stream.
/// </summary>
/// <returns>
/// The <see cref="PngChunk"/>.
/// </returns>
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;
}
/// <summary>
/// Reads the cycle redundancy chunk from the data.
/// </summary>
/// <param name="chunk">The chunk.</param>
/// <param name="typeBuffer">The type buffer.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid or corrupt.
/// </exception>
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!");
}
}
/// <summary>
/// Reads the chunk data from the stream.
/// </summary>
/// <param name="chunk">The chunk.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the chunk length exceeds the maximum allowable size.
/// </exception>
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);
}
/// <summary>
/// Identifies the chunk type from the chunk.
/// </summary>
/// <param name="chunk">The chunk.</param>
/// <returns>
/// The <see cref="T:byte[]"/> containing identifying information.
/// </returns>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
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;
}
/// <summary>
/// Calculates the length of the given chunk.
/// </summary>
/// <param name="chunk">he chunk.</param>
/// <returns>
/// The <see cref="int"/> representing the chunk length.
/// </returns>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
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;
}
}
}

488
src/ImageProcessor/Formats/Png/PngEncoder.cs

@ -0,0 +1,488 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PngEncoder.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Image encoder for writing image data to a stream in png format.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
using System;
using System.IO;
using ICSharpCode.SharpZipLib.Checksums;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
/// <summary>
/// Image encoder for writing image data to a stream in png format.
/// </summary>
public class PngEncoder : IImageEncoder
{
/// <summary>
/// The maximum block size.
/// </summary>
private const int MaxBlockSize = 0xFFFF;
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoder"/> class.
/// </summary>
public PngEncoder()
{
this.Gamma = 2.2f;
}
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
/// <remarks>Png is a lossless format so this is not used in this encoder.</remarks>
public int Quality { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this encoder
/// will write the image uncompressed the stream.
/// </summary>
/// <value>
/// <c>true</c> if the image should be written uncompressed to
/// the stream; otherwise, <c>false</c>.
/// </value>
public bool IsWritingUncompressed { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is writing
/// gamma information to the stream. The default value is false.
/// </summary>
/// <value>
/// <c>true</c> if this instance is writing gamma
/// information to the stream.; otherwise, <c>false</c>.
/// </value>
public bool IsWritingGamma { get; set; }
/// <summary>
/// Gets or sets the gamma value, that will be written
/// the the stream, when the <see cref="IsWritingGamma"/> property
/// is set to true. The default value is 2.2f.
/// </summary>
/// <value>The gamma value of the image.</value>
public double Gamma { get; set; }
/// <summary>
/// Gets the default file extension for this encoder.
/// </summary>
/// <value>The default file extension for this encoder.</value>
public string Extension
{
get { return "PNG"; }
}
/// <summary>
/// Indicates if the image encoder supports the specified
/// file extension.
/// </summary>
/// <param name="extension">The file extension.</param>
/// <returns><c>true</c>, if the encoder supports the specified
/// extensions; otherwise <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="extension"/>
/// is null (Nothing in Visual Basic).</exception>
/// <exception cref="ArgumentException"><paramref name="extension"/> is a string
/// of length zero or contains only blanks.</exception>
public bool IsSupportedFileExtension(string extension)
{
Guard.NotNullOrEmpty(extension, "extension");
extension = extension.StartsWith(".") ? extension.Substring(1) : extension;
return extension.Equals("PNG", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Encodes the data of the specified image and writes the result to
/// the specified stream.
/// </summary>
/// <param name="image">The image, where the data should be get from.
/// Cannot be null (Nothing in Visual Basic).</param>
/// <param name="stream">The stream, where the image data should be written to.
/// Cannot be null (Nothing in Visual Basic).</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="image"/> is null (Nothing in Visual Basic).</para>
/// <para>- or -</para>
/// <para><paramref name="stream"/> is null (Nothing in Visual Basic).</para>
/// </exception>
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();
}
/// <summary>
/// Writes an integer to the byte array.
/// </summary>
/// <param name="data">The <see cref="T:byte[]"/> containing image data.</param>
/// <param name="offset">The amount to offset by.</param>
/// <param name="value">The value to write.</param>
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);
}
/// <summary>
/// Writes an integer to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="value">The value to write.</param>
private static void WriteInteger(Stream stream, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
stream.Write(buffer, 0, 4);
}
/// <summary>
/// Writes an unsigned integer to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="value">The value to write.</param>
private static void WriteInteger(Stream stream, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
stream.Write(buffer, 0, 4);
}
/// <summary>
/// Writes the physical dimension information to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="imageBase">The image base.</param>
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);
}
}
/// <summary>
/// Writes the gamma information to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
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);
}
}
/// <summary>
/// Writes the pixel information to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="imageBase">The image base.</param>
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);
}
}
/// <summary>
/// Writes the pixel information to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="imageBase">The image base.</param>
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);
}
}
/// <summary>
/// Writes the chunk end to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteEndChunk(Stream stream)
{
this.WriteChunk(stream, PngChunkTypes.End, null);
}
/// <summary>
/// Writes the header chunk to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="header">The <see cref="PngHeader"/>.</param>
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);
}
/// <summary>
/// Writes a chunk to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
private void WriteChunk(Stream stream, string type, byte[] data)
{
this.WriteChunk(stream, type, data, 0, data != null ? data.Length : 0);
}
/// <summary>
/// Writes a chunk of a specified length to the stream at the given offset.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
/// <param name="offset">The position to offset the data at.</param>
/// <param name="length">The of the data to write.</param>
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);
}
}
}

67
src/ImageProcessor/Formats/Png/PngHeader.cs

@ -0,0 +1,67 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PngHeader.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Represents the png header chunk.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// Represents the png header chunk.
/// </summary>
public sealed class PngHeader
{
/// <summary>
/// Gets or sets the dimension in x-direction of the image in pixels.
/// </summary>
public int Width { get; set; }
/// <summary>
/// Gets or sets the dimension in y-direction of the image in pixels.
/// </summary>
public int Height { get; set; }
/// <summary>
/// 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.
/// </summary>
public byte BitDepth { get; set; }
/// <summary>
/// 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).
/// </summary>
public byte ColorType { get; set; }
/// <summary>
/// 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.
/// </summary>
public byte CompressionMethod { get; set; }
/// <summary>
/// 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.
/// </summary>
public byte FilterMethod { get; set; }
/// <summary>
/// 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).
/// </summary>
public byte InterlaceMethod { get; set; }
}
}

81
src/ImageProcessor/Formats/Png/TrueColorReader.cs

@ -0,0 +1,81 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TrueColorReader.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// 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.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// 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.
/// </summary>
public sealed class TrueColorReader : IColorReader
{
/// <summary>
/// Whether t also read the alpha channel.
/// </summary>
private readonly bool useAlpha;
/// <summary>
/// The current row.
/// </summary>
private int row;
/// <summary>
/// Initializes a new instance of the <see cref="TrueColorReader"/> class.
/// </summary>
/// <param name="useAlpha">if set to <c>true</c> the color reader will also read the
/// alpha channel from the scanline.</param>
public TrueColorReader(bool useAlpha)
{
this.useAlpha = useAlpha;
}
/// <summary>
/// Reads the specified scanline.
/// </summary>
/// <param name="scanline">The scanline.</param>
/// <param name="pixels">The pixels, where the colors should be stored in RGBA format.</param>
/// <param name="header">The header, which contains information about the png file, like
/// the width of the image and the height.</param>
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++;
}
}
}

78
src/ImageProcessor/IImageBase.cs

@ -1,78 +0,0 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IImageBase.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates all the basic properties and methods required to manipulate images.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor
{
/// <summary>
/// Encapsulates all the basic properties and methods required to manipulate images.
/// </summary>
public interface IImageBase
{
/// <summary>
/// Gets the image pixels as byte array.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
byte[] Pixels { get; }
/// <summary>
/// Gets the width in pixels.
/// </summary>
int Width { get; }
/// <summary>
/// Gets the height in pixels.
/// </summary>
int Height { get; }
/// <summary>
/// Gets the pixel ratio made up of the width and height.
/// </summary>
double PixelRatio { get; }
/// <summary>
/// Gets the <see cref="Rectangle"/> representing the bounds of the image.
/// </summary>
Rectangle Bounds { get; }
/// <summary>
/// Gets or sets the color of a pixel at the specified position.
/// </summary>
/// <param name="x">
/// The x-coordinate of the pixel. Must be greater
/// than zero and smaller than the width of the pixel.
/// </param>
/// <param name="y">
/// The y-coordinate of the pixel. Must be greater
/// than zero and smaller than the width of the pixel.
/// </param>
/// <returns>The <see cref="Color"/> at the specified position.</returns>
Color this[int x, int y]
{
get;
set;
}
/// <summary>
/// Sets the pixel array of the image.
/// </summary>
/// <param name="width">
/// The new width of the image. Must be greater than zero.</param>
/// <param name="height">The new height of the image. Must be greater than zero.</param>
/// <param name="pixels">
/// The array with colors. Must be a multiple
/// of four, width and height.
/// </param>
void SetPixels(int width, int height, byte[] pixels);
}
}

299
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;
/// <summary>
/// Image class with stores the pixels and provides common functionality
/// such as loading images from files and streams or operation like resizing or cutting.
/// </summary>
/// <remarks>The image data is alway stored in RGBA format, where the red, the blue, the
/// alpha values are simple bytes.</remarks>
[DebuggerDisplay("Image: {PixelWidth}x{PixelHeight}")]
public class Image : ImageBase
{
#region Constants
/// <summary>
/// The default density value (dots per inch) in x direction. The default value is 75 dots per inch.
/// </summary>
public const double DefaultDensityX = 75;
/// <summary>
/// The default density value (dots per inch) in y direction. The default value is 75 dots per inch.
/// </summary>
public const double DefaultDensityY = 75;
private static readonly Lazy<List<IImageDecoder>> defaultDecoders = new Lazy<List<IImageDecoder>>(() => new List<IImageDecoder>
{
//new BmpDecoder(),
//new JpegDecoder(),
//new PngDecoder(),
new GifDecoder(),
});
private static readonly Lazy<List<IImageEncoder>> defaultEncoders = new Lazy<List<IImageEncoder>>(() => new List<IImageEncoder>
{
//new BmpEncoder(),
//new JpegEncoder(),
//new PngEncoder(),
});
/// <summary>
/// Gets a list of default decoders.
/// </summary>
public static IList<IImageDecoder> Decoders
{
get { return defaultDecoders.Value; }
}
/// <summary>
/// Gets a list of default encoders.
/// </summary>
public static IList<IImageEncoder> Encoders
{
get { return defaultEncoders.Value; }
}
#endregion
#region Fields
private readonly object _lockObject = new object();
#endregion
/// <summary>
/// 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.
/// </summary>
public int? DelayTime { get; set; }
#region Properties
/// <summary>
/// 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.
/// </summary>
/// <value>The density of the image in x direction.</value>
public double DensityX { get; set; }
/// <summary>
/// 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.
/// </summary>
/// <value>The density of the image in y direction.</value>
public double DensityY { get; set; }
/// <summary>
/// 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.
/// </summary>
/// <value>The width of the image in inches.</value>
public double InchWidth
{
get
{
double densityX = DensityX;
if (densityX <= 0)
{
densityX = DefaultDensityX;
}
return Width / densityX;
}
}
/// <summary>
/// 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.
/// </summary>
/// <value>The height of the image in inches.</value>
public double InchHeight
{
get
{
double densityY = DensityY;
if (densityY <= 0)
{
densityY = DefaultDensityY;
}
return Height / densityY;
}
}
/// <summary>
/// Gets a value indicating whether this image is animated.
/// </summary>
/// <value>
/// <c>true</c> if this image is animated; otherwise, <c>false</c>.
/// </value>
public bool IsAnimated
{
get { return _frames.Count > 0; }
}
private IList<ImageFrame> _frames = new List<ImageFrame>();
/// <summary>
/// Get the other frames for the animation.
/// </summary>
/// <value>The list of frame images.</value>
public IList<ImageFrame> Frames
{
get { return _frames; }
}
private IList<ImageProperty> _properties = new List<ImageProperty>();
/// <summary>
/// Gets the list of properties for storing meta information about this image.
/// </summary>
/// <value>A list of image properties.</value>
public IList<ImageProperty> Properties
{
get { return _properties; }
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
public Image(int width, int height)
: base(width, height)
{
DensityX = DefaultDensityX;
DensityY = DefaultDensityY;
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class
/// by making a copy from another image.
/// </summary>
/// <param name="other">The other image, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null
/// (Nothing in Visual Basic).</exception>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
public Image()
{
DensityX = DefaultDensityX;
DensityY = DefaultDensityY;
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
public Image(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
Load(stream, Decoders);
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
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<IImageDecoder> 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
}
}

53
src/ImageProcessor/ImageBase.cs

@ -14,11 +14,28 @@ namespace ImageProcessor
using System;
/// <summary>
/// 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.
/// </summary>
public abstract class ImageBase : IImageBase
public abstract class ImageBase
{
/// <summary>
/// The maximum allowable width in pixels.
/// </summary>
private static int maxWidth = int.MaxValue;
/// <summary>
/// The maximum allowable height in pixels.
/// </summary>
private static int maxHeight = int.MaxValue;
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase"/> class.
/// </summary>
protected ImageBase()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase"/> class.
/// </summary>
@ -73,6 +90,38 @@ namespace ImageProcessor
Array.Copy(pixels, this.Pixels, pixels.Length);
}
/// <summary>
/// Gets or sets the maximum allowable width in pixels.
/// </summary>
public static int MaxWidth
{
get
{
return maxWidth;
}
set
{
maxWidth = value;
}
}
/// <summary>
/// Gets or sets the maximum allowable height in pixels.
/// </summary>
public static int MaxHeight
{
get
{
return maxHeight;
}
set
{
maxHeight = value;
}
}
/// <summary>
/// Gets the image pixels as byte array.
/// </summary>

41
src/ImageProcessor/ImageFrame.cs

@ -0,0 +1,41 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ImageFrame.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Represents a single frame in a animation.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor
{
using System;
/// <summary>
/// Represents a single frame in a animation.
/// </summary>
public class ImageFrame : ImageBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame"/> class.
/// </summary>
public ImageFrame()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame"/> class.
/// </summary>
/// <param name="other">
/// The other <see cref="ImageBase"/> to create this instance from.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown if the given <see cref="ImageFrame"/> is null.
/// </exception>
public ImageFrame(ImageFrame other)
: base(other)
{
}
}
}

39
src/ImageProcessor/ImageProcessor.csproj

@ -35,21 +35,52 @@
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<Folder Include="Common\Extensions\" />
<Folder Include="Common\Helpers\" />
<Folder Include="Encoders\" />
<Folder Include="Filters\" />
</ItemGroup>
<ItemGroup>
<Compile Include="Common\Extensions\ByteExtensions.cs" />
<Compile Include="Common\Extensions\ComparableExtensions.cs" />
<Compile Include="Common\Helpers\Utils.cs" />
<Compile Include="Formats\Png\PngDecoder.cs" />
<Compile Include="Formats\Png\PngDecoderCore.cs" />
<Compile Include="Formats\Png\PngEncoder.cs" />
<Compile Include="Formats\Png\PngColorTypeInformation.cs" />
<Compile Include="Formats\Png\PngChunk.cs" />
<Compile Include="Formats\Png\PngChunkTypes.cs" />
<Compile Include="Formats\Png\TrueColorReader.cs" />
<Compile Include="Formats\Png\PaletteIndexReader.cs" />
<Compile Include="Formats\Png\GrayscaleReader.cs" />
<Compile Include="Formats\Png\IColorReader.cs" />
<Compile Include="Formats\Png\PngHeader.cs" />
<Compile Include="ImageProperty.cs" />
<Compile Include="ImageFrame.cs" />
<Compile Include="Colors\Color.cs" />
<Compile Include="Common\Exceptions\ImageFormatException.cs" />
<Compile Include="IImageBase.cs" />
<Compile Include="Common\Helpers\Guard.cs" />
<Compile Include="Formats\Gif\GifDecoder.cs" />
<Compile Include="Formats\Gif\LzwDecoder.cs" />
<Compile Include="Formats\Gif\GifLogicalScreenDescriptor.cs" />
<Compile Include="Formats\Gif\GifImageDescriptor.cs" />
<Compile Include="Formats\Gif\GifGraphicsControlExtension.cs" />
<Compile Include="Formats\Gif\DisposalMethod.cs" />
<Compile Include="Formats\IImageDecoder.cs" />
<Compile Include="Formats\IImageEncoder.cs" />
<Compile Include="Image.cs" />
<Compile Include="ImageBase.cs" />
<Compile Include="Numerics\Point.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Numerics\Rectangle.cs" />
<Compile Include="Numerics\Size.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="ICSharpCode.SharpZipLib.Portable, Version=0.86.0.51802, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\SharpZipLib.Portable.0.86.0.0002\lib\portable-net45+netcore45+wp8+win8+wpa81+MonoTouch+MonoAndroid\ICSharpCode.SharpZipLib.Portable.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

3
src/ImageProcessor/ImageProcessor.csproj.DotSettings

@ -4,4 +4,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common_005Cexceptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common_005Cextensions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common_005Chelpers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=encoders_005Cgif/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cgif/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cpng/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=numerics/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

167
src/ImageProcessor/ImageProperty.cs

@ -0,0 +1,167 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ImageProperty.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Stores meta information about a image, like the name of the author,
// the copyright information, the date, where the image was created
// or some other information.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor
{
using System;
/// <summary>
/// Stores meta information about a image, like the name of the author,
/// the copyright information, the date, where the image was created
/// or some other information.
/// </summary>
public struct ImageProperty : IEquatable<ImageProperty>
{
/// <summary>
/// The name of this <see cref="ImageProperty"/> indicating which kind of
/// information this property stores.
/// </summary>
/// <example>
/// Typical properties are the author, copyright
/// information or other meta information.
/// </example>
public string Name;
/// <summary>
/// The value of this <see cref="ImageProperty"/>.
/// </summary>
public string Value;
/// <summary>
/// Initializes a new instance of the <see cref="ImageProperty"/> struct.
/// </summary>
/// <param name="name">
/// The name of the property.
/// </param>
/// <param name="value">
/// The value of the property.
/// </param>
public ImageProperty(string name, string value)
{
this.Name = name;
this.Value = value;
}
/// <summary>
/// Compares two <see cref="ImageProperty"/> objects. The result specifies whether the values
/// of the <see cref="ImageProperty.Name"/> or <see cref="ImageProperty.Value"/> properties of the two
/// <see cref="ImageProperty"/> objects are equal.
/// </summary>
/// <param name="left">
/// The <see cref="ImageProperty"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="ImageProperty"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(ImageProperty left, ImageProperty right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="ImageProperty"/> objects. The result specifies whether the values
/// of the <see cref="ImageProperty.Name"/> or <see cref="ImageProperty.Value"/> properties of the two
/// <see cref="ImageProperty"/> objects are unequal.
/// </summary>
/// <param name="left">
/// The <see cref="ImageProperty"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="ImageProperty"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(ImageProperty left, ImageProperty right)
{
return !left.Equals(right);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <param name="obj">
/// The object to compare with the current instance.
/// </param>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the
/// same value; otherwise, false.
/// </returns>
public override bool Equals(object obj)
{
if (!(obj is ImageProperty))
{
return false;
}
ImageProperty other = (ImageProperty)obj;
return other.Name == this.Name && other.Value == this.Value;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
public override string ToString()
{
return "{Name=" + this.Name + ",Value=" + this.Value + "}";
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(ImageProperty other)
{
return this.Name.Equals(other.Name) && this.Value.Equals(other.Value);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="imageProperty">
/// The instance of <see cref="ImageProperty"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(ImageProperty imageProperty)
{
unchecked
{
int hashCode = imageProperty.Name.GetHashCode();
hashCode = (hashCode * 397) ^ imageProperty.Value.GetHashCode();
return hashCode;
}
}
}
}

12
src/ImageProcessor/Settings.StyleCop

@ -0,0 +1,12 @@
<StyleCopSettings Version="105">
<GlobalSettings>
<CollectionProperty Name="RecognizedWords">
<Value>EX</Value>
<Value>png</Value>
<Value>scanline</Value>
<Value>scanlines</Value>
<Value>tEXt</Value>
<Value>xt</Value>
</CollectionProperty>
</GlobalSettings>
</StyleCopSettings>

4
src/ImageProcessor/packages.config

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="SharpZipLib.Portable" version="0.86.0.0002" targetFramework="portable-net45+win+wp80" />
</packages>

102
tests/ImageProcessor.Tests/Colors/ColorTests.cs

@ -0,0 +1,102 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ColorTests.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Tests the <see cref="Color" /> struct.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Tests
{
using Xunit;
/// <summary>
/// Tests the <see cref="Color"/> struct.
/// </summary>
public class ColorTests
{
/// <summary>
/// Tests the equality operators for equality.
/// </summary>
[Fact]
public void AreEqual()
{
Color color1 = new Color(0, 0, 0, 255);
Color color2 = new Color(0, 0, 0, 255);
Color color3 = new Color("#000");
Color color4 = new Color("#000000");
Color color5 = new Color("#FF000000");
Color color6 = new Color(-16777216);
Assert.Equal(color1, color2);
Assert.Equal(color1, color3);
Assert.Equal(color1, color4);
Assert.Equal(color1, color5);
Assert.Equal(color1, color6);
}
/// <summary>
/// Tests the equality operators for inequality.
/// </summary>
[Fact]
public void AreNotEqual()
{
Color color1 = new Color(255, 0, 0, 255);
Color color2 = new Color(0, 0, 0, 255);
Color color3 = new Color("#000");
Color color4 = new Color("#000000");
Color color5 = new Color("#FF000000");
Color color6 = new Color(-16777216);
Assert.NotEqual(color1, color2);
Assert.NotEqual(color1, color3);
Assert.NotEqual(color1, color4);
Assert.NotEqual(color1, color5);
Assert.NotEqual(color1, color6);
}
/// <summary>
/// Tests whether the color constructor correctly assign properties.
/// </summary>
[Fact]
public void ConstructorAssignsProperties()
{
Color color1 = new Color(255, 10, 34, 220);
Assert.Equal(255, color1.B);
Assert.Equal(10, color1.G);
Assert.Equal(34, color1.R);
Assert.Equal(220, color1.A);
Color color2 = new Color(255, 10, 34);
Assert.Equal(255, color2.B);
Assert.Equal(10, color2.G);
Assert.Equal(34, color2.R);
Assert.Equal(255, color2.A);
Color color3 = new Color(-1);
Assert.Equal(255, color3.B);
Assert.Equal(255, color3.G);
Assert.Equal(255, color3.R);
Assert.Equal(255, color3.A);
Color color4 = new Color("#FF0000");
Assert.Equal(0, color4.B);
Assert.Equal(0, color4.G);
Assert.Equal(255, color4.R);
Assert.Equal(255, color4.A);
}
/// <summary>
/// Tests to see that in the input hex matches that of the output.
/// </summary>
[Fact]
public void ConvertHex()
{
const string First = "FF000000";
string second = new Color(0, 0, 0, 255).Bgra.ToString("X");
Assert.Equal(First, second);
}
}
}

1
tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj

@ -60,6 +60,7 @@
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="Colors\ColorTests.cs" />
<Compile Include="Numerics\RectangleTests.cs" />
<Compile Include="Numerics\PointTests.cs" />
<Compile Include="Numerics\SizeTests.cs" />

1
tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj.DotSettings

@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=colors/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=numerics/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Loading…
Cancel
Save