mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: 28605ca292741e3de1e36caef5a4a14b5b425618 Former-commit-id: 45c538d36742adbd6487910e0fba74da1cccf30a Former-commit-id: 2d524bbb0d9835d5688c762b215a612cc8d9d530pull/17/head
36 changed files with 3611 additions and 84 deletions
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace ImageProcessor.Encoders |
||||
|
{ |
||||
|
public interface IImageFormat |
||||
|
{ |
||||
|
IImageEncoder Encoder { get; } |
||||
|
|
||||
|
IImageDecoder Decoder { get; } |
||||
|
} |
||||
|
} |
||||
@ -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++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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"; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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
|
||||
|
} |
||||
|
} |
||||
@ -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) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -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> |
||||
@ -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,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"> |
<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> |
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=numerics/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> |
||||
Loading…
Reference in new issue