mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: b08ac8597fa0f3ee4025b2a8814b34dd008c5291 Former-commit-id: 9bb7361434f7c87ed463a67c52b563c0d0b1b72b Former-commit-id: c8fa576e7a251a9a4954c6b605ef4eea4104368epull/17/head
12 changed files with 710 additions and 503 deletions
@ -0,0 +1,359 @@ |
|||
namespace ImageProcessor.Formats |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
internal class GifDecoderCore |
|||
{ |
|||
/// <summary>
|
|||
/// The maximum comment length.
|
|||
/// </summary>
|
|||
private const int MaxCommentLength = 1024 * 8; |
|||
|
|||
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 currentStream; |
|||
private byte[] globalColorTable; |
|||
private byte[] _currentFrame; |
|||
private GifLogicalScreenDescriptor logicalScreenDescriptor; |
|||
private GifGraphicsControlExtension _graphicsControl; |
|||
|
|||
public void Decode(Image image, Stream stream) |
|||
{ |
|||
this.image = image; |
|||
|
|||
this.currentStream = stream; |
|||
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); |
|||
} |
|||
|
|||
int nextFlag = stream.ReadByte(); |
|||
while (nextFlag != 0) |
|||
{ |
|||
if (nextFlag == ImageLabel) |
|||
{ |
|||
this.ReadFrame(); |
|||
} |
|||
else if (nextFlag == ExtensionIntroducer) |
|||
{ |
|||
int label = stream.ReadByte(); |
|||
switch (label) |
|||
{ |
|||
case GraphicControlLabel: |
|||
this.ReadGraphicalControlExtension(); |
|||
break; |
|||
case CommentLabel: |
|||
this.ReadComments(); |
|||
break; |
|||
case ApplicationExtensionLabel: |
|||
this.Skip(12); |
|||
break; |
|||
case PlainTextLabel: |
|||
this.Skip(13); |
|||
break; |
|||
} |
|||
} |
|||
else if (nextFlag == EndIntroducer) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
nextFlag = stream.ReadByte(); |
|||
} |
|||
} |
|||
|
|||
private void ReadGraphicalControlExtension() |
|||
{ |
|||
byte[] buffer = new byte[6]; |
|||
|
|||
this.currentStream.Read(buffer, 0, buffer.Length); |
|||
|
|||
byte packed = buffer[1]; |
|||
|
|||
_graphicsControl = new GifGraphicsControlExtension |
|||
{ |
|||
DelayTime = BitConverter.ToInt16(buffer, 2), |
|||
TransparencyIndex = buffer[4], |
|||
TransparencyFlag = (packed & 0x01) == 1, |
|||
DisposalMethod = (DisposalMethod)((packed & 0x1C) >> 2) |
|||
}; |
|||
} |
|||
|
|||
private GifImageDescriptor ReadImageDescriptor() |
|||
{ |
|||
byte[] buffer = new byte[9]; |
|||
|
|||
this.currentStream.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]; |
|||
|
|||
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(string.Format("Invalid gif colormap size '{0}'", this.logicalScreenDescriptor.GlobalColorTableSize)); |
|||
} |
|||
|
|||
if (this.logicalScreenDescriptor.Width > ImageBase.MaxWidth || this.logicalScreenDescriptor.Height > ImageBase.MaxHeight) |
|||
{ |
|||
throw new ArgumentOutOfRangeException( |
|||
string.Format( |
|||
"The input gif '{0}x{1}' is bigger then the max allowed size '{2}x{3}'", |
|||
this.logicalScreenDescriptor.Width, |
|||
this.logicalScreenDescriptor.Height, |
|||
ImageBase.MaxWidth, |
|||
ImageBase.MaxHeight)); |
|||
} |
|||
} |
|||
|
|||
private void Skip(int length) |
|||
{ |
|||
this.currentStream.Seek(length, SeekOrigin.Current); |
|||
|
|||
int flag = 0; |
|||
|
|||
while ((flag = this.currentStream.ReadByte()) != 0) |
|||
{ |
|||
this.currentStream.Seek(flag, SeekOrigin.Current); |
|||
} |
|||
} |
|||
|
|||
private void ReadComments() |
|||
{ |
|||
int flag = 0; |
|||
|
|||
while ((flag = this.currentStream.ReadByte()) != 0) |
|||
{ |
|||
if (flag > MaxCommentLength) |
|||
{ |
|||
throw new ImageFormatException(string.Format("Gif comment length '{0}' exceeds max '{1}'", flag, MaxCommentLength)); |
|||
} |
|||
|
|||
byte[] buffer = new byte[flag]; |
|||
|
|||
this.currentStream.Read(buffer, 0, flag); |
|||
|
|||
this.image.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(buffer))); |
|||
} |
|||
} |
|||
|
|||
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); |
|||
} |
|||
|
|||
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; |
|||
} |
|||
|
|||
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; |
|||
} |
|||
|
|||
private void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor) |
|||
{ |
|||
int imageWidth = this.logicalScreenDescriptor.Width; |
|||
int imageHeight = this.logicalScreenDescriptor.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; |
|||
|
|||
if (this.image.Pixels == null) |
|||
{ |
|||
currentImage = this.image; |
|||
currentImage.SetPixels(imageWidth, imageHeight, pixels); |
|||
|
|||
if (_graphicsControl != null && _graphicsControl.DelayTime > 0) |
|||
{ |
|||
this.image.FrameDelay = _graphicsControl.DelayTime; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
ImageFrame frame = new ImageFrame(); |
|||
|
|||
currentImage = frame; |
|||
currentImage.SetPixels(imageWidth, imageHeight, pixels); |
|||
|
|||
this.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,139 @@ |
|||
|
|||
namespace ImageProcessor.Formats |
|||
{ |
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.IO; |
|||
|
|||
public class GifEncoder : IImageEncoder |
|||
{ |
|||
/// <summary>
|
|||
/// The quality.
|
|||
/// </summary>
|
|||
private int quality = 256; |
|||
|
|||
/// <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 |
|||
{ |
|||
return this.quality; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
this.quality = value.Clamp(1, 256); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the default file extension for this encoder.
|
|||
/// </summary>
|
|||
public string Extension |
|||
{ |
|||
get { return "GIF"; } |
|||
} |
|||
|
|||
/// <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("GIF", StringComparison.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
public void Encode(ImageBase image, Stream stream) |
|||
{ |
|||
Guard.NotNull(image, "image"); |
|||
Guard.NotNull(stream, "stream"); |
|||
|
|||
// Write the header.
|
|||
// File Header signature and version.
|
|||
this.WriteString(stream, "GIF"); |
|||
this.WriteString(stream, "89a"); |
|||
|
|||
GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor |
|||
{ |
|||
Width = (short)image.Width, |
|||
Height = (short)image.Height, |
|||
GlobalColorTableFlag = true, |
|||
GlobalColorTableSize = this.Quality |
|||
}; |
|||
|
|||
this.WriteGlobalLogicalScreenDescriptor(stream, descriptor); |
|||
|
|||
throw new System.NotImplementedException(); |
|||
} |
|||
|
|||
private void WriteGlobalLogicalScreenDescriptor(Stream stream, GifLogicalScreenDescriptor descriptor) |
|||
{ |
|||
this.WriteShort(stream, descriptor.Width); |
|||
this.WriteShort(stream, descriptor.Width); |
|||
int bitdepth = this.GetBitsNeededForColorDepth(descriptor.GlobalColorTableSize) - 1; |
|||
int packed = 0x80 | // 1 : global color table flag = 1 (gct used)
|
|||
0x70 | // 2-4 : color resolution
|
|||
0x00 | // 5 : gct sort flag = 0
|
|||
bitdepth; // 6-8 : gct size`
|
|||
|
|||
this.WriteByte(stream, packed); |
|||
this.WriteByte(stream, descriptor.BackgroundColorIndex); // Background Color Index
|
|||
this.WriteByte(stream, descriptor.PixelAspectRatio); // Pixel aspect ratio
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a short to the given stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|||
/// <param name="value">The value to write.</param>
|
|||
private void WriteShort(Stream stream, int value) |
|||
{ |
|||
// Leave only one significant byte.
|
|||
stream.WriteByte(Convert.ToByte(value & 0xff)); |
|||
stream.WriteByte(Convert.ToByte((value >> 8) & 0xff)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a short to the given stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|||
/// <param name="value">The value to write.</param>
|
|||
private void WriteByte(Stream stream, int value) |
|||
{ |
|||
stream.WriteByte(Convert.ToByte(value)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a string to the given stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|||
/// <param name="value">The value to write.</param>
|
|||
private void WriteString(Stream stream, string value) |
|||
{ |
|||
char[] chars = value.ToCharArray(); |
|||
foreach (char c in chars) |
|||
{ |
|||
stream.WriteByte((byte)c); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns how many bits are required to store the specified number of colors.
|
|||
/// Performs a Log2() on the value.
|
|||
/// </summary>
|
|||
private int GetBitsNeededForColorDepth(int colors) |
|||
{ |
|||
return (int)Math.Ceiling(Math.Log(colors, 2)); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue