diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
index cbd2aa8e7f..46030dde32 100644
--- a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
@@ -27,7 +27,7 @@ internal static class AlphaEncoder
/// The size in bytes of the alpha data.
/// The encoded alpha data.
public static IMemoryOwner EncodeAlpha(
- ImageFrame frame,
+ Buffer2DRegion frame,
Configuration configuration,
MemoryAllocator memoryAllocator,
bool skipMetadata,
@@ -35,8 +35,6 @@ internal static class AlphaEncoder
out int size)
where TPixel : unmanaged, IPixel
{
- int width = frame.Width;
- int height = frame.Height;
IMemoryOwner alphaData = ExtractAlphaChannel(frame, configuration, memoryAllocator);
if (compress)
@@ -46,8 +44,8 @@ internal static class AlphaEncoder
using Vp8LEncoder lossLessEncoder = new(
memoryAllocator,
configuration,
- width,
- height,
+ frame.Width,
+ frame.Height,
quality,
skipMetadata,
effort,
@@ -58,14 +56,14 @@ internal static class AlphaEncoder
// The transparency information will be stored in the green channel of the ARGB quadruplet.
// The green channel is allowed extra transformation steps in the specification -- unlike the other channels,
// that can improve compression.
- using ImageFrame alphaAsFrame = DispatchAlphaToGreen(frame, alphaData.GetSpan());
+ using ImageFrame alphaAsFrame = DispatchAlphaToGreen(configuration, frame, alphaData.GetSpan());
- size = lossLessEncoder.EncodeAlphaImageData(alphaAsFrame, alphaData);
+ size = lossLessEncoder.EncodeAlphaImageData(alphaAsFrame.PixelBuffer.GetRegion(), alphaData);
return alphaData;
}
- size = width * height;
+ size = frame.Width * frame.Height;
return alphaData;
}
@@ -73,25 +71,28 @@ internal static class AlphaEncoder
/// Store the transparency in the green channel.
///
/// The pixel format.
- /// The to encode from.
+ /// The configuration.
+ /// The pixel buffer to encode from.
/// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.
/// The transparency frame.
- private static ImageFrame DispatchAlphaToGreen(ImageFrame frame, Span alphaData)
+ private static ImageFrame DispatchAlphaToGreen(Configuration configuration, Buffer2DRegion frame, Span alphaData)
where TPixel : unmanaged, IPixel
{
int width = frame.Width;
int height = frame.Height;
- ImageFrame alphaAsFrame = new ImageFrame(Configuration.Default, width, height);
+ ImageFrame alphaAsFrame = new(configuration, width, height);
for (int y = 0; y < height; y++)
{
- Memory rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y);
- Span pixelRow = rowBuffer.Span;
+ Memory rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y);
+ Span pixelRow = rowBuffer.Span;
Span alphaRow = alphaData.Slice(y * width, width);
+
+ // TODO: This can be probably simd optimized.
for (int x = 0; x < width; x++)
{
// Leave A/R/B channels zero'd.
- pixelRow[x] = new Rgba32(0, alphaRow[x], 0, 0);
+ pixelRow[x] = new Bgra32(0, alphaRow[x], 0, 0);
}
}
@@ -106,12 +107,12 @@ internal static class AlphaEncoder
/// The global configuration.
/// The memory manager.
/// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.
- private static IMemoryOwner ExtractAlphaChannel(ImageFrame frame, Configuration configuration, MemoryAllocator memoryAllocator)
+ private static IMemoryOwner ExtractAlphaChannel(Buffer2DRegion frame, Configuration configuration, MemoryAllocator memoryAllocator)
where TPixel : unmanaged, IPixel
{
- Buffer2D imageBuffer = frame.PixelBuffer;
- int height = frame.Height;
int width = frame.Width;
+ int height = frame.Height;
+
IMemoryOwner alphaDataBuffer = memoryAllocator.Allocate(width * height);
Span alphaData = alphaDataBuffer.GetSpan();
@@ -120,7 +121,7 @@ internal static class AlphaEncoder
for (int y = 0; y < height; y++)
{
- Span rowSpan = imageBuffer.DangerousGetRowSpan(y);
+ Span rowSpan = frame.DangerousGetRowSpan(y);
PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow);
int offset = y * width;
for (int x = 0; x < width; x++)
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
index 49b059b078..9ffda0f51f 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-using System.Diagnostics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@@ -100,9 +99,7 @@ internal abstract class BitWriterBase
bool hasAnimation)
{
// Write file size later
- long pos = RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc);
-
- Debug.Assert(pos is 4, "Stream should be written from position 0.");
+ RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc);
// Write VP8X, header if necessary.
bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation;
diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
index 230f69c32d..c8c4a74a00 100644
--- a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
+++ b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
@@ -83,7 +83,7 @@ internal readonly struct WebpFrameData
///
public WebpDisposalMethod DisposalMethod { get; }
- public Rectangle Bounds => new((int)this.X * 2, (int)this.Y * 2, (int)this.Width, (int)this.Height);
+ public Rectangle Bounds => new((int)this.X, (int)this.Y, (int)this.Width, (int)this.Height);
///
/// Writes the animation frame() to the stream.
@@ -107,8 +107,8 @@ internal readonly struct WebpFrameData
long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.FrameData);
- WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.X);
- WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Y);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.X / 2);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Y / 2);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Duration);
@@ -128,8 +128,8 @@ internal readonly struct WebpFrameData
WebpFrameData data = new(
dataSize: WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
- x: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
- y: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
+ x: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2,
+ y: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2,
width: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
height: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
duration: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
index b9e2519fa4..518c09ff4d 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
@@ -240,7 +240,7 @@ internal class Vp8LEncoder : IDisposable
public void EncodeHeader(Image image, Stream stream, bool hasAnimation)
where TPixel : unmanaged, IPixel
{
- // Write bytes from the bitwriter buffer to the stream.
+ // Write bytes from the bit-writer buffer to the stream.
ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
@@ -267,7 +267,7 @@ internal class Vp8LEncoder : IDisposable
public void EncodeFooter(Image image, Stream stream)
where TPixel : unmanaged, IPixel
{
- // Write bytes from the bitwriter buffer to the stream.
+ // Write bytes from the bit-writer buffer to the stream.
ImageMetadata metadata = image.Metadata;
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
@@ -280,26 +280,25 @@ internal class Vp8LEncoder : IDisposable
/// Encodes the image as lossless webp to the specified stream.
///
/// The pixel format.
- /// The to encode from.
+ /// The image frame to encode from.
+ /// The region of interest within the frame to encode.
+ /// The frame metadata.
/// The to encode the image data to.
/// Flag indicating, if an animation parameter is present.
- public void Encode(ImageFrame frame, Stream stream, bool hasAnimation)
+ public void Encode(ImageFrame frame, Rectangle bounds, WebpFrameMetadata frameMetadata, 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(frame, width, height);
+ bool hasAlpha = this.ConvertPixelsToBgra(frame.PixelBuffer.GetRegion(bounds));
// Write the image size.
- this.WriteImageSize(width, height);
+ this.WriteImageSize(bounds.Width, bounds.Height);
// Write the non-trivial Alpha flag and lossless version.
this.WriteAlphaAndVersion(hasAlpha);
// Encode the main image stream.
- this.EncodeStream(frame);
+ this.EncodeStream(bounds.Width, bounds.Height);
this.bitWriter.Finish();
@@ -307,21 +306,18 @@ internal class Vp8LEncoder : IDisposable
if (hasAnimation)
{
- WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(frame);
-
- // TODO: If we can clip the indexed frame for transparent bounds we can set properties here.
prevPosition = new WebpFrameData(
- 0,
- 0,
- (uint)frame.Width,
- (uint)frame.Height,
+ (uint)bounds.Left,
+ (uint)bounds.Top,
+ (uint)bounds.Width,
+ (uint)bounds.Height,
frameMetadata.FrameDelay,
frameMetadata.BlendMethod,
frameMetadata.DisposalMethod)
.WriteHeaderTo(stream);
}
- // Write bytes from the bitwriter buffer to the stream.
+ // Write bytes from the bit-writer buffer to the stream.
this.bitWriter.WriteEncodedImageToStream(stream);
if (hasAnimation)
@@ -334,12 +330,12 @@ internal class Vp8LEncoder : IDisposable
/// Encodes the alpha image data using the webp lossless compression.
///
/// The type of the pixel.
- /// The to encode from.
+ /// The alpha-pixel data to encode from.
/// The destination buffer to write the encoded alpha data to.
/// The size of the compressed data in bytes.
/// If the size of the data is the same as the pixel count, the compression would not yield in smaller data and is left uncompressed.
///
- public int EncodeAlphaImageData(ImageFrame frame, IMemoryOwner alphaData)
+ public int EncodeAlphaImageData(Buffer2DRegion frame, IMemoryOwner alphaData)
where TPixel : unmanaged, IPixel
{
int width = frame.Width;
@@ -347,10 +343,10 @@ internal class Vp8LEncoder : IDisposable
int pixelCount = width * height;
// Convert image pixels to bgra array.
- this.ConvertPixelsToBgra(frame, width, height);
+ this.ConvertPixelsToBgra(frame);
// The image-stream will NOT contain any headers describing the image dimension, the dimension is already known.
- this.EncodeStream(frame);
+ this.EncodeStream(width, height);
this.bitWriter.Finish();
int size = this.bitWriter.NumBytes;
if (size >= pixelCount)
@@ -364,7 +360,7 @@ internal class Vp8LEncoder : IDisposable
}
///
- /// Writes the image size to the bitwriter buffer.
+ /// Writes the image size to the bit writer buffer.
///
/// The input image width.
/// The input image height.
@@ -381,7 +377,7 @@ internal class Vp8LEncoder : IDisposable
}
///
- /// Writes a flag indicating if alpha channel is used and the VP8L version to the bitwriter buffer.
+ /// Writes a flag indicating if alpha channel is used and the VP8L version to the bit-writer buffer.
///
/// Indicates if a alpha channel is present.
private void WriteAlphaAndVersion(bool hasAlpha)
@@ -393,14 +389,10 @@ internal class Vp8LEncoder : IDisposable
///
/// Encodes the image stream using lossless webp format.
///
- /// The pixel type.
- /// The frame to encode.
- private void EncodeStream(ImageFrame frame)
- where TPixel : unmanaged, IPixel
+ /// The image frame width.
+ /// The image frame height.
+ private void EncodeStream(int width, int height)
{
- int width = frame.Width;
- int height = frame.Height;
-
Span bgra = this.Bgra.GetSpan();
Span encodedData = this.EncodedData.GetSpan();
bool lowEffort = this.method == 0;
@@ -508,23 +500,20 @@ internal class Vp8LEncoder : IDisposable
/// Converts the pixels of the image to bgra.
///
/// The type of the pixels.
- /// The frame to convert.
- /// The width of the image.
- /// The height of the image.
+ /// The frame pixel buffer to convert.
/// true, if the image is non opaque.
- private bool ConvertPixelsToBgra(ImageFrame frame, int width, int height)
+ private bool ConvertPixelsToBgra(Buffer2DRegion pixels)
where TPixel : unmanaged, IPixel
{
- Buffer2D imageBuffer = frame.PixelBuffer;
bool nonOpaque = false;
Span bgra = this.Bgra.GetSpan();
Span bgraBytes = MemoryMarshal.Cast(bgra);
- int widthBytes = width * 4;
- for (int y = 0; y < height; y++)
+ int widthBytes = pixels.Width * 4;
+ for (int y = 0; y < pixels.Height; y++)
{
- Span rowSpan = imageBuffer.DangerousGetRowSpan(y);
+ Span rowSpan = pixels.DangerousGetRowSpan(y);
Span rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes);
- PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width);
+ PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, pixels.Width);
if (!nonOpaque)
{
Span rowBgra = MemoryMarshal.Cast(rowBytes);
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index e6148a0660..2b74c300a4 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -351,44 +351,53 @@ internal class Vp8Encoder : IDisposable
}
///
- /// Encodes the image to the specified stream from the .
+ /// Encodes the animated image frame to the specified stream.
///
/// The pixel format.
- /// The to encode from.
- /// The to encode the image data to.
- public void EncodeAnimation(ImageFrame frame, Stream stream)
+ /// The image frame to encode from.
+ /// The stream to encode the image data to.
+ /// The region of interest within the frame to encode.
+ /// The frame metadata.
+ public void EncodeAnimation(ImageFrame frame, Stream stream, Rectangle bounds, WebpFrameMetadata frameMetadata)
where TPixel : unmanaged, IPixel =>
- this.Encode(frame, stream, true, null);
+ this.Encode(stream, frame, bounds, frameMetadata, true, null);
///
- /// Encodes the image to the specified stream from the .
+ /// Encodes the static image frame to the specified stream.
///
/// The pixel format.
- /// The to encode from.
- /// The to encode the image data to.
- public void EncodeStatic(Image image, Stream stream)
- where TPixel : unmanaged, IPixel =>
- this.Encode(image.Frames.RootFrame, stream, false, image);
+ /// The stream to encode the image data to.
+ /// The image to encode from.
+ public void EncodeStatic(Stream stream, Image image)
+ where TPixel : unmanaged, IPixel
+ {
+ ImageFrame frame = image.Frames.RootFrame;
+ this.Encode(stream, frame, image.Bounds, WebpCommonUtils.GetWebpFrameMetadata(frame), false, image);
+ }
///
- /// Encodes the image to the specified stream from the .
+ /// Encodes the image to the specified stream.
///
/// The pixel format.
- /// The to encode from.
- /// The to encode the image data to.
+ /// The stream to encode the image data to.
+ /// The image frame to encode from.
+ /// The region of interest within the frame to encode.
+ /// The frame metadata.
/// Flag indicating, if an animation parameter is present.
- /// The to encode from.
- private void Encode(ImageFrame frame, Stream stream, bool hasAnimation, Image image)
+ /// The image to encode from.
+ private void Encode(Stream stream, ImageFrame frame, Rectangle bounds, WebpFrameMetadata frameMetadata, bool hasAnimation, Image image)
where TPixel : unmanaged, IPixel
{
- int width = frame.Width;
- int height = frame.Height;
+ int width = bounds.Width;
+ int height = bounds.Height;
int pixelCount = width * height;
Span y = this.Y.GetSpan();
Span u = this.U.GetSpan();
Span v = this.V.GetSpan();
- bool hasAlpha = YuvConversion.ConvertRgbToYuv(frame, this.configuration, this.memoryAllocator, y, u, v);
+
+ Buffer2DRegion pixels = frame.PixelBuffer.GetRegion(bounds);
+ bool hasAlpha = YuvConversion.ConvertRgbToYuv(pixels, this.configuration, this.memoryAllocator, y, u, v);
if (!hasAnimation)
{
@@ -456,7 +465,7 @@ internal class Vp8Encoder : IDisposable
{
// TODO: This can potentially run in an separate task.
encodedAlphaData = AlphaEncoder.EncodeAlpha(
- frame,
+ pixels,
this.configuration,
this.memoryAllocator,
this.skipMetadata,
@@ -477,14 +486,11 @@ internal class Vp8Encoder : IDisposable
if (hasAnimation)
{
- WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(frame);
-
- // TODO: If we can clip the indexed frame for transparent bounds we can set properties here.
prevPosition = new WebpFrameData(
- 0,
- 0,
- (uint)frame.Width,
- (uint)frame.Height,
+ (uint)bounds.X,
+ (uint)bounds.Y,
+ (uint)bounds.Width,
+ (uint)bounds.Height,
frameMetadata.FrameDelay,
frameMetadata.BlendMethod,
frameMetadata.DisposalMethod)
diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
index d669a37b74..f8e664ed03 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
@@ -259,7 +259,7 @@ internal static class YuvConversion
}
///
- /// Converts the RGB values of the image to YUV.
+ /// Converts the pixel values of the image to YUV.
///
/// The pixel type of the image.
/// The frame to convert.
@@ -269,12 +269,11 @@ internal static class YuvConversion
/// Span to store the u component of the image.
/// Span to store the v component of the image.
/// true, if the image contains alpha data.
- public static bool ConvertRgbToYuv(ImageFrame frame, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v)
+ public static bool ConvertRgbToYuv(Buffer2DRegion frame, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v)
where TPixel : unmanaged, IPixel
{
- Buffer2D imageBuffer = frame.PixelBuffer;
- int width = imageBuffer.Width;
- int height = imageBuffer.Height;
+ int width = frame.Width;
+ int height = frame.Height;
int uvWidth = (width + 1) >> 1;
// Temporary storage for accumulated R/G/B values during conversion to U/V.
@@ -289,8 +288,8 @@ internal static class YuvConversion
bool hasAlpha = false;
for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2)
{
- Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex);
- Span nextRowSpan = imageBuffer.DangerousGetRowSpan(rowIndex + 1);
+ Span rowSpan = frame.DangerousGetRowSpan(rowIndex);
+ Span nextRowSpan = frame.DangerousGetRowSpan(rowIndex + 1);
PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0);
PixelOperations.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1);
@@ -320,7 +319,7 @@ internal static class YuvConversion
// Extra last row.
if ((height & 1) != 0)
{
- Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex);
+ Span rowSpan = frame.DangerousGetRowSpan(rowIndex);
PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0);
ConvertRgbaToY(bgraRow0, y[(rowIndex * width)..], width);
diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
index f081cfcd89..d85096c2e8 100644
--- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
+++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
@@ -253,7 +253,7 @@ internal class WebpAnimationDecoder : IDisposable
private Buffer2D DecodeImageFrameData(WebpFrameData frameData, WebpImageInfo webpInfo)
where TPixel : unmanaged, IPixel
{
- ImageFrame decodedFrame = new(Configuration.Default, (int)frameData.Width, (int)frameData.Height);
+ ImageFrame decodedFrame = new(this.configuration, (int)frameData.Width, (int)frameData.Height);
try
{
diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
index 8374870473..7357e097c9 100644
--- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Numerics;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Memory;
@@ -129,6 +130,8 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
if (lossless)
{
+ bool hasAnimation = image.Frames.Count > 1;
+
using Vp8LEncoder encoder = new(
this.memoryAllocator,
this.configuration,
@@ -141,17 +144,34 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
this.nearLossless,
this.nearLosslessQuality);
- bool hasAnimation = image.Frames.Count > 1;
encoder.EncodeHeader(image, stream, hasAnimation);
+
+ // Encode the first frame.
+ ImageFrame previousFrame = image.Frames.RootFrame;
+ WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(previousFrame);
+ encoder.Encode(previousFrame, previousFrame.Bounds(), frameMetadata, stream, hasAnimation);
+
if (hasAnimation)
{
- foreach (ImageFrame imageFrame in image.Frames)
+ WebpDisposalMethod previousDisposal = frameMetadata.DisposalMethod;
+
+ // Encode additional frames
+ // This frame is reused to store de-duplicated pixel buffers.
+ using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size());
+
+ for (int i = 1; i < image.Frames.Count; i++)
{
- using Vp8LEncoder enc = new(
+ ImageFrame currentFrame = image.Frames[i];
+ frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame);
+
+ ImageFrame? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame;
+ (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero);
+
+ using Vp8LEncoder animatedEncoder = new(
this.memoryAllocator,
this.configuration,
- image.Width,
- image.Height,
+ bounds.Width,
+ bounds.Height,
this.quality,
this.skipMetadata,
this.method,
@@ -159,13 +179,12 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
this.nearLossless,
this.nearLosslessQuality);
- enc.Encode(imageFrame, stream, true);
+ animatedEncoder.Encode(encodingFrame, bounds, frameMetadata, stream, hasAnimation);
+
+ previousFrame = currentFrame;
+ previousDisposal = frameMetadata.DisposalMethod;
}
}
- else
- {
- encoder.Encode(image.Frames.RootFrame, stream, false);
- }
encoder.EncodeFooter(image, stream);
}
@@ -183,17 +202,36 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
this.filterStrength,
this.spatialNoiseShaping,
this.alphaCompression);
+
if (image.Frames.Count > 1)
{
+ // TODO: What about alpha here?
encoder.EncodeHeader(image, stream, false, true);
- foreach (ImageFrame imageFrame in image.Frames)
+ // Encode the first frame.
+ ImageFrame previousFrame = image.Frames.RootFrame;
+ WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(previousFrame);
+ WebpDisposalMethod previousDisposal = frameMetadata.DisposalMethod;
+
+ encoder.EncodeAnimation(previousFrame, stream, previousFrame.Bounds(), frameMetadata);
+
+ // Encode additional frames
+ // This frame is reused to store de-duplicated pixel buffers.
+ using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size());
+
+ for (int i = 1; i < image.Frames.Count; i++)
{
- using Vp8Encoder enc = new(
+ ImageFrame currentFrame = image.Frames[i];
+ frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame);
+
+ ImageFrame? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame;
+ (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero);
+
+ using Vp8Encoder animatedEncoder = new(
this.memoryAllocator,
this.configuration,
- image.Width,
- image.Height,
+ bounds.Width,
+ bounds.Height,
this.quality,
this.skipMetadata,
this.method,
@@ -202,12 +240,15 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
this.spatialNoiseShaping,
this.alphaCompression);
- enc.EncodeAnimation(imageFrame, stream);
+ animatedEncoder.EncodeAnimation(encodingFrame, stream, bounds, frameMetadata);
+
+ previousFrame = currentFrame;
+ previousDisposal = frameMetadata.DisposalMethod;
}
}
else
{
- encoder.EncodeStatic(image, stream);
+ encoder.EncodeStatic(stream, image);
}
encoder.EncodeFooter(image, stream);
diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs
index 433b280bc3..3ae6601b18 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Memory;
@@ -143,7 +142,7 @@ public class YuvConversionTests
};
// act
- YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame, config, memoryAllocator, y, u, v);
+ YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame.PixelBuffer.GetRegion(), config, memoryAllocator, y, u, v);
// assert
Assert.True(expectedY.AsSpan().SequenceEqual(y));
@@ -249,7 +248,7 @@ public class YuvConversionTests
};
// act
- YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame, config, memoryAllocator, y, u, v);
+ YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame.PixelBuffer.GetRegion(), config, memoryAllocator, y, u, v);
// assert
Assert.True(expectedY.AsSpan().SequenceEqual(y));