Browse Source

Merge pull request #2770 from SixLabors/af/backport-2759-2.1.x

Backport 2759 to 2.1.x
pull/2868/head v2.1.9
James Jackson-South 2 years ago
committed by GitHub
parent
commit
9816ca4501
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  2. 465
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  3. 7
      src/ImageSharp/Formats/Gif/GifThrowHelper.cs
  4. 5
      src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
  5. 299
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  6. 2
      src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
  7. 11
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  8. 26
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  9. 2
      tests/ImageSharp.Tests/TestImages.cs
  10. 4
      tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png
  11. 3
      tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/00.png
  12. 3
      tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/01.png
  13. 4
      tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png
  14. 3
      tests/Images/Input/Gif/issues/issue_2758.gif
  15. 3
      tests/Images/Input/Jpg/issues/issue-2758.jpg

5
src/ImageSharp/Formats/Gif/GifDecoder.cs

@ -23,6 +23,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
/// <summary>
/// Gets or sets the maximum number of gif frames.
/// </summary>
public uint MaxFrames { get; set; } = uint.MaxValue;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>

465
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -22,19 +23,24 @@ namespace SixLabors.ImageSharp.Formats.Gif
internal sealed class GifDecoderCore : IImageDecoderInternals
{
/// <summary>
/// The temp buffer used to reduce allocations.
/// The temp buffer.
/// </summary>
private readonly byte[] buffer = new byte[16];
private byte[] buffer = new byte[16];
/// <summary>
/// The currently loaded stream.
/// The global color table.
/// </summary>
private BufferedReadStream stream;
private IMemoryOwner<byte> globalColorTable;
/// <summary>
/// The global color table.
/// The current local color table.
/// </summary>
private IMemoryOwner<byte> globalColorTable;
private IMemoryOwner<byte> currentLocalColorTable;
/// <summary>
/// Gets the size in bytes of the current local color table.
/// </summary>
private int currentLocalColorTableSize;
/// <summary>
/// The area to restore.
@ -56,6 +62,26 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
private GifImageDescriptor imageDescriptor;
/// <summary>
/// The global configuration.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The maximum number of frames to decode. Inclusive.
/// </summary>
private readonly uint maxFrames;
/// <summary>
/// Whether to skip metadata during decode.
/// </summary>
private readonly bool skipMetadata;
/// <summary>
/// The abstract metadata.
/// </summary>
@ -73,23 +99,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="options">The decoder options.</param>
public GifDecoderCore(Configuration configuration, IGifDecoderOptions options)
{
this.IgnoreMetadata = options.IgnoreMetadata;
this.DecodingMode = options.DecodingMode;
this.Configuration = configuration ?? Configuration.Default;
this.skipMetadata = options.IgnoreMetadata;
this.configuration = configuration ?? Configuration.Default;
this.maxFrames = options.DecodingMode == FrameDecodingMode.All ? options.MaxFrames : 1;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <inheritdoc />
public Configuration Configuration { get; }
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; internal set; }
/// <summary>
/// Gets the decoding mode for multi-frame images.
/// </summary>
public FrameDecodingMode DecodingMode { get; }
public Configuration Configuration => this.configuration;
/// <summary>
/// Gets the dimensions of the image.
@ -102,6 +119,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
uint frameCount = 0;
Image<TPixel> image = null;
ImageFrame<TPixel> previousFrame = null;
try
@ -114,28 +132,32 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
if (nextFlag == GifConstants.ImageLabel)
{
if (previousFrame != null && this.DecodingMode == FrameDecodingMode.First)
if (previousFrame != null && ++frameCount == this.maxFrames)
{
break;
}
this.ReadFrame(ref image, ref previousFrame);
this.ReadFrame(stream, ref image, ref previousFrame);
// Reset per-frame state.
this.imageDescriptor = default;
this.graphicsControlExtension = default;
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
{
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.
SkipBlock(stream); // Not supported by any known decoder.
break;
}
}
@ -154,6 +176,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
finally
{
this.globalColorTable?.Dispose();
this.currentLocalColorTable?.Dispose();
}
if (image is null)
{
GifThrowHelper.ThrowInvalidImageContentException("No data");
}
return image;
@ -162,6 +190,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
uint frameCount = 0;
ImageFrameMetadata? previousFrame = null;
List<ImageFrameMetadata> framesMetadata = new();
try
{
this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream);
@ -172,23 +203,32 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
if (nextFlag == GifConstants.ImageLabel)
{
this.ReadImageDescriptor();
if (previousFrame != null && ++frameCount == this.maxFrames)
{
break;
}
this.ReadFrameMetadata(stream, framesMetadata, ref previousFrame);
// Reset per-frame state.
this.imageDescriptor = default;
this.graphicsControlExtension = default;
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
{
switch (stream.ReadByte())
{
case GifConstants.GraphicControlLabel:
this.SkipBlock(); // Skip graphic control extension block
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.
SkipBlock(stream); // Not supported by any known decoder.
break;
}
}
@ -207,6 +247,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
finally
{
this.globalColorTable?.Dispose();
this.currentLocalColorTable?.Dispose();
}
if (this.logicalScreenDescriptor.Width == 0 && this.logicalScreenDescriptor.Height == 0)
{
GifThrowHelper.ThrowNoHeader();
}
return new ImageInfo(
@ -219,9 +265,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <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");
@ -233,9 +280,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <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");
@ -251,9 +299,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <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");
@ -266,103 +315,110 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// 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 = 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.IgnoreMetadata)
if (isXmp && !this.skipMetadata)
{
var 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.
stream.Position = position;
SkipBlock(stream, appLength);
}
return;
}
else
{
int subBlockSize = this.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.
return;
}
int subBlockSize = stream.ReadByte();
// Could be something else not supported yet.
// Skip the subblock and terminator.
this.SkipBlock(subBlockSize);
// TODO: There's also a NETSCAPE buffer extension.
// http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize)
{
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.
SkipBlock(stream, subBlockSize);
return;
}
this.SkipBlock(appLength); // Not supported by any known decoder.
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>
/// </summary>
private void SkipBlock(int blockSize = 0)
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="blockSize">The length of the block to skip.</param>
private static 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)
StringBuilder stringBuilder = new();
while ((length = stream.ReadByte()) != 0)
{
if (length > GifConstants.MaxCommentSubBlockLength)
{
GifThrowHelper.ThrowInvalidImageContentException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block");
}
if (this.IgnoreMetadata)
if (this.skipMetadata)
{
this.stream.Seek(length, SeekOrigin.Current);
stream.Seek(length, SeekOrigin.Current);
continue;
}
using IMemoryOwner<byte> commentsBuffer = this.MemoryAllocator.Allocate<byte>(length);
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());
}
}
@ -370,93 +426,77 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// 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;
try
{
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
if (this.imageDescriptor.LocalColorTableFlag)
{
int length = this.imageDescriptor.LocalColorTableSize * 3;
localColorTable = this.Configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
this.stream.Read(localColorTable.GetSpan());
}
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
bool hasLocalColorTable = this.imageDescriptor.LocalColorTableFlag;
indices = this.Configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
this.ReadFrameIndices(indices);
Span<byte> rawColorTable = default;
if (localColorTable != null)
{
rawColorTable = localColorTable.GetSpan();
}
else if (this.globalColorTable != null)
{
rawColorTable = this.globalColorTable.GetSpan();
}
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor);
if (hasLocalColorTable)
{
// Read and store the local color table. We allocate the maximum possible size and slice to match.
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
stream.Read(this.currentLocalColorTable.GetSpan().Slice(0, length));
}
// Skip any remaining blocks
this.SkipBlock();
Span<byte> rawColorTable = default;
if (hasLocalColorTable)
{
rawColorTable = this.currentLocalColorTable!.GetSpan().Slice(0, this.currentLocalColorTableSize);
}
finally
else if (this.globalColorTable != null)
{
localColorTable?.Dispose();
indices?.Dispose();
rawColorTable = this.globalColorTable.GetSpan();
}
}
/// <summary>
/// Reads the frame indices marking the color to use for each pixel.
/// </summary>
/// <param name="indices">The 2D pixel buffer to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(Buffer2D<byte> indices)
{
int minCodeSize = this.stream.ReadByte();
using var lzwDecoder = new LzwDecoder(this.Configuration.MemoryAllocator, this.stream);
lzwDecoder.DecodePixels(minCodeSize, indices);
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
this.ReadFrameColors(stream, ref image, ref previousFrame, colorTable, this.imageDescriptor);
// Skip any remaining blocks
SkipBlock(stream);
}
/// <summary>
/// Reads the frames colors, mapping indices to colors.
/// </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>
/// <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>(
BufferedReadStream stream,
ref Image<TPixel>? image,
ref ImageFrame<TPixel>? previousFrame,
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)
{
if (!transFlag)
{
image = new Image<TPixel>(this.Configuration, imageWidth, imageHeight, Color.Black.ToPixel<TPixel>(), this.metadata);
image = new Image<TPixel>(this.configuration, imageWidth, imageHeight, Color.Black.ToPixel<TPixel>(), this.metadata);
}
else
{
// This initializes the image to become fully transparent because the alpha channel is zero.
image = new Image<TPixel>(this.Configuration, imageWidth, imageHeight, this.metadata);
image = new Image<TPixel>(this.configuration, imageWidth, imageHeight, this.metadata);
}
this.SetFrameMetadata(image.Frames.RootFrame.Metadata);
@ -470,7 +510,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
prevFrame = previousFrame;
}
currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection
// We create a clone of the frame and add it.
// We will overpaint the difference of pixels on the current frame to create a complete image.
// This ensures that we have enough pixel data to process without distortion. #2450
currentFrame = image!.Frames.AddFrame(previousFrame);
this.SetFrameMetadata(currentFrame.Metadata);
@ -494,64 +537,79 @@ namespace SixLabors.ImageSharp.Formats.Gif
byte transIndex = this.graphicsControlExtension.TransparencyIndex;
int colorTableMaxIdx = colorTable.Length - 1;
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
// For a properly encoded gif the descriptor dimensions will never exceed the logical screen dimensions.
// However we have images that exceed this that can be decoded by other libraries. #1530
using IMemoryOwner<byte> indicesRowOwner = this.memoryAllocator.Allocate<byte>(descriptor.Width);
Span<byte> indicesRow = indicesRowOwner.Memory.Span;
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indicesRow);
int minCodeSize = stream.ReadByte();
if (LzwDecoder.IsValidMinCodeSize(minCodeSize))
{
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.DangerousGetRowSpan(y - descriptorTop));
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream, minCodeSize);
// Check if this image is interlaced.
int writeY; // the target y offset to write to
if (descriptor.InterlaceFlag)
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
{
// 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)
// Check if this image is interlaced.
int writeY; // the target y offset to write to
if (descriptor.InterlaceFlag)
{
interlacePass++;
switch (interlacePass)
// 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)
{
case 1:
interlaceY = 4;
break;
case 2:
interlaceY = 2;
interlaceIncrement = 4;
break;
case 3:
interlaceY = 1;
interlaceIncrement = 2;
break;
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;
}
writeY = Math.Min(interlaceY + descriptor.Top, image.Height);
interlaceY += interlaceIncrement;
}
else
{
writeY = y;
}
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
lzwDecoder.DecodePixelRow(indicesRow);
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
if (!transFlag)
{
// #403 The left + width value can be larger than the image width
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
if (!transFlag)
{
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx);
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
// #403 The left + width value can be larger than the image width
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
{
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx);
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
}
}
}
else
{
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
else
{
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx);
if (transIndex != index)
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
{
int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft);
// Treat any out of bounds values as transparent.
if (index > colorTableMaxIdx || index == transIndex)
{
continue;
}
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
@ -574,6 +632,43 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
}
/// <summary>
/// Reads the frames metadata.
/// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="frameMetadata">The collection of frame metadata.</param>
/// <param name="previousFrame">The previous frame metadata.</param>
private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadata> frameMetadata, ref ImageFrameMetadata? previousFrame)
{
this.ReadImageDescriptor(stream);
// Skip the color table for this frame if local.
if (this.imageDescriptor.LocalColorTableFlag)
{
// Read and store the local color table. We allocate the maximum possible size and slice to match.
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
stream.Read(this.currentLocalColorTable.GetSpan().Slice(0, length));
}
// Skip the frame indices. Pixels length + mincode size.
// The gif format does not tell us the length of the compressed data beforehand.
int minCodeSize = stream.ReadByte();
if (LzwDecoder.IsValidMinCodeSize(minCodeSize))
{
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream, minCodeSize);
lzwDecoder.SkipIndices(this.imageDescriptor.Width * this.imageDescriptor.Height);
}
ImageFrameMetadata currentFrame = new();
frameMetadata.Add(currentFrame);
this.SetFrameMetadata(currentFrame);
previousFrame = currentFrame;
// Skip any remaining blocks
SkipBlock(stream);
}
/// <summary>
/// Restores the current frame area to the background.
/// </summary>
@ -587,7 +682,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
return;
}
var interest = Rectangle.Intersect(frame.Bounds(), this.restoreArea.Value);
Rectangle interest = Rectangle.Intersect(frame.Bounds(), this.restoreArea.Value);
Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(interest);
pixelRegion.Clear();
@ -595,7 +690,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
/// <summary>
/// Sets the frames metadata.
/// Sets the metadata for the image frame.
/// </summary>
/// <param name="meta">The metadata.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -628,13 +723,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="stream">The stream containing image data. </param>
private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream stream)
{
this.stream = stream;
// Skip the identifier
this.stream.Skip(6);
this.ReadLogicalScreenDescriptor();
stream.Skip(6);
this.ReadLogicalScreenDescriptor(stream);
var meta = new ImageMetadata();
ImageMetadata meta = new();
// The Pixel Aspect Ratio is defined to be the quotient of the pixel's
// width over its height. The value range in this field allows
@ -671,16 +764,26 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
{
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.gifMetadata.GlobalColorTableLength = globalColorTableLength;
if (globalColorTableLength > 0)
{
this.globalColorTable = this.MemoryAllocator.Allocate<byte>(globalColorTableLength, AllocationOptions.Clean);
this.globalColorTable = this.memoryAllocator.Allocate<byte>(globalColorTableLength, AllocationOptions.Clean);
// Read the global color table data from the stream
stream.Read(this.globalColorTable.GetSpan());
// Read the global color table data from the stream and preserve it in the gif metadata
Span<byte> globalColorTableSpan = this.globalColorTable.GetSpan();
stream.Read(globalColorTableSpan);
//Color[] colorTable = new Color[this.logicalScreenDescriptor.GlobalColorTableSize];
//ReadOnlySpan<Rgb24> rgbTable = MemoryMarshal.Cast<byte, Rgb24>(globalColorTableSpan);
//for (int i = 0; i < colorTable.Length; i++)
//{
// colorTable[i] = new Color(rgbTable[i]);
//}
//this.gifMetadata.GlobalColorTable = colorTable;
}
}
//this.gifMetadata.BackgroundColorIndex = this.logicalScreenDescriptor.BackgroundColorIndex;
}
}
}

7
src/ImageSharp/Formats/Gif/GifThrowHelper.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Gif
@ -24,5 +25,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// if no inner exception is specified.</param>
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) => throw new InvalidImageContentException(errorMessage, innerException);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoHeader() => throw new InvalidImageContentException("Gif image does not contain a Logical Screen Descriptor.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoData() => throw new InvalidImageContentException("Unable to read Gif image data");
}
}

5
src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs

@ -19,5 +19,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets the decoding mode for multi-frame images.
/// </summary>
FrameDecodingMode DecodingMode { get; }
/// <summary>
/// Gets or sets the maximum number of gif frames.
/// </summary>
uint MaxFrames { get; set; }
}
}

299
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -45,10 +45,29 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
private readonly IMemoryOwner<int> suffix;
/// <summary>
/// The scratch buffer for reading data blocks.
/// </summary>
private readonly IMemoryOwner<byte> scratchBuffer;
/// <summary>
/// The pixel stack buffer.
/// </summary>
private readonly IMemoryOwner<int> pixelStack;
private readonly int minCodeSize;
private readonly int clearCode;
private readonly int endCode;
private int code;
private int codeSize;
private int codeMask;
private int availableCode;
private int oldCode = NullCode;
private int bits;
private int top;
private int count;
private int bufferIndex;
private int data;
private int first;
/// <summary>
/// Initializes a new instance of the <see cref="LzwDecoder"/> class
@ -56,185 +75,280 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
/// <param name="stream">The stream to read from.</param>
/// <param name="minCodeSize">The minimum code size.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream)
public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream, int minCodeSize)
{
this.stream = stream ?? throw new ArgumentNullException(nameof(stream));
this.prefix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.suffix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.pixelStack = memoryAllocator.Allocate<int>(MaxStackSize + 1, AllocationOptions.Clean);
this.scratchBuffer = memoryAllocator.Allocate<byte>(byte.MaxValue, AllocationOptions.None);
this.minCodeSize = minCodeSize;
// Calculate the clear code. The value of the clear code is 2 ^ minCodeSize
this.clearCode = 1 << minCodeSize;
this.codeSize = minCodeSize + 1;
this.codeMask = (1 << this.codeSize) - 1;
this.endCode = this.clearCode + 1;
this.availableCode = this.clearCode + 2;
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
for (this.code = 0; this.code < this.clearCode; this.code++)
{
Unsafe.Add(ref suffixRef, this.code) = (byte)this.code;
}
}
/// <summary>
/// Decodes and decompresses all pixel indices from the stream.
/// Gets a value indicating whether the minimum code size is valid.
/// </summary>
/// <param name="minCodeSize">Minimum code size of the data.</param>
/// <param name="pixels">The pixel array to decode to.</param>
public void DecodePixels(int minCodeSize, Buffer2D<byte> pixels)
/// <param name="minCodeSize">The minimum code size.</param>
/// <returns>
/// <see langword="true"/> if the minimum code size is valid; otherwise, <see langword="false"/>.
/// </returns>
public static bool IsValidMinCodeSize(int minCodeSize)
{
// Calculate the clear code. The value of the clear code is 2 ^ minCodeSize
int clearCode = 1 << minCodeSize;
// It is possible to specify a larger LZW minimum code size than the palette length in bits
// which may leave a gap in the codes where no colors are assigned.
// http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp#lzw_compression
int clearCode = 1 << minCodeSize;
if (minCodeSize < 2 || minCodeSize > MaximumLzwBits || clearCode > MaxStackSize)
{
// Don't attempt to decode the frame indices.
// Theoretically we could determine a min code size from the length of the provided
// color palette but we won't bother since the image is most likely corrupted.
return;
return false;
}
// The resulting index table length.
int width = pixels.Width;
int height = pixels.Height;
int length = width * height;
return true;
}
int codeSize = minCodeSize + 1;
/// <summary>
/// Decodes and decompresses all pixel indices for a single row from the stream, assigning the pixel values to the buffer.
/// </summary>
/// <param name="indices">The pixel indices array to decode to.</param>
public void DecodePixelRow(Span<byte> indices)
{
indices.Clear();
// Calculate the end code
int endCode = clearCode + 1;
ref byte pixelsRowRef = ref MemoryMarshal.GetReference(indices);
ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan());
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan());
Span<byte> buffer = this.scratchBuffer.GetSpan();
// Calculate the available code.
int availableCode = clearCode + 2;
int x = 0;
int xyz = 0;
while (xyz < indices.Length)
{
if (this.top == 0)
{
if (this.bits < this.codeSize)
{
// Load bytes until there are enough bits for a code.
if (this.count == 0)
{
// Read a new data block.
this.count = this.ReadBlock(buffer);
if (this.count == 0)
{
break;
}
// 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;
this.bufferIndex = 0;
}
int top = 0;
int count = 0;
int bi = 0;
int xyz = 0;
this.data += buffer[this.bufferIndex] << this.bits;
int data = 0;
int first = 0;
this.bits += 8;
this.bufferIndex++;
this.count--;
continue;
}
ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan());
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan());
// Get the next code
this.code = this.data & this.codeMask;
this.data >>= this.codeSize;
this.bits -= this.codeSize;
for (code = 0; code < clearCode; code++)
{
Unsafe.Add(ref suffixRef, code) = (byte)code;
// Interpret the code
if (this.code > this.availableCode || this.code == this.endCode)
{
break;
}
if (this.code == this.clearCode)
{
// Reset the decoder
this.codeSize = this.minCodeSize + 1;
this.codeMask = (1 << this.codeSize) - 1;
this.availableCode = this.clearCode + 2;
this.oldCode = NullCode;
continue;
}
if (this.oldCode == NullCode)
{
Unsafe.Add(ref pixelStackRef, this.top++) = Unsafe.Add(ref suffixRef, this.code);
this.oldCode = this.code;
this.first = this.code;
continue;
}
int inCode = this.code;
if (this.code == this.availableCode)
{
Unsafe.Add(ref pixelStackRef, this.top++) = (byte)this.first;
this.code = this.oldCode;
}
while (this.code > this.clearCode)
{
Unsafe.Add(ref pixelStackRef, this.top++) = Unsafe.Add(ref suffixRef, this.code);
this.code = Unsafe.Add(ref prefixRef, this.code);
}
int suffixCode = Unsafe.Add(ref suffixRef, this.code);
this.first = suffixCode;
Unsafe.Add(ref pixelStackRef, this.top++) = suffixCode;
// Fix for Gifs that have "deferred clear code" as per here :
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918
if (this.availableCode < MaxStackSize)
{
Unsafe.Add(ref prefixRef, this.availableCode) = this.oldCode;
Unsafe.Add(ref suffixRef, this.availableCode) = this.first;
this.availableCode++;
if (this.availableCode == this.codeMask + 1 && this.availableCode < MaxStackSize)
{
this.codeSize++;
this.codeMask = (1 << this.codeSize) - 1;
}
}
this.oldCode = inCode;
}
// Pop a pixel off the pixel stack.
this.top--;
// Clear missing pixels
xyz++;
Unsafe.Add(ref pixelsRowRef, x++) = (byte)Unsafe.Add(ref pixelStackRef, this.top);
}
}
Span<byte> buffer = stackalloc byte[byte.MaxValue];
/// <summary>
/// Decodes and decompresses all pixel indices from the stream allowing skipping of the data.
/// </summary>
/// <param name="length">The resulting index table length.</param>
public void SkipIndices(int length)
{
ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan());
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan());
Span<byte> buffer = this.scratchBuffer.GetSpan();
int y = 0;
int x = 0;
int rowMax = width;
ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(y));
int xyz = 0;
while (xyz < length)
{
// Reset row reference.
if (xyz == rowMax)
{
x = 0;
pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(++y));
rowMax = (y * width) + width;
}
if (top == 0)
if (this.top == 0)
{
if (bits < codeSize)
if (this.bits < this.codeSize)
{
// Load bytes until there are enough bits for a code.
if (count == 0)
if (this.count == 0)
{
// Read a new data block.
count = this.ReadBlock(buffer);
if (count == 0)
this.count = this.ReadBlock(buffer);
if (this.count == 0)
{
break;
}
bi = 0;
this.bufferIndex = 0;
}
data += buffer[bi] << bits;
this.data += buffer[this.bufferIndex] << this.bits;
bits += 8;
bi++;
count--;
this.bits += 8;
this.bufferIndex++;
this.count--;
continue;
}
// Get the next code
code = data & codeMask;
data >>= codeSize;
bits -= codeSize;
this.code = this.data & this.codeMask;
this.data >>= this.codeSize;
this.bits -= this.codeSize;
// Interpret the code
if (code > availableCode || code == endCode)
if (this.code > this.availableCode || this.code == this.endCode)
{
break;
}
if (code == clearCode)
if (this.code == this.clearCode)
{
// Reset the decoder
codeSize = minCodeSize + 1;
codeMask = (1 << codeSize) - 1;
availableCode = clearCode + 2;
oldCode = NullCode;
this.codeSize = this.minCodeSize + 1;
this.codeMask = (1 << this.codeSize) - 1;
this.availableCode = this.clearCode + 2;
this.oldCode = NullCode;
continue;
}
if (oldCode == NullCode)
if (this.oldCode == NullCode)
{
Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code);
oldCode = code;
first = code;
Unsafe.Add(ref pixelStackRef, this.top++) = Unsafe.Add(ref suffixRef, this.code);
this.oldCode = this.code;
this.first = this.code;
continue;
}
int inCode = code;
if (code == availableCode)
int inCode = this.code;
if (this.code == this.availableCode)
{
Unsafe.Add(ref pixelStackRef, top++) = (byte)first;
Unsafe.Add(ref pixelStackRef, this.top++) = (byte)this.first;
code = oldCode;
this.code = this.oldCode;
}
while (code > clearCode)
while (this.code > this.clearCode)
{
Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code);
code = Unsafe.Add(ref prefixRef, code);
Unsafe.Add(ref pixelStackRef, this.top++) = Unsafe.Add(ref suffixRef, this.code);
this.code = Unsafe.Add(ref prefixRef, this.code);
}
int suffixCode = Unsafe.Add(ref suffixRef, code);
first = suffixCode;
Unsafe.Add(ref pixelStackRef, top++) = suffixCode;
int suffixCode = Unsafe.Add(ref suffixRef, this.code);
this.first = suffixCode;
Unsafe.Add(ref pixelStackRef, this.top++) = suffixCode;
// Fix for Gifs that have "deferred clear code" as per here :
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918
if (availableCode < MaxStackSize)
if (this.availableCode < MaxStackSize)
{
Unsafe.Add(ref prefixRef, availableCode) = oldCode;
Unsafe.Add(ref suffixRef, availableCode) = first;
availableCode++;
if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
Unsafe.Add(ref prefixRef, this.availableCode) = this.oldCode;
Unsafe.Add(ref suffixRef, this.availableCode) = this.first;
this.availableCode++;
if (this.availableCode == this.codeMask + 1 && this.availableCode < MaxStackSize)
{
codeSize++;
codeMask = (1 << codeSize) - 1;
this.codeSize++;
this.codeMask = (1 << this.codeSize) - 1;
}
}
oldCode = inCode;
this.oldCode = inCode;
}
// Pop a pixel off the pixel stack.
top--;
this.top--;
// Clear missing pixels
xyz++;
Unsafe.Add(ref pixelsRowRef, x++) = (byte)Unsafe.Add(ref pixelStackRef, top);
}
}
@ -267,6 +381,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.prefix.Dispose();
this.suffix.Dispose();
this.pixelStack.Dispose();
this.scratchBuffer.Dispose();
}
}
}

2
src/ImageSharp/Formats/Jpeg/Components/Quantization.cs

@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
quality = (int)Math.Round(5000.0 / sumPercent);
}
return quality;
return Numerics.Clamp(quality, MinQualityFactor, MaxQualityFactor);
}
/// <summary>

11
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -183,6 +183,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
}
// https://github.com/SixLabors/ImageSharp/issues/2758
[Theory]
[WithFile(TestImages.Gif.Issues.Issue2758, PixelTypes.Rgba32)]
public void Issue2758_BadDescriptorDimensions<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
image.DebugSaveMultiFrame(provider);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
}
// https://github.com/SixLabors/ImageSharp/issues/405
[Theory]
[WithFile(TestImages.Gif.Issues.BadAppExtLength, PixelTypes.Rgba32)]

26
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
@ -364,6 +365,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
[Theory(Skip = "2.1 JPEG decoder detects this image as invalid.")]
[WithFile(TestImages.Jpeg.Issues.Issue2758, PixelTypes.L8)]
public void Issue2758_DecodeWorks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
Assert.Equal(59787, image.Width);
Assert.Equal(511, image.Height);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
// Quality determination should be between 1-100.
Assert.Equal(15, meta.LuminanceQuality);
Assert.Equal(1, meta.ChrominanceQuality);
// We want to test the encoder to ensure the determined values can be encoded but not by encoding
// the full size image as it would be too slow.
// We will crop the image to a smaller size and then encode it.
image.Mutate(x => x.Crop(new(0, 0, 100, 100)));
using MemoryStream ms = new();
image.Save(ms, new JpegEncoder());
}
private static void VerifyEncodedStrings(ExifProfile exif)
{
Assert.NotNull(exif);

2
tests/ImageSharp.Tests/TestImages.cs

@ -269,6 +269,7 @@ namespace SixLabors.ImageSharp.Tests
public const string ValidExifArgumentNullExceptionOnEncode = "Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg";
public const string Issue2133DeduceColorSpace = "Jpg/issues/Issue2133.jpg";
public const string HangBadScan = "Jpg/issues/Hang_C438A851.jpg";
public const string Issue2758 = "Jpg/issues/issue-2758.jpg";
public static class Fuzz
{
@ -463,6 +464,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Issue1962NoColorTable = "Gif/issues/issue1962_tiniest_gif_1st.gif";
public const string Issue2012EmptyXmp = "Gif/issues/issue2012_Stronghold-Crusader-Extreme-Cover.gif";
public const string Issue2012BadMinCode = "Gif/issues/issue2012_drona1.gif";
public const string Issue2758 = "Gif/issues/issue_2758.gif";
}
public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 };

4
tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3a24c066895fd3a76649da376485cbc1912d6a3ae15369575f523e66364b3b6
size 141563
oid sha256:588d055a93c7b4fdb62e8b77f3ae08753a9e8990151cb0523f5e761996189b70
size 142244

3
tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/00.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f39b23217f1d095eeb8eed5ccea36be813c307a60ef4b1942e9f74028451c38
size 81944

3
tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/01.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f39b23217f1d095eeb8eed5ccea36be813c307a60ef4b1942e9f74028451c38
size 81944

4
tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:489642f0c81fd12e97007fe6feb11b0e93e351199a922ce038069a3782ad0722
size 135
oid sha256:5016a323018f09e292165ad5392d82dcbad5e79c2b6b93aff3322dffff80b309
size 126

3
tests/Images/Input/Gif/issues/issue_2758.gif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:13e9374181c7536d1d2ecb514753a5290c0ec06234ca079c6c8c8a832586b668
size 199

3
tests/Images/Input/Jpg/issues/issue-2758.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f32a238b57b7073f7442f8ae7efd6ba3ae4cda30d57e6666fb8a1eaa27108558
size 1412
Loading…
Cancel
Save