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]