|
|
@ -17,9 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Performs the gif decoding operation.
|
|
|
/// Performs the gif decoding operation.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
internal sealed class GifDecoderCore |
|
|
internal sealed class GifDecoderCore<TPixel> |
|
|
|
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
|
|
|
{ |
|
|
{ |
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// The temp buffer used to reduce allocations.
|
|
|
/// The temp buffer used to reduce allocations.
|
|
|
@ -46,11 +44,6 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private int globalColorTableLength; |
|
|
private int globalColorTableLength; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The previous frame.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private ImageFrame<TPixel> previousFrame; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// The area to restore.
|
|
|
/// The area to restore.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
@ -72,12 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
private ImageMetaData metaData; |
|
|
private ImageMetaData metaData; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// The image to decode the information to.
|
|
|
/// Initializes a new instance of the <see cref="GifDecoderCore"/> class.
|
|
|
/// </summary>
|
|
|
|
|
|
private Image<TPixel> image; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initializes a new instance of the <see cref="GifDecoderCore{TPixel}"/> class.
|
|
|
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <param name="configuration">The configuration.</param>
|
|
|
/// <param name="configuration">The configuration.</param>
|
|
|
/// <param name="options">The decoder options.</param>
|
|
|
/// <param name="options">The decoder options.</param>
|
|
|
@ -107,28 +95,17 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Decodes the stream to the image.
|
|
|
/// Decodes the stream to the image.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
/// <param name="stream">The stream containing image data. </param>
|
|
|
/// <param name="stream">The stream containing image data. </param>
|
|
|
/// <returns>The decoded image</returns>
|
|
|
/// <returns>The decoded image</returns>
|
|
|
public Image<TPixel> Decode(Stream stream) |
|
|
public Image<TPixel> Decode<TPixel>(Stream stream) |
|
|
|
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
{ |
|
|
{ |
|
|
|
|
|
Image<TPixel> image = null; |
|
|
|
|
|
ImageFrame<TPixel> previousFrame = null; |
|
|
try |
|
|
try |
|
|
{ |
|
|
{ |
|
|
this.metaData = new ImageMetaData(); |
|
|
this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); |
|
|
|
|
|
|
|
|
this.currentStream = stream; |
|
|
|
|
|
|
|
|
|
|
|
// Skip the identifier
|
|
|
|
|
|
this.currentStream.Skip(6); |
|
|
|
|
|
this.ReadLogicalScreenDescriptor(); |
|
|
|
|
|
|
|
|
|
|
|
if (this.logicalScreenDescriptor.GlobalColorTableFlag) |
|
|
|
|
|
{ |
|
|
|
|
|
this.globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; |
|
|
|
|
|
this.globalColorTable = Buffer<byte>.CreateClean(this.globalColorTableLength); |
|
|
|
|
|
|
|
|
|
|
|
// Read the global color table from the stream
|
|
|
|
|
|
stream.Read(this.globalColorTable.Array, 0, this.globalColorTableLength); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Loop though the respective gif parts and read the data.
|
|
|
// Loop though the respective gif parts and read the data.
|
|
|
int nextFlag = stream.ReadByte(); |
|
|
int nextFlag = stream.ReadByte(); |
|
|
@ -136,12 +113,12 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
{ |
|
|
{ |
|
|
if (nextFlag == GifConstants.ImageLabel) |
|
|
if (nextFlag == GifConstants.ImageLabel) |
|
|
{ |
|
|
{ |
|
|
if (this.previousFrame != null && this.DecodingMode == FrameDecodingMode.First) |
|
|
if (previousFrame != null && this.DecodingMode == FrameDecodingMode.First) |
|
|
{ |
|
|
{ |
|
|
break; |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.ReadFrame(); |
|
|
this.ReadFrame(ref image, ref previousFrame); |
|
|
} |
|
|
} |
|
|
else if (nextFlag == GifConstants.ExtensionIntroducer) |
|
|
else if (nextFlag == GifConstants.ExtensionIntroducer) |
|
|
{ |
|
|
{ |
|
|
@ -184,7 +161,72 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
this.globalColorTable?.Dispose(); |
|
|
this.globalColorTable?.Dispose(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return this.image; |
|
|
return image; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Reads the raw image information from the specified stream.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|
|
|
|
|
public IImageInfo Identify(Stream stream) |
|
|
|
|
|
{ |
|
|
|
|
|
try |
|
|
|
|
|
{ |
|
|
|
|
|
this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); |
|
|
|
|
|
|
|
|
|
|
|
// Loop though the respective gif parts and read the data.
|
|
|
|
|
|
int nextFlag = stream.ReadByte(); |
|
|
|
|
|
while (nextFlag != GifConstants.Terminator) |
|
|
|
|
|
{ |
|
|
|
|
|
if (nextFlag == GifConstants.ImageLabel) |
|
|
|
|
|
{ |
|
|
|
|
|
// Skip image block
|
|
|
|
|
|
this.Skip(0); |
|
|
|
|
|
} |
|
|
|
|
|
else if (nextFlag == GifConstants.ExtensionIntroducer) |
|
|
|
|
|
{ |
|
|
|
|
|
int label = stream.ReadByte(); |
|
|
|
|
|
switch (label) |
|
|
|
|
|
{ |
|
|
|
|
|
case GifConstants.GraphicControlLabel: |
|
|
|
|
|
|
|
|
|
|
|
// Skip graphic control extension block
|
|
|
|
|
|
this.Skip(0); |
|
|
|
|
|
break; |
|
|
|
|
|
case GifConstants.CommentLabel: |
|
|
|
|
|
this.ReadComments(); |
|
|
|
|
|
break; |
|
|
|
|
|
case GifConstants.ApplicationExtensionLabel: |
|
|
|
|
|
|
|
|
|
|
|
// The application extension length should be 11 but we've got test images that incorrectly
|
|
|
|
|
|
// set this to 252.
|
|
|
|
|
|
int appLength = stream.ReadByte(); |
|
|
|
|
|
this.Skip(appLength); // No need to read.
|
|
|
|
|
|
break; |
|
|
|
|
|
case GifConstants.PlainTextLabel: |
|
|
|
|
|
int plainLength = stream.ReadByte(); |
|
|
|
|
|
this.Skip(plainLength); // Not supported by any known decoder.
|
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else if (nextFlag == GifConstants.EndIntroducer) |
|
|
|
|
|
{ |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
nextFlag = stream.ReadByte(); |
|
|
|
|
|
if (nextFlag == -1) |
|
|
|
|
|
{ |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
finally |
|
|
|
|
|
{ |
|
|
|
|
|
this.globalColorTable?.Dispose(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return new ImageInfo(new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel), this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height, this.metaData); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
@ -242,6 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
{ |
|
|
{ |
|
|
Width = BitConverter.ToInt16(this.buffer, 0), |
|
|
Width = BitConverter.ToInt16(this.buffer, 0), |
|
|
Height = BitConverter.ToInt16(this.buffer, 2), |
|
|
Height = BitConverter.ToInt16(this.buffer, 2), |
|
|
|
|
|
BitsPerPixel = (this.buffer[4] & 0x07) + 1, // The lowest 3 bits represent the bit depth minus 1
|
|
|
BackgroundColorIndex = this.buffer[5], |
|
|
BackgroundColorIndex = this.buffer[5], |
|
|
PixelAspectRatio = this.buffer[6], |
|
|
PixelAspectRatio = this.buffer[6], |
|
|
GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1, |
|
|
GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1, |
|
|
@ -308,7 +351,11 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Reads an individual gif frame.
|
|
|
/// Reads an individual gif frame.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private void ReadFrame() |
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
|
|
/// <param name="image">The image to decode the information to.</param>
|
|
|
|
|
|
/// <param name="previousFrame">The previous frame.</param>
|
|
|
|
|
|
private void ReadFrame<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame) |
|
|
|
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
{ |
|
|
{ |
|
|
GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); |
|
|
GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); |
|
|
|
|
|
|
|
|
@ -327,7 +374,7 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
indices = Buffer<byte>.CreateClean(imageDescriptor.Width * imageDescriptor.Height); |
|
|
indices = Buffer<byte>.CreateClean(imageDescriptor.Width * imageDescriptor.Height); |
|
|
|
|
|
|
|
|
this.ReadFrameIndices(imageDescriptor, indices); |
|
|
this.ReadFrameIndices(imageDescriptor, indices); |
|
|
this.ReadFrameColors(indices, localColorTable ?? this.globalColorTable, imageDescriptor); |
|
|
this.ReadFrameColors(ref image, ref previousFrame, indices, localColorTable ?? this.globalColorTable, imageDescriptor); |
|
|
|
|
|
|
|
|
// Skip any remaining blocks
|
|
|
// Skip any remaining blocks
|
|
|
this.Skip(0); |
|
|
this.Skip(0); |
|
|
@ -357,10 +404,14 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Reads the frames colors, mapping indices to colors.
|
|
|
/// Reads the frames colors, mapping indices to colors.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
|
|
/// <param name="image">The image to decode the information to.</param>
|
|
|
|
|
|
/// <param name="previousFrame">The previous frame.</param>
|
|
|
/// <param name="indices">The indexed pixels.</param>
|
|
|
/// <param name="indices">The indexed pixels.</param>
|
|
|
/// <param name="colorTable">The color table containing the available colors.</param>
|
|
|
/// <param name="colorTable">The color table containing the available colors.</param>
|
|
|
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
|
|
|
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
|
|
|
private void ReadFrameColors(Span<byte> indices, Span<byte> colorTable, GifImageDescriptor descriptor) |
|
|
private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, Span<byte> indices, Span<byte> colorTable, GifImageDescriptor descriptor) |
|
|
|
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
{ |
|
|
{ |
|
|
int imageWidth = this.logicalScreenDescriptor.Width; |
|
|
int imageWidth = this.logicalScreenDescriptor.Width; |
|
|
int imageHeight = this.logicalScreenDescriptor.Height; |
|
|
int imageHeight = this.logicalScreenDescriptor.Height; |
|
|
@ -371,30 +422,30 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
|
|
|
|
|
|
ImageFrame<TPixel> imageFrame; |
|
|
ImageFrame<TPixel> imageFrame; |
|
|
|
|
|
|
|
|
if (this.previousFrame == null) |
|
|
if (previousFrame == null) |
|
|
{ |
|
|
{ |
|
|
// This initializes the image to become fully transparent because the alpha channel is zero.
|
|
|
// This initializes the image to become fully transparent because the alpha channel is zero.
|
|
|
this.image = new Image<TPixel>(this.configuration, imageWidth, imageHeight, this.metaData); |
|
|
image = new Image<TPixel>(this.configuration, imageWidth, imageHeight, this.metaData); |
|
|
|
|
|
|
|
|
this.SetFrameMetaData(this.image.Frames.RootFrame.MetaData); |
|
|
this.SetFrameMetaData(image.Frames.RootFrame.MetaData); |
|
|
|
|
|
|
|
|
imageFrame = this.image.Frames.RootFrame; |
|
|
imageFrame = image.Frames.RootFrame; |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
if (this.graphicsControlExtension != null && |
|
|
if (this.graphicsControlExtension != null && |
|
|
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) |
|
|
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) |
|
|
{ |
|
|
{ |
|
|
prevFrame = this.previousFrame; |
|
|
prevFrame = previousFrame; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
currentFrame = this.image.Frames.AddFrame(this.previousFrame); // This clones the frame and adds it the collection
|
|
|
currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection
|
|
|
|
|
|
|
|
|
this.SetFrameMetaData(currentFrame.MetaData); |
|
|
this.SetFrameMetaData(currentFrame.MetaData); |
|
|
|
|
|
|
|
|
imageFrame = currentFrame; |
|
|
imageFrame = currentFrame; |
|
|
|
|
|
|
|
|
this.RestoreToBackground(imageFrame); |
|
|
this.RestoreToBackground(imageFrame, image.Width, image.Height); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
int i = 0; |
|
|
int i = 0; |
|
|
@ -466,11 +517,11 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
|
|
|
|
|
|
if (prevFrame != null) |
|
|
if (prevFrame != null) |
|
|
{ |
|
|
{ |
|
|
this.previousFrame = prevFrame; |
|
|
previousFrame = prevFrame; |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.previousFrame = currentFrame ?? this.image.Frames.RootFrame; |
|
|
previousFrame = currentFrame ?? image.Frames.RootFrame; |
|
|
|
|
|
|
|
|
if (this.graphicsControlExtension != null && |
|
|
if (this.graphicsControlExtension != null && |
|
|
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) |
|
|
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) |
|
|
@ -482,8 +533,12 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Restores the current frame area to the background.
|
|
|
/// Restores the current frame area to the background.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
/// <param name="frame">The frame.</param>
|
|
|
/// <param name="frame">The frame.</param>
|
|
|
private void RestoreToBackground(ImageFrame<TPixel> frame) |
|
|
/// <param name="imageWidth">Width of the image.</param>
|
|
|
|
|
|
/// <param name="imageHeight">Height of the image.</param>
|
|
|
|
|
|
private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame, int imageWidth, int imageHeight) |
|
|
|
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
{ |
|
|
{ |
|
|
if (this.restoreArea == null) |
|
|
if (this.restoreArea == null) |
|
|
{ |
|
|
{ |
|
|
@ -491,8 +546,8 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Optimization for when the size of the frame is the same as the image size.
|
|
|
// Optimization for when the size of the frame is the same as the image size.
|
|
|
if (this.restoreArea.Value.Width == this.image.Width && |
|
|
if (this.restoreArea.Value.Width == imageWidth && |
|
|
this.restoreArea.Value.Height == this.image.Height) |
|
|
this.restoreArea.Value.Height == imageHeight) |
|
|
{ |
|
|
{ |
|
|
using (PixelAccessor<TPixel> pixelAccessor = frame.Lock()) |
|
|
using (PixelAccessor<TPixel> pixelAccessor = frame.Lock()) |
|
|
{ |
|
|
{ |
|
|
@ -533,5 +588,29 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
meta.DisposalMethod = this.graphicsControlExtension.DisposalMethod; |
|
|
meta.DisposalMethod = this.graphicsControlExtension.DisposalMethod; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Reads the logical screen descriptor and global color table blocks
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="stream">The stream containing image data. </param>
|
|
|
|
|
|
private void ReadLogicalScreenDescriptorAndGlobalColorTable(Stream stream) |
|
|
|
|
|
{ |
|
|
|
|
|
this.metaData = new ImageMetaData(); |
|
|
|
|
|
|
|
|
|
|
|
this.currentStream = stream; |
|
|
|
|
|
|
|
|
|
|
|
// Skip the identifier
|
|
|
|
|
|
this.currentStream.Skip(6); |
|
|
|
|
|
this.ReadLogicalScreenDescriptor(); |
|
|
|
|
|
|
|
|
|
|
|
if (this.logicalScreenDescriptor.GlobalColorTableFlag) |
|
|
|
|
|
{ |
|
|
|
|
|
this.globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; |
|
|
|
|
|
this.globalColorTable = Buffer<byte>.CreateClean(this.globalColorTableLength); |
|
|
|
|
|
|
|
|
|
|
|
// Read the global color table from the stream
|
|
|
|
|
|
stream.Read(this.globalColorTable.Array, 0, this.globalColorTableLength); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |