|
|
|
@ -1,8 +1,8 @@ |
|
|
|
// Copyright (c) Six Labors.
|
|
|
|
// Licensed under the Six Labors Split License.
|
|
|
|
#nullable disable |
|
|
|
|
|
|
|
using System.Buffers; |
|
|
|
using System.Diagnostics.CodeAnalysis; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
using System.Runtime.InteropServices; |
|
|
|
using System.Text; |
|
|
|
@ -24,15 +24,10 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
/// </summary>
|
|
|
|
private readonly byte[] buffer = new byte[16]; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The currently loaded stream.
|
|
|
|
/// </summary>
|
|
|
|
private BufferedReadStream stream; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The global color table.
|
|
|
|
/// </summary>
|
|
|
|
private IMemoryOwner<byte> globalColorTable; |
|
|
|
private IMemoryOwner<byte>? globalColorTable; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The area to restore.
|
|
|
|
@ -77,12 +72,12 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
/// <summary>
|
|
|
|
/// The abstract metadata.
|
|
|
|
/// </summary>
|
|
|
|
private ImageMetadata metadata; |
|
|
|
private ImageMetadata? metadata; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The gif specific metadata.
|
|
|
|
/// </summary>
|
|
|
|
private GifMetadata gifMetadata; |
|
|
|
private GifMetadata? gifMetadata; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="GifDecoderCore"/> class.
|
|
|
|
@ -108,8 +103,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
uint frameCount = 0; |
|
|
|
Image<TPixel> image = null; |
|
|
|
ImageFrame<TPixel> previousFrame = null; |
|
|
|
Image<TPixel>? image = null; |
|
|
|
ImageFrame<TPixel>? previousFrame = null; |
|
|
|
try |
|
|
|
{ |
|
|
|
this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); |
|
|
|
@ -125,7 +120,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
this.ReadFrame(ref image, ref previousFrame); |
|
|
|
this.ReadFrame(stream, ref image, ref previousFrame); |
|
|
|
|
|
|
|
// Reset per-frame state.
|
|
|
|
this.imageDescriptor = default; |
|
|
|
@ -136,16 +131,16 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
switch (stream.ReadByte()) |
|
|
|
{ |
|
|
|
case GifConstants.GraphicControlLabel: |
|
|
|
this.ReadGraphicalControlExtension(); |
|
|
|
this.ReadGraphicalControlExtension(stream); |
|
|
|
break; |
|
|
|
case GifConstants.CommentLabel: |
|
|
|
this.ReadComments(); |
|
|
|
this.ReadComments(stream); |
|
|
|
break; |
|
|
|
case GifConstants.ApplicationExtensionLabel: |
|
|
|
this.ReadApplicationExtension(); |
|
|
|
this.ReadApplicationExtension(stream); |
|
|
|
break; |
|
|
|
case GifConstants.PlainTextLabel: |
|
|
|
this.SkipBlock(); // Not supported by any known decoder.
|
|
|
|
this.SkipBlock(stream); // Not supported by any known decoder.
|
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -187,23 +182,23 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
{ |
|
|
|
if (nextFlag == GifConstants.ImageLabel) |
|
|
|
{ |
|
|
|
this.ReadImageDescriptor(); |
|
|
|
this.ReadImageDescriptor(stream); |
|
|
|
} |
|
|
|
else if (nextFlag == GifConstants.ExtensionIntroducer) |
|
|
|
{ |
|
|
|
switch (stream.ReadByte()) |
|
|
|
{ |
|
|
|
case GifConstants.GraphicControlLabel: |
|
|
|
this.SkipBlock(); // Skip graphic control extension block
|
|
|
|
this.SkipBlock(stream); // Skip graphic control extension block
|
|
|
|
break; |
|
|
|
case GifConstants.CommentLabel: |
|
|
|
this.ReadComments(); |
|
|
|
this.ReadComments(stream); |
|
|
|
break; |
|
|
|
case GifConstants.ApplicationExtensionLabel: |
|
|
|
this.ReadApplicationExtension(); |
|
|
|
this.ReadApplicationExtension(stream); |
|
|
|
break; |
|
|
|
case GifConstants.PlainTextLabel: |
|
|
|
this.SkipBlock(); // Not supported by any known decoder.
|
|
|
|
this.SkipBlock(stream); // Not supported by any known decoder.
|
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -239,9 +234,10 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
/// <summary>
|
|
|
|
/// Reads the graphic control extension.
|
|
|
|
/// </summary>
|
|
|
|
private void ReadGraphicalControlExtension() |
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
private void ReadGraphicalControlExtension(BufferedReadStream stream) |
|
|
|
{ |
|
|
|
int bytesRead = this.stream.Read(this.buffer, 0, 6); |
|
|
|
int bytesRead = stream.Read(this.buffer, 0, 6); |
|
|
|
if (bytesRead != 6) |
|
|
|
{ |
|
|
|
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension"); |
|
|
|
@ -253,9 +249,10 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
/// <summary>
|
|
|
|
/// Reads the image descriptor.
|
|
|
|
/// </summary>
|
|
|
|
private void ReadImageDescriptor() |
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
private void ReadImageDescriptor(BufferedReadStream stream) |
|
|
|
{ |
|
|
|
int bytesRead = this.stream.Read(this.buffer, 0, 9); |
|
|
|
int bytesRead = stream.Read(this.buffer, 0, 9); |
|
|
|
if (bytesRead != 9) |
|
|
|
{ |
|
|
|
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor"); |
|
|
|
@ -271,9 +268,10 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
/// <summary>
|
|
|
|
/// Reads the logical screen descriptor.
|
|
|
|
/// </summary>
|
|
|
|
private void ReadLogicalScreenDescriptor() |
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
private void ReadLogicalScreenDescriptor(BufferedReadStream stream) |
|
|
|
{ |
|
|
|
int bytesRead = this.stream.Read(this.buffer, 0, 7); |
|
|
|
int bytesRead = stream.Read(this.buffer, 0, 7); |
|
|
|
if (bytesRead != 7) |
|
|
|
{ |
|
|
|
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor"); |
|
|
|
@ -286,84 +284,87 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
/// Reads the application extension block parsing any animation or XMP information
|
|
|
|
/// if present.
|
|
|
|
/// </summary>
|
|
|
|
private void ReadApplicationExtension() |
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
private void ReadApplicationExtension(BufferedReadStream stream) |
|
|
|
{ |
|
|
|
int appLength = this.stream.ReadByte(); |
|
|
|
int appLength = stream.ReadByte(); |
|
|
|
|
|
|
|
// If the length is 11 then it's a valid extension and most likely
|
|
|
|
// a NETSCAPE, XMP or ANIMEXTS extension. We want the loop count from this.
|
|
|
|
long position = this.stream.Position; |
|
|
|
long position = stream.Position; |
|
|
|
if (appLength == GifConstants.ApplicationBlockSize) |
|
|
|
{ |
|
|
|
this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize); |
|
|
|
stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize); |
|
|
|
bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes); |
|
|
|
if (isXmp && !this.skipMetadata) |
|
|
|
{ |
|
|
|
GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(this.stream, this.memoryAllocator); |
|
|
|
GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator); |
|
|
|
if (extension.Data.Length > 0) |
|
|
|
{ |
|
|
|
this.metadata.XmpProfile = new XmpProfile(extension.Data); |
|
|
|
this.metadata!.XmpProfile = new XmpProfile(extension.Data); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// Reset the stream position and continue.
|
|
|
|
this.stream.Position = position; |
|
|
|
this.SkipBlock(appLength); |
|
|
|
stream.Position = position; |
|
|
|
this.SkipBlock(stream, appLength); |
|
|
|
} |
|
|
|
|
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
int subBlockSize = this.stream.ReadByte(); |
|
|
|
int subBlockSize = stream.ReadByte(); |
|
|
|
|
|
|
|
// TODO: There's also a NETSCAPE buffer extension.
|
|
|
|
// http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
|
|
|
|
if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) |
|
|
|
{ |
|
|
|
this.stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize); |
|
|
|
this.gifMetadata.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount; |
|
|
|
this.stream.Skip(1); // Skip the terminator.
|
|
|
|
stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize); |
|
|
|
this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount; |
|
|
|
stream.Skip(1); // Skip the terminator.
|
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// Could be something else not supported yet.
|
|
|
|
// Skip the subblock and terminator.
|
|
|
|
this.SkipBlock(subBlockSize); |
|
|
|
this.SkipBlock(stream, subBlockSize); |
|
|
|
|
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
this.SkipBlock(appLength); // Not supported by any known decoder.
|
|
|
|
this.SkipBlock(stream, appLength); // Not supported by any known decoder.
|
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Skips over a block or reads its terminator.
|
|
|
|
/// <param name="blockSize">The length of the block to skip.</param>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// </summary>
|
|
|
|
private void SkipBlock(int blockSize = 0) |
|
|
|
private void SkipBlock(BufferedReadStream stream, int blockSize = 0) |
|
|
|
{ |
|
|
|
if (blockSize > 0) |
|
|
|
{ |
|
|
|
this.stream.Skip(blockSize); |
|
|
|
stream.Skip(blockSize); |
|
|
|
} |
|
|
|
|
|
|
|
int flag; |
|
|
|
|
|
|
|
while ((flag = this.stream.ReadByte()) > 0) |
|
|
|
while ((flag = stream.ReadByte()) > 0) |
|
|
|
{ |
|
|
|
this.stream.Skip(flag); |
|
|
|
stream.Skip(flag); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Reads the gif comments.
|
|
|
|
/// </summary>
|
|
|
|
private void ReadComments() |
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
private void ReadComments(BufferedReadStream stream) |
|
|
|
{ |
|
|
|
int length; |
|
|
|
|
|
|
|
var stringBuilder = new StringBuilder(); |
|
|
|
while ((length = this.stream.ReadByte()) != 0) |
|
|
|
while ((length = stream.ReadByte()) != 0) |
|
|
|
{ |
|
|
|
if (length > GifConstants.MaxCommentSubBlockLength) |
|
|
|
{ |
|
|
|
@ -372,21 +373,21 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
if (this.skipMetadata) |
|
|
|
{ |
|
|
|
this.stream.Seek(length, SeekOrigin.Current); |
|
|
|
stream.Seek(length, SeekOrigin.Current); |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
using IMemoryOwner<byte> commentsBuffer = this.memoryAllocator.Allocate<byte>(length); |
|
|
|
Span<byte> commentsSpan = commentsBuffer.GetSpan(); |
|
|
|
|
|
|
|
this.stream.Read(commentsSpan); |
|
|
|
stream.Read(commentsSpan); |
|
|
|
string commentPart = GifConstants.Encoding.GetString(commentsSpan); |
|
|
|
stringBuilder.Append(commentPart); |
|
|
|
} |
|
|
|
|
|
|
|
if (stringBuilder.Length > 0) |
|
|
|
{ |
|
|
|
this.gifMetadata.Comments.Add(stringBuilder.ToString()); |
|
|
|
this.gifMetadata!.Comments.Add(stringBuilder.ToString()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -394,15 +395,16 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
/// Reads an individual gif frame.
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <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) |
|
|
|
private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? image, ref ImageFrame<TPixel>? previousFrame) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
this.ReadImageDescriptor(); |
|
|
|
this.ReadImageDescriptor(stream); |
|
|
|
|
|
|
|
IMemoryOwner<byte> localColorTable = null; |
|
|
|
Buffer2D<byte> indices = null; |
|
|
|
IMemoryOwner<byte>? localColorTable = null; |
|
|
|
Buffer2D<byte>? indices = null; |
|
|
|
try |
|
|
|
{ |
|
|
|
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
|
|
|
|
@ -410,11 +412,11 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
{ |
|
|
|
int length = this.imageDescriptor.LocalColorTableSize * 3; |
|
|
|
localColorTable = this.configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean); |
|
|
|
this.stream.Read(localColorTable.GetSpan()); |
|
|
|
stream.Read(localColorTable.GetSpan()); |
|
|
|
} |
|
|
|
|
|
|
|
indices = this.configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); |
|
|
|
this.ReadFrameIndices(indices); |
|
|
|
this.ReadFrameIndices(stream, indices); |
|
|
|
|
|
|
|
Span<byte> rawColorTable = default; |
|
|
|
if (localColorTable != null) |
|
|
|
@ -430,7 +432,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor); |
|
|
|
|
|
|
|
// Skip any remaining blocks
|
|
|
|
this.SkipBlock(); |
|
|
|
this.SkipBlock(stream); |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
@ -442,12 +444,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
/// <summary>
|
|
|
|
/// Reads the frame indices marking the color to use for each pixel.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="indices">The 2D pixel buffer to write to.</param>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private void ReadFrameIndices(Buffer2D<byte> indices) |
|
|
|
private void ReadFrameIndices(BufferedReadStream stream, Buffer2D<byte> indices) |
|
|
|
{ |
|
|
|
int minCodeSize = this.stream.ReadByte(); |
|
|
|
using var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream); |
|
|
|
int minCodeSize = stream.ReadByte(); |
|
|
|
using LzwDecoder lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, stream); |
|
|
|
lzwDecoder.DecodePixels(minCodeSize, indices); |
|
|
|
} |
|
|
|
|
|
|
|
@ -460,15 +463,15 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
/// <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<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, Buffer2D<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor) |
|
|
|
private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TPixel>? previousFrame, Buffer2D<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
int imageWidth = this.logicalScreenDescriptor.Width; |
|
|
|
int imageHeight = this.logicalScreenDescriptor.Height; |
|
|
|
bool transFlag = this.graphicsControlExtension.TransparencyFlag; |
|
|
|
|
|
|
|
ImageFrame<TPixel> prevFrame = null; |
|
|
|
ImageFrame<TPixel> currentFrame = null; |
|
|
|
ImageFrame<TPixel>? prevFrame = null; |
|
|
|
ImageFrame<TPixel>? currentFrame = null; |
|
|
|
ImageFrame<TPixel> imageFrame; |
|
|
|
|
|
|
|
if (previousFrame is null) |
|
|
|
@ -494,7 +497,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
prevFrame = previousFrame; |
|
|
|
} |
|
|
|
|
|
|
|
currentFrame = image.Frames.CreateFrame(); |
|
|
|
currentFrame = image!.Frames.CreateFrame(); |
|
|
|
|
|
|
|
this.SetFrameMetadata(currentFrame.Metadata, false); |
|
|
|
|
|
|
|
@ -661,13 +664,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals |
|
|
|
/// Reads the logical screen descriptor and global color table blocks
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The stream containing image data. </param>
|
|
|
|
[MemberNotNull(nameof(metadata))] |
|
|
|
[MemberNotNull(nameof(gifMetadata))] |
|
|
|
private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream stream) |
|
|
|
{ |
|
|
|
this.stream = stream; |
|
|
|
|
|
|
|
// Skip the identifier
|
|
|
|
this.stream.Skip(6); |
|
|
|
this.ReadLogicalScreenDescriptor(); |
|
|
|
stream.Skip(6); |
|
|
|
this.ReadLogicalScreenDescriptor(stream); |
|
|
|
|
|
|
|
ImageMetadata meta = new(); |
|
|
|
|
|
|
|
|