mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: da82a46559b9964bf02563bd0dd102bf68f8ff10 Former-commit-id: a71fb9608d926ad5fc48b6dc780a100641c00763 Former-commit-id: 4dabf320c3ef95ae3c27f437f7551c2a737c87ccaf/merge-core
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"> |
|||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=colors/@EntryIndexedValue">True</s:Boolean> |
|||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=numerics/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> |
|||
Loading…
Reference in new issue