Skip to content

Commit addec72

Browse files
committed
Make PngEncoder respect DefaultImageAnimated
1 parent 85fd431 commit addec72

File tree

2 files changed

+53
-13
lines changed

2 files changed

+53
-13
lines changed

src/ImageSharp/Formats/Png/PngEncoderCore.cs

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
161161

162162
ImageFrame<TPixel>? clonedFrame = null;
163163
ImageFrame<TPixel> currentFrame = image.Frames.RootFrame;
164+
int currentFrameIndex = 0;
164165

165166
bool clearTransparency = this.encoder.TransparentColorMode is PngTransparentColorMode.Clear;
166167
if (clearTransparency)
@@ -190,28 +191,49 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
190191
if (image.Frames.Count > 1)
191192
{
192193
this.WriteAnimationControlChunk(stream, (uint)image.Frames.Count, pngMetadata.RepeatCount);
194+
}
195+
196+
// If the first frame isn't animated, write it as usual and skip it when writing animated frames
197+
if (!pngMetadata.DefaultImageAnimated || image.Frames.Count == 1)
198+
{
199+
FrameControl frameControl = new((uint)this.width, (uint)this.height);
200+
this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
201+
currentFrameIndex++;
202+
}
193203

194-
// Write the first frame.
204+
if (image.Frames.Count > 1)
205+
{
206+
// Write the first animated frame.
207+
currentFrame = image.Frames[currentFrameIndex];
195208
PngFrameMetadata frameMetadata = GetPngFrameMetadata(currentFrame);
196209
PngDisposalMethod previousDisposal = frameMetadata.DisposalMethod;
197210
FrameControl frameControl = this.WriteFrameControlChunk(stream, frameMetadata, currentFrame.Bounds(), 0);
198-
this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
211+
uint sequenceNumber = 1;
212+
if (pngMetadata.DefaultImageAnimated)
213+
{
214+
this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
215+
}
216+
else
217+
{
218+
sequenceNumber += this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, true);
219+
}
220+
221+
currentFrameIndex++;
199222

200223
// Capture the global palette for reuse on subsequent frames.
201224
ReadOnlyMemory<TPixel>? previousPalette = quantized?.Palette.ToArray();
202225

203226
// Write following frames.
204-
uint increment = 0;
205227
ImageFrame<TPixel> previousFrame = image.Frames.RootFrame;
206228

207229
// This frame is reused to store de-duplicated pixel buffers.
208230
using ImageFrame<TPixel> encodingFrame = new(image.Configuration, previousFrame.Size());
209231

210-
for (int i = 1; i < image.Frames.Count; i++)
232+
for (; currentFrameIndex < image.Frames.Count; currentFrameIndex++)
211233
{
212234
ImageFrame<TPixel>? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame;
213-
currentFrame = image.Frames[i];
214-
ImageFrame<TPixel>? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null;
235+
currentFrame = image.Frames[currentFrameIndex];
236+
ImageFrame<TPixel>? nextFrame = currentFrameIndex < image.Frames.Count - 1 ? image.Frames[currentFrameIndex + 1] : null;
215237

216238
frameMetadata = GetPngFrameMetadata(currentFrame);
217239
bool blend = frameMetadata.BlendMethod == PngBlendMethod.Over;
@@ -232,22 +254,17 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
232254
}
233255

234256
// Each frame control sequence number must be incremented by the number of frame data chunks that follow.
235-
frameControl = this.WriteFrameControlChunk(stream, frameMetadata, bounds, (uint)i + increment);
257+
frameControl = this.WriteFrameControlChunk(stream, frameMetadata, bounds, sequenceNumber);
236258

237259
// Dispose of previous quantized frame and reassign.
238260
quantized?.Dispose();
239261
quantized = this.CreateQuantizedImageAndUpdateBitDepth(pngMetadata, encodingFrame, bounds, previousPalette);
240-
increment += this.WriteDataChunks(frameControl, encodingFrame.PixelBuffer.GetRegion(bounds), quantized, stream, true);
262+
sequenceNumber += this.WriteDataChunks(frameControl, encodingFrame.PixelBuffer.GetRegion(bounds), quantized, stream, true) + 1;
241263

242264
previousFrame = currentFrame;
243265
previousDisposal = frameMetadata.DisposalMethod;
244266
}
245267
}
246-
else
247-
{
248-
FrameControl frameControl = new((uint)this.width, (uint)this.height);
249-
this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
250-
}
251268

252269
this.WriteEndChunk(stream);
253270

tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,29 @@ public void Encode_AnimatedFormatTransform_FromWebp<TPixel>(TestImageProvider<TP
586586
}
587587
}
588588

589+
[Theory]
590+
[WithFile(TestImages.Png.DefaultNotAnimated, PixelTypes.Rgba32)]
591+
public void Encode_DefaultNotAnimated<TPixel>(TestImageProvider<TPixel> provider)
592+
where TPixel : unmanaged, IPixel<TPixel>
593+
{
594+
using Image<TPixel> image = provider.GetImage(PngDecoder.Instance);
595+
using MemoryStream memStream = new();
596+
image.Save(memStream, PngEncoder);
597+
memStream.Position = 0;
598+
599+
image.DebugSave(provider: provider, encoder: PngEncoder, null, false);
600+
601+
using Image<Rgba32> output = Image.Load<Rgba32>(memStream);
602+
ImageComparer.Exact.VerifySimilarity(output, image);
603+
604+
Assert.Equal(2, image.Frames.Count);
605+
Assert.Equal(image.Frames.Count, output.Frames.Count);
606+
607+
PngMetadata originalMetadata = image.Metadata.GetPngMetadata();
608+
PngMetadata outputMetadata = output.Metadata.GetPngMetadata();
609+
Assert.Equal(originalMetadata.DefaultImageAnimated, outputMetadata.DefaultImageAnimated);
610+
}
611+
589612
[Theory]
590613
[MemberData(nameof(PngTrnsFiles))]
591614
public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType)

0 commit comments

Comments
 (0)