mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: 085c4bc99388de35847b2b8dd26b20d518e272de Former-commit-id: e8b3af949ca0f63f001483188dbab20cd263c10f Former-commit-id: 8660e1a08e5da2bfebcc269d75ee20488542d4f6pull/1/head
21 changed files with 2160 additions and 33 deletions
@ -0,0 +1,132 @@ |
|||
// <copyright file="BitEncoder.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Formats |
|||
{ |
|||
using System.Collections.Generic; |
|||
|
|||
/// <summary>
|
|||
/// Handles the encoding of bits for compression.
|
|||
/// </summary>
|
|||
internal class BitEncoder |
|||
{ |
|||
/// <summary>
|
|||
/// The inner list for collecting the bits.
|
|||
/// </summary>
|
|||
private readonly List<byte> list = new List<byte>(); |
|||
|
|||
/// <summary>
|
|||
/// The current working bit.
|
|||
/// </summary>
|
|||
private int currentBit; |
|||
|
|||
/// <summary>
|
|||
/// The current value.
|
|||
/// </summary>
|
|||
private int currentValue; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BitEncoder"/> class.
|
|||
/// </summary>
|
|||
/// <param name="initial">
|
|||
/// The initial bits.
|
|||
/// </param>
|
|||
public BitEncoder(int initial) |
|||
{ |
|||
this.IntitialBit = initial; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the intitial bit.
|
|||
/// </summary>
|
|||
public int IntitialBit { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The number of bytes in the encoder.
|
|||
/// </summary>
|
|||
public int Length => this.list.Count; |
|||
|
|||
/// <summary>
|
|||
/// Adds the current byte to the end of the encoder.
|
|||
/// </summary>
|
|||
/// <param name="item">
|
|||
/// The byte to add.
|
|||
/// </param>
|
|||
public void Add(int item) |
|||
{ |
|||
this.currentValue |= item << this.currentBit; |
|||
|
|||
this.currentBit += this.IntitialBit; |
|||
|
|||
while (this.currentBit >= 8) |
|||
{ |
|||
byte value = (byte)(this.currentValue & 0XFF); |
|||
this.currentValue = this.currentValue >> 8; |
|||
this.currentBit -= 8; |
|||
this.list.Add(value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds the collection of bytes to the end of the encoder.
|
|||
/// </summary>
|
|||
/// <param name="collection">
|
|||
/// The collection of bytes to add.
|
|||
/// The collection itself cannot be null but can contain elements that are null.</param>
|
|||
public void AddRange(byte[] collection) |
|||
{ |
|||
this.list.AddRange(collection); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies a range of elements from the encoder to a compatible one-dimensional array,
|
|||
/// starting at the specified index of the target array.
|
|||
/// </summary>
|
|||
/// <param name="index">
|
|||
/// The zero-based index in the source <see cref="BitEncoder"/> at which copying begins.
|
|||
/// </param>
|
|||
/// <param name="array">
|
|||
/// The one-dimensional Array that is the destination of the elements copied
|
|||
/// from <see cref="BitEncoder"/>. The Array must have zero-based indexing
|
|||
/// </param>
|
|||
/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
|
|||
/// <param name="count">The number of bytes to copy.</param>
|
|||
public void CopyTo(int index, byte[] array, int arrayIndex, int count) |
|||
{ |
|||
this.list.CopyTo(index, array, arrayIndex, count); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes all the bytes from the encoder.
|
|||
/// </summary>
|
|||
public void Clear() |
|||
{ |
|||
this.list.Clear(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies the bytes into a new array.
|
|||
/// </summary>
|
|||
/// <returns><see cref="T:byte[]"/></returns>
|
|||
public byte[] ToArray() |
|||
{ |
|||
return this.list.ToArray(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The end.
|
|||
/// </summary>
|
|||
internal void End() |
|||
{ |
|||
while (this.currentBit > 0) |
|||
{ |
|||
byte value = (byte)(this.currentValue & 0XFF); |
|||
this.currentValue = this.currentValue >> 8; |
|||
this.currentBit -= 8; |
|||
this.list.Add(value); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// <copyright file="DisposalMethod.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Formats |
|||
{ |
|||
/// <summary>
|
|||
/// Provides enumeration for instructing the decoder what to do with the last image
|
|||
/// in an animation sequence.
|
|||
/// <see href="http://www.w3.org/Graphics/GIF/spec-gif89a.txt"/> section 23
|
|||
/// </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,83 @@ |
|||
// <copyright file="GifConstants.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Formats |
|||
{ |
|||
/// <summary>
|
|||
/// Constants that define specific points within a gif.
|
|||
/// </summary>
|
|||
internal sealed class GifConstants |
|||
{ |
|||
/// <summary>
|
|||
/// The file type.
|
|||
/// </summary>
|
|||
public const string FileType = "GIF"; |
|||
|
|||
/// <summary>
|
|||
/// The file version.
|
|||
/// </summary>
|
|||
public const string FileVersion = "89a"; |
|||
|
|||
/// <summary>
|
|||
/// The extension block introducer <value>!</value>.
|
|||
/// </summary>
|
|||
public const byte ExtensionIntroducer = 0x21; |
|||
|
|||
/// <summary>
|
|||
/// The graphic control label.
|
|||
/// </summary>
|
|||
public const byte GraphicControlLabel = 0xF9; |
|||
|
|||
/// <summary>
|
|||
/// The application extension label.
|
|||
/// </summary>
|
|||
public const byte ApplicationExtensionLabel = 0xFF; |
|||
|
|||
/// <summary>
|
|||
/// The application identification.
|
|||
/// </summary>
|
|||
public const string ApplicationIdentification = "NETSCAPE2.0"; |
|||
|
|||
/// <summary>
|
|||
/// The application block size.
|
|||
/// </summary>
|
|||
public const byte ApplicationBlockSize = 0x0b; |
|||
|
|||
/// <summary>
|
|||
/// The comment label.
|
|||
/// </summary>
|
|||
public const byte CommentLabel = 0xFE; |
|||
|
|||
/// <summary>
|
|||
/// The maximum comment length.
|
|||
/// </summary>
|
|||
public const int MaxCommentLength = 1024 * 8; |
|||
|
|||
/// <summary>
|
|||
/// The image descriptor label <value>,</value>.
|
|||
/// </summary>
|
|||
public const byte ImageDescriptorLabel = 0x2C; |
|||
|
|||
/// <summary>
|
|||
/// The plain text label.
|
|||
/// </summary>
|
|||
public const byte PlainTextLabel = 0x01; |
|||
|
|||
/// <summary>
|
|||
/// The image label introducer <value>,</value>.
|
|||
/// </summary>
|
|||
public const byte ImageLabel = 0x2C; |
|||
|
|||
/// <summary>
|
|||
/// The terminator.
|
|||
/// </summary>
|
|||
public const byte Terminator = 0; |
|||
|
|||
/// <summary>
|
|||
/// The end introducer trailer <value>;</value>.
|
|||
/// </summary>
|
|||
public const byte EndIntroducer = 0x3B; |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// <copyright file="GifDecoder.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Formats |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
/// <summary>
|
|||
/// Decoder for generating an image out of a gif encoded stream.
|
|||
/// </summary>
|
|||
public class GifDecoder : IImageDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the size of the header for this image type.
|
|||
/// </summary>
|
|||
/// <value>The size of the header.</value>
|
|||
public int HeaderSize => 6; |
|||
|
|||
/// <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, nameof(extension)); |
|||
|
|||
extension = extension.StartsWith(".") ? extension.Substring(1) : extension; |
|||
return extension.Equals("GIF", 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 >= 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
|
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Decode<T, TP>(Image<T, TP> image, Stream stream) |
|||
where T : IPackedVector<TP> |
|||
where TP : struct |
|||
{ |
|||
new GifDecoderCore<T, TP>().Decode(image, stream); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,426 @@ |
|||
// <copyright file="GifDecoderCore.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Formats |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
/// <summary>
|
|||
/// Performs the gif decoding operation.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The pixel format.</typeparam>
|
|||
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
|
|||
internal class GifDecoderCore<T, TP> |
|||
where T : IPackedVector<TP> |
|||
where TP : struct |
|||
{ |
|||
/// <summary>
|
|||
/// The image to decode the information to.
|
|||
/// </summary>
|
|||
private Image<T, TP> decodedImage; |
|||
|
|||
/// <summary>
|
|||
/// The currently loaded stream.
|
|||
/// </summary>
|
|||
private Stream currentStream; |
|||
|
|||
/// <summary>
|
|||
/// The global color table.
|
|||
/// </summary>
|
|||
private byte[] globalColorTable; |
|||
|
|||
/// <summary>
|
|||
/// The current frame.
|
|||
/// </summary>
|
|||
private T[] currentFrame; |
|||
|
|||
/// <summary>
|
|||
/// The logical screen descriptor.
|
|||
/// </summary>
|
|||
private GifLogicalScreenDescriptor logicalScreenDescriptor; |
|||
|
|||
/// <summary>
|
|||
/// The graphics control extension.
|
|||
/// </summary>
|
|||
private GifGraphicsControlExtension graphicsControlExtension; |
|||
|
|||
/// <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>
|
|||
public void Decode(Image<T, TP> image, Stream stream) |
|||
{ |
|||
this.decodedImage = image; |
|||
|
|||
this.currentStream = stream; |
|||
|
|||
// Skip the identifier
|
|||
this.currentStream.Seek(6, SeekOrigin.Current); |
|||
this.ReadLogicalScreenDescriptor(); |
|||
|
|||
if (this.logicalScreenDescriptor.GlobalColorTableFlag) |
|||
{ |
|||
this.globalColorTable = new byte[this.logicalScreenDescriptor.GlobalColorTableSize * 3]; |
|||
|
|||
// Read the global color table from the stream
|
|||
stream.Read(this.globalColorTable, 0, this.globalColorTable.Length); |
|||
} |
|||
|
|||
// Loop though the respective gif parts and read the data.
|
|||
int nextFlag = stream.ReadByte(); |
|||
while (nextFlag != GifConstants.Terminator) |
|||
{ |
|||
if (nextFlag == GifConstants.ImageLabel) |
|||
{ |
|||
this.ReadFrame(); |
|||
} |
|||
else if (nextFlag == GifConstants.ExtensionIntroducer) |
|||
{ |
|||
int label = stream.ReadByte(); |
|||
switch (label) |
|||
{ |
|||
case GifConstants.GraphicControlLabel: |
|||
this.ReadGraphicalControlExtension(); |
|||
break; |
|||
case GifConstants.CommentLabel: |
|||
this.ReadComments(); |
|||
break; |
|||
case GifConstants.ApplicationExtensionLabel: |
|||
this.Skip(12); // No need to read.
|
|||
break; |
|||
case GifConstants.PlainTextLabel: |
|||
this.Skip(13); // Not supported by any known decoder.
|
|||
break; |
|||
} |
|||
} |
|||
else if (nextFlag == GifConstants.EndIntroducer) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
nextFlag = stream.ReadByte(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the graphic control extension.
|
|||
/// </summary>
|
|||
private void ReadGraphicalControlExtension() |
|||
{ |
|||
byte[] buffer = new byte[6]; |
|||
|
|||
this.currentStream.Read(buffer, 0, buffer.Length); |
|||
|
|||
byte packed = buffer[1]; |
|||
|
|||
this.graphicsControlExtension = new GifGraphicsControlExtension |
|||
{ |
|||
DelayTime = BitConverter.ToInt16(buffer, 2), |
|||
TransparencyIndex = buffer[4], |
|||
TransparencyFlag = (packed & 0x01) == 1, |
|||
DisposalMethod = (DisposalMethod)((packed & 0x1C) >> 2) |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the image descriptor
|
|||
/// </summary>
|
|||
/// <returns><see cref="GifImageDescriptor"/></returns>
|
|||
private GifImageDescriptor ReadImageDescriptor() |
|||
{ |
|||
byte[] buffer = new byte[9]; |
|||
|
|||
this.currentStream.Read(buffer, 0, buffer.Length); |
|||
|
|||
byte packed = buffer[8]; |
|||
|
|||
GifImageDescriptor imageDescriptor = new GifImageDescriptor |
|||
{ |
|||
Left = BitConverter.ToInt16(buffer, 0), |
|||
Top = BitConverter.ToInt16(buffer, 2), |
|||
Width = BitConverter.ToInt16(buffer, 4), |
|||
Height = BitConverter.ToInt16(buffer, 6), |
|||
LocalColorTableFlag = ((packed & 0x80) >> 7) == 1, |
|||
LocalColorTableSize = 2 << (packed & 0x07), |
|||
InterlaceFlag = ((packed & 0x40) >> 6) == 1 |
|||
}; |
|||
|
|||
return imageDescriptor; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the logical screen descriptor.
|
|||
/// </summary>
|
|||
private void ReadLogicalScreenDescriptor() |
|||
{ |
|||
byte[] buffer = new byte[7]; |
|||
|
|||
this.currentStream.Read(buffer, 0, buffer.Length); |
|||
|
|||
byte packed = buffer[4]; |
|||
|
|||
this.logicalScreenDescriptor = new GifLogicalScreenDescriptor |
|||
{ |
|||
Width = BitConverter.ToInt16(buffer, 0), |
|||
Height = BitConverter.ToInt16(buffer, 2), |
|||
BackgroundColorIndex = buffer[5], |
|||
PixelAspectRatio = buffer[6], |
|||
GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1, |
|||
GlobalColorTableSize = 2 << (packed & 0x07) |
|||
}; |
|||
|
|||
if (this.logicalScreenDescriptor.GlobalColorTableSize > 255 * 4) |
|||
{ |
|||
throw new ImageFormatException( |
|||
$"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'"); |
|||
} |
|||
|
|||
if (this.logicalScreenDescriptor.Width > this.decodedImage.MaxWidth || this.logicalScreenDescriptor.Height > this.decodedImage.MaxHeight) |
|||
{ |
|||
throw new ArgumentOutOfRangeException( |
|||
$"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{this.decodedImage.MaxWidth}x{this.decodedImage.MaxHeight}'"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Skips the designated number of bytes in the stream.
|
|||
/// </summary>
|
|||
/// <param name="length">The number of bytes to skip.</param>
|
|||
private void Skip(int length) |
|||
{ |
|||
this.currentStream.Seek(length, SeekOrigin.Current); |
|||
|
|||
int flag; |
|||
|
|||
while ((flag = this.currentStream.ReadByte()) != 0) |
|||
{ |
|||
this.currentStream.Seek(flag, SeekOrigin.Current); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the gif comments.
|
|||
/// </summary>
|
|||
private void ReadComments() |
|||
{ |
|||
int flag; |
|||
|
|||
while ((flag = this.currentStream.ReadByte()) != 0) |
|||
{ |
|||
if (flag > GifConstants.MaxCommentLength) |
|||
{ |
|||
throw new ImageFormatException($"Gif comment length '{flag}' exceeds max '{GifConstants.MaxCommentLength}'"); |
|||
} |
|||
|
|||
byte[] buffer = new byte[flag]; |
|||
|
|||
this.currentStream.Read(buffer, 0, flag); |
|||
|
|||
this.decodedImage.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(buffer))); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads an individual gif frame.
|
|||
/// </summary>
|
|||
private void ReadFrame() |
|||
{ |
|||
GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); |
|||
|
|||
byte[] localColorTable = this.ReadFrameLocalColorTable(imageDescriptor); |
|||
|
|||
byte[] indices = this.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; |
|||
|
|||
this.ReadFrameColors(indices, colorTable, imageDescriptor); |
|||
|
|||
// Skip any remaining blocks
|
|||
this.Skip(0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the frame indices marking the color to use for each pixel.
|
|||
/// </summary>
|
|||
/// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param>
|
|||
/// <returns>The <see cref="T:byte[]"/></returns>
|
|||
private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor) |
|||
{ |
|||
int dataSize = this.currentStream.ReadByte(); |
|||
LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream); |
|||
|
|||
byte[] indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize); |
|||
|
|||
return indices; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the local color table from the current frame.
|
|||
/// </summary>
|
|||
/// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param>
|
|||
/// <returns>The <see cref="T:byte[]"/></returns>
|
|||
private byte[] ReadFrameLocalColorTable(GifImageDescriptor imageDescriptor) |
|||
{ |
|||
byte[] localColorTable = null; |
|||
|
|||
if (imageDescriptor.LocalColorTableFlag) |
|||
{ |
|||
localColorTable = new byte[imageDescriptor.LocalColorTableSize * 3]; |
|||
|
|||
this.currentStream.Read(localColorTable, 0, localColorTable.Length); |
|||
} |
|||
|
|||
return localColorTable; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the frames colors, mapping indices to colors.
|
|||
/// </summary>
|
|||
/// <param name="indices">The indexed pixels.</param>
|
|||
/// <param name="colorTable">The color table containing the available colors.</param>
|
|||
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
|
|||
private void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor) |
|||
{ |
|||
int imageWidth = this.logicalScreenDescriptor.Width; |
|||
int imageHeight = this.logicalScreenDescriptor.Height; |
|||
|
|||
if (this.currentFrame == null) |
|||
{ |
|||
this.currentFrame = new T[imageWidth * imageHeight]; |
|||
} |
|||
|
|||
T[] lastFrame = null; |
|||
|
|||
if (this.graphicsControlExtension != null && |
|||
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) |
|||
{ |
|||
lastFrame = new T[imageWidth * imageHeight]; |
|||
|
|||
Array.Copy(this.currentFrame, lastFrame, lastFrame.Length); |
|||
} |
|||
|
|||
int offset, i = 0; |
|||
int interlacePass = 0; // The interlace pass
|
|||
int interlaceIncrement = 8; // The interlacing line increment
|
|||
int interlaceY = 0; // The current interlaced line
|
|||
|
|||
for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) |
|||
{ |
|||
// Check if this image is interlaced.
|
|||
int writeY; // the target y offset to write to
|
|||
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 (interlaceY >= descriptor.Height) |
|||
{ |
|||
interlacePass++; |
|||
switch (interlacePass) |
|||
{ |
|||
case 1: |
|||
interlaceY = 4; |
|||
break; |
|||
case 2: |
|||
interlaceY = 2; |
|||
interlaceIncrement = 4; |
|||
break; |
|||
case 3: |
|||
interlaceY = 1; |
|||
interlaceIncrement = 2; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
writeY = interlaceY + descriptor.Top; |
|||
|
|||
interlaceY += interlaceIncrement; |
|||
} |
|||
else |
|||
{ |
|||
writeY = y; |
|||
} |
|||
|
|||
for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) |
|||
{ |
|||
offset = (writeY * imageWidth) + x; |
|||
int index = indices[i]; |
|||
|
|||
if (this.graphicsControlExtension == null || |
|||
this.graphicsControlExtension.TransparencyFlag == false || |
|||
this.graphicsControlExtension.TransparencyIndex != index) |
|||
{ |
|||
// Stored in r-> g-> b-> a order.
|
|||
int indexOffset = index * 3; |
|||
|
|||
T pixel = default(T); |
|||
pixel.PackBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); |
|||
this.currentFrame[offset] = pixel; |
|||
} |
|||
|
|||
i++; |
|||
} |
|||
} |
|||
|
|||
T[] pixels = new T[imageWidth * imageHeight]; |
|||
|
|||
Array.Copy(this.currentFrame, pixels, pixels.Length); |
|||
|
|||
ImageBase<T, TP> currentImage; |
|||
|
|||
if (this.decodedImage.Pixels == null) |
|||
{ |
|||
currentImage = this.decodedImage; |
|||
currentImage.SetPixels(imageWidth, imageHeight, pixels); |
|||
currentImage.Quality = colorTable.Length / 3; |
|||
|
|||
if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) |
|||
{ |
|||
this.decodedImage.FrameDelay = this.graphicsControlExtension.DelayTime; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
ImageFrame<T, TP> frame = new ImageFrame<T, TP>(); |
|||
|
|||
currentImage = frame; |
|||
currentImage.SetPixels(imageWidth, imageHeight, pixels); |
|||
currentImage.Quality = colorTable.Length / 3; |
|||
|
|||
if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) |
|||
{ |
|||
currentImage.FrameDelay = this.graphicsControlExtension.DelayTime; |
|||
} |
|||
|
|||
this.decodedImage.Frames.Add(frame); |
|||
} |
|||
|
|||
if (this.graphicsControlExtension != null) |
|||
{ |
|||
if (this.graphicsControlExtension.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; |
|||
|
|||
// Stored in r-> g-> b-> a order.
|
|||
this.currentFrame[offset] = default(T); |
|||
} |
|||
} |
|||
} |
|||
else if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) |
|||
{ |
|||
this.currentFrame = lastFrame; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
// <copyright file="GifEncoder.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Formats |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
using ImageProcessorCore.Quantizers; |
|||
|
|||
/// <summary>
|
|||
/// Image encoder for writing image data to a stream in gif format.
|
|||
/// </summary>
|
|||
public class GifEncoder : IImageEncoder |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the quality of output for images.
|
|||
/// </summary>
|
|||
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
|
|||
public int Quality { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the transparency threshold.
|
|||
/// </summary>
|
|||
public byte Threshold { get; set; } = 128; |
|||
|
|||
/// <summary>
|
|||
/// The quantizer for reducing the color count.
|
|||
/// </summary>
|
|||
public IQuantizer Quantizer { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Extension => "gif"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public string MimeType => "image/gif"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool IsSupportedFileExtension(string extension) |
|||
{ |
|||
Guard.NotNullOrEmpty(extension, nameof(extension)); |
|||
|
|||
extension = extension.StartsWith(".") ? extension.Substring(1) : extension; |
|||
return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Encode<T,TP>(ImageBase<T,TP> image, Stream stream) |
|||
where T : IPackedVector<TP> |
|||
where TP : struct |
|||
{ |
|||
GifEncoderCore encoder = new GifEncoderCore |
|||
{ |
|||
Quality = this.Quality, |
|||
Quantizer = this.Quantizer, |
|||
Threshold = this.Threshold |
|||
}; |
|||
|
|||
encoder.Encode(image, stream); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,313 @@ |
|||
// <copyright file="GifEncoderCore.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Formats |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
using IO; |
|||
using Quantizers; |
|||
|
|||
/// <summary>
|
|||
/// Performs the gif encoding operation.
|
|||
/// </summary>
|
|||
internal sealed class GifEncoderCore |
|||
{ |
|||
/// <summary>
|
|||
/// The number of bits requires to store the image palette.
|
|||
/// </summary>
|
|||
private int bitDepth; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the quality of output for images.
|
|||
/// </summary>
|
|||
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
|
|||
public int Quality { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the transparency threshold.
|
|||
/// </summary>
|
|||
public byte Threshold { get; set; } = 128; |
|||
|
|||
/// <summary>
|
|||
/// The quantizer for reducing the color count.
|
|||
/// </summary>
|
|||
public IQuantizer Quantizer { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image to the specified stream from the <see cref="ImageBase{T,TP}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The pixel format.</typeparam>
|
|||
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
|
|||
/// <param name="imageBase">The <see cref="ImageBase{T,TP}"/> to encode from.</param>
|
|||
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
|
|||
public void Encode<T, TP>(ImageBase<T, TP> imageBase, Stream stream) |
|||
where T : IPackedVector<TP> |
|||
where TP : struct |
|||
{ |
|||
Guard.NotNull(imageBase, nameof(imageBase)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
Image<T, TP> image = (Image<T, TP>)imageBase; |
|||
|
|||
if (this.Quantizer == null) |
|||
{ |
|||
this.Quantizer = new OctreeQuantizer<T, TP> { Threshold = this.Threshold }; |
|||
} |
|||
|
|||
// Do not use IDisposable pattern here as we want to preserve the stream.
|
|||
EndianBinaryWriter writer = new EndianBinaryWriter(EndianBitConverter.Little, stream); |
|||
|
|||
// Ensure that quality can be set but has a fallback.
|
|||
int quality = this.Quality > 0 ? this.Quality : imageBase.Quality; |
|||
this.Quality = quality > 0 ? quality.Clamp(1, 256) : 256; |
|||
|
|||
// Get the number of bits.
|
|||
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.Quality); |
|||
|
|||
// Quantize the image returning a palette.
|
|||
QuantizedImage<T, TP> quantized = ((IQuantizer<T, TP>)this.Quantizer).Quantize(image, this.Quality); |
|||
|
|||
// Write the header.
|
|||
this.WriteHeader(writer); |
|||
|
|||
// Write the LSD. We'll use local color tables for now.
|
|||
this.WriteLogicalScreenDescriptor(image, writer, quantized.TransparentIndex); |
|||
|
|||
// Write the first frame.
|
|||
this.WriteGraphicalControlExtension(imageBase, writer, quantized.TransparentIndex); |
|||
this.WriteImageDescriptor(image, writer); |
|||
this.WriteColorTable(quantized, writer); |
|||
this.WriteImageData(quantized, writer); |
|||
|
|||
// Write additional frames.
|
|||
if (image.Frames.Any()) |
|||
{ |
|||
this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count); |
|||
foreach (ImageFrame<T, TP> frame in image.Frames) |
|||
{ |
|||
QuantizedImage<T, TP> quantizedFrame = ((IQuantizer<T, TP>)this.Quantizer).Quantize(frame, this.Quality); |
|||
this.WriteGraphicalControlExtension(frame, writer, quantizedFrame.TransparentIndex); |
|||
this.WriteImageDescriptor(frame, writer); |
|||
this.WriteColorTable(quantizedFrame, writer); |
|||
this.WriteImageData(quantizedFrame, writer); |
|||
} |
|||
} |
|||
|
|||
// TODO: Write Comments extension etc
|
|||
writer.Write(GifConstants.EndIntroducer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the file header signature and version to the stream.
|
|||
/// </summary>
|
|||
/// <param name="writer">The writer to write to the stream with.</param>
|
|||
private void WriteHeader(EndianBinaryWriter writer) |
|||
{ |
|||
writer.Write((GifConstants.FileType + GifConstants.FileVersion).ToCharArray()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the logical screen descriptor to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The pixel format.</typeparam>
|
|||
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
|
|||
/// <param name="image">The image to encode.</param>
|
|||
/// <param name="writer">The writer to write to the stream with.</param>
|
|||
/// <param name="tranparencyIndex">The transparency index to set the default backgound index to.</param>
|
|||
private void WriteLogicalScreenDescriptor<T, TP>(Image<T, TP> image, EndianBinaryWriter writer, int tranparencyIndex) |
|||
where T : IPackedVector<TP> |
|||
where TP : struct |
|||
{ |
|||
GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor |
|||
{ |
|||
Width = (short)image.Width, |
|||
Height = (short)image.Height, |
|||
GlobalColorTableFlag = false, // Always false for now.
|
|||
GlobalColorTableSize = this.bitDepth - 1, |
|||
BackgroundColorIndex = (byte)(tranparencyIndex > -1 ? tranparencyIndex : 255) |
|||
}; |
|||
|
|||
writer.Write((ushort)descriptor.Width); |
|||
writer.Write((ushort)descriptor.Height); |
|||
|
|||
PackedField field = new PackedField(); |
|||
field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used)
|
|||
field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution
|
|||
field.SetBit(4, false); // 5 : GCT sort flag = 0
|
|||
field.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1)
|
|||
|
|||
// Reduce the number of writes
|
|||
byte[] arr = { |
|||
field.Byte, |
|||
descriptor.BackgroundColorIndex, // Background Color Index
|
|||
descriptor.PixelAspectRatio // Pixel aspect ratio. Assume 1:1
|
|||
}; |
|||
|
|||
writer.Write(arr); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the application exstension to the stream.
|
|||
/// </summary>
|
|||
/// <param name="writer">The writer to write to the stream with.</param>
|
|||
/// <param name="repeatCount">The animated image repeat count.</param>
|
|||
/// <param name="frames">Th number of image frames.</param>
|
|||
private void WriteApplicationExtension(EndianBinaryWriter writer, ushort repeatCount, int frames) |
|||
{ |
|||
// Application Extension Header
|
|||
if (repeatCount != 1 && frames > 0) |
|||
{ |
|||
byte[] ext = |
|||
{ |
|||
GifConstants.ExtensionIntroducer, |
|||
GifConstants.ApplicationExtensionLabel, |
|||
GifConstants.ApplicationBlockSize |
|||
}; |
|||
|
|||
writer.Write(ext); |
|||
|
|||
writer.Write(GifConstants.ApplicationIdentification.ToCharArray()); // NETSCAPE2.0
|
|||
writer.Write((byte)3); // Application block length
|
|||
writer.Write((byte)1); // Data sub-block index (always 1)
|
|||
|
|||
// 0 means loop indefinitely. Count is set as play n + 1 times.
|
|||
repeatCount = (ushort)(Math.Max((ushort)0, repeatCount) - 1); |
|||
|
|||
writer.Write(repeatCount); // Repeat count for images.
|
|||
|
|||
writer.Write(GifConstants.Terminator); // Terminator
|
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the graphics control extension to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The pixel format.</typeparam>
|
|||
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
|
|||
/// <param name="image">The <see cref="ImageBase{T,TP}"/> to encode.</param>
|
|||
/// <param name="writer">The stream to write to.</param>
|
|||
/// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param>
|
|||
private void WriteGraphicalControlExtension<T, TP>(ImageBase<T, TP> image, EndianBinaryWriter writer, int transparencyIndex) |
|||
where T : IPackedVector<TP> |
|||
where TP : struct |
|||
{ |
|||
// TODO: Check transparency logic.
|
|||
bool hasTransparent = transparencyIndex > -1; |
|||
DisposalMethod disposalMethod = hasTransparent |
|||
? DisposalMethod.RestoreToBackground |
|||
: DisposalMethod.Unspecified; |
|||
|
|||
GifGraphicsControlExtension extension = new GifGraphicsControlExtension() |
|||
{ |
|||
DisposalMethod = disposalMethod, |
|||
TransparencyFlag = hasTransparent, |
|||
TransparencyIndex = transparencyIndex, |
|||
DelayTime = image.FrameDelay |
|||
}; |
|||
|
|||
// Reduce the number of writes.
|
|||
byte[] intro = { |
|||
GifConstants.ExtensionIntroducer, |
|||
GifConstants.GraphicControlLabel, |
|||
4 // Size
|
|||
}; |
|||
|
|||
writer.Write(intro); |
|||
|
|||
PackedField field = new PackedField(); |
|||
field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal
|
|||
|
|||
// TODO: Allow this as an option.
|
|||
field.SetBit(6, false); // 7 : User input - 0 = none
|
|||
field.SetBit(7, extension.TransparencyFlag); // 8: Has transparent.
|
|||
|
|||
writer.Write(field.Byte); |
|||
writer.Write((ushort)extension.DelayTime); |
|||
writer.Write((byte)(extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex)); |
|||
writer.Write(GifConstants.Terminator); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the image descriptor to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The pixel format.</typeparam>
|
|||
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
|
|||
/// <param name="image">The <see cref="ImageBase{T,TP}"/> to be encoded.</param>
|
|||
/// <param name="writer">The stream to write to.</param>
|
|||
private void WriteImageDescriptor<T, TP>(ImageBase<T, TP> image, EndianBinaryWriter writer) |
|||
where T : IPackedVector<TP> |
|||
where TP : struct |
|||
{ |
|||
writer.Write(GifConstants.ImageDescriptorLabel); // 2c
|
|||
// TODO: Can we capture this?
|
|||
writer.Write((ushort)0); // Left position
|
|||
writer.Write((ushort)0); // Top position
|
|||
writer.Write((ushort)image.Width); |
|||
writer.Write((ushort)image.Height); |
|||
|
|||
PackedField field = new PackedField(); |
|||
field.SetBit(0, true); // 1: Local color table flag = 1 (LCT used)
|
|||
field.SetBit(1, false); // 2: Interlace flag 0
|
|||
field.SetBit(2, false); // 3: Sort flag 0
|
|||
field.SetBits(5, 3, this.bitDepth - 1); // 4-5: Reserved, 6-8 : LCT size. 2^(N+1)
|
|||
|
|||
writer.Write(field.Byte); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the color table to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The pixel format.</typeparam>
|
|||
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
|
|||
/// <param name="image">The <see cref="ImageBase{T,TP}"/> to encode.</param>
|
|||
/// <param name="writer">The writer to write to the stream with.</param>
|
|||
private void WriteColorTable<T, TP>(QuantizedImage<T, TP> image, EndianBinaryWriter writer) |
|||
where T : IPackedVector<TP> |
|||
where TP : struct |
|||
{ |
|||
// Grab the palette and write it to the stream.
|
|||
T[] palette = image.Palette; |
|||
int pixelCount = palette.Length; |
|||
|
|||
// Get max colors for bit depth.
|
|||
int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; |
|||
byte[] colorTable = new byte[colorTableLength]; |
|||
|
|||
Parallel.For(0, pixelCount, |
|||
i => |
|||
{ |
|||
int offset = i * 3; |
|||
byte[] color = palette[i].ToBytes(); |
|||
|
|||
colorTable[offset] = color[0]; |
|||
colorTable[offset + 1] = color[1]; |
|||
colorTable[offset + 2] = color[2]; |
|||
}); |
|||
|
|||
writer.Write(colorTable, 0, colorTableLength); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the image pixel data to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The pixel format.</typeparam>
|
|||
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
|
|||
/// <param name="image">The <see cref="QuantizedImage{T,TP}"/> containing indexed pixels.</param>
|
|||
/// <param name="writer">The stream to write to.</param>
|
|||
private void WriteImageData<T, TP>(QuantizedImage<T, TP> image, EndianBinaryWriter writer) |
|||
where T : IPackedVector<TP> |
|||
where TP : struct |
|||
{ |
|||
byte[] indexedPixels = image.Pixels; |
|||
|
|||
LzwEncoder encoder = new LzwEncoder(indexedPixels, (byte)this.bitDepth); |
|||
encoder.Encode(writer.BaseStream); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// <copyright file="GifFormat.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Formats |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates the means to encode and decode gif images.
|
|||
/// </summary>
|
|||
public class GifFormat : IImageFormat |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public IImageDecoder Decoder => new GifDecoder(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageEncoder Encoder => new GifEncoder(); |
|||
} |
|||
} |
|||
@ -0,0 +1,231 @@ |
|||
// <copyright file="LzwDecoder.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Formats |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
/// <summary>
|
|||
/// Decompresses and decodes data using the dynamic LZW algorithms.
|
|||
/// </summary>
|
|||
internal sealed class LzwDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// The max decoder pixel stack size.
|
|||
/// </summary>
|
|||
private const int MaxStackSize = 4096; |
|||
|
|||
/// <summary>
|
|||
/// The null code.
|
|||
/// </summary>
|
|||
private const int NullCode = -1; |
|||
|
|||
/// <summary>
|
|||
/// The stream to decode.
|
|||
/// </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 to read from.</param>
|
|||
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
|
|||
public LzwDecoder(Stream stream) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
this.stream = stream; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes and decompresses all pixel indices from the stream.
|
|||
/// <remarks>
|
|||
/// </remarks>
|
|||
/// </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.MustBeLessThan(dataSize, int.MaxValue, nameof(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; |
|||
|
|||
// Jillzhangs Code see: http://giflib.codeplex.com/
|
|||
// Adapted from John Cristy's ImageMagick.
|
|||
int code; |
|||
int oldCode = NullCode; |
|||
int codeMask = (1 << codeSize) - 1; |
|||
int bits = 0; |
|||
|
|||
int[] prefix = new int[MaxStackSize]; |
|||
int[] suffix = new int[MaxStackSize]; |
|||
int[] pixelStatck = new int[MaxStackSize + 1]; |
|||
|
|||
int top = 0; |
|||
int count = 0; |
|||
int bi = 0; |
|||
int xyz = 0; |
|||
|
|||
int data = 0; |
|||
int first = 0; |
|||
|
|||
for (code = 0; code < clearCode; code++) |
|||
{ |
|||
prefix[code] = 0; |
|||
suffix[code] = (byte)code; |
|||
} |
|||
|
|||
byte[] buffer = null; |
|||
while (xyz < pixels.Length) |
|||
{ |
|||
if (top == 0) |
|||
{ |
|||
if (bits < codeSize) |
|||
{ |
|||
// Load bytes until there are enough bits for a code.
|
|||
if (count == 0) |
|||
{ |
|||
// Read a new data block.
|
|||
buffer = this.ReadBlock(); |
|||
count = buffer.Length; |
|||
if (count == 0) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
bi = 0; |
|||
} |
|||
|
|||
if (buffer != null) |
|||
{ |
|||
data += buffer[bi] << bits; |
|||
} |
|||
|
|||
bits += 8; |
|||
bi++; |
|||
count--; |
|||
continue; |
|||
} |
|||
|
|||
// Get the next code
|
|||
code = data & codeMask; |
|||
data >>= codeSize; |
|||
bits -= codeSize; |
|||
|
|||
// Interpret the code
|
|||
if (code > availableCode || code == endCode) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
if (code == clearCode) |
|||
{ |
|||
// Reset the decoder
|
|||
codeSize = dataSize + 1; |
|||
codeMask = (1 << codeSize) - 1; |
|||
availableCode = clearCode + 2; |
|||
oldCode = NullCode; |
|||
continue; |
|||
} |
|||
|
|||
if (oldCode == NullCode) |
|||
{ |
|||
pixelStatck[top++] = suffix[code]; |
|||
oldCode = code; |
|||
first = code; |
|||
continue; |
|||
} |
|||
|
|||
int 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 < MaxStackSize) |
|||
{ |
|||
prefix[availableCode] = oldCode; |
|||
suffix[availableCode] = first; |
|||
availableCode++; |
|||
if (availableCode == codeMask + 1 && availableCode < MaxStackSize) |
|||
{ |
|||
codeSize++; |
|||
codeMask = (1 << codeSize) - 1; |
|||
} |
|||
} |
|||
|
|||
oldCode = inCode; |
|||
} |
|||
|
|||
// Pop a pixel off the pixel stack.
|
|||
top--; |
|||
|
|||
// Clear missing pixels
|
|||
pixels[xyz++] = (byte)pixelStatck[top]; |
|||
} |
|||
|
|||
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,385 @@ |
|||
// <copyright file="LzwEncoder.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Formats |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
/// <summary>
|
|||
/// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00
|
|||
/// <para>
|
|||
/// GIFCOMPR.C - GIF Image compression routines
|
|||
///
|
|||
/// Lempel-Ziv compression based on 'compress'. GIF modifications by
|
|||
/// David Rowley (mgardi@watdcsu.waterloo.edu)
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// GIF Image compression - modified 'compress'
|
|||
///
|
|||
/// Based on: compress.c - File compression ala IEEE Computer, June 1984.
|
|||
///
|
|||
/// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
|
|||
/// Jim McKie (decvax!mcvax!jim)
|
|||
/// Steve Davies (decvax!vax135!petsd!peora!srd)
|
|||
/// Ken Turkowski (decvax!decwrl!turtlevax!ken)
|
|||
/// James A. Woods (decvax!ihnp4!ames!jaw)
|
|||
/// Joe Orost (decvax!vax135!petsd!joe)
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
internal sealed class LzwEncoder |
|||
{ |
|||
private const int Eof = -1; |
|||
|
|||
private const int Bits = 12; |
|||
|
|||
private const int HashSize = 5003; // 80% occupancy
|
|||
|
|||
private readonly byte[] pixelArray; |
|||
|
|||
private readonly int initialCodeSize; |
|||
|
|||
private int curPixel; |
|||
|
|||
/// <summary>
|
|||
/// Number of bits/code
|
|||
/// </summary>
|
|||
private int bitCount; |
|||
|
|||
/// <summary>
|
|||
/// User settable max # bits/code
|
|||
/// </summary>
|
|||
private int maxbits = Bits; |
|||
|
|||
private int maxcode; // maximum code, given bitCount
|
|||
|
|||
private int maxmaxcode = 1 << Bits; // should NEVER generate this code
|
|||
|
|||
private readonly int[] hashTable = new int[HashSize]; |
|||
|
|||
private readonly int[] codeTable = new int[HashSize]; |
|||
|
|||
/// <summary>
|
|||
/// For dynamic table sizing
|
|||
/// </summary>
|
|||
private int hsize = HashSize; |
|||
|
|||
/// <summary>
|
|||
/// First unused entry
|
|||
/// </summary>
|
|||
private int freeEntry; |
|||
|
|||
/// <summary>
|
|||
/// Block compression parameters -- after all codes are used up,
|
|||
/// and compression rate changes, start over.
|
|||
/// </summary>
|
|||
private bool clearFlag; |
|||
|
|||
// Algorithm: use open addressing double hashing (no chaining) on the
|
|||
// prefix code / next character combination. We do a variant of Knuth's
|
|||
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
|
|||
// secondary probe. Here, the modular division first probe is gives way
|
|||
// to a faster exclusive-or manipulation. Also do block compression with
|
|||
// an adaptive reset, whereby the code table is cleared when the compression
|
|||
// ratio decreases, but after the table fills. The variable-length output
|
|||
// codes are re-sized at this point, and a special CLEAR code is generated
|
|||
// for the decompressor. Late addition: construct the table according to
|
|||
// file size for noticeable speed improvement on small files. Please direct
|
|||
// questions about this implementation to ames!jaw.
|
|||
|
|||
private int globalInitialBits; |
|||
|
|||
private int clearCode; |
|||
|
|||
private int eofCode; |
|||
|
|||
// output
|
|||
//
|
|||
// Output the given code.
|
|||
// Inputs:
|
|||
// code: A bitCount-bit integer. If == -1, then EOF. This assumes
|
|||
// that bitCount =< wordsize - 1.
|
|||
// Outputs:
|
|||
// Outputs code to the file.
|
|||
// Assumptions:
|
|||
// Chars are 8 bits long.
|
|||
// Algorithm:
|
|||
// Maintain a BITS character long buffer (so that 8 codes will
|
|||
// fit in it exactly). Use the VAX insv instruction to insert each
|
|||
// code in turn. When the buffer fills up empty it and start over.
|
|||
|
|||
private int currentAccumulator; |
|||
|
|||
private int currentBits; |
|||
|
|||
private readonly int[] masks = |
|||
{ |
|||
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, |
|||
0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Number of characters so far in this 'packet'
|
|||
/// </summary>
|
|||
private int accumulatorCount; |
|||
|
|||
/// <summary>
|
|||
/// Define the storage for the packet accumulator.
|
|||
/// </summary>
|
|||
private readonly byte[] accumulators = new byte[256]; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LzwEncoder"/> class.
|
|||
/// </summary>
|
|||
/// <param name="indexedPixels">The array of indexed pixels.</param>
|
|||
/// <param name="colorDepth">The color depth in bits.</param>
|
|||
public LzwEncoder(byte[] indexedPixels, int colorDepth) |
|||
{ |
|||
this.pixelArray = indexedPixels; |
|||
this.initialCodeSize = Math.Max(2, colorDepth); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes and compresses the indexed pixels to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
public void Encode(Stream stream) |
|||
{ |
|||
// Write "initial code size" byte
|
|||
stream.WriteByte((byte)this.initialCodeSize); |
|||
|
|||
this.curPixel = 0; |
|||
|
|||
// Compress and write the pixel data
|
|||
this.Compress(this.initialCodeSize + 1, stream); |
|||
|
|||
// Write block terminator
|
|||
stream.WriteByte(GifConstants.Terminator); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum code value
|
|||
/// </summary>
|
|||
/// <param name="bitCount">The number of bits</param>
|
|||
/// <returns>See <see cref="int"/></returns>
|
|||
private static int GetMaxcode(int bitCount) |
|||
{ |
|||
return (1 << bitCount) - 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Add a character to the end of the current packet, and if it is 254 characters,
|
|||
/// flush the packet to disk.
|
|||
/// </summary>
|
|||
/// <param name="c">The character to add.</param>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
private void AddCharacter(byte c, Stream stream) |
|||
{ |
|||
this.accumulators[this.accumulatorCount++] = c; |
|||
if (this.accumulatorCount >= 254) |
|||
{ |
|||
this.FlushPacket(stream); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Table clear for block compress
|
|||
/// </summary>
|
|||
/// <param name="stream">The output stream.</param>
|
|||
private void ClearBlock(Stream stream) |
|||
{ |
|||
this.ResetCodeTable(this.hsize); |
|||
this.freeEntry = this.clearCode + 2; |
|||
this.clearFlag = true; |
|||
|
|||
this.Output(this.clearCode, stream); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reset the code table.
|
|||
/// </summary>
|
|||
/// <param name="size">The hash size.</param>
|
|||
private void ResetCodeTable(int size) |
|||
{ |
|||
for (int i = 0; i < size; ++i) |
|||
{ |
|||
this.hashTable[i] = -1; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compress the packets to the stream.
|
|||
/// </summary>
|
|||
/// <param name="intialBits">The inital bits.</param>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
private void Compress(int intialBits, Stream stream) |
|||
{ |
|||
int fcode; |
|||
int c; |
|||
int ent; |
|||
int hsizeReg; |
|||
int hshift; |
|||
|
|||
// Set up the globals: globalInitialBits - initial number of bits
|
|||
this.globalInitialBits = intialBits; |
|||
|
|||
// Set up the necessary values
|
|||
this.clearFlag = false; |
|||
this.bitCount = this.globalInitialBits; |
|||
this.maxcode = GetMaxcode(this.bitCount); |
|||
|
|||
this.clearCode = 1 << (intialBits - 1); |
|||
this.eofCode = this.clearCode + 1; |
|||
this.freeEntry = this.clearCode + 2; |
|||
|
|||
this.accumulatorCount = 0; // clear packet
|
|||
|
|||
ent = this.NextPixel(); |
|||
|
|||
hshift = 0; |
|||
for (fcode = this.hsize; fcode < 65536; fcode *= 2) { ++hshift; } |
|||
hshift = 8 - hshift; // set hash code range bound
|
|||
|
|||
hsizeReg = this.hsize; |
|||
|
|||
this.ResetCodeTable(hsizeReg); // clear hash table
|
|||
|
|||
this.Output(this.clearCode, stream); |
|||
|
|||
while ((c = this.NextPixel()) != Eof) |
|||
{ |
|||
fcode = (c << this.maxbits) + ent; |
|||
int i = (c << hshift) ^ ent /* = 0 */; |
|||
|
|||
if (this.hashTable[i] == fcode) |
|||
{ |
|||
ent = this.codeTable[i]; |
|||
continue; |
|||
} |
|||
|
|||
// Non-empty slot
|
|||
if (this.hashTable[i] >= 0) |
|||
{ |
|||
int disp = hsizeReg - i; |
|||
if (i == 0) disp = 1; |
|||
do |
|||
{ |
|||
if ((i -= disp) < 0) { i += hsizeReg; } |
|||
|
|||
if (this.hashTable[i] == fcode) |
|||
{ |
|||
ent = this.codeTable[i]; |
|||
break; |
|||
} |
|||
} |
|||
while (this.hashTable[i] >= 0); |
|||
|
|||
if (this.hashTable[i] == fcode) { continue; } |
|||
} |
|||
|
|||
this.Output(ent, stream); |
|||
ent = c; |
|||
if (this.freeEntry < this.maxmaxcode) |
|||
{ |
|||
this.codeTable[i] = this.freeEntry++; // code -> hashtable
|
|||
this.hashTable[i] = fcode; |
|||
} |
|||
else this.ClearBlock(stream); |
|||
} |
|||
|
|||
// Put out the final code.
|
|||
this.Output(ent, stream); |
|||
|
|||
this.Output(this.eofCode, stream); |
|||
} |
|||
|
|||
// Flush the packet to disk, and reset the accumulator
|
|||
private void FlushPacket(Stream outs) |
|||
{ |
|||
if (this.accumulatorCount > 0) |
|||
{ |
|||
outs.WriteByte((byte)this.accumulatorCount); |
|||
outs.Write(this.accumulators, 0, this.accumulatorCount); |
|||
this.accumulatorCount = 0; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Return the next pixel from the image
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The <see cref="int"/>
|
|||
/// </returns>
|
|||
private int NextPixel() |
|||
{ |
|||
if (this.curPixel == this.pixelArray.Length) |
|||
{ |
|||
return Eof; |
|||
} |
|||
|
|||
if (this.curPixel == this.pixelArray.Length) |
|||
return Eof; |
|||
|
|||
this.curPixel++; |
|||
return this.pixelArray[this.curPixel - 1] & 0xff; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Output the current code to the stream.
|
|||
/// </summary>
|
|||
/// <param name="code">The code.</param>
|
|||
/// <param name="outs">The stream to write to.</param>
|
|||
private void Output(int code, Stream outs) |
|||
{ |
|||
this.currentAccumulator &= this.masks[this.currentBits]; |
|||
|
|||
if (this.currentBits > 0) this.currentAccumulator |= (code << this.currentBits); |
|||
else this.currentAccumulator = code; |
|||
|
|||
this.currentBits += this.bitCount; |
|||
|
|||
while (this.currentBits >= 8) |
|||
{ |
|||
this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); |
|||
this.currentAccumulator >>= 8; |
|||
this.currentBits -= 8; |
|||
} |
|||
|
|||
// If the next entry is going to be too big for the code size,
|
|||
// then increase it, if possible.
|
|||
if (this.freeEntry > this.maxcode || this.clearFlag) |
|||
{ |
|||
if (this.clearFlag) |
|||
{ |
|||
this.maxcode = GetMaxcode(this.bitCount = this.globalInitialBits); |
|||
this.clearFlag = false; |
|||
} |
|||
else |
|||
{ |
|||
++this.bitCount; |
|||
this.maxcode = this.bitCount == this.maxbits |
|||
? this.maxmaxcode |
|||
: GetMaxcode(this.bitCount); |
|||
} |
|||
} |
|||
|
|||
if (code == this.eofCode) |
|||
{ |
|||
// At EOF, write the rest of the buffer.
|
|||
while (this.currentBits > 0) |
|||
{ |
|||
this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); |
|||
this.currentAccumulator >>= 8; |
|||
this.currentBits -= 8; |
|||
} |
|||
|
|||
this.FlushPacket(outs); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,194 @@ |
|||
// <copyright file="PackedField.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Formats |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// Represents a byte of data in a GIF data stream which contains a number
|
|||
/// of data items.
|
|||
/// </summary>
|
|||
internal struct PackedField : IEquatable<PackedField> |
|||
{ |
|||
/// <summary>
|
|||
/// The individual bits representing the packed byte.
|
|||
/// </summary>
|
|||
private static readonly bool[] Bits = new bool[8]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the byte which represents the data items held in this instance.
|
|||
/// </summary>
|
|||
public byte Byte |
|||
{ |
|||
get |
|||
{ |
|||
int returnValue = 0; |
|||
int bitShift = 7; |
|||
foreach (bool bit in Bits) |
|||
{ |
|||
int bitValue; |
|||
if (bit) |
|||
{ |
|||
bitValue = 1 << bitShift; |
|||
} |
|||
else |
|||
{ |
|||
bitValue = 0; |
|||
} |
|||
returnValue |= bitValue; |
|||
bitShift--; |
|||
} |
|||
return Convert.ToByte(returnValue & 0xFF); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a new <see cref="PackedField"/> with the bits in the packed fields to
|
|||
/// the corresponding bits from the supplied byte.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to pack.</param>
|
|||
/// <returns>The <see cref="PackedField"/></returns>
|
|||
public static PackedField FromInt(byte value) |
|||
{ |
|||
PackedField packed = new PackedField(); |
|||
packed.SetBits(0, 8, value); |
|||
return packed; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the specified bit within the packed fields to the supplied
|
|||
/// value.
|
|||
/// </summary>
|
|||
/// <param name="index">
|
|||
/// The zero-based index within the packed fields of the bit to set.
|
|||
/// </param>
|
|||
/// <param name="valueToSet">
|
|||
/// The value to set the bit to.
|
|||
/// </param>
|
|||
public void SetBit(int index, bool valueToSet) |
|||
{ |
|||
if (index < 0 || index > 7) |
|||
{ |
|||
string message |
|||
= "Index must be between 0 and 7. Supplied index: " |
|||
+ index; |
|||
throw new ArgumentOutOfRangeException(nameof(index), message); |
|||
} |
|||
Bits[index] = valueToSet; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the specified bits within the packed fields to the supplied
|
|||
/// value.
|
|||
/// </summary>
|
|||
/// <param name="startIndex">The zero-based index within the packed fields of the first bit to set.</param>
|
|||
/// <param name="length">The number of bits to set.</param>
|
|||
/// <param name="valueToSet">The value to set the bits to.</param>
|
|||
public void SetBits(int startIndex, int length, int valueToSet) |
|||
{ |
|||
if (startIndex < 0 || startIndex > 7) |
|||
{ |
|||
string message = $"Start index must be between 0 and 7. Supplied index: {startIndex}"; |
|||
throw new ArgumentOutOfRangeException(nameof(startIndex), message); |
|||
} |
|||
|
|||
if (length < 1 || startIndex + length > 8) |
|||
{ |
|||
string message = "Length must be greater than zero and the sum of length and start index must be less than 8. " |
|||
+ $"Supplied length: {length}. Supplied start index: {startIndex}"; |
|||
throw new ArgumentOutOfRangeException(nameof(length), message); |
|||
} |
|||
|
|||
int bitShift = length - 1; |
|||
for (int i = startIndex; i < startIndex + length; i++) |
|||
{ |
|||
int bitValueIfSet = (1 << bitShift); |
|||
int bitValue = (valueToSet & bitValueIfSet); |
|||
int bitIsSet = (bitValue >> bitShift); |
|||
Bits[i] = (bitIsSet == 1); |
|||
bitShift--; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the specified bit within the byte.
|
|||
/// </summary>
|
|||
/// <param name="index">The zero-based index of the bit to get.</param>
|
|||
/// <returns>
|
|||
/// The value of the specified bit within the byte.
|
|||
/// </returns>
|
|||
public bool GetBit(int index) |
|||
{ |
|||
if (index < 0 || index > 7) |
|||
{ |
|||
string message = $"Index must be between 0 and 7. Supplied index: {index}"; |
|||
throw new ArgumentOutOfRangeException(nameof(index), message); |
|||
} |
|||
return Bits[index]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the specified bits within the byte.
|
|||
/// </summary>
|
|||
/// <param name="startIndex">The zero-based index of the first bit to get.</param>
|
|||
/// <param name="length">The number of bits to get.</param>
|
|||
/// <returns>
|
|||
/// The value of the specified bits within the byte.
|
|||
/// </returns>
|
|||
public int GetBits(int startIndex, int length) |
|||
{ |
|||
if (startIndex < 0 || startIndex > 7) |
|||
{ |
|||
string message = $"Start index must be between 0 and 7. Supplied index: {startIndex}"; |
|||
throw new ArgumentOutOfRangeException(nameof(startIndex), message); |
|||
} |
|||
|
|||
if (length < 1 || startIndex + length > 8) |
|||
{ |
|||
string message = "Length must be greater than zero and the sum of length and start index must be less than 8. " |
|||
+ $"Supplied length: {length}. Supplied start index: {startIndex}"; |
|||
|
|||
throw new ArgumentOutOfRangeException(nameof(length), message); |
|||
} |
|||
|
|||
int returnValue = 0; |
|||
int bitShift = length - 1; |
|||
for (int i = startIndex; i < startIndex + length; i++) |
|||
{ |
|||
int bitValue = (Bits[i] ? 1 : 0) << bitShift; |
|||
returnValue += bitValue; |
|||
bitShift--; |
|||
} |
|||
return returnValue; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
PackedField? field = obj as PackedField?; |
|||
|
|||
return this.Byte == field?.Byte; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(PackedField other) |
|||
{ |
|||
return this.Byte.Equals(other.Byte); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
return $"PackedField [ Byte={this.Byte} ]"; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return this.Byte.GetHashCode(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
Encoder/Decoder adapted and extended from: |
|||
|
|||
https://github.com/yufeih/Nine.Imaging/ |
|||
https://imagetools.codeplex.com/ |
|||
@ -0,0 +1,42 @@ |
|||
// <copyright file="GifGraphicsControlExtension.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.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.
|
|||
/// </summary>
|
|||
public int DelayTime { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// <copyright file="GifImageDescriptor.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.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,55 @@ |
|||
// <copyright file="GifLogicalScreenDescriptor.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.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 BackgroundColorIndex { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the pixel aspect ratio. Default to 0.
|
|||
/// </summary>
|
|||
public byte PixelAspectRatio { 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; } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue