diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 08f9865490..d8305a3f57 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1258,7 +1258,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals { this.animationControl = AnimationControl.Parse(data); - pngMetadata.NumberPlays = this.animationControl.NumberPlays; + pngMetadata.RepeatCount = this.animationControl.NumberPlays; } /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index ef179e8261..04e3b1d840 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -176,7 +176,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable if (image.Frames.Count > 1) { - this.WriteAnimationControlChunk(stream, image.Frames.Count, pngMetadata.NumberPlays); + this.WriteAnimationControlChunk(stream, image.Frames.Count, pngMetadata.RepeatCount); // TODO: We should attempt to optimize the output by clipping the indexed result to // non-transparent bounds. That way we can assign frame control bounds and encode @@ -996,8 +996,8 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable height: (uint)imageFrame.Height, xOffset: 0, yOffset: 0, - delayNumerator: frameMetadata.DelayNumerator, - delayDenominator: frameMetadata.DelayDenominator, + delayNumerator: (ushort)frameMetadata.FrameDelay.Numerator, + delayDenominator: (ushort)frameMetadata.FrameDelay.Denominator, disposeOperation: frameMetadata.DisposalMethod, blendOperation: frameMetadata.BlendMethod); diff --git a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs index 3325c6ba1a..ca4d8c1f45 100644 --- a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs @@ -23,21 +23,18 @@ public class PngFrameMetadata : IDeepCloneable /// The metadata to create an instance from. private PngFrameMetadata(PngFrameMetadata other) { - this.DelayNumerator = other.DelayNumerator; - this.DelayDenominator = other.DelayDenominator; + this.FrameDelay = other.FrameDelay; this.DisposalMethod = other.DisposalMethod; this.BlendMethod = other.BlendMethod; } /// - /// Gets or sets the frame delay fraction numerator + /// Gets or sets the frame delay for animated images. + /// If not 0, when utilized in Png animation, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. /// - public ushort DelayNumerator { get; set; } - - /// - /// Gets or sets the frame delay fraction denominator - /// - public ushort DelayDenominator { get; set; } + public Rational FrameDelay { get; set; } /// /// Gets or sets the type of frame area disposal to be done after rendering this frame @@ -55,8 +52,7 @@ public class PngFrameMetadata : IDeepCloneable /// The chunk to create an instance from. internal void FromChunk(in FrameControl frameControl) { - this.DelayNumerator = frameControl.DelayNumerator; - this.DelayDenominator = frameControl.DelayDenominator; + this.FrameDelay = new Rational(frameControl.DelayNumerator, frameControl.DelayDenominator); this.DisposalMethod = frameControl.DisposeOperation; this.BlendMethod = frameControl.BlendOperation; } diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index 92b8572bf6..b113dbfc17 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -29,7 +29,7 @@ public class PngMetadata : IDeepCloneable this.Gamma = other.Gamma; this.InterlaceMethod = other.InterlaceMethod; this.TransparentColor = other.TransparentColor; - this.NumberPlays = other.NumberPlays; + this.RepeatCount = other.RepeatCount; if (other.ColorTable?.Length > 0) { @@ -80,9 +80,9 @@ public class PngMetadata : IDeepCloneable public IList TextData { get; set; } = new List(); /// - /// Gets or sets the number of times to loop this APNG. 0 indicates infinite looping. TODO: RepeatCount!! + /// Gets or sets the number of times to loop this APNG. 0 indicates infinite looping. /// - public int NumberPlays { get; set; } + public int RepeatCount { get; set; } /// public IDeepCloneable DeepClone() => new PngMetadata(this); diff --git a/src/ImageSharp/Primitives/Rational.cs b/src/ImageSharp/Primitives/Rational.cs index 59f34331a7..201219f7e0 100644 --- a/src/ImageSharp/Primitives/Rational.cs +++ b/src/ImageSharp/Primitives/Rational.cs @@ -70,7 +70,7 @@ public readonly struct Rational : IEquatable /// Whether to use the best possible precision when parsing the value. public Rational(double value, bool bestPrecision) { - var rational = LongRational.FromDouble(Math.Abs(value), bestPrecision); + LongRational rational = LongRational.FromDouble(Math.Abs(value), bestPrecision); this.Numerator = (uint)rational.Numerator; this.Denominator = (uint)rational.Denominator; @@ -109,7 +109,7 @@ public readonly struct Rational : IEquatable /// /// The . /// - public static Rational FromDouble(double value) => new Rational(value, false); + public static Rational FromDouble(double value) => new(value, false); /// /// Converts the specified to an instance of this type. @@ -119,24 +119,19 @@ public readonly struct Rational : IEquatable /// /// The . /// - public static Rational FromDouble(double value, bool bestPrecision) => new Rational(value, bestPrecision); + public static Rational FromDouble(double value, bool bestPrecision) => new(value, bestPrecision); /// public override bool Equals(object? obj) => obj is Rational other && this.Equals(other); /// public bool Equals(Rational other) - { - var left = new LongRational(this.Numerator, this.Denominator); - var right = new LongRational(other.Numerator, other.Denominator); - - return left.Equals(right); - } + => this.Numerator == other.Numerator && this.Denominator == other.Denominator; /// public override int GetHashCode() { - var self = new LongRational(this.Numerator, this.Denominator); + LongRational self = new(this.Numerator, this.Denominator); return self.GetHashCode(); } @@ -169,7 +164,7 @@ public readonly struct Rational : IEquatable /// The public string ToString(IFormatProvider provider) { - var rational = new LongRational(this.Numerator, this.Denominator); + LongRational rational = new(this.Numerator, this.Denominator); return rational.ToString(provider); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index f6dfcd178f..92c07a27a6 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -457,8 +457,23 @@ public partial class PngEncoderTests using Image output = Image.Load(memStream); ImageComparer.Exact.VerifySimilarity(output, image); - // TODO: Additional assertations regarding metadata. Assert.Equal(5, image.Frames.Count); + Assert.Equal(image.Frames.Count, output.Frames.Count); + + PngMetadata originalMetadata = image.Metadata.GetPngMetadata(); + PngMetadata outputMetadata = output.Metadata.GetPngMetadata(); + + Assert.Equal(originalMetadata.RepeatCount, outputMetadata.RepeatCount); + + for (int i = 0; i < image.Frames.Count; i++) + { + PngFrameMetadata originalFrameMetadata = image.Frames[i].Metadata.GetPngFrameMetadata(); + PngFrameMetadata outputFrameMetadata = output.Frames[i].Metadata.GetPngFrameMetadata(); + + Assert.Equal(originalFrameMetadata.FrameDelay, outputFrameMetadata.FrameDelay); + Assert.Equal(originalFrameMetadata.BlendMethod, outputFrameMetadata.BlendMethod); + Assert.Equal(originalFrameMetadata.DisposalMethod, outputFrameMetadata.DisposalMethod); + } } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs new file mode 100644 index 0000000000..e29585c2dc --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Png; + +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class PngFrameMetadataTests +{ + [Fact] + public void CloneIsDeep() + { + PngFrameMetadata meta = new() + { + FrameDelay = new(1, 0), + DisposalMethod = PngDisposalMethod.Background, + BlendMethod = PngBlendMethod.Over, + }; + + PngFrameMetadata clone = (PngFrameMetadata)meta.DeepClone(); + + Assert.True(meta.FrameDelay.Equals(clone.FrameDelay)); + Assert.True(meta.DisposalMethod.Equals(clone.DisposalMethod)); + Assert.True(meta.BlendMethod.Equals(clone.BlendMethod)); + + clone.FrameDelay = new(2, 1); + clone.DisposalMethod = PngDisposalMethod.Previous; + clone.BlendMethod = PngBlendMethod.Source; + + Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); + Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod)); + Assert.False(meta.BlendMethod.Equals(clone.BlendMethod)); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index 4492934f1a..b3c122a7a8 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -31,15 +31,25 @@ public class PngMetadataTests ColorType = PngColorType.GrayscaleWithAlpha, InterlaceMethod = PngInterlaceMode.Adam7, Gamma = 2, - TextData = new List { new PngTextData("name", "value", "foo", "bar") } + TextData = new List { new PngTextData("name", "value", "foo", "bar") }, + RepeatCount = 123 }; PngMetadata clone = (PngMetadata)meta.DeepClone(); + Assert.True(meta.BitDepth == clone.BitDepth); + Assert.True(meta.ColorType == clone.ColorType); + Assert.True(meta.InterlaceMethod == clone.InterlaceMethod); + Assert.True(meta.Gamma.Equals(clone.Gamma)); + Assert.False(meta.TextData.Equals(clone.TextData)); + Assert.True(meta.TextData.SequenceEqual(clone.TextData)); + Assert.True(meta.RepeatCount == clone.RepeatCount); + clone.BitDepth = PngBitDepth.Bit2; clone.ColorType = PngColorType.Palette; clone.InterlaceMethod = PngInterlaceMode.None; clone.Gamma = 1; + clone.RepeatCount = 321; Assert.False(meta.BitDepth == clone.BitDepth); Assert.False(meta.ColorType == clone.ColorType); @@ -47,6 +57,7 @@ public class PngMetadataTests Assert.False(meta.Gamma.Equals(clone.Gamma)); Assert.False(meta.TextData.Equals(clone.TextData)); Assert.True(meta.TextData.SequenceEqual(clone.TextData)); + Assert.False(meta.RepeatCount == clone.RepeatCount); } [Theory]