diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 5859d8a872..d301df94f6 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -10,7 +10,6 @@ using SixLabors.ImageSharp.Formats.Webp.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; @@ -236,26 +235,59 @@ internal class Vp8LEncoder : IDisposable /// public Vp8LHashChain HashChain { get; } - /// - /// Encodes the image as lossless webp to the specified stream. - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void Encode(Image image, Stream stream) + public void EncodeHeader(Image image, Stream stream, bool hasAnimation, uint background = 0, uint loopCount = 0) where TPixel : unmanaged, IPixel { - int width = image.Width; - int height = image.Height; - + // Write bytes from the bitwriter buffer to the stream. ImageMetadata metadata = image.Metadata; metadata.SyncProfiles(); ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile; XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile; + BitWriterBase.WriteTrunksBeforeData( + stream, + (uint)image.Width, + (uint)image.Height, + exifProfile, + xmpProfile, + metadata.IccProfile, + false, + hasAnimation); + + if (hasAnimation) + { + BitWriterBase.WriteAnimationParameter(stream, background, (ushort)loopCount); + } + } + + public void EncodeFooter(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + // Write bytes from the bitwriter buffer to the stream. + ImageMetadata metadata = image.Metadata; + + ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile; + XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile; + + BitWriterBase.WriteTrunksAfterData(stream, exifProfile, xmpProfile); + } + + /// + /// Encodes the image as lossless webp to the specified stream. + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// Flag indicating, if an animation parameter is present. + public void Encode(ImageFrame frame, Stream stream, bool hasAnimation) + where TPixel : unmanaged, IPixel + { + int width = frame.Width; + int height = frame.Height; + // Convert image pixels to bgra array. - bool hasAlpha = this.ConvertPixelsToBgra(image.Frames.RootFrame, width, height); + bool hasAlpha = this.ConvertPixelsToBgra(frame, width, height); // Write the image size. this.WriteImageSize(width, height); @@ -264,23 +296,28 @@ internal class Vp8LEncoder : IDisposable this.WriteAlphaAndVersion(hasAlpha); // Encode the main image stream. - this.EncodeStream(image.Frames.RootFrame); + this.EncodeStream(frame); this.bitWriter.Finish(); - BitWriterBase.WriteTrunksBeforeData( - stream, - (uint)width, - (uint)height, - exifProfile, - xmpProfile, - metadata.IccProfile, - false /*hasAlpha*/, - false); + + long prevPosition = 0; + + if (hasAnimation) + { + prevPosition = BitWriterBase.WriteAnimationFrame(stream, new() + { + Width = (uint)frame.Width, + Height = (uint)frame.Height + }); + } // Write bytes from the bitwriter buffer to the stream. this.bitWriter.WriteEncodedImageToStream(stream); - BitWriterBase.WriteTrunksAfterData(stream, exifProfile, xmpProfile); + if (hasAnimation) + { + BitWriterBase.OverwriteFrameSize(stream, prevPosition); + } } /// @@ -1843,9 +1880,9 @@ internal class Vp8LEncoder : IDisposable { this.Bgra.Dispose(); this.EncodedData.Dispose(); - this.BgraScratch.Dispose(); + this.BgraScratch?.Dispose(); this.Palette.Dispose(); - this.TransformData.Dispose(); + this.TransformData?.Dispose(); this.HashChain.Dispose(); } diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 2751f99134..47712071bf 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 enc = new( + using Vp8LEncoder encoder = new( this.memoryAllocator, this.configuration, image.Width, @@ -140,7 +140,34 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals this.transparentColorMode, this.nearLossless, this.nearLosslessQuality); - enc.Encode(image, stream); + + bool hasAnimation = image.Frames.Count > 1; + encoder.EncodeHeader(image, stream, hasAnimation); + if (hasAnimation) + { + foreach (ImageFrame imageFrame in image.Frames) + { + using Vp8LEncoder enc = new( + this.memoryAllocator, + this.configuration, + image.Width, + image.Height, + this.quality, + this.skipMetadata, + this.method, + this.transparentColorMode, + this.nearLossless, + this.nearLosslessQuality); + + enc.Encode(imageFrame, stream, true); + } + } + else + { + encoder.Encode(image.Frames.RootFrame, stream, false); + } + + encoder.EncodeFooter(image, stream); } else { @@ -174,6 +201,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals this.filterStrength, this.spatialNoiseShaping, this.alphaCompression); + enc.EncodeAnimation(imageFrame, stream); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 4b100e854e..1721cd938b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -20,8 +20,11 @@ public class WebpEncoderTests [Fact] public void Encode_AnimatedLossy() { - Image image = Image.Load(@"C:\Users\poker\Desktop\1.webp"); - image.SaveAsWebp(@"C:\Users\poker\Desktop\3.webp"); + Image image = Image.Load(@"C:\WorkSpace\ImageSharp\tests\Images\Input\Webp\leo_animated_lossless.webp"); + image.SaveAsWebp(@"C:\Users\poker\Desktop\3.webp", new WebpEncoder() + { + FileFormat = WebpFileFormatType.Lossless + }); } [Theory]