From b89bc54aa20b7868010d34734bd1a6ea88be92a2 Mon Sep 17 00:00:00 2001 From: Poker Date: Tue, 31 Oct 2023 18:27:35 +0800 Subject: [PATCH] Add new member to WebpFrameMetadata --- .../Formats/Webp/BitWriter/BitWriterBase.cs | 8 +++-- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 30 +++++++++++-------- .../Formats/Webp/Lossy/Vp8Encoder.cs | 26 +++++++++------- .../Formats/Webp/WebpAnimationDecoder.cs | 29 +++++++++--------- ...lendingMethod.cs => WebpBlendingMethod.cs} | 2 +- .../Formats/Webp/WebpDecoderCore.cs | 16 +++++----- ...isposalMethod.cs => WebpDisposalMethod.cs} | 2 +- src/ImageSharp/Formats/Webp/WebpEncoder.cs | 4 +-- .../Formats/Webp/WebpEncoderCore.cs | 8 ++--- ...AnimationFrameData.cs => WebpFrameData.cs} | 14 ++++----- .../Formats/Webp/WebpFrameMetadata.cs | 19 ++++++++++-- src/ImageSharp/Formats/Webp/WebpMetadata.cs | 9 ++++++ .../Formats/WebP/WebpDecoderTests.cs | 4 +-- 13 files changed, 102 insertions(+), 69 deletions(-) rename src/ImageSharp/Formats/Webp/{AnimationBlendingMethod.cs => WebpBlendingMethod.cs} (95%) rename src/ImageSharp/Formats/Webp/{AnimationDisposalMethod.cs => WebpDisposalMethod.cs} (94%) rename src/ImageSharp/Formats/Webp/{AnimationFrameData.cs => WebpFrameData.cs} (83%) diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs index 89db7ed64..c1860c9c5 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs @@ -231,14 +231,14 @@ internal abstract class BitWriterBase /// The background color is also used when the Disposal method is 1. /// /// The number of times to loop the animation. If it is 0, this means infinitely. - public static void WriteAnimationParameter(Stream stream, uint background, ushort loopCount) + public static void WriteAnimationParameter(Stream stream, Color background, ushort loopCount) { Span buf = stackalloc byte[4]; BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.AnimationParameter); stream.Write(buf); BinaryPrimitives.WriteUInt32LittleEndian(buf, sizeof(uint) + sizeof(ushort)); stream.Write(buf); - BinaryPrimitives.WriteUInt32LittleEndian(buf, background); + BinaryPrimitives.WriteUInt32LittleEndian(buf, background.ToRgba32().Rgba); stream.Write(buf); BinaryPrimitives.WriteUInt16LittleEndian(buf[..2], loopCount); stream.Write(buf[..2]); @@ -249,7 +249,7 @@ internal abstract class BitWriterBase /// /// The stream to write to. /// Animation frame data. - public static long WriteAnimationFrame(Stream stream, AnimationFrameData animation) + public static long WriteAnimationFrame(Stream stream, WebpFrameData animation) { Span buf = stackalloc byte[4]; BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Animation); @@ -262,6 +262,8 @@ internal abstract class BitWriterBase WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Width - 1); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Height - 1); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Duration); + + // TODO: If we can clip the indexed frame for transparent bounds we can set properties here. byte flag = (byte)(((int)animation.BlendingMethod << 1) | (int)animation.DisposalMethod); stream.WriteByte(flag); return position; diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 3da27229a..9156d5bdf 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -235,7 +235,7 @@ internal class Vp8LEncoder : IDisposable /// public Vp8LHashChain HashChain { get; } - public void EncodeHeader(Image image, Stream stream, bool hasAnimation, uint background = 0, uint loopCount = 0) + public void EncodeHeader(Image image, Stream stream, bool hasAnimation) where TPixel : unmanaged, IPixel { // Write bytes from the bitwriter buffer to the stream. @@ -257,7 +257,8 @@ internal class Vp8LEncoder : IDisposable if (hasAnimation) { - BitWriterBase.WriteAnimationParameter(stream, background, (ushort)loopCount); + WebpMetadata webpMetadata = metadata.GetWebpMetadata(); + BitWriterBase.WriteAnimationParameter(stream, webpMetadata.AnimationBackground, webpMetadata.AnimationLoopCount); } } @@ -304,11 +305,14 @@ internal class Vp8LEncoder : IDisposable if (hasAnimation) { - prevPosition = BitWriterBase.WriteAnimationFrame(stream, new AnimationFrameData + WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata(); + prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData { Width = (uint)frame.Width, Height = (uint)frame.Height, - Duration = frame.Metadata.GetWebpMetadata().FrameDuration + Duration = frameMetadata.FrameDelay, + BlendingMethod = frameMetadata.BlendMethod, + DisposalMethod = frameMetadata.DisposalMethod }); } @@ -547,7 +551,7 @@ internal class Vp8LEncoder : IDisposable EntropyIx entropyIdx = this.AnalyzeEntropy(bgra, width, height, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero); bool doNotCache = false; - List crunchConfigs = new List(); + List crunchConfigs = new(); if (this.method == WebpEncodingMethod.BestQuality && this.quality == 100) { @@ -641,8 +645,8 @@ internal class Vp8LEncoder : IDisposable Vp8LBackwardRefs refsTmp = this.Refs[refsBest.Equals(this.Refs[0]) ? 1 : 0]; this.bitWriter.Reset(bwInit); - Vp8LHistogram tmpHisto = new Vp8LHistogram(cacheBits); - List histogramImage = new List(histogramImageXySize); + Vp8LHistogram tmpHisto = new(cacheBits); + List histogramImage = new(histogramImageXySize); for (int i = 0; i < histogramImageXySize; i++) { histogramImage.Add(new Vp8LHistogram(cacheBits)); @@ -839,7 +843,7 @@ internal class Vp8LEncoder : IDisposable refsTmp1, refsTmp2); - List histogramImage = new List + List histogramImage = new() { new Vp8LHistogram(cacheBits) }; @@ -941,7 +945,7 @@ internal class Vp8LEncoder : IDisposable int i; byte[] codeLengthBitDepth = new byte[WebpConstants.CodeLengthCodes]; short[] codeLengthBitDepthSymbols = new short[WebpConstants.CodeLengthCodes]; - HuffmanTreeCode huffmanCode = new HuffmanTreeCode + HuffmanTreeCode huffmanCode = new() { NumSymbols = WebpConstants.CodeLengthCodes, CodeLengths = codeLengthBitDepth, @@ -1192,7 +1196,7 @@ internal class Vp8LEncoder : IDisposable histo[(int)HistoIx.HistoBluePred * 256]++; histo[(int)HistoIx.HistoAlphaPred * 256]++; - Vp8LBitEntropy bitEntropy = new Vp8LBitEntropy(); + Vp8LBitEntropy bitEntropy = new(); for (int j = 0; j < (int)HistoIx.HistoTotal; j++) { bitEntropy.Init(); @@ -1318,7 +1322,7 @@ internal class Vp8LEncoder : IDisposable /// The number of palette entries. private static int GetColorPalette(ReadOnlySpan bgra, int width, int height, Span palette) { - HashSet colors = new HashSet(); + HashSet colors = new(); for (int y = 0; y < height; y++) { ReadOnlySpan bgraRow = bgra.Slice(y * width, width); @@ -1870,9 +1874,9 @@ internal class Vp8LEncoder : IDisposable /// public void ClearRefs() { - for (int i = 0; i < this.Refs.Length; i++) + foreach (Vp8LBackwardRefs t in this.Refs) { - this.Refs[i].Refs.Clear(); + t.Refs.Clear(); } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index e62eb6cfc..3a6e9a2cc 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -309,7 +309,7 @@ internal class Vp8Encoder : IDisposable /// private int MbHeaderLimit { get; } - public void EncodeHeader(Image image, Stream stream, bool hasAlpha, bool hasAnimation, uint background = 0, uint loopCount = 0) + public void EncodeHeader(Image image, Stream stream, bool hasAlpha, bool hasAnimation) where TPixel : unmanaged, IPixel { // Write bytes from the bitwriter buffer to the stream. @@ -331,7 +331,8 @@ internal class Vp8Encoder : IDisposable if (hasAnimation) { - BitWriterBase.WriteAnimationParameter(stream, background, (ushort)loopCount); + WebpMetadata webpMetadata = metadata.GetWebpMetadata(); + BitWriterBase.WriteAnimationParameter(stream, webpMetadata.AnimationBackground, webpMetadata.AnimationLoopCount); } } @@ -395,7 +396,7 @@ internal class Vp8Encoder : IDisposable int yStride = width; int uvStride = (yStride + 1) >> 1; - Vp8EncIterator it = new Vp8EncIterator(this); + Vp8EncIterator it = new(this); Span alphas = stackalloc int[WebpConstants.MaxAlpha + 1]; this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); int totalMb = this.Mbw * this.Mbw; @@ -416,8 +417,8 @@ internal class Vp8Encoder : IDisposable this.StatLoop(width, height, yStride, uvStride); it.Init(); Vp8EncIterator.InitFilter(); - Vp8ModeScore info = new Vp8ModeScore(); - Vp8Residual residual = new Vp8Residual(); + Vp8ModeScore info = new(); + Vp8Residual residual = new(); do { bool dontUseSkip = !this.Proba.UseSkipProba; @@ -474,11 +475,14 @@ internal class Vp8Encoder : IDisposable if (hasAnimation) { - prevPosition = BitWriterBase.WriteAnimationFrame(stream, new AnimationFrameData + WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata(); + prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData { Width = (uint)frame.Width, Height = (uint)frame.Height, - Duration = frame.Metadata.GetWebpMetadata().FrameDuration + Duration = frameMetadata.FrameDelay, + BlendingMethod = frameMetadata.BlendMethod, + DisposalMethod = frameMetadata.DisposalMethod }); } @@ -529,7 +533,7 @@ internal class Vp8Encoder : IDisposable Vp8RdLevel rdOpt = this.method >= WebpEncodingMethod.Level3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int nbMbs = this.Mbw * this.Mbh; - PassStats stats = new PassStats(targetSize, targetPsnr, QMin, QMax, this.quality); + PassStats stats = new(targetSize, targetPsnr, QMin, QMax, this.quality); this.Proba.ResetTokenStats(); // Fast mode: quick analysis pass over few mbs. Better than nothing. @@ -597,7 +601,7 @@ internal class Vp8Encoder : IDisposable Span y = this.Y.GetSpan(); Span u = this.U.GetSpan(); Span v = this.V.GetSpan(); - Vp8EncIterator it = new Vp8EncIterator(this); + Vp8EncIterator it = new(this); long size = 0; long sizeP0 = 0; long distortion = 0; @@ -605,7 +609,7 @@ internal class Vp8Encoder : IDisposable it.Init(); this.SetLoopParams(stats.Q); - Vp8ModeScore info = new Vp8ModeScore(); + Vp8ModeScore info = new(); do { info.Clear(); @@ -1167,7 +1171,7 @@ internal class Vp8Encoder : IDisposable private void RecordResiduals(Vp8EncIterator it, Vp8ModeScore rd) { int x, y, ch; - Vp8Residual residual = new Vp8Residual(); + Vp8Residual residual = new(); bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; it.NzToBytes(); diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index 6922e37d6..87657dfab 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -138,7 +138,7 @@ internal class WebpAnimationDecoder : IDisposable private uint ReadFrame(BufferedReadStream stream, ref Image? image, ref ImageFrame? previousFrame, uint width, uint height, Color backgroundColor) where TPixel : unmanaged, IPixel { - AnimationFrameData frameData = AnimationFrameData.Parse(stream); + WebpFrameData frameData = WebpFrameData.Parse(stream); long streamStartPosition = stream.Position; Span buffer = stackalloc byte[4]; @@ -153,7 +153,7 @@ internal class WebpAnimationDecoder : IDisposable } WebpImageInfo? webpInfo = null; - WebpFeatures features = new WebpFeatures(); + WebpFeatures features = new(); switch (chunkType) { case WebpChunkType.Vp8: @@ -180,7 +180,7 @@ internal class WebpAnimationDecoder : IDisposable { image = new Image(this.configuration, (int)width, (int)height, backgroundColor.ToPixel(), this.metadata); - SetFrameMetadata(image.Frames.RootFrame.Metadata, frameData.Duration); + SetFrameMetadata(image.Frames.RootFrame.Metadata, frameData); imageFrame = image.Frames.RootFrame; } @@ -188,7 +188,7 @@ internal class WebpAnimationDecoder : IDisposable { currentFrame = image!.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection. - SetFrameMetadata(currentFrame.Metadata, frameData.Duration); + SetFrameMetadata(currentFrame.Metadata, frameData); imageFrame = currentFrame; } @@ -199,7 +199,7 @@ internal class WebpAnimationDecoder : IDisposable int frameHeight = (int)frameData.Height; Rectangle regionRectangle = Rectangle.FromLTRB(frameX, frameY, frameX + frameWidth, frameY + frameHeight); - if (frameData.DisposalMethod is AnimationDisposalMethod.Dispose) + if (frameData.DisposalMethod is WebpDisposalMethod.Dispose) { this.RestoreToBackground(imageFrame, backgroundColor); } @@ -207,7 +207,7 @@ internal class WebpAnimationDecoder : IDisposable using Buffer2D decodedImage = this.DecodeImageData(frameData, webpInfo); DrawDecodedImageOnCanvas(decodedImage, imageFrame, frameX, frameY, frameWidth, frameHeight); - if (previousFrame != null && frameData.BlendingMethod is AnimationBlendingMethod.AlphaBlending) + if (previousFrame != null && frameData.BlendingMethod is WebpBlendingMethod.AlphaBlending) { this.AlphaBlend(previousFrame, imageFrame, frameX, frameY, frameWidth, frameHeight); } @@ -222,12 +222,13 @@ internal class WebpAnimationDecoder : IDisposable /// Sets the frames metadata. /// /// The metadata. - /// The frame duration. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetFrameMetadata(ImageFrameMetadata meta, uint duration) + /// The frame data. + private static void SetFrameMetadata(ImageFrameMetadata meta, WebpFrameData frameData) { WebpFrameMetadata frameMetadata = meta.GetWebpMetadata(); - frameMetadata.FrameDuration = duration; + frameMetadata.FrameDelay = frameData.Duration; + frameMetadata.BlendMethod = frameData.BlendingMethod; + frameMetadata.DisposalMethod = frameData.DisposalMethod; } /// @@ -256,10 +257,10 @@ internal class WebpAnimationDecoder : IDisposable /// The frame data. /// The webp information. /// A decoded image. - private Buffer2D DecodeImageData(AnimationFrameData frameData, WebpImageInfo webpInfo) + private Buffer2D DecodeImageData(WebpFrameData frameData, WebpImageInfo webpInfo) where TPixel : unmanaged, IPixel { - Image decodedImage = new Image((int)frameData.Width, (int)frameData.Height); + Image decodedImage = new((int)frameData.Width, (int)frameData.Height); try { @@ -267,13 +268,13 @@ internal class WebpAnimationDecoder : IDisposable if (webpInfo.IsLossless) { WebpLosslessDecoder losslessDecoder = - new WebpLosslessDecoder(webpInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); + new(webpInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); losslessDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height); } else { WebpLossyDecoder lossyDecoder = - new WebpLossyDecoder(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration); + new(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration); lossyDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.alphaData); } diff --git a/src/ImageSharp/Formats/Webp/AnimationBlendingMethod.cs b/src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs similarity index 95% rename from src/ImageSharp/Formats/Webp/AnimationBlendingMethod.cs rename to src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs index 99b2462ce..cbd0e9a8c 100644 --- a/src/ImageSharp/Formats/Webp/AnimationBlendingMethod.cs +++ b/src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp; /// /// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas. /// -internal enum AnimationBlendingMethod +public enum WebpBlendingMethod { /// /// Use alpha blending. After disposing of the previous frame, render the current frame on the canvas using alpha-blending. diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index bc875c889..de188b137 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -73,7 +73,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable public DecoderOptions Options { get; } /// - public Size Dimensions => new Size((int)this.webImageInfo!.Width, (int)this.webImageInfo.Height); + public Size Dimensions => new((int)this.webImageInfo!.Width, (int)this.webImageInfo.Height); /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) @@ -82,7 +82,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable Image? image = null; try { - ImageMetadata metadata = new ImageMetadata(); + ImageMetadata metadata = new(); Span buffer = stackalloc byte[4]; uint fileSize = ReadImageHeader(stream, buffer); @@ -91,7 +91,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable { if (this.webImageInfo.Features is { Animation: true }) { - using WebpAnimationDecoder animationDecoder = new WebpAnimationDecoder( + using WebpAnimationDecoder animationDecoder = new( this.memoryAllocator, this.configuration, this.maxFrames, @@ -103,7 +103,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable Buffer2D pixels = image.GetRootFramePixelBuffer(); if (this.webImageInfo.IsLossless) { - WebpLosslessDecoder losslessDecoder = new WebpLosslessDecoder( + WebpLosslessDecoder losslessDecoder = new( this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); @@ -111,7 +111,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable } else { - WebpLossyDecoder lossyDecoder = new WebpLossyDecoder( + WebpLossyDecoder lossyDecoder = new( this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration); @@ -139,7 +139,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable { ReadImageHeader(stream, stackalloc byte[4]); - ImageMetadata metadata = new ImageMetadata(); + ImageMetadata metadata = new(); using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true)) { return new ImageInfo( @@ -185,7 +185,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable Span buffer = stackalloc byte[4]; WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); - WebpFeatures features = new WebpFeatures(); + WebpFeatures features = new(); switch (chunkType) { case WebpChunkType.Vp8: @@ -392,7 +392,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk"); } - IccProfile profile = new IccProfile(iccpData); + IccProfile profile = new(iccpData); if (profile.CheckIsValid()) { metadata.IccProfile = profile; diff --git a/src/ImageSharp/Formats/Webp/AnimationDisposalMethod.cs b/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs similarity index 94% rename from src/ImageSharp/Formats/Webp/AnimationDisposalMethod.cs rename to src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs index 23bc37c28..d409973a9 100644 --- a/src/ImageSharp/Formats/Webp/AnimationDisposalMethod.cs +++ b/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp; /// /// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas. /// -internal enum AnimationDisposalMethod +public enum WebpDisposalMethod { /// /// Do not dispose. Leave the canvas as is. diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index 13c9798db..bc93df3a5 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Advanced; - namespace SixLabors.ImageSharp.Formats.Webp; /// @@ -82,7 +80,7 @@ public sealed class WebpEncoder : ImageEncoder /// protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { - WebpEncoderCore encoder = new WebpEncoderCore(this, image.Configuration); + WebpEncoderCore encoder = new(this, image.Configuration); encoder.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index d945cc399..47712071b 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -129,7 +129,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals if (lossless) { - using Vp8LEncoder encoder = new Vp8LEncoder( + using Vp8LEncoder encoder = new( this.memoryAllocator, this.configuration, image.Width, @@ -147,7 +147,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals { foreach (ImageFrame imageFrame in image.Frames) { - using Vp8LEncoder enc = new Vp8LEncoder( + using Vp8LEncoder enc = new( this.memoryAllocator, this.configuration, image.Width, @@ -171,7 +171,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals } else { - using Vp8Encoder encoder = new Vp8Encoder( + using Vp8Encoder encoder = new( this.memoryAllocator, this.configuration, image.Width, @@ -189,7 +189,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals foreach (ImageFrame imageFrame in image.Frames) { - using Vp8Encoder enc = new Vp8Encoder( + using Vp8Encoder enc = new( this.memoryAllocator, this.configuration, image.Width, diff --git a/src/ImageSharp/Formats/Webp/AnimationFrameData.cs b/src/ImageSharp/Formats/Webp/WebpFrameData.cs similarity index 83% rename from src/ImageSharp/Formats/Webp/AnimationFrameData.cs rename to src/ImageSharp/Formats/Webp/WebpFrameData.cs index 27a1815fe..e2bcfd7c3 100644 --- a/src/ImageSharp/Formats/Webp/AnimationFrameData.cs +++ b/src/ImageSharp/Formats/Webp/WebpFrameData.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.IO; namespace SixLabors.ImageSharp.Formats.Webp; -internal struct AnimationFrameData +internal struct WebpFrameData { /// /// The animation chunk size. @@ -46,23 +46,23 @@ internal struct AnimationFrameData /// /// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas. /// - public AnimationBlendingMethod BlendingMethod; + public WebpBlendingMethod BlendingMethod; /// /// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas. /// - public AnimationDisposalMethod DisposalMethod; + public WebpDisposalMethod DisposalMethod; /// /// Reads the animation frame header. /// /// The stream to read from. /// Animation frame data. - public static AnimationFrameData Parse(BufferedReadStream stream) + public static WebpFrameData Parse(BufferedReadStream stream) { Span buffer = stackalloc byte[4]; - AnimationFrameData data = new AnimationFrameData + WebpFrameData data = new() { DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer), @@ -83,8 +83,8 @@ internal struct AnimationFrameData }; byte flags = (byte)stream.ReadByte(); - data.DisposalMethod = (flags & 1) == 1 ? AnimationDisposalMethod.Dispose : AnimationDisposalMethod.DoNotDispose; - data.BlendingMethod = (flags & (1 << 1)) != 0 ? AnimationBlendingMethod.DoNotBlend : AnimationBlendingMethod.AlphaBlending; + data.DisposalMethod = (flags & 1) == 1 ? WebpDisposalMethod.Dispose : WebpDisposalMethod.DoNotDispose; + data.BlendingMethod = (flags & (1 << 1)) != 0 ? WebpBlendingMethod.DoNotBlend : WebpBlendingMethod.AlphaBlending; return data; } diff --git a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs index bce1b09d6..ef21d8b6f 100644 --- a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs @@ -19,13 +19,28 @@ public class WebpFrameMetadata : IDeepCloneable /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private WebpFrameMetadata(WebpFrameMetadata other) => this.FrameDuration = other.FrameDuration; + private WebpFrameMetadata(WebpFrameMetadata other) + { + this.FrameDelay = other.FrameDelay; + this.DisposalMethod = other.DisposalMethod; + this.BlendMethod = other.BlendMethod; + } + + /// + /// Gets or sets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas. + /// + public WebpBlendingMethod BlendMethod { get; set; } + + /// + /// Gets or sets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas. + /// + public WebpDisposalMethod DisposalMethod { get; set; } /// /// 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 uint FrameDelay { 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 5d1051c75..a6bb0a7b8 100644 --- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs @@ -23,6 +23,7 @@ public class WebpMetadata : IDeepCloneable { this.FileFormat = other.FileFormat; this.AnimationLoopCount = other.AnimationLoopCount; + this.AnimationBackground = other.AnimationBackground; } /// @@ -35,6 +36,14 @@ public class WebpMetadata : IDeepCloneable /// public ushort AnimationLoopCount { get; set; } = 1; + /// + /// Gets or sets the default background color of the canvas in [Blue, Green, Red, Alpha] byte order. + /// This color MAY be used to fill the unused space on the canvas around the frames, + /// as well as the transparent pixels of the first frame. + /// The background color is also used when the Disposal method is 1. + /// + public Color AnimationBackground { get; set; } + /// public IDeepCloneable DeepClone() => new WebpMetadata(this); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index c0fc00b82..c3a777c15 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -308,7 +308,7 @@ public class WebpDecoderTests image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); Assert.Equal(0, webpMetaData.AnimationLoopCount); - Assert.Equal(150U, frameMetaData.FrameDuration); + Assert.Equal(150U, frameMetaData.FrameDelay); Assert.Equal(12, image.Frames.Count); } @@ -325,7 +325,7 @@ public class WebpDecoderTests image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Tolerant(0.04f)); Assert.Equal(0, webpMetaData.AnimationLoopCount); - Assert.Equal(150U, frameMetaData.FrameDuration); + Assert.Equal(150U, frameMetaData.FrameDelay); Assert.Equal(12, image.Frames.Count); }