diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index a96c53c10..c4b10b4dc 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -228,8 +228,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
PngThrowHelper.ThrowMissingFrameControl();
}
- previousFrameControl ??= new((uint)this.header.Width, (uint)this.header.Height);
- this.InitializeFrame(previousFrameControl.Value, currentFrameControl.Value, image, previousFrame, out currentFrame);
+ this.InitializeFrame(previousFrameControl, currentFrameControl.Value, image, previousFrame, out currentFrame);
this.currentStream.Position += 4;
this.ReadScanlines(
@@ -240,11 +239,16 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
currentFrameControl.Value,
cancellationToken);
- previousFrame = currentFrame;
- previousFrameControl = currentFrameControl;
+ // if current frame dispose is restore to previous, then from future frame's perspective, it never happened
+ if (currentFrameControl.Value.DisposeOperation != PngDisposalMethod.RestoreToPrevious)
+ {
+ previousFrame = currentFrame;
+ previousFrameControl = currentFrameControl;
+ }
+
break;
case PngChunkType.Data:
-
+ pngMetadata.AnimateRootFrame = currentFrameControl != null;
currentFrameControl ??= new((uint)this.header.Width, (uint)this.header.Height);
if (image is null)
{
@@ -261,9 +265,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.ReadNextDataChunk,
currentFrameControl.Value,
cancellationToken);
+ if (pngMetadata.AnimateRootFrame)
+ {
+ previousFrame = currentFrame;
+ previousFrameControl = currentFrameControl;
+ }
- previousFrame = currentFrame;
- previousFrameControl = currentFrameControl;
break;
case PngChunkType.Palette:
this.palette = chunk.Data.GetSpan().ToArray();
@@ -632,7 +639,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// The previous frame.
/// The created frame
private void InitializeFrame(
- FrameControl previousFrameControl,
+ FrameControl? previousFrameControl,
FrameControl currentFrameControl,
Image image,
ImageFrame? previousFrame,
@@ -645,12 +652,16 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
frame = image.Frames.AddFrame(previousFrame ?? image.Frames.RootFrame);
// If the first `fcTL` chunk uses a `dispose_op` of APNG_DISPOSE_OP_PREVIOUS it should be treated as APNG_DISPOSE_OP_BACKGROUND.
- if (previousFrameControl.DisposeOperation == PngDisposalMethod.RestoreToBackground
- || (previousFrame is null && previousFrameControl.DisposeOperation == PngDisposalMethod.RestoreToPrevious))
+ // So, if restoring to before first frame, clear entire area. Same if first frame (previousFrameControl null).
+ if (previousFrameControl == null || (previousFrame is null && previousFrameControl.Value.DisposeOperation == PngDisposalMethod.RestoreToPrevious))
+ {
+ Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion();
+ pixelRegion.Clear();
+ }
+ else if (previousFrameControl.Value.DisposeOperation == PngDisposalMethod.RestoreToBackground)
{
- Rectangle restoreArea = previousFrameControl.Bounds;
- Rectangle interest = Rectangle.Intersect(frame.Bounds(), restoreArea);
- Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion(interest);
+ Rectangle restoreArea = previousFrameControl.Value.Bounds;
+ Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion(restoreArea);
pixelRegion.Clear();
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index ea94270f2..802e4dd6a 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -161,6 +161,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
ImageFrame? clonedFrame = null;
ImageFrame currentFrame = image.Frames.RootFrame;
+ int currentFrameIndex = 0;
bool clearTransparency = this.encoder.TransparentColorMode is PngTransparentColorMode.Clear;
if (clearTransparency)
@@ -189,29 +190,50 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
if (image.Frames.Count > 1)
{
- this.WriteAnimationControlChunk(stream, (uint)image.Frames.Count, pngMetadata.RepeatCount);
+ this.WriteAnimationControlChunk(stream, (uint)(image.Frames.Count - (pngMetadata.AnimateRootFrame ? 0 : 1)), pngMetadata.RepeatCount);
+ }
+
+ // If the first frame isn't animated, write it as usual and skip it when writing animated frames
+ if (!pngMetadata.AnimateRootFrame || image.Frames.Count == 1)
+ {
+ FrameControl frameControl = new((uint)this.width, (uint)this.height);
+ this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
+ currentFrameIndex++;
+ }
- // Write the first frame.
+ if (image.Frames.Count > 1)
+ {
+ // Write the first animated frame.
+ currentFrame = image.Frames[currentFrameIndex];
PngFrameMetadata frameMetadata = GetPngFrameMetadata(currentFrame);
PngDisposalMethod previousDisposal = frameMetadata.DisposalMethod;
FrameControl frameControl = this.WriteFrameControlChunk(stream, frameMetadata, currentFrame.Bounds(), 0);
- this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
+ uint sequenceNumber = 1;
+ if (pngMetadata.AnimateRootFrame)
+ {
+ this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
+ }
+ else
+ {
+ sequenceNumber += this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, true);
+ }
+
+ currentFrameIndex++;
// Capture the global palette for reuse on subsequent frames.
ReadOnlyMemory? previousPalette = quantized?.Palette.ToArray();
// Write following frames.
- uint increment = 0;
ImageFrame previousFrame = image.Frames.RootFrame;
// This frame is reused to store de-duplicated pixel buffers.
using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size());
- for (int i = 1; i < image.Frames.Count; i++)
+ for (; currentFrameIndex < image.Frames.Count; currentFrameIndex++)
{
ImageFrame? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame;
- currentFrame = image.Frames[i];
- ImageFrame? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null;
+ currentFrame = image.Frames[currentFrameIndex];
+ ImageFrame? nextFrame = currentFrameIndex < image.Frames.Count - 1 ? image.Frames[currentFrameIndex + 1] : null;
frameMetadata = GetPngFrameMetadata(currentFrame);
bool blend = frameMetadata.BlendMethod == PngBlendMethod.Over;
@@ -232,22 +254,17 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
}
// Each frame control sequence number must be incremented by the number of frame data chunks that follow.
- frameControl = this.WriteFrameControlChunk(stream, frameMetadata, bounds, (uint)i + increment);
+ frameControl = this.WriteFrameControlChunk(stream, frameMetadata, bounds, sequenceNumber);
// Dispose of previous quantized frame and reassign.
quantized?.Dispose();
quantized = this.CreateQuantizedImageAndUpdateBitDepth(pngMetadata, encodingFrame, bounds, previousPalette);
- increment += this.WriteDataChunks(frameControl, encodingFrame.PixelBuffer.GetRegion(bounds), quantized, stream, true);
+ sequenceNumber += this.WriteDataChunks(frameControl, encodingFrame.PixelBuffer.GetRegion(bounds), quantized, stream, true) + 1;
previousFrame = currentFrame;
previousDisposal = frameMetadata.DisposalMethod;
}
}
- else
- {
- FrameControl frameControl = new((uint)this.width, (uint)this.height);
- this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
- }
this.WriteEndChunk(stream);
diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs
index 93ddcf263..d9028dd80 100644
--- a/src/ImageSharp/Formats/Png/PngMetadata.cs
+++ b/src/ImageSharp/Formats/Png/PngMetadata.cs
@@ -29,6 +29,7 @@ public class PngMetadata : IDeepCloneable
this.InterlaceMethod = other.InterlaceMethod;
this.TransparentColor = other.TransparentColor;
this.RepeatCount = other.RepeatCount;
+ this.AnimateRootFrame = other.AnimateRootFrame;
if (other.ColorTable?.Length > 0)
{
@@ -83,6 +84,11 @@ public class PngMetadata : IDeepCloneable
///
public uint RepeatCount { get; set; } = 1;
+ ///
+ /// Gets or sets a value indicating whether the root frame is shown as part of the animated sequence
+ ///
+ public bool AnimateRootFrame { get; set; } = true;
+
///
public IDeepCloneable DeepClone() => new PngMetadata(this);
diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
index f217515e3..b327ed21b 100644
--- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
+++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
@@ -198,8 +198,9 @@ internal static class PngScanlineProcessor
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
ref Color paletteBase = ref MemoryMarshal.GetReference(palette.Value.Span);
+ uint offset = pixelOffset + frameControl.XOffset;
- for (nuint x = pixelOffset, o = 0; x < frameControl.XMax; x += increment, o++)
+ for (nuint x = offset, o = 0; x < frameControl.XMax; x += increment, o++)
{
uint index = Unsafe.Add(ref scanlineSpanRef, o);
pixel.FromRgba32(Unsafe.Add(ref paletteBase, index).ToRgba32());
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
index 9b165526e..9ff368d4e 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
@@ -87,7 +87,9 @@ public partial class PngDecoderTests
TestImages.Png.DisposeBackgroundRegion,
TestImages.Png.DisposePreviousFirst,
TestImages.Png.DisposeBackgroundBeforeRegion,
- TestImages.Png.BlendOverMultiple
+ TestImages.Png.BlendOverMultiple,
+ TestImages.Png.FrameOffset,
+ TestImages.Png.DefaultNotAnimated
};
[Theory]
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs
index 044da2193..76fd260dd 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs
@@ -3,6 +3,7 @@
using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable InconsistentNaming
@@ -59,6 +60,38 @@ public partial class PngEncoderTests
}
}
+ [Theory]
+ [WithFile(TestImages.Png.DefaultNotAnimated, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)]
+ public void AcTL_CorrectlyWritten(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage(PngDecoder.Instance);
+ PngMetadata metadata = image.Metadata.GetPngMetadata();
+ int correctFrameCount = image.Frames.Count - (metadata.AnimateRootFrame ? 0 : 1);
+ using MemoryStream memStream = new();
+ image.Save(memStream, PngEncoder);
+ memStream.Position = 0;
+ Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
+ bool foundAcTl = false;
+ while (bytesSpan.Length > 0 && !foundAcTl)
+ {
+ int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]);
+ PngChunkType type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ if (type == PngChunkType.AnimationControl)
+ {
+ AnimationControl control = AnimationControl.Parse(bytesSpan[8..]);
+ foundAcTl = true;
+ Assert.True(control.NumberFrames == correctFrameCount);
+ Assert.True(control.NumberPlays == metadata.RepeatCount);
+ }
+
+ bytesSpan = bytesSpan[(4 + 4 + length + 4)..];
+ }
+
+ Assert.True(foundAcTl);
+ }
+
[Theory]
[InlineData(PngChunkType.Gamma)]
[InlineData(PngChunkType.Chroma)]
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index 950f1d2e3..ca5aae961 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
@@ -447,6 +447,8 @@ public partial class PngEncoderTests
[Theory]
[WithFile(TestImages.Png.APng, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Png.DefaultNotAnimated, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Png.FrameOffset, PixelTypes.Rgba32)]
public void Encode_APng(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
@@ -458,15 +460,17 @@ public partial class PngEncoderTests
image.DebugSave(provider: provider, encoder: PngEncoder, null, false);
using Image output = Image.Load(memStream);
- ImageComparer.Exact.VerifySimilarity(output, image);
- Assert.Equal(5, image.Frames.Count);
+ // some loss from original, due to compositing
+ ImageComparer.TolerantPercentage(0.01f).VerifySimilarity(output, image);
+
Assert.Equal(image.Frames.Count, output.Frames.Count);
PngMetadata originalMetadata = image.Metadata.GetPngMetadata();
PngMetadata outputMetadata = output.Metadata.GetPngMetadata();
Assert.Equal(originalMetadata.RepeatCount, outputMetadata.RepeatCount);
+ Assert.Equal(originalMetadata.AnimateRootFrame, outputMetadata.AnimateRootFrame);
for (int i = 0; i < image.Frames.Count; i++)
{
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
index b3c122a7a..225e4deef 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
@@ -32,7 +32,8 @@ public class PngMetadataTests
InterlaceMethod = PngInterlaceMode.Adam7,
Gamma = 2,
TextData = new List { new PngTextData("name", "value", "foo", "bar") },
- RepeatCount = 123
+ RepeatCount = 123,
+ AnimateRootFrame = false
};
PngMetadata clone = (PngMetadata)meta.DeepClone();
@@ -44,6 +45,7 @@ public class PngMetadataTests
Assert.False(meta.TextData.Equals(clone.TextData));
Assert.True(meta.TextData.SequenceEqual(clone.TextData));
Assert.True(meta.RepeatCount == clone.RepeatCount);
+ Assert.True(meta.AnimateRootFrame == clone.AnimateRootFrame);
clone.BitDepth = PngBitDepth.Bit2;
clone.ColorType = PngColorType.Palette;
@@ -144,6 +146,26 @@ public class PngMetadataTests
VerifyExifDataIsPresent(exif);
}
+ [Theory]
+ [WithFile(TestImages.Png.DefaultNotAnimated, PixelTypes.Rgba32)]
+ public void Decode_IdentifiesDefaultFrameNotAnimated(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage(PngDecoder.Instance);
+ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
+ Assert.False(meta.AnimateRootFrame);
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)]
+ public void Decode_IdentifiesDefaultFrameAnimated(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage(PngDecoder.Instance);
+ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
+ Assert.True(meta.AnimateRootFrame);
+ }
+
[Theory]
[WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)]
public void Decode_IgnoresExifData_WhenIgnoreMetadataIsTrue(TestImageProvider provider)
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index fe633fddc..afb13363d 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -73,6 +73,8 @@ public static class TestImages
public const string DisposeBackgroundRegion = "Png/animated/15-dispose-background-region.png";
public const string DisposePreviousFirst = "Png/animated/12-dispose-prev-first.png";
public const string BlendOverMultiple = "Png/animated/21-blend-over-multiple.png";
+ public const string FrameOffset = "Png/animated/frame-offset.png";
+ public const string DefaultNotAnimated = "Png/animated/default-not-animated.png";
public const string Issue2666 = "Png/issues/Issue_2666.png";
// Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html
diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/00.png
new file mode 100644
index 000000000..4c5ea8169
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/00.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8d4716e18655be53630d6d50daebe8c38e0eedb2432c7a73840b55d1473d5944
+size 1050
diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/01.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/01.png
new file mode 100644
index 000000000..790fe45e4
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/01.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9b5a6d3cf1a777f6b719c2a1cf79bffe2251355d75e6c0f7ce7a973b3d033419
+size 1177
diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/00.png
new file mode 100644
index 000000000..870ed61a4
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/00.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b85aaf7153e0ca538856a58d7b069bcc13fadc468ea603c85f8782cc691f86c3
+size 387
diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/01.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/01.png
new file mode 100644
index 000000000..cab85d946
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/01.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fcb83d6893dcfd869b764ff9846c259eaa0caf26cec3f0fc2cbae2c26f2eeaa5
+size 660
diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/02.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/02.png
new file mode 100644
index 000000000..1a2c5adcf
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/02.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:562ec382f6d2af68e66092bf6949f66147d5f608d3c618eea5a7c1ea400737ff
+size 768
diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/03.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/03.png
new file mode 100644
index 000000000..d850459ee
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/03.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d12a7791b960072e32b78bd9aaf456dc99341eea1c66ea05050433d8c082c6ac
+size 579
diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/04.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/04.png
new file mode 100644
index 000000000..000b0567d
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/04.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2db38d7ffcc95c23a5c94a06f10c6cc67406ae581a955c99ede4af97b1a044f8
+size 628
diff --git a/tests/Images/Input/Png/animated/default-not-animated.png b/tests/Images/Input/Png/animated/default-not-animated.png
new file mode 100644
index 000000000..1ed72698d
--- /dev/null
+++ b/tests/Images/Input/Png/animated/default-not-animated.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:647d484c8f320b55824b9219270524df3edc434a4793e1627e0ee14af8d6e4f8
+size 1689
diff --git a/tests/Images/Input/Png/animated/frame-offset.png b/tests/Images/Input/Png/animated/frame-offset.png
new file mode 100644
index 000000000..4eebb44a3
--- /dev/null
+++ b/tests/Images/Input/Png/animated/frame-offset.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3c019073841b48b02cb07c779fed8654c6052aee700e7620d07f5d775d97088f
+size 2156