Browse Source

Add dedup to webp

pull/2588/head
James Jackson-South 3 years ago
parent
commit
cc0727b286
  1. 37
      src/ImageSharp/Formats/Webp/AlphaEncoder.cs
  2. 5
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  3. 10
      src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
  4. 69
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  5. 60
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  6. 15
      src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
  7. 2
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  8. 73
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
  9. 5
      tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs

37
src/ImageSharp/Formats/Webp/AlphaEncoder.cs

@ -27,7 +27,7 @@ internal static class AlphaEncoder
/// <param name="size">The size in bytes of the alpha data.</param>
/// <returns>The encoded alpha data.</returns>
public static IMemoryOwner<byte> EncodeAlpha<TPixel>(
ImageFrame<TPixel> frame,
Buffer2DRegion<TPixel> frame,
Configuration configuration,
MemoryAllocator memoryAllocator,
bool skipMetadata,
@ -35,8 +35,6 @@ internal static class AlphaEncoder
out int size)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = frame.Width;
int height = frame.Height;
IMemoryOwner<byte> 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<Rgba32> alphaAsFrame = DispatchAlphaToGreen(frame, alphaData.GetSpan());
using ImageFrame<Bgra32> 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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="frame">The pixel buffer to encode from.</param>
/// <param name="alphaData">A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.</param>
/// <returns>The transparency frame.</returns>
private static ImageFrame<Rgba32> DispatchAlphaToGreen<TPixel>(ImageFrame<TPixel> frame, Span<byte> alphaData)
private static ImageFrame<Bgra32> DispatchAlphaToGreen<TPixel>(Configuration configuration, Buffer2DRegion<TPixel> frame, Span<byte> alphaData)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = frame.Width;
int height = frame.Height;
ImageFrame<Rgba32> alphaAsFrame = new ImageFrame<Rgba32>(Configuration.Default, width, height);
ImageFrame<Bgra32> alphaAsFrame = new(configuration, width, height);
for (int y = 0; y < height; y++)
{
Memory<Rgba32> rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y);
Span<Rgba32> pixelRow = rowBuffer.Span;
Memory<Bgra32> rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y);
Span<Bgra32> pixelRow = rowBuffer.Span;
Span<byte> 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
/// <param name="configuration">The global configuration.</param>
/// <param name="memoryAllocator">The memory manager.</param>
/// <returns>A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.</returns>
private static IMemoryOwner<byte> ExtractAlphaChannel<TPixel>(ImageFrame<TPixel> frame, Configuration configuration, MemoryAllocator memoryAllocator)
private static IMemoryOwner<byte> ExtractAlphaChannel<TPixel>(Buffer2DRegion<TPixel> frame, Configuration configuration, MemoryAllocator memoryAllocator)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> imageBuffer = frame.PixelBuffer;
int height = frame.Height;
int width = frame.Width;
int height = frame.Height;
IMemoryOwner<byte> alphaDataBuffer = memoryAllocator.Allocate<byte>(width * height);
Span<byte> alphaData = alphaDataBuffer.GetSpan();
@ -120,7 +121,7 @@ internal static class AlphaEncoder
for (int y = 0; y < height; y++)
{
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(y);
Span<TPixel> rowSpan = frame.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgba32(configuration, rowSpan, rgbaRow);
int offset = y * width;
for (int x = 0; x < width; x++)

5
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;

10
src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs

@ -83,7 +83,7 @@ internal readonly struct WebpFrameData
/// </summary>
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);
/// <summary>
/// Writes the animation frame(<see cref="WebpChunkType.FrameData"/>) 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),

69
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -240,7 +240,7 @@ internal class Vp8LEncoder : IDisposable
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation)
where TPixel : unmanaged, IPixel<TPixel>
{
// 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<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// 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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="frame">The image frame to encode from.</param>
/// <param name="bounds">The region of interest within the frame to encode.</param>
/// <param name="frameMetadata">The frame metadata.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
public void Encode<TPixel>(ImageFrame<TPixel> frame, Stream stream, bool hasAnimation)
public void Encode<TPixel>(ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, Stream stream, bool hasAnimation)
where TPixel : unmanaged, IPixel<TPixel>
{
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.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="frame">The alpha-pixel data to encode from.</param>
/// <param name="alphaData">The destination buffer to write the encoded alpha data to.</param>
/// <returns>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.
/// </returns>
public int EncodeAlphaImageData<TPixel>(ImageFrame<TPixel> frame, IMemoryOwner<byte> alphaData)
public int EncodeAlphaImageData<TPixel>(Buffer2DRegion<TPixel> frame, IMemoryOwner<byte> alphaData)
where TPixel : unmanaged, IPixel<TPixel>
{
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
}
/// <summary>
/// Writes the image size to the bitwriter buffer.
/// Writes the image size to the bit writer buffer.
/// </summary>
/// <param name="inputImgWidth">The input image width.</param>
/// <param name="inputImgHeight">The input image height.</param>
@ -381,7 +377,7 @@ internal class Vp8LEncoder : IDisposable
}
/// <summary>
/// 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.
/// </summary>
/// <param name="hasAlpha">Indicates if a alpha channel is present.</param>
private void WriteAlphaAndVersion(bool hasAlpha)
@ -393,14 +389,10 @@ internal class Vp8LEncoder : IDisposable
/// <summary>
/// Encodes the image stream using lossless webp format.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="frame">The frame to encode.</param>
private void EncodeStream<TPixel>(ImageFrame<TPixel> frame)
where TPixel : unmanaged, IPixel<TPixel>
/// <param name="width">The image frame width.</param>
/// <param name="height">The image frame height.</param>
private void EncodeStream(int width, int height)
{
int width = frame.Width;
int height = frame.Height;
Span<uint> bgra = this.Bgra.GetSpan();
Span<uint> 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.
/// </summary>
/// <typeparam name="TPixel">The type of the pixels.</typeparam>
/// <param name="frame">The frame to convert.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The frame pixel buffer to convert.</param>
/// <returns>true, if the image is non opaque.</returns>
private bool ConvertPixelsToBgra<TPixel>(ImageFrame<TPixel> frame, int width, int height)
private bool ConvertPixelsToBgra<TPixel>(Buffer2DRegion<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> imageBuffer = frame.PixelBuffer;
bool nonOpaque = false;
Span<uint> bgra = this.Bgra.GetSpan();
Span<byte> bgraBytes = MemoryMarshal.Cast<uint, byte>(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<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(y);
Span<TPixel> rowSpan = pixels.DangerousGetRowSpan(y);
Span<byte> rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, pixels.Width);
if (!nonOpaque)
{
Span<Bgra32> rowBgra = MemoryMarshal.Cast<byte, Bgra32>(rowBytes);

60
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -351,44 +351,53 @@ internal class Vp8Encoder : IDisposable
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// Encodes the animated image frame to the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void EncodeAnimation<TPixel>(ImageFrame<TPixel> frame, Stream stream)
/// <param name="frame">The image frame to encode from.</param>
/// <param name="stream">The stream to encode the image data to.</param>
/// <param name="bounds">The region of interest within the frame to encode.</param>
/// <param name="frameMetadata">The frame metadata.</param>
public void EncodeAnimation<TPixel>(ImageFrame<TPixel> frame, Stream stream, Rectangle bounds, WebpFrameMetadata frameMetadata)
where TPixel : unmanaged, IPixel<TPixel> =>
this.Encode(frame, stream, true, null);
this.Encode(stream, frame, bounds, frameMetadata, true, null);
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// Encodes the static image frame to the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void EncodeStatic<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> =>
this.Encode(image.Frames.RootFrame, stream, false, image);
/// <param name="stream">The stream to encode the image data to.</param>
/// <param name="image">The image to encode from.</param>
public void EncodeStatic<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
ImageFrame<TPixel> frame = image.Frames.RootFrame;
this.Encode(stream, frame, image.Bounds, WebpCommonUtils.GetWebpFrameMetadata(frame), false, image);
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// Encodes the image to the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="stream">The stream to encode the image data to.</param>
/// <param name="frame">The image frame to encode from.</param>
/// <param name="bounds">The region of interest within the frame to encode.</param>
/// <param name="frameMetadata">The frame metadata.</param>
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
private void Encode<TPixel>(ImageFrame<TPixel> frame, Stream stream, bool hasAnimation, Image<TPixel> image)
/// <param name="image">The image to encode from.</param>
private void Encode<TPixel>(Stream stream, ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, bool hasAnimation, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = frame.Width;
int height = frame.Height;
int width = bounds.Width;
int height = bounds.Height;
int pixelCount = width * height;
Span<byte> y = this.Y.GetSpan();
Span<byte> u = this.U.GetSpan();
Span<byte> v = this.V.GetSpan();
bool hasAlpha = YuvConversion.ConvertRgbToYuv(frame, this.configuration, this.memoryAllocator, y, u, v);
Buffer2DRegion<TPixel> 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)

15
src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs

@ -259,7 +259,7 @@ internal static class YuvConversion
}
/// <summary>
/// Converts the RGB values of the image to YUV.
/// Converts the pixel values of the image to YUV.
/// </summary>
/// <typeparam name="TPixel">The pixel type of the image.</typeparam>
/// <param name="frame">The frame to convert.</param>
@ -269,12 +269,11 @@ internal static class YuvConversion
/// <param name="u">Span to store the u component of the image.</param>
/// <param name="v">Span to store the v component of the image.</param>
/// <returns>true, if the image contains alpha data.</returns>
public static bool ConvertRgbToYuv<TPixel>(ImageFrame<TPixel> frame, Configuration configuration, MemoryAllocator memoryAllocator, Span<byte> y, Span<byte> u, Span<byte> v)
public static bool ConvertRgbToYuv<TPixel>(Buffer2DRegion<TPixel> frame, Configuration configuration, MemoryAllocator memoryAllocator, Span<byte> y, Span<byte> u, Span<byte> v)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> 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<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex);
Span<TPixel> nextRowSpan = imageBuffer.DangerousGetRowSpan(rowIndex + 1);
Span<TPixel> rowSpan = frame.DangerousGetRowSpan(rowIndex);
Span<TPixel> nextRowSpan = frame.DangerousGetRowSpan(rowIndex + 1);
PixelOperations<TPixel>.Instance.ToBgra32(configuration, rowSpan, bgraRow0);
PixelOperations<TPixel>.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1);
@ -320,7 +319,7 @@ internal static class YuvConversion
// Extra last row.
if ((height & 1) != 0)
{
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex);
Span<TPixel> rowSpan = frame.DangerousGetRowSpan(rowIndex);
PixelOperations<TPixel>.Instance.ToBgra32(configuration, rowSpan, bgraRow0);
ConvertRgbaToY(bgraRow0, y[(rowIndex * width)..], width);

2
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -253,7 +253,7 @@ internal class WebpAnimationDecoder : IDisposable
private Buffer2D<TPixel> DecodeImageFrameData<TPixel>(WebpFrameData frameData, WebpImageInfo webpInfo)
where TPixel : unmanaged, IPixel<TPixel>
{
ImageFrame<TPixel> decodedFrame = new(Configuration.Default, (int)frameData.Width, (int)frameData.Height);
ImageFrame<TPixel> decodedFrame = new(this.configuration, (int)frameData.Width, (int)frameData.Height);
try
{

73
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<TPixel> previousFrame = image.Frames.RootFrame;
WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(previousFrame);
encoder.Encode(previousFrame, previousFrame.Bounds(), frameMetadata, stream, hasAnimation);
if (hasAnimation)
{
foreach (ImageFrame<TPixel> imageFrame in image.Frames)
WebpDisposalMethod previousDisposal = frameMetadata.DisposalMethod;
// Encode additional frames
// This frame is reused to store de-duplicated pixel buffers.
using ImageFrame<TPixel> encodingFrame = new(image.Configuration, previousFrame.Size());
for (int i = 1; i < image.Frames.Count; i++)
{
using Vp8LEncoder enc = new(
ImageFrame<TPixel> currentFrame = image.Frames[i];
frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame);
ImageFrame<TPixel>? 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<TPixel> imageFrame in image.Frames)
// Encode the first frame.
ImageFrame<TPixel> 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<TPixel> encodingFrame = new(image.Configuration, previousFrame.Size());
for (int i = 1; i < image.Frames.Count; i++)
{
using Vp8Encoder enc = new(
ImageFrame<TPixel> currentFrame = image.Frames[i];
frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame);
ImageFrame<TPixel>? 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);

5
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));

Loading…
Cancel
Save