diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs index 459f0068b..fcb0fe5b3 100644 --- a/src/ImageSharp/Formats/Gif/GifFormat.cs +++ b/src/ImageSharp/Formats/Gif/GifFormat.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Gets the current instance. /// - public static GifFormat Instance { get; } = new GifFormat(); + public static GifFormat Instance { get; } = new(); /// public string Name => "GIF"; @@ -32,9 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Gif public IEnumerable FileExtensions => GifConstants.FileExtensions; /// - public GifMetadata CreateDefaultFormatMetadata() => new GifMetadata(); + public GifMetadata CreateDefaultFormatMetadata() => new(); /// - public GifFrameMetadata CreateDefaultFormatFrameMetadata() => new GifFrameMetadata(); + public GifFrameMetadata CreateDefaultFormatFrameMetadata() => new(); } } diff --git a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs index 63f8e3427..3a85b5441 100644 --- a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs @@ -17,5 +17,12 @@ namespace SixLabors.ImageSharp /// The metadata this method extends. /// The . public static WebpMetadata GetWebpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance); + + /// + /// Gets the webp format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// The . + public static WebpFrameMetadata GetWebpMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance); } } diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index b31cf7a26..60d41c984 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.IO; @@ -37,6 +38,16 @@ namespace SixLabors.ImageSharp.Formats.Webp /// private Rectangle? restoreArea; + /// + /// The abstract metadata. + /// + private ImageMetadata metadata; + + /// + /// The gif specific metadata. + /// + private WebpMetadata webpMetadata; + /// /// Initializes a new instance of the class. /// @@ -68,6 +79,10 @@ namespace SixLabors.ImageSharp.Formats.Webp Image image = null; ImageFrame previousFrame = null; + this.metadata = new ImageMetadata(); + this.webpMetadata = this.metadata.GetWebpMetadata(); + this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount; + int remainingBytes = (int)completeDataSize; while (remainingBytes > 0) { @@ -140,17 +155,22 @@ namespace SixLabors.ImageSharp.Formats.Webp break; } - var metaData = new ImageMetadata(); ImageFrame currentFrame = null; ImageFrame imageFrame; if (previousFrame is null) { - image = new Image(this.configuration, (int)width, (int)height, backgroundColor.ToPixel(), metaData); + image = new Image(this.configuration, (int)width, (int)height, backgroundColor.ToPixel(), this.metadata); + + this.SetFrameMetadata(image.Frames.RootFrame.Metadata, frameData.Duration); + imageFrame = image.Frames.RootFrame; } else { currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection. + + this.SetFrameMetadata(currentFrame.Metadata, frameData.Duration); + imageFrame = currentFrame; } @@ -179,6 +199,18 @@ namespace SixLabors.ImageSharp.Formats.Webp return (uint)(stream.Position - streamStartPosition); } + /// + /// Sets the frames metadata. + /// + /// The metadata. + /// The frame duration. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetFrameMetadata(ImageFrameMetadata meta, uint duration) + { + WebpFrameMetadata frameMetadata = meta.GetWebpMetadata(); + frameMetadata.FrameDuration = duration; + } + /// /// Reads the ALPH chunk data. /// diff --git a/src/ImageSharp/Formats/Webp/WebpFormat.cs b/src/ImageSharp/Formats/Webp/WebpFormat.cs index 1f27c4d84..bc3fb09c3 100644 --- a/src/ImageSharp/Formats/Webp/WebpFormat.cs +++ b/src/ImageSharp/Formats/Webp/WebpFormat.cs @@ -6,9 +6,9 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.Webp { /// - /// Registers the image encoders, decoders and mime type detectors for the Webp format + /// Registers the image encoders, decoders and mime type detectors for the Webp format. /// - public sealed class WebpFormat : IImageFormat + public sealed class WebpFormat : IImageFormat { private WebpFormat() { @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Gets the current instance. /// - public static WebpFormat Instance { get; } = new WebpFormat(); + public static WebpFormat Instance { get; } = new(); /// public string Name => "Webp"; @@ -32,6 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Webp public IEnumerable FileExtensions => WebpConstants.FileExtensions; /// - public WebpMetadata CreateDefaultFormatMetadata() => new WebpMetadata(); + public WebpMetadata CreateDefaultFormatMetadata() => new(); + + /// + public WebpFrameMetadata CreateDefaultFormatFrameMetadata() => new(); } } diff --git a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs new file mode 100644 index 000000000..bebfb9d79 --- /dev/null +++ b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Webp +{ + /// + /// Provides webp specific metadata information for the image frame. + /// + public class WebpFrameMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public WebpFrameMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private WebpFrameMetadata(WebpFrameMetadata other) => this.FrameDuration = other.FrameDuration; + + /// + /// Gets or sets the frame duration. The time to wait before displaying the next frame, + /// in 1 millisecond units. Note the interpretation of frame duration of 0 (and often smaller and equal to 10) is implementation defined. + /// + public uint FrameDuration { get; set; } + + /// + public IDeepCloneable DeepClone() => new WebpFrameMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs index f398d3d87..5dd010502 100644 --- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs @@ -19,13 +19,22 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private WebpMetadata(WebpMetadata other) => this.FileFormat = other.FileFormat; + private WebpMetadata(WebpMetadata other) + { + this.FileFormat = other.FileFormat; + this.AnimationLoopCount = other.AnimationLoopCount; + } /// /// Gets or sets the webp file format used. Either lossless or lossy. /// public WebpFileFormatType? FileFormat { get; set; } + /// + /// Gets or sets the loop count. The number of times to loop the animation. 0 means infinitely. + /// + public ushort AnimationLoopCount { get; set; } = 1; + /// public IDeepCloneable DeepClone() => new WebpMetadata(this); } diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs index 1cad4ebe8..f8ed18e28 100644 --- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs +++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Metadata /// public sealed class ImageFrameMetadata : IDeepCloneable { - private readonly Dictionary formatMetadata = new Dictionary(); + private readonly Dictionary formatMetadata = new(); /// /// Initializes a new instance of the class. @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Metadata public IptcProfile IptcProfile { get; set; } /// - public ImageFrameMetadata DeepClone() => new ImageFrameMetadata(this); + public ImageFrameMetadata DeepClone() => new(this); /// /// Gets the metadata value associated with the specified key. diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index 89592f776..cddb76d10 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Metadata /// public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch; - private readonly Dictionary formatMetadata = new Dictionary(); + private readonly Dictionary formatMetadata = new(); private double horizontalResolution; private double verticalResolution; @@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp.Metadata } /// - 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/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 63eab77bf..ae1dddba0 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -337,8 +337,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using (Image image = provider.GetImage(WebpDecoder)) { + WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); + WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); + image.DebugSaveMultiFrame(provider); image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + + Assert.Equal(0, webpMetaData.AnimationLoopCount); + Assert.Equal(150U, frameMetaData.FrameDuration); } } @@ -349,8 +355,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using (Image image = provider.GetImage(WebpDecoder)) { + WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); + WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); + image.DebugSaveMultiFrame(provider); image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Tolerant(0.04f)); + + Assert.Equal(0, webpMetaData.AnimationLoopCount); + Assert.Equal(150U, frameMetaData.FrameDuration); } }