diff --git a/ImageSharp.sln b/ImageSharp.sln
index 17d293b434..5428f3394d 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -142,6 +142,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gif", "Gif", "{EE3FB0B3-1C3
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{BF8DFDC1-CEE5-4A37-B216-D3085360C776}"
ProjectSection(SolutionItems) = preProject
+ tests\Images\Input\Gif\issues\bugzilla-55918.gif = tests\Images\Input\Gif\issues\bugzilla-55918.gif
+ tests\Images\Input\Gif\issues\issue1505_argumentoutofrange.png = tests\Images\Input\Gif\issues\issue1505_argumentoutofrange.png
+ tests\Images\Input\Gif\issues\issue1530.gif = tests\Images\Input\Gif\issues\issue1530.gif
+ tests\Images\Input\Gif\issues\issue1668_invalidcolorindex.gif = tests\Images\Input\Gif\issues\issue1668_invalidcolorindex.gif
+ tests\Images\Input\Gif\issues\issue1962_tiniest_gif_1st.gif = tests\Images\Input\Gif\issues\issue1962_tiniest_gif_1st.gif
+ tests\Images\Input\Gif\issues\issue2012_drona1.gif = tests\Images\Input\Gif\issues\issue2012_drona1.gif
+ tests\Images\Input\Gif\issues\issue2012_Stronghold-Crusader-Extreme-Cover.gif = tests\Images\Input\Gif\issues\issue2012_Stronghold-Crusader-Extreme-Cover.gif
tests\Images\Input\Gif\issues\issue403_baddescriptorwidth.gif = tests\Images\Input\Gif\issues\issue403_baddescriptorwidth.gif
tests\Images\Input\Gif\issues\issue405_badappextlength252-2.gif = tests\Images\Input\Gif\issues\issue405_badappextlength252-2.gif
tests\Images\Input\Gif\issues\issue405_badappextlength252.gif = tests\Images\Input\Gif\issues\issue405_badappextlength252.gif
diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs
index 1193eccee3..8746989b38 100644
--- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs
+++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp
internal static class StreamExtensions
{
///
- /// Writes data from a stream into the provided buffer.
+ /// Writes data from a stream from the provided buffer.
///
/// The stream.
/// The buffer.
diff --git a/src/ImageSharp/Compression/Zlib/Adler32.cs b/src/ImageSharp/Compression/Zlib/Adler32.cs
index 7eb3f4516f..648339230c 100644
--- a/src/ImageSharp/Compression/Zlib/Adler32.cs
+++ b/src/ImageSharp/Compression/Zlib/Adler32.cs
@@ -3,9 +3,13 @@
using System;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
+#if NET5_0_OR_GREATER
+using System.Runtime.Intrinsics.Arm;
+#endif
#endif
#pragma warning disable IDE0007 // Use implicit type
@@ -23,14 +27,17 @@ internal static class Adler32
public const uint SeedValue = 1U;
// Largest prime smaller than 65536
- private const uint BASE = 65521;
+ private const uint Base = 65521;
// NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
- private const uint NMAX = 5552;
+ private const uint Nmax = 5552;
#if SUPPORTS_RUNTIME_INTRINSICS
private const int MinBufferSize = 64;
+ // Data will be processed in blocks of 32 bytes.
+ private const int BlockSize = 1 << 5;
+
// The C# compiler emits this as a compile-time constant embedded in the PE file.
private static ReadOnlySpan Tap1Tap2 => new byte[]
{
@@ -67,11 +74,14 @@ public static uint Calculate(uint adler, ReadOnlySpan buffer)
{
return CalculateSse(adler, buffer);
}
-
- return CalculateScalar(adler, buffer);
-#else
- return CalculateScalar(adler, buffer);
+#if NET5_0_OR_GREATER
+ if (AdvSimd.IsSupported && buffer.Length >= MinBufferSize)
+ {
+ return CalculateArm(adler, buffer);
+ }
+#endif
#endif
+ return CalculateScalar(adler, buffer);
}
// Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c
@@ -83,19 +93,15 @@ private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer)
uint s2 = (adler >> 16) & 0xFFFF;
// Process the data in blocks.
- const int BLOCK_SIZE = 1 << 5;
-
uint length = (uint)buffer.Length;
- uint blocks = length / BLOCK_SIZE;
- length -= blocks * BLOCK_SIZE;
+ uint blocks = length / BlockSize;
+ length -= blocks * BlockSize;
- int index = 0;
fixed (byte* bufferPtr = buffer)
{
fixed (byte* tapPtr = Tap1Tap2)
{
- index += (int)blocks * BLOCK_SIZE;
- var localBufferPtr = bufferPtr;
+ byte* localBufferPtr = bufferPtr;
// _mm_setr_epi8 on x86
Vector128 tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
@@ -105,7 +111,7 @@ private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer)
while (blocks > 0)
{
- uint n = NMAX / BLOCK_SIZE; /* The NMAX constraint. */
+ uint n = Nmax / BlockSize; /* The NMAX constraint. */
if (n > blocks)
{
n = blocks;
@@ -138,7 +144,7 @@ private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer)
Vector128 mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32());
- localBufferPtr += BLOCK_SIZE;
+ localBufferPtr += BlockSize;
}
while (--n > 0);
@@ -158,52 +164,156 @@ private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer)
s2 = v_s2.ToScalar();
// Reduce.
- s1 %= BASE;
- s2 %= BASE;
+ s1 %= Base;
+ s2 %= Base;
}
if (length > 0)
{
- if (length >= 16)
- {
- s2 += s1 += localBufferPtr[0];
- s2 += s1 += localBufferPtr[1];
- s2 += s1 += localBufferPtr[2];
- s2 += s1 += localBufferPtr[3];
- s2 += s1 += localBufferPtr[4];
- s2 += s1 += localBufferPtr[5];
- s2 += s1 += localBufferPtr[6];
- s2 += s1 += localBufferPtr[7];
- s2 += s1 += localBufferPtr[8];
- s2 += s1 += localBufferPtr[9];
- s2 += s1 += localBufferPtr[10];
- s2 += s1 += localBufferPtr[11];
- s2 += s1 += localBufferPtr[12];
- s2 += s1 += localBufferPtr[13];
- s2 += s1 += localBufferPtr[14];
- s2 += s1 += localBufferPtr[15];
-
- localBufferPtr += 16;
- length -= 16;
- }
+ HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
+ }
- while (length-- > 0)
- {
- s2 += s1 += *localBufferPtr++;
- }
+ return s1 | (s2 << 16);
+ }
+ }
+ }
- if (s1 >= BASE)
- {
- s1 -= BASE;
- }
+ private static unsafe void HandleLeftOver(byte* localBufferPtr, uint length, ref uint s1, ref uint s2)
+ {
+ if (length >= 16)
+ {
+ s2 += s1 += localBufferPtr[0];
+ s2 += s1 += localBufferPtr[1];
+ s2 += s1 += localBufferPtr[2];
+ s2 += s1 += localBufferPtr[3];
+ s2 += s1 += localBufferPtr[4];
+ s2 += s1 += localBufferPtr[5];
+ s2 += s1 += localBufferPtr[6];
+ s2 += s1 += localBufferPtr[7];
+ s2 += s1 += localBufferPtr[8];
+ s2 += s1 += localBufferPtr[9];
+ s2 += s1 += localBufferPtr[10];
+ s2 += s1 += localBufferPtr[11];
+ s2 += s1 += localBufferPtr[12];
+ s2 += s1 += localBufferPtr[13];
+ s2 += s1 += localBufferPtr[14];
+ s2 += s1 += localBufferPtr[15];
+
+ localBufferPtr += 16;
+ length -= 16;
+ }
+
+ while (length-- > 0)
+ {
+ s2 += s1 += *localBufferPtr++;
+ }
+
+ if (s1 >= Base)
+ {
+ s1 -= Base;
+ }
- s2 %= BASE;
+ s2 %= Base;
+ }
+
+#if NET5_0_OR_GREATER
+
+ // Based on: https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c
+ [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
+ private static unsafe uint CalculateArm(uint adler, ReadOnlySpan buffer)
+ {
+ // Split Adler-32 into component sums.
+ uint s1 = adler & 0xFFFF;
+ uint s2 = (adler >> 16) & 0xFFFF;
+ uint length = (uint)buffer.Length;
+
+ // Process the data in blocks.
+ long blocks = length / BlockSize;
+ length -= (uint)(blocks * BlockSize);
+ fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
+ {
+ byte* localBufferPtr = bufferPtr;
+
+ while (blocks != 0)
+ {
+ uint n = Nmax / BlockSize;
+ if (n > blocks)
+ {
+ n = (uint)blocks;
}
- return s1 | (s2 << 16);
+ blocks -= n;
+
+ // Process n blocks of data. At most nMax data bytes can be
+ // processed before s2 must be reduced modulo Base.
+ Vector128 vs1 = Vector128.Zero;
+ Vector128 vs2 = vs1.WithElement(3, s1 * n);
+ Vector128 vColumnSum1 = Vector128.Zero;
+ Vector128 vColumnSum2 = Vector128.Zero;
+ Vector128 vColumnSum3 = Vector128.Zero;
+ Vector128 vColumnSum4 = Vector128.Zero;
+
+ do
+ {
+ // Load 32 input bytes.
+ Vector128 bytes1 = AdvSimd.LoadVector128(localBufferPtr).AsUInt16();
+ Vector128 bytes2 = AdvSimd.LoadVector128(localBufferPtr + 0x10).AsUInt16();
+
+ // Add previous block byte sum to v_s2.
+ vs2 = AdvSimd.Add(vs2, vs1);
+
+ // Horizontally add the bytes for s1.
+ vs1 = AdvSimd.AddPairwiseWideningAndAdd(
+ vs1.AsUInt32(),
+ AdvSimd.AddPairwiseWideningAndAdd(AdvSimd.AddPairwiseWidening(bytes1.AsByte()).AsUInt16(), bytes2.AsByte()));
+
+ // Vertically add the bytes for s2.
+ vColumnSum1 = AdvSimd.AddWideningLower(vColumnSum1, bytes1.GetLower().AsByte());
+ vColumnSum2 = AdvSimd.AddWideningLower(vColumnSum2, bytes1.GetUpper().AsByte());
+ vColumnSum3 = AdvSimd.AddWideningLower(vColumnSum3, bytes2.GetLower().AsByte());
+ vColumnSum4 = AdvSimd.AddWideningLower(vColumnSum4, bytes2.GetUpper().AsByte());
+
+ localBufferPtr += BlockSize;
+ }
+ while (--n > 0);
+
+ vs2 = AdvSimd.ShiftLeftLogical(vs2, 5);
+
+ // Multiply-add bytes by [ 32, 31, 30, ... ] for s2.
+ vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum1.GetLower(), Vector64.Create((ushort)32, 31, 30, 29));
+ vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum1.GetUpper(), Vector64.Create((ushort)28, 27, 26, 25));
+ vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum2.GetLower(), Vector64.Create((ushort)24, 23, 22, 21));
+ vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum2.GetUpper(), Vector64.Create((ushort)20, 19, 18, 17));
+ vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum3.GetLower(), Vector64.Create((ushort)16, 15, 14, 13));
+ vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum3.GetUpper(), Vector64.Create((ushort)12, 11, 10, 9));
+ vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum4.GetLower(), Vector64.Create((ushort)8, 7, 6, 5));
+ vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum4.GetUpper(), Vector64.Create((ushort)4, 3, 2, 1));
+
+ // Sum epi32 ints v_s1(s2) and accumulate in s1(s2).
+ Vector64 sum1 = AdvSimd.AddPairwise(vs1.GetLower(), vs1.GetUpper());
+ Vector64 sum2 = AdvSimd.AddPairwise(vs2.GetLower(), vs2.GetUpper());
+ Vector64 s1s2 = AdvSimd.AddPairwise(sum1, sum2);
+
+ // Store the results.
+ s1 += AdvSimd.Extract(s1s2, 0);
+ s2 += AdvSimd.Extract(s1s2, 1);
+
+ // Reduce.
+ s1 %= Base;
+ s2 %= Base;
+ }
+
+ if (length != 0)
+ {
+ HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
}
}
+
+ // Return the recombined sums.
+ return s1 | (s2 << 16);
}
+
+#endif
#endif
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
@@ -211,16 +321,15 @@ private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan buffer
{
uint s1 = adler & 0xFFFF;
uint s2 = (adler >> 16) & 0xFFFF;
- uint k;
- fixed (byte* bufferPtr = buffer)
+ fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
{
- var localBufferPtr = bufferPtr;
+ byte* localBufferPtr = bufferPtr;
uint length = (uint)buffer.Length;
while (length > 0)
{
- k = length < NMAX ? length : NMAX;
+ uint k = length < Nmax ? length : Nmax;
length -= k;
while (k >= 16)
@@ -251,8 +360,8 @@ private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan buffer
s2 += s1 += *localBufferPtr++;
}
- s1 %= BASE;
- s2 %= BASE;
+ s1 %= Base;
+ s2 %= Base;
}
return (s2 << 16) | s1;
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index 72e3ed144c..16dca3324f 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -265,10 +265,14 @@ private void ReadApplicationExtension()
this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize);
bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes);
- if (isXmp)
+ if (isXmp && !this.IgnoreMetadata)
{
- var extension = GifXmpApplicationExtension.Read(this.stream);
- this.metadata.XmpProfile = new XmpProfile(extension.Data);
+ var extension = GifXmpApplicationExtension.Read(this.stream, this.MemoryAllocator);
+ if (extension.Data.Length > 0)
+ {
+ this.metadata.XmpProfile = new XmpProfile(extension.Data);
+ }
+
return;
}
else
@@ -374,8 +378,8 @@ private void ReadFrame(ref Image image, ref ImageFrame p
}
indices = this.Configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
-
this.ReadFrameIndices(indices);
+
Span rawColorTable = default;
if (localColorTable != null)
{
@@ -406,9 +410,9 @@ private void ReadFrame(ref Image image, ref ImageFrame p
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(Buffer2D indices)
{
- int dataSize = this.stream.ReadByte();
+ int minCodeSize = this.stream.ReadByte();
using var lzwDecoder = new LzwDecoder(this.Configuration.MemoryAllocator, this.stream);
- lzwDecoder.DecodePixels(dataSize, indices);
+ lzwDecoder.DecodePixels(minCodeSize, indices);
}
///
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index a21b050a81..da5b1cb236 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -123,7 +123,8 @@ public void Encode(Image image, Stream stream, CancellationToken
this.WriteComments(gifMetadata, stream);
// Write application extensions.
- this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, metadata.XmpProfile);
+ XmpProfile xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile;
+ this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, xmpProfile);
if (useGlobalTable)
{
@@ -137,7 +138,6 @@ public void Encode(Image image, Stream stream, CancellationToken
// Clean up.
quantized.Dispose();
- // TODO: Write extension etc
stream.WriteByte(GifConstants.EndIntroducer);
}
@@ -428,26 +428,31 @@ private void WriteExtension(TGifExtension extension, Stream strea
where TGifExtension : struct, IGifExtension
{
IMemoryOwner owner = null;
- Span buffer;
+ Span extensionBuffer;
int extensionSize = extension.ContentLength;
- if (extensionSize > this.buffer.Length - 3)
+
+ if (extensionSize == 0)
+ {
+ return;
+ }
+ else if (extensionSize > this.buffer.Length - 3)
{
owner = this.memoryAllocator.Allocate(extensionSize + 3);
- buffer = owner.GetSpan();
+ extensionBuffer = owner.GetSpan();
}
else
{
- buffer = this.buffer;
+ extensionBuffer = this.buffer;
}
- buffer[0] = GifConstants.ExtensionIntroducer;
- buffer[1] = extension.Label;
+ extensionBuffer[0] = GifConstants.ExtensionIntroducer;
+ extensionBuffer[1] = extension.Label;
- extension.WriteTo(buffer.Slice(2));
+ extension.WriteTo(extensionBuffer.Slice(2));
- buffer[extensionSize + 2] = GifConstants.Terminator;
+ extensionBuffer[extensionSize + 2] = GifConstants.Terminator;
- stream.Write(buffer, 0, extensionSize + 3);
+ stream.Write(extensionBuffer, 0, extensionSize + 3);
owner?.Dispose();
}
diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs
index 68227db53d..2a07200016 100644
--- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs
+++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs
@@ -64,21 +64,30 @@ public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream)
///
/// Decodes and decompresses all pixel indices from the stream.
///
- /// Size of the data.
+ /// Minimum code size of the data.
/// The pixel array to decode to.
- public void DecodePixels(int dataSize, Buffer2D pixels)
+ public void DecodePixels(int minCodeSize, Buffer2D pixels)
{
- Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize));
+ // 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
+ if (minCodeSize < 2 || 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.
+ GifThrowHelper.ThrowInvalidImageContentException("Gif Image does not contain a valid LZW minimum code.");
+ }
// The resulting index table length.
int width = pixels.Width;
int height = pixels.Height;
int length = width * height;
- // Calculate the clear code. The value of the clear code is 2 ^ dataSize
- int clearCode = 1 << dataSize;
-
- int codeSize = dataSize + 1;
+ int codeSize = minCodeSize + 1;
// Calculate the end code
int endCode = clearCode + 1;
@@ -165,7 +174,7 @@ public void DecodePixels(int dataSize, Buffer2D pixels)
if (code == clearCode)
{
// Reset the decoder
- codeSize = dataSize + 1;
+ codeSize = minCodeSize + 1;
codeMask = (1 << codeSize) - 1;
availableCode = clearCode + 2;
oldCode = NullCode;
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
index 801849c9b8..8476336942 100644
--- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
+++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
@@ -71,13 +71,11 @@ public int WriteTo(Span buffer)
dest = this;
- return 5;
+ return ((IGifExtension)this).ContentLength;
}
public static GifGraphicControlExtension Parse(ReadOnlySpan buffer)
- {
- return MemoryMarshal.Cast(buffer)[0];
- }
+ => MemoryMarshal.Cast(buffer)[0];
public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false)
{
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
index 2c7bed6115..c9e8033dbd 100644
--- a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
+++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
@@ -40,7 +40,7 @@ public int WriteTo(Span buffer)
// 0 means loop indefinitely. Count is set as play n + 1 times.
BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.RepeatCount);
- return 16; // Length - Introducer + Label + Terminator.
+ return this.ContentLength; // Length - Introducer + Label + Terminator.
}
}
}
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs
index 236508fe99..8c396e7fb3 100644
--- a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs
+++ b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs
@@ -2,9 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Collections.Generic;
using System.IO;
-using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
+using SixLabors.ImageSharp.IO;
+using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Gif
{
@@ -14,7 +14,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
public byte Label => GifConstants.ApplicationExtensionLabel;
- public int ContentLength => this.Data.Length + 269; // 12 + Data Length + 1 + 256
+ // size : 1
+ // identifier : 11
+ // magic trailer : 257
+ public int ContentLength => (this.Data.Length > 0) ? this.Data.Length + 269 : 0;
///
/// Gets the raw Data.
@@ -25,51 +28,28 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Reads the XMP metadata from the specified stream.
///
/// The stream to read from.
+ /// The memory allocator.
/// The XMP metadata
/// Thrown if the XMP block is not properly terminated.
- public static GifXmpApplicationExtension Read(Stream stream)
+ public static GifXmpApplicationExtension Read(Stream stream, MemoryAllocator allocator)
{
- // Read data in blocks, until an \0 character is encountered.
- // We overshoot, indicated by the terminatorIndex variable.
- const int bufferSize = 256;
- var list = new List();
- int terminationIndex = -1;
- while (terminationIndex < 0)
- {
- byte[] temp = new byte[bufferSize];
- int bytesRead = stream.Read(temp);
- list.Add(temp);
- terminationIndex = Array.IndexOf(temp, (byte)1);
- }
+ byte[] xmpBytes = ReadXmpData(stream, allocator);
- // Pack all the blocks (except magic trailer) into one single array again.
- int dataSize = ((list.Count - 1) * bufferSize) + terminationIndex;
- byte[] buffer = new byte[dataSize];
- Span bufferSpan = buffer;
- int pos = 0;
- for (int j = 0; j < list.Count - 1; j++)
+ // Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF
+ int xmpLength = xmpBytes.Length - 256; // 257 - unread 0x0
+ byte[] buffer = Array.Empty();
+ if (xmpLength > 0)
{
- list[j].CopyTo(bufferSpan.Slice(pos));
- pos += bufferSize;
+ buffer = new byte[xmpLength];
+ xmpBytes.AsSpan(0, xmpLength).CopyTo(buffer);
+ stream.Skip(1); // Skip the terminator.
}
- // Last one only needs the portion until terminationIndex copied over.
- Span lastBytes = list[list.Count - 1];
- lastBytes.Slice(0, terminationIndex).CopyTo(bufferSpan.Slice(pos));
-
- // Skip the remainder of the magic trailer.
- stream.Skip(258 - (bufferSize - terminationIndex));
return new GifXmpApplicationExtension(buffer);
}
public int WriteTo(Span buffer)
{
- int totalSize = this.ContentLength;
- if (buffer.Length < totalSize)
- {
- throw new InsufficientMemoryException("Unable to write XMP metadata to GIF image");
- }
-
int bytesWritten = 0;
buffer[bytesWritten++] = GifConstants.ApplicationBlockSize;
@@ -91,7 +71,28 @@ public int WriteTo(Span buffer)
buffer[bytesWritten++] = 0x00;
- return totalSize;
+ return this.ContentLength;
+ }
+
+ private static byte[] ReadXmpData(Stream stream, MemoryAllocator allocator)
+ {
+ using ChunkedMemoryStream bytes = new(allocator);
+
+ // XMP data doesn't have a fixed length nor is there an indicator of the length.
+ // So we simply read one byte at a time until we hit the 0x0 value at the end
+ // of the magic trailer or the end of the stream.
+ // Using ChunkedMemoryStream reduces the array resize allocation normally associated
+ // with writing from a non fixed-size buffer.
+ while (true)
+ {
+ int b = stream.ReadByte();
+ if (b <= 0)
+ {
+ return bytes.ToArray();
+ }
+
+ bytes.WriteByte((byte)b);
+ }
}
}
}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index f5fc86ee4d..60437e015e 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -429,10 +429,17 @@ private void ReadPhysicalChunk(ImageMetadata metadata, ReadOnlySpan data)
/// The metadata to read to.
/// The data containing physical data.
private void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan data)
+ {
+ if (data.Length < 4)
+ {
+ // Ignore invalid gamma chunks.
+ return;
+ }
- // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
// For example, a gamma of 1/2.2 would be stored as 45455.
- => pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F;
+ // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
+ pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F;
+ }
///
/// Initializes the image and various buffers needed for processing
diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
index 58fa5aca82..26bc566d65 100644
--- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
+++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
@@ -240,6 +240,11 @@ public static void ProcessPaletteScanline(
byte[] paletteAlpha)
where TPixel : unmanaged, IPixel
{
+ if (palette.IsEmpty)
+ {
+ PngThrowHelper.ThrowMissingPalette();
+ }
+
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
index 8700438bd9..ae7d16ec71 100644
--- a/src/ImageSharp/Formats/Png/PngThrowHelper.cs
+++ b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
@@ -21,6 +21,9 @@ public static void ThrowInvalidImageContentException(string errorMessage, Except
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoData() => throw new InvalidImageContentException("PNG Image does not contain a data chunk");
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowMissingPalette() => throw new InvalidImageContentException("PNG Image does not contain a palette chunk");
+
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidChunkType() => throw new InvalidImageContentException("Invalid PNG data.");
diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs
index 16a3cb73d1..0da4ff9f8c 100644
--- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs
+++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs
@@ -1,11 +1,10 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using System.Threading;
using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
@@ -22,7 +21,7 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato
private readonly int poolCapacity;
private readonly UniformUnmanagedMemoryPool.TrimSettings trimSettings;
- private UniformUnmanagedMemoryPool pool;
+ private readonly UniformUnmanagedMemoryPool pool;
private readonly UnmanagedMemoryAllocator nonPoolAllocator;
public UniformUnmanagedMemoryPoolMemoryAllocator(int? maxPoolSizeMegabytes)
@@ -74,6 +73,12 @@ internal UniformUnmanagedMemoryPoolMemoryAllocator(
this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes);
}
+#if NETCOREAPP3_1_OR_GREATER
+ // This delegate allows overriding the method returning the available system memory,
+ // so we can test our workaround for https://github.com/dotnet/runtime/issues/65466
+ internal static Func GetTotalAvailableMemoryBytes { get; set; } = () => GC.GetGCMemoryInfo().TotalAvailableMemoryBytes;
+#endif
+
///
protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes;
@@ -152,8 +157,13 @@ private static long GetDefaultMaxPoolSizeBytes()
// https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327
if (Environment.Is64BitProcess || !RuntimeInformation.FrameworkDescription.StartsWith(".NET 5.0"))
{
- GCMemoryInfo info = GC.GetGCMemoryInfo();
- return info.TotalAvailableMemoryBytes / 8;
+ long total = GetTotalAvailableMemoryBytes();
+
+ // Workaround for https://github.com/dotnet/runtime/issues/65466
+ if (total > 0)
+ {
+ return total / 8;
+ }
}
#endif
diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs
index 89592f776c..4760fa141e 100644
--- a/src/ImageSharp/Metadata/ImageMetadata.cs
+++ b/src/ImageSharp/Metadata/ImageMetadata.cs
@@ -68,6 +68,7 @@ private ImageMetadata(ImageMetadata other)
this.ExifProfile = other.ExifProfile?.DeepClone();
this.IccProfile = other.IccProfile?.DeepClone();
this.IptcProfile = other.IptcProfile?.DeepClone();
+ this.XmpProfile = other.XmpProfile?.DeepClone();
}
///
@@ -175,7 +176,7 @@ public TFormatMetadata GetFormatMetadata(IImageFormat
- public ImageMetadata DeepClone() => new ImageMetadata(this);
+ public ImageMetadata DeepClone() => new(this);
///
/// Synchronizes the profiles with the current metadata.
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
index c8ecdb717b..30bcb72554 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
@@ -259,5 +259,46 @@ public void Issue1962(TestImageProvider provider)
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
+
+ // https://github.com/SixLabors/ImageSharp/issues/2012
+ [Theory]
+ [WithFile(TestImages.Gif.Issues.Issue2012EmptyXmp, PixelTypes.Rgba32)]
+ public void Issue2012EmptyXmp(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage();
+
+ image.DebugSave(provider);
+ image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
+ }
+
+ // https://github.com/SixLabors/ImageSharp/issues/2012
+ [Theory]
+ [WithFile(TestImages.Gif.Issues.Issue2012BadMinCode, PixelTypes.Rgba32)]
+ public void Issue2012BadMinCode(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ Exception ex = Record.Exception(
+ () =>
+ {
+ using Image image = provider.GetImage();
+ image.DebugSave(provider);
+ });
+
+ Assert.NotNull(ex);
+ Assert.Contains("Gif Image does not contain a valid LZW minimum code.", ex.Message);
+ }
+
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=55918
+ [Theory]
+ [WithFile(TestImages.Gif.Issues.DeferredClearCode, PixelTypes.Rgba32)]
+ public void IssueDeferredClearCode(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage();
+
+ image.DebugSave(provider);
+ image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs
index 0886bd84dc..97d9e904e1 100644
--- a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs
@@ -3,6 +3,7 @@
using System;
using SixLabors.ImageSharp.Compression.Zlib;
+using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32;
@@ -15,10 +16,7 @@ public class Adler32Tests
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
- public void ReturnsCorrectWhenEmpty(uint input)
- {
- Assert.Equal(input, Adler32.Calculate(input, default));
- }
+ public void CalculateAdler_ReturnsCorrectWhenEmpty(uint input) => Assert.Equal(input, Adler32.Calculate(input, default));
[Theory]
[InlineData(0)]
@@ -28,24 +26,46 @@ public void ReturnsCorrectWhenEmpty(uint input)
[InlineData(1024 + 15)]
[InlineData(2034)]
[InlineData(4096)]
- public void MatchesReference(int length)
+ public void CalculateAdler_MatchesReference(int length) => CalculateAdlerAndCompareToReference(length);
+
+ private static void CalculateAdlerAndCompareToReference(int length)
{
- var data = GetBuffer(length);
+ // arrange
+ byte[] data = GetBuffer(length);
var adler = new SharpAdler32();
adler.Update(data);
-
long expected = adler.Value;
+
+ // act
long actual = Adler32.Calculate(data);
+ // assert
Assert.Equal(expected, actual);
}
private static byte[] GetBuffer(int length)
{
- var data = new byte[length];
+ byte[] data = new byte[length];
new Random(1).NextBytes(data);
return data;
}
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ [Fact]
+ public void RunCalculateAdlerTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll);
+
+ [Fact]
+ public void RunCalculateAdlerTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.DisableHWIntrinsic);
+
+ private static void RunCalculateAdlerTest()
+ {
+ int[] testData = { 0, 8, 215, 1024, 1024 + 15, 2034, 4096 };
+ for (int i = 0; i < testData.Length; i++)
+ {
+ CalculateAdlerAndCompareToReference(testData[i]);
+ }
+ }
+#endif
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
index c29f8c5891..0af5d99950 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
@@ -265,6 +265,36 @@ public void Decode_MissingDataChunk_ThrowsException(TestImageProvider(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ Exception ex = Record.Exception(
+ () =>
+ {
+ using Image image = provider.GetImage(PngDecoder);
+ image.DebugSave(provider);
+ });
+ Assert.NotNull(ex);
+ Assert.Contains("PNG Image does not contain a palette chunk", ex.Message);
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.Bad.InvalidGammaChunk, PixelTypes.Rgba32)]
+ public void Decode_InvalidGammaChunk_Ignored(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ Exception ex = Record.Exception(
+ () =>
+ {
+ using Image image = provider.GetImage(PngDecoder);
+ image.DebugSave(provider);
+ });
+ Assert.Null(ex);
+ }
+
[Theory]
[WithFile(TestImages.Png.Bad.BitDepthZero, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Bad.BitDepthThree, PixelTypes.Rgba32)]
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
index 7fba86b4fe..eaa7fb5646 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
+using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
@@ -66,7 +67,7 @@ public void IgnoreMetadata_ControlsWhetherIccpIsParsed(TestImageProvider
[Theory]
[WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32, false)]
[WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32, true)]
- public async void IgnoreMetadata_ControlsWhetherXmpIsParsed(TestImageProvider provider, bool ignoreMetadata)
+ public async Task IgnoreMetadata_ControlsWhetherXmpIsParsed(TestImageProvider provider, bool ignoreMetadata)
where TPixel : unmanaged, IPixel
{
var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata };
diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs
index 45a7cc278e..414f991f70 100644
--- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs
+++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs
@@ -379,5 +379,20 @@ private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllo
g1.GetSpan()[0] = 42;
}
}
+
+#if NETCOREAPP3_1_OR_GREATER
+ [Fact]
+ public void Issue2001_NegativeMemoryReportedByGc()
+ {
+ RemoteExecutor.Invoke(RunTest).Dispose();
+
+ static void RunTest()
+ {
+ // Emulate GC.GetGCMemoryInfo() issue https://github.com/dotnet/runtime/issues/65466
+ UniformUnmanagedMemoryPoolMemoryAllocator.GetTotalAvailableMemoryBytes = () => -402354176;
+ _ = MemoryAllocator.Create();
+ }
+ }
+#endif
}
}
diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs
index 81dad699a1..a3c15d0b3f 100644
--- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs
+++ b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs
@@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Text;
+using System.Threading.Tasks;
using System.Xml.Linq;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
@@ -31,7 +32,7 @@ public class XmpProfileTests
[Theory]
[WithFile(TestImages.Gif.Receipt, PixelTypes.Rgba32)]
- public async void ReadXmpMetadata_FromGif_Works(TestImageProvider provider)
+ public async Task ReadXmpMetadata_FromGif_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
using (Image image = await provider.GetImageAsync(GifDecoder))
@@ -45,7 +46,7 @@ public async void ReadXmpMetadata_FromGif_Works(TestImageProvider(TestImageProvider provider)
+ public async Task ReadXmpMetadata_FromJpg_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
using (Image image = await provider.GetImageAsync(JpegDecoder))
@@ -57,7 +58,7 @@ public async void ReadXmpMetadata_FromJpg_Works(TestImageProvider(TestImageProvider provider)
+ public async Task ReadXmpMetadata_FromPng_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
using (Image image = await provider.GetImageAsync(PngDecoder))
@@ -69,7 +70,7 @@ public async void ReadXmpMetadata_FromPng_Works(TestImageProvider(TestImageProvider provider)
+ public async Task ReadXmpMetadata_FromTiff_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
using (Image image = await provider.GetImageAsync(TiffDecoder))
@@ -81,7 +82,7 @@ public async void ReadXmpMetadata_FromTiff_Works(TestImageProvider(TestImageProvider provider)
+ public async Task ReadXmpMetadata_FromWebp_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
using (Image image = await provider.GetImageAsync(WebpDecoder))
@@ -157,7 +158,7 @@ public void WritingJpeg_PreservesXmpProfile()
}
[Fact]
- public async void WritingJpeg_PreservesExtendedXmpProfile()
+ public async Task WritingJpeg_PreservesExtendedXmpProfile()
{
// arrange
var provider = TestImageProvider.File(TestImages.Jpeg.Baseline.ExtendedXmp);
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 8b943194a5..5bce99ce12 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -127,6 +127,9 @@ public static class Bad
public const string MissingDataChunk = "Png/xdtn0g01.png";
public const string WrongCrcDataChunk = "Png/xcsn0g01.png";
public const string CorruptedChunk = "Png/big-corrupted-chunk.png";
+ public const string MissingPaletteChunk1 = "Png/missing_plte.png";
+ public const string MissingPaletteChunk2 = "Png/missing_plte_2.png";
+ public const string InvalidGammaChunk = "Png/length_gama.png";
// Zlib errors.
public const string ZlibOverflow = "Png/zlib-overflow.png";
@@ -448,10 +451,13 @@ public static class Issues
public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif";
public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif";
public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif";
+ public const string DeferredClearCode = "Gif/issues/bugzilla-55918.gif";
public const string Issue1505 = "Gif/issues/issue1505_argumentoutofrange.png";
public const string Issue1530 = "Gif/issues/issue1530.gif";
public const string InvalidColorIndex = "Gif/issues/issue1668_invalidcolorindex.gif";
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 static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 };
diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012EmptyXmp_Rgba32_issue2012_Stronghold-Crusader-Extreme-Cover.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012EmptyXmp_Rgba32_issue2012_Stronghold-Crusader-Extreme-Cover.png
new file mode 100644
index 0000000000..c646eb8605
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012EmptyXmp_Rgba32_issue2012_Stronghold-Crusader-Extreme-Cover.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3ba8295d8a4b087d6c19fbad7e97cef7b5ce1a69b9c4c4f79cee6bc77e41f236
+size 62778
diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueDeferredClearCode_Rgba32_bugzilla-55918.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueDeferredClearCode_Rgba32_bugzilla-55918.png
new file mode 100644
index 0000000000..b5769c2c42
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueDeferredClearCode_Rgba32_bugzilla-55918.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6b33733518b855b25c5e9a1b2f5c93cacf0699a40a459dde795b0ed91a978909
+size 12776
diff --git a/tests/Images/Input/Gif/issues/bugzilla-55918.gif b/tests/Images/Input/Gif/issues/bugzilla-55918.gif
new file mode 100644
index 0000000000..929ea67c35
--- /dev/null
+++ b/tests/Images/Input/Gif/issues/bugzilla-55918.gif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d11148669a093c2e39be62bc3482c5863362d28c03c7f26c5a2386d5de28c339
+size 14551
diff --git a/tests/Images/Input/Gif/issues/issue2012_Stronghold-Crusader-Extreme-Cover.gif b/tests/Images/Input/Gif/issues/issue2012_Stronghold-Crusader-Extreme-Cover.gif
new file mode 100644
index 0000000000..90f0e0f1c5
--- /dev/null
+++ b/tests/Images/Input/Gif/issues/issue2012_Stronghold-Crusader-Extreme-Cover.gif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:97f8fdbabfbd9663bf9940dc33f81edf330b62789d1aa573ae85a520903723e5
+size 77498
diff --git a/tests/Images/Input/Gif/issues/issue2012_drona1.gif b/tests/Images/Input/Gif/issues/issue2012_drona1.gif
new file mode 100644
index 0000000000..803d684874
--- /dev/null
+++ b/tests/Images/Input/Gif/issues/issue2012_drona1.gif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cbb23b2a19e314969c6da99374ae133d834d76c3f0ab9df4a7edc9334bb065e6
+size 10508
diff --git a/tests/Images/Input/Png/length_gama.png b/tests/Images/Input/Png/length_gama.png
new file mode 100644
index 0000000000..caf0fb01d0
--- /dev/null
+++ b/tests/Images/Input/Png/length_gama.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:824766b34739727c722e88611d7b55401452c2970cd433f56e5f9f1b36d6950d
+size 1285
diff --git a/tests/Images/Input/Png/missing_plte.png b/tests/Images/Input/Png/missing_plte.png
new file mode 100644
index 0000000000..0c24883fbd
--- /dev/null
+++ b/tests/Images/Input/Png/missing_plte.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:73fd17a394f8258f4767986bc427c0160277819349c937f18cb29044e7549bc8
+size 506
diff --git a/tests/Images/Input/Png/missing_plte_2.png b/tests/Images/Input/Png/missing_plte_2.png
new file mode 100644
index 0000000000..8fc6580e53
--- /dev/null
+++ b/tests/Images/Input/Png/missing_plte_2.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:797844db61a937c6f31ecb392c8416fbf106017413ba55c6576e0b1fcfc1cf9c
+size 597