diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
index 596715b205..a18d44fde4 100644
--- a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
@@ -19,7 +19,7 @@ internal static class AlphaEncoder
/// Data is either compressed as lossless webp image or uncompressed.
///
/// The pixel format.
- /// The to encode from.
+ /// The to encode from.
/// The global configuration.
/// The memory manager.
/// Whether to skip metadata encoding.
@@ -27,7 +27,7 @@ internal static class AlphaEncoder
/// The size in bytes of the alpha data.
/// The encoded alpha data.
public static IMemoryOwner EncodeAlpha(
- Image image,
+ ImageFrame frame,
Configuration configuration,
MemoryAllocator memoryAllocator,
bool skipMetadata,
@@ -35,9 +35,9 @@ internal static class AlphaEncoder
out int size)
where TPixel : unmanaged, IPixel
{
- int width = image.Width;
- int height = image.Height;
- IMemoryOwner alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
+ int width = frame.Width;
+ int height = frame.Height;
+ IMemoryOwner alphaData = ExtractAlphaChannel(frame, configuration, memoryAllocator);
if (compress)
{
@@ -58,9 +58,9 @@ 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 Image alphaAsImage = DispatchAlphaToGreen(image, alphaData.GetSpan());
+ using ImageFrame alphaAsFrame = DispatchAlphaToGreen(frame, alphaData.GetSpan());
- size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, alphaData);
+ size = lossLessEncoder.EncodeAlphaImageData(alphaAsFrame, alphaData);
return alphaData;
}
@@ -73,45 +73,45 @@ internal static class AlphaEncoder
/// Store the transparency in the green channel.
///
/// The pixel format.
- /// The to encode from.
+ /// The to encode from.
/// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.
- /// The transparency image.
- private static Image DispatchAlphaToGreen(Image image, Span alphaData)
+ /// The transparency frame.
+ private static ImageFrame DispatchAlphaToGreen(ImageFrame frame, Span alphaData)
where TPixel : unmanaged, IPixel
{
- int width = image.Width;
- int height = image.Height;
- Image alphaAsImage = new(width, height);
+ int width = frame.Width;
+ int height = frame.Height;
+ ImageFrame alphaAsFrame = new(Configuration.Default, width, height);
for (int y = 0; y < height; y++)
{
- Memory rowBuffer = alphaAsImage.DangerousGetPixelRowMemory(y);
+ Memory rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y);
Span pixelRow = rowBuffer.Span;
Span alphaRow = alphaData.Slice(y * width, width);
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(0, alphaRow[x], 0, 0);
}
}
- return alphaAsImage;
+ return alphaAsFrame;
}
///
/// Extract the alpha data of the image.
///
/// The pixel format.
- /// The to encode from.
+ /// The to encode from.
/// 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(Image image, Configuration configuration, MemoryAllocator memoryAllocator)
+ private static IMemoryOwner ExtractAlphaChannel(ImageFrame frame, Configuration configuration, MemoryAllocator memoryAllocator)
where TPixel : unmanaged, IPixel
{
- Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer;
- int height = image.Height;
- int width = image.Width;
+ Buffer2D imageBuffer = frame.PixelBuffer;
+ int height = frame.Height;
+ int width = frame.Width;
IMemoryOwner alphaDataBuffer = memoryAllocator.Allocate(width * height);
Span alphaData = alphaDataBuffer.GetSpan();
diff --git a/src/ImageSharp/Formats/Webp/AnimationFrameData.cs b/src/ImageSharp/Formats/Webp/AnimationFrameData.cs
index 714ec428ec..3400fef17d 100644
--- a/src/ImageSharp/Formats/Webp/AnimationFrameData.cs
+++ b/src/ImageSharp/Formats/Webp/AnimationFrameData.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.IO;
+
namespace SixLabors.ImageSharp.Formats.Webp;
internal struct AnimationFrameData
@@ -10,6 +12,11 @@ internal struct AnimationFrameData
///
public uint DataSize;
+ ///
+ /// X(3) + Y(3) + Width(3) + Height(3) + Duration(3) + 1 byte for flags.
+ ///
+ public const uint HeaderSize = 16;
+
///
/// The X coordinate of the upper left corner of the frame is Frame X * 2.
///
@@ -45,4 +52,40 @@ internal struct AnimationFrameData
/// 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;
+
+ ///
+ /// Reads the animation frame header.
+ ///
+ /// The stream to read from.
+ /// Animation frame data.
+ public static AnimationFrameData Parse(BufferedReadStream stream)
+ {
+ Span buffer = stackalloc byte[4];
+
+ AnimationFrameData data = new()
+ {
+ DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
+
+ // 3 bytes for the X coordinate of the upper left corner of the frame.
+ X = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
+
+ // 3 bytes for the Y coordinate of the upper left corner of the frame.
+ Y = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
+
+ // Frame width Minus One.
+ Width = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
+
+ // Frame height Minus One.
+ Height = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
+
+ // Frame duration.
+ Duration = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer)
+ };
+
+ byte flags = (byte)stream.ReadByte();
+ data.DisposalMethod = (flags & 1) == 1 ? AnimationDisposalMethod.Dispose : AnimationDisposalMethod.DoNotDispose;
+ data.BlendingMethod = (flags & (1 << 1)) != 0 ? AnimationBlendingMethod.DoNotBlend : AnimationBlendingMethod.AlphaBlending;
+
+ return data;
+ }
}
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
index ab78d18604..d7787b3a00 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
@@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
+using System.Drawing;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
@@ -92,7 +93,7 @@ internal abstract class BitWriterBase
{
stream.Write(WebpConstants.RiffFourCc);
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer.Span, riffSize);
- stream.Write(this.scratchBuffer.Span.Slice(0, 4));
+ stream.Write(this.scratchBuffer.Span[..4]);
stream.Write(WebpConstants.WebpHeader);
}
@@ -129,7 +130,7 @@ internal abstract class BitWriterBase
DebugGuard.NotNull(metadataBytes, nameof(metadataBytes));
uint size = (uint)metadataBytes.Length;
- Span buf = this.scratchBuffer.Span.Slice(0, 4);
+ Span buf = this.scratchBuffer.Span[..4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@@ -143,6 +144,61 @@ internal abstract class BitWriterBase
}
}
+ ///
+ /// Writes the color profile() to the stream.
+ ///
+ /// The stream to write to.
+ /// The color profile bytes.
+ protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes) => this.WriteMetadataProfile(stream, iccProfileBytes, WebpChunkType.Iccp);
+
+ ///
+ /// Writes the animation parameter() to the stream.
+ ///
+ /// The stream to write to.
+ ///
+ /// 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.
+ ///
+ /// The number of times to loop the animation. If it is 0, this means infinitely.
+ protected void WriteAnimationParameter(Stream stream, uint background, ushort loopCount)
+ {
+ Span buf = this.scratchBuffer.Span[..4];
+ BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.AnimationParameter);
+ stream.Write(buf);
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, sizeof(uint) + sizeof(ushort));
+ stream.Write(buf);
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, background);
+ stream.Write(buf);
+ BinaryPrimitives.WriteUInt16LittleEndian(buf[..2], loopCount);
+ stream.Write(buf[..2]);
+ }
+
+ ///
+ /// Writes the animation frame() to the stream.
+ ///
+ /// The stream to write to.
+ /// Animation frame data.
+ /// Frame data.
+ protected void WriteAnimationFrame(Stream stream, AnimationFrameData animation, byte[] data)
+ {
+ uint size = AnimationFrameData.HeaderSize + (uint)data.Length;
+ Span buf = this.scratchBuffer.Span[..4];
+ BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Animation);
+ stream.Write(buf);
+ BinaryPrimitives.WriteUInt32BigEndian(buf, size);
+ stream.Write(buf);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.X);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Y);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Width - 1);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Height - 1);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Duration);
+ byte flag = (byte)(((int)animation.BlendingMethod << 1) | (int)animation.DisposalMethod);
+ stream.WriteByte(flag);
+ stream.Write(data);
+ }
+
///
/// Writes the alpha chunk to the stream.
///
@@ -152,7 +208,7 @@ internal abstract class BitWriterBase
protected void WriteAlphaChunk(Stream stream, Span dataBytes, bool alphaDataIsCompressed)
{
uint size = (uint)dataBytes.Length + 1;
- Span buf = this.scratchBuffer.Span.Slice(0, 4);
+ Span buf = this.scratchBuffer.Span[..4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@@ -161,7 +217,7 @@ internal abstract class BitWriterBase
byte flags = 0;
if (alphaDataIsCompressed)
{
- flags |= 1;
+ flags = 1;
}
stream.WriteByte(flags);
@@ -174,30 +230,6 @@ internal abstract class BitWriterBase
}
}
- ///
- /// Writes the color profile to the stream.
- ///
- /// The stream to write to.
- /// The color profile bytes.
- protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes)
- {
- uint size = (uint)iccProfileBytes.Length;
-
- Span buf = this.scratchBuffer.Span.Slice(0, 4);
- BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Iccp);
- stream.Write(buf);
- BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
- stream.Write(buf);
-
- stream.Write(iccProfileBytes);
-
- // Add padding byte if needed.
- if ((size & 1) == 1)
- {
- stream.WriteByte(0);
- }
- }
-
///
/// Writes a VP8X header to the stream.
///
@@ -246,8 +278,9 @@ internal abstract class BitWriterBase
flags |= 32;
}
- Span buf = this.scratchBuffer.Span.Slice(0, 4);
- stream.Write(WebpConstants.Vp8XMagicBytes);
+ Span buf = this.scratchBuffer.Span[..4];
+ BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Vp8X);
+ stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, flags);
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
index 5b4eab64a3..597ecef42a 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
@@ -701,12 +701,11 @@ internal class Vp8BitWriter : BitWriterBase
private void WriteVp8Header(Stream stream, uint size)
{
- Span vp8ChunkHeader = stackalloc byte[WebpConstants.ChunkHeaderSize];
-
- WebpConstants.Vp8MagicBytes.AsSpan().CopyTo(vp8ChunkHeader);
- BinaryPrimitives.WriteUInt32LittleEndian(vp8ChunkHeader[4..], size);
-
- stream.Write(vp8ChunkHeader);
+ Span buf = stackalloc byte[WebpConstants.TagSize];
+ BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Vp8);
+ stream.Write(buf);
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
+ stream.Write(buf);
}
private void WriteFrameHeader(Stream stream, uint size0)
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
index 9dc7912392..a042f68968 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
@@ -105,7 +105,7 @@ internal class Vp8LBitWriter : BitWriterBase
{
byte[] clonedBuffer = new byte[this.Buffer.Length];
System.Buffer.BlockCopy(this.Buffer, 0, clonedBuffer, 0, this.cur);
- return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur);
+ return new(clonedBuffer, this.bits, this.used, this.cur);
}
///
@@ -186,12 +186,13 @@ internal class Vp8LBitWriter : BitWriterBase
}
// Write magic bytes indicating its a lossless webp.
- stream.Write(WebpConstants.Vp8LMagicBytes);
+ Span scratchBuffer = stackalloc byte[WebpConstants.TagSize];
+ BinaryPrimitives.WriteUInt32BigEndian(scratchBuffer, (uint)WebpChunkType.Vp8L);
+ stream.Write(scratchBuffer);
// Write Vp8 Header.
- Span scratchBuffer = stackalloc byte[8];
BinaryPrimitives.WriteUInt32LittleEndian(scratchBuffer, size);
- stream.Write(scratchBuffer.Slice(0, 4));
+ stream.Write(scratchBuffer);
stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte);
// Write the encoded bytes of the image to the stream.
@@ -226,7 +227,7 @@ internal class Vp8LBitWriter : BitWriterBase
Span scratchBuffer = stackalloc byte[8];
BinaryPrimitives.WriteUInt64LittleEndian(scratchBuffer, this.bits);
- scratchBuffer.Slice(0, 4).CopyTo(this.Buffer.AsSpan(this.cur));
+ scratchBuffer[..4].CopyTo(this.Buffer.AsSpan(this.cur));
this.cur += WriterBytes;
this.bits >>= WriterBits;
diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
index 469e4c9ab0..9b82cc5983 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
@@ -254,7 +254,7 @@ internal class Vp8LEncoder : IDisposable
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
// Convert image pixels to bgra array.
- bool hasAlpha = this.ConvertPixelsToBgra(image, width, height);
+ bool hasAlpha = this.ConvertPixelsToBgra(image.Frames.RootFrame, width, height);
// Write the image size.
this.WriteImageSize(width, height);
@@ -263,7 +263,7 @@ internal class Vp8LEncoder : IDisposable
this.WriteAlphaAndVersion(hasAlpha);
// Encode the main image stream.
- this.EncodeStream(image);
+ this.EncodeStream(image.Frames.RootFrame);
// Write bytes from the bitwriter buffer to the stream.
this.bitWriter.WriteEncodedImageToStream(stream, exifProfile, xmpProfile, metadata.IccProfile, (uint)width, (uint)height, hasAlpha);
@@ -273,23 +273,23 @@ internal class Vp8LEncoder : IDisposable
/// Encodes the alpha image data using the webp lossless compression.
///
/// The type of the pixel.
- /// The to encode from.
+ /// The 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(Image image, IMemoryOwner alphaData)
+ public int EncodeAlphaImageData(ImageFrame frame, IMemoryOwner alphaData)
where TPixel : unmanaged, IPixel
{
- int width = image.Width;
- int height = image.Height;
+ int width = frame.Width;
+ int height = frame.Height;
int pixelCount = width * height;
// Convert image pixels to bgra array.
- this.ConvertPixelsToBgra(image, width, height);
+ this.ConvertPixelsToBgra(frame, width, height);
// The image-stream will NOT contain any headers describing the image dimension, the dimension is already known.
- this.EncodeStream(image);
+ this.EncodeStream(frame);
this.bitWriter.Finish();
int size = this.bitWriter.NumBytes();
if (size >= pixelCount)
@@ -333,12 +333,12 @@ internal class Vp8LEncoder : IDisposable
/// Encodes the image stream using lossless webp format.
///
/// The pixel type.
- /// The image to encode.
- private void EncodeStream(Image image)
+ /// The frame to encode.
+ private void EncodeStream(ImageFrame frame)
where TPixel : unmanaged, IPixel
{
- int width = image.Width;
- int height = image.Height;
+ int width = frame.Width;
+ int height = frame.Height;
Span bgra = this.Bgra.GetSpan();
Span encodedData = this.EncodedData.GetSpan();
@@ -447,14 +447,14 @@ internal class Vp8LEncoder : IDisposable
/// Converts the pixels of the image to bgra.
///
/// The type of the pixels.
- /// The image to convert.
+ /// The frame to convert.
/// The width of the image.
/// The height of the image.
/// true, if the image is non opaque.
- private bool ConvertPixelsToBgra(Image image, int width, int height)
+ private bool ConvertPixelsToBgra(ImageFrame frame, int width, int height)
where TPixel : unmanaged, IPixel
{
- Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer;
+ Buffer2D imageBuffer = frame.PixelBuffer;
bool nonOpaque = false;
Span bgra = this.Bgra.GetSpan();
Span bgraBytes = MemoryMarshal.Cast(bgra);
@@ -1149,35 +1149,41 @@ internal class Vp8LEncoder : IDisposable
entropyComp[j] = bitEntropy.BitsEntropyRefine();
}
- entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] +
- entropyComp[(int)HistoIx.HistoRed] +
- entropyComp[(int)HistoIx.HistoGreen] +
- entropyComp[(int)HistoIx.HistoBlue];
- entropy[(int)EntropyIx.Spatial] = entropyComp[(int)HistoIx.HistoAlphaPred] +
- entropyComp[(int)HistoIx.HistoRedPred] +
- entropyComp[(int)HistoIx.HistoGreenPred] +
- entropyComp[(int)HistoIx.HistoBluePred];
- entropy[(int)EntropyIx.SubGreen] = entropyComp[(int)HistoIx.HistoAlpha] +
- entropyComp[(int)HistoIx.HistoRedSubGreen] +
- entropyComp[(int)HistoIx.HistoGreen] +
- entropyComp[(int)HistoIx.HistoBlueSubGreen];
- entropy[(int)EntropyIx.SpatialSubGreen] = entropyComp[(int)HistoIx.HistoAlphaPred] +
- entropyComp[(int)HistoIx.HistoRedPredSubGreen] +
- entropyComp[(int)HistoIx.HistoGreenPred] +
- entropyComp[(int)HistoIx.HistoBluePredSubGreen];
+ entropy[(int)EntropyIx.Direct] =
+ entropyComp[(int)HistoIx.HistoAlpha] +
+ entropyComp[(int)HistoIx.HistoRed] +
+ entropyComp[(int)HistoIx.HistoGreen] +
+ entropyComp[(int)HistoIx.HistoBlue];
+ entropy[(int)EntropyIx.Spatial] =
+ entropyComp[(int)HistoIx.HistoAlphaPred] +
+ entropyComp[(int)HistoIx.HistoRedPred] +
+ entropyComp[(int)HistoIx.HistoGreenPred] +
+ entropyComp[(int)HistoIx.HistoBluePred];
+ entropy[(int)EntropyIx.SubGreen] =
+ entropyComp[(int)HistoIx.HistoAlpha] +
+ entropyComp[(int)HistoIx.HistoRedSubGreen] +
+ entropyComp[(int)HistoIx.HistoGreen] +
+ entropyComp[(int)HistoIx.HistoBlueSubGreen];
+ entropy[(int)EntropyIx.SpatialSubGreen] =
+ entropyComp[(int)HistoIx.HistoAlphaPred] +
+ entropyComp[(int)HistoIx.HistoRedPredSubGreen] +
+ entropyComp[(int)HistoIx.HistoGreenPred] +
+ entropyComp[(int)HistoIx.HistoBluePredSubGreen];
entropy[(int)EntropyIx.Palette] = entropyComp[(int)HistoIx.HistoPalette];
// When including transforms, there is an overhead in bits from
// storing them. This overhead is small but matters for small images.
// For spatial, there are 14 transformations.
- entropy[(int)EntropyIx.Spatial] += LosslessUtils.SubSampleSize(width, transformBits) *
- LosslessUtils.SubSampleSize(height, transformBits) *
- LosslessUtils.FastLog2(14);
+ entropy[(int)EntropyIx.Spatial] +=
+ LosslessUtils.SubSampleSize(width, transformBits) *
+ LosslessUtils.SubSampleSize(height, transformBits) *
+ LosslessUtils.FastLog2(14);
// For color transforms: 24 as only 3 channels are considered in a ColorTransformElement.
- entropy[(int)EntropyIx.SpatialSubGreen] += LosslessUtils.SubSampleSize(width, transformBits) *
- LosslessUtils.SubSampleSize(height, transformBits) *
- LosslessUtils.FastLog2(24);
+ entropy[(int)EntropyIx.SpatialSubGreen] +=
+ LosslessUtils.SubSampleSize(width, transformBits) *
+ LosslessUtils.SubSampleSize(height, transformBits) *
+ LosslessUtils.FastLog2(24);
// For palettes, add the cost of storing the palette.
// We empirically estimate the cost of a compressed entry as 8 bits.
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index f17d965e87..ce5d3bac11 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -323,7 +323,7 @@ internal class Vp8Encoder : IDisposable
Span y = this.Y.GetSpan();
Span u = this.U.GetSpan();
Span v = this.V.GetSpan();
- bool hasAlpha = YuvConversion.ConvertRgbToYuv(image, this.configuration, this.memoryAllocator, y, u, v);
+ bool hasAlpha = YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame, this.configuration, this.memoryAllocator, y, u, v);
int yStride = width;
int uvStride = (yStride + 1) >> 1;
@@ -393,7 +393,7 @@ internal class Vp8Encoder : IDisposable
{
// TODO: This can potentially run in an separate task.
encodedAlphaData = AlphaEncoder.EncodeAlpha(
- image,
+ image.Frames.RootFrame,
this.configuration,
this.memoryAllocator,
this.skipMetadata,
diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
index 8ef7fe9cba..d669a37b74 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
@@ -262,17 +262,17 @@ internal static class YuvConversion
/// Converts the RGB values of the image to YUV.
///
/// The pixel type of the image.
- /// The image to convert.
+ /// The frame to convert.
/// The global configuration.
/// The memory allocator.
/// Span to store the luma component of the image.
/// 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(Image image, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v)
+ public static bool ConvertRgbToYuv(ImageFrame frame, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v)
where TPixel : unmanaged, IPixel
{
- Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer;
+ Buffer2D imageBuffer = frame.PixelBuffer;
int width = imageBuffer.Width;
int height = imageBuffer.Height;
int uvWidth = (width + 1) >> 1;
diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
index 90c9c70b26..65f654dddc 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 = this.ReadFrameHeader(stream);
+ AnimationFrameData frameData = AnimationFrameData.Parse(stream);
long streamStartPosition = stream.Position;
Span buffer = stackalloc byte[4];
@@ -173,7 +173,7 @@ internal class WebpAnimationDecoder : IDisposable
ImageFrame imageFrame;
if (previousFrame is null)
{
- image = new Image(this.configuration, (int)width, (int)height, backgroundColor.ToPixel(), this.metadata);
+ image = new(this.configuration, (int)width, (int)height, backgroundColor.ToPixel(), this.metadata);
SetFrameMetadata(image.Frames.RootFrame.Metadata, frameData.Duration);
@@ -258,7 +258,7 @@ internal class WebpAnimationDecoder : IDisposable
try
{
- Buffer2D pixelBufferDecoded = decodedImage.Frames.RootFrame.PixelBuffer;
+ Buffer2D pixelBufferDecoded = decodedImage.GetRootFramePixelBuffer();
if (webpInfo.IsLossless)
{
WebpLosslessDecoder losslessDecoder = new(webpInfo.Vp8LBitReader, this.memoryAllocator, this.configuration);
@@ -353,42 +353,6 @@ internal class WebpAnimationDecoder : IDisposable
pixelRegion.Fill(backgroundPixel);
}
- ///
- /// Reads the animation frame header.
- ///
- /// The stream to read from.
- /// Animation frame data.
- private AnimationFrameData ReadFrameHeader(BufferedReadStream stream)
- {
- Span buffer = stackalloc byte[4];
-
- AnimationFrameData data = new()
- {
- DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
-
- // 3 bytes for the X coordinate of the upper left corner of the frame.
- X = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer),
-
- // 3 bytes for the Y coordinate of the upper left corner of the frame.
- Y = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer),
-
- // Frame width Minus One.
- Width = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer) + 1,
-
- // Frame height Minus One.
- Height = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer) + 1,
-
- // Frame duration.
- Duration = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer)
- };
-
- byte flags = (byte)stream.ReadByte();
- data.DisposalMethod = (flags & 1) == 1 ? AnimationDisposalMethod.Dispose : AnimationDisposalMethod.DoNotDispose;
- data.BlendingMethod = (flags & (1 << 1)) != 0 ? AnimationBlendingMethod.DoNotBlend : AnimationBlendingMethod.AlphaBlending;
-
- return data;
- }
-
///
public void Dispose() => this.alphaData?.Dispose();
}
diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationEncoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationEncoder.cs
new file mode 100644
index 0000000000..bfa64b6797
--- /dev/null
+++ b/src/ImageSharp/Formats/Webp/WebpAnimationEncoder.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Webp;
+
+///
+/// Encoder for animated webp images.
+///
+public class WebpAnimationEncoder
+{
+ // 可能不需要这个屌东西
+}
diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
index a7ae474e46..becd622e17 100644
--- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
+++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
@@ -2,13 +2,12 @@
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
+using System.Drawing;
using SixLabors.ImageSharp.Formats.Webp.BitReader;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
-using SixLabors.ImageSharp.Metadata.Profiles.Exif;
-using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
namespace SixLabors.ImageSharp.Formats.Webp;
@@ -91,7 +90,7 @@ internal static class WebpChunkParsingUtils
uint tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer);
uint width = tmp & 0x3fff;
sbyte xScale = (sbyte)(tmp >> 6);
- tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(2));
+ tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer[2..]);
uint height = tmp & 0x3fff;
sbyte yScale = (sbyte)(tmp >> 6);
remaining -= 7;
@@ -105,14 +104,14 @@ internal static class WebpChunkParsingUtils
WebpThrowHelper.ThrowImageFormatException("bad partition length");
}
- var vp8FrameHeader = new Vp8FrameHeader()
+ Vp8FrameHeader vp8FrameHeader = new()
{
KeyFrame = true,
Profile = (sbyte)version,
PartitionLength = partitionLength
};
- var bitReader = new Vp8BitReader(
+ Vp8BitReader bitReader = new(
stream,
remaining,
memoryAllocator,
@@ -121,7 +120,7 @@ internal static class WebpChunkParsingUtils
Remaining = remaining
};
- return new WebpImageInfo()
+ return new()
{
Width = width,
Height = height,
@@ -145,7 +144,7 @@ internal static class WebpChunkParsingUtils
// VP8 data size.
uint imageDataSize = ReadChunkSize(stream, buffer);
- var bitReader = new Vp8LBitReader(stream, imageDataSize, memoryAllocator);
+ Vp8LBitReader bitReader = new(stream, imageDataSize, memoryAllocator);
// One byte signature, should be 0x2f.
uint signature = bitReader.ReadValue(8);
@@ -174,7 +173,7 @@ internal static class WebpChunkParsingUtils
WebpThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header");
}
- return new WebpImageInfo()
+ return new()
{
Width = width,
Height = height,
@@ -231,13 +230,13 @@ internal static class WebpChunkParsingUtils
}
// 3 bytes for the width.
- uint width = ReadUnsignedInt24Bit(stream, buffer) + 1;
+ uint width = ReadUInt24LittleEndian(stream, buffer) + 1;
// 3 bytes for the height.
- uint height = ReadUnsignedInt24Bit(stream, buffer) + 1;
+ uint height = ReadUInt24LittleEndian(stream, buffer) + 1;
// Read all the chunks in the order they occur.
- var info = new WebpImageInfo()
+ WebpImageInfo info = new()
{
Width = width,
Height = height,
@@ -253,7 +252,7 @@ internal static class WebpChunkParsingUtils
/// The stream to read from.
/// The buffer to store the read data into.
/// A unsigned 24 bit integer.
- public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, Span buffer)
+ public static uint ReadUInt24LittleEndian(BufferedReadStream stream, Span buffer)
{
if (stream.Read(buffer, 0, 3) == 3)
{
@@ -261,7 +260,28 @@ internal static class WebpChunkParsingUtils
return BinaryPrimitives.ReadUInt32LittleEndian(buffer);
}
- throw new ImageFormatException("Invalid Webp data, could not read unsigned integer.");
+ throw new ImageFormatException("Invalid Webp data, could not read unsigned 24 bit integer.");
+ }
+
+ ///
+ /// Writes a unsigned 24 bit integer.
+ ///
+ /// The stream to read from.
+ /// The uint24 data to write.
+ public static unsafe void WriteUInt24LittleEndian(Stream stream, uint data)
+ {
+ if (data >= 1 << 24)
+ {
+ throw new InvalidDataException($"Invalid data, {data} is not a unsigned 24 bit integer.");
+ }
+
+ uint* ptr = &data;
+ byte* b = (byte*)ptr;
+
+ // Write the data in little endian.
+ stream.WriteByte(b[0]);
+ stream.WriteByte(b[1]);
+ stream.WriteByte(b[2]);
}
///
@@ -298,7 +318,7 @@ internal static class WebpChunkParsingUtils
if (stream.Read(buffer) == 4)
{
- var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
+ WebpChunkType chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
return chunkType;
}
@@ -335,7 +355,7 @@ internal static class WebpChunkParsingUtils
if (metadata.ExifProfile != null)
{
- metadata.ExifProfile = new ExifProfile(exifData);
+ metadata.ExifProfile = new(exifData);
}
break;
@@ -349,7 +369,7 @@ internal static class WebpChunkParsingUtils
if (metadata.XmpProfile != null)
{
- metadata.XmpProfile = new XmpProfile(xmpData);
+ metadata.XmpProfile = new(xmpData);
}
break;
diff --git a/src/ImageSharp/Formats/Webp/WebpChunkType.cs b/src/ImageSharp/Formats/Webp/WebpChunkType.cs
index 802d7f7288..5836dc6c09 100644
--- a/src/ImageSharp/Formats/Webp/WebpChunkType.cs
+++ b/src/ImageSharp/Formats/Webp/WebpChunkType.cs
@@ -12,45 +12,54 @@ internal enum WebpChunkType : uint
///
/// Header signaling the use of the VP8 format.
///
+ /// VP8 (Single)
Vp8 = 0x56503820U,
///
/// Header signaling the image uses lossless encoding.
///
+ /// VP8L (Single)
Vp8L = 0x5650384CU,
///
/// Header for a extended-VP8 chunk.
///
+ /// VP8X (Single)
Vp8X = 0x56503858U,
///
/// Chunk contains information about the alpha channel.
///
+ /// ALPH (Single)
Alpha = 0x414C5048U,
///
/// Chunk which contains a color profile.
///
+ /// ICCP (Single)
Iccp = 0x49434350U,
///
/// Chunk which contains EXIF metadata about the image.
///
+ /// EXIF (Single)
Exif = 0x45584946U,
///
/// Chunk contains XMP metadata about the image.
///
+ /// XMP (Single)
Xmp = 0x584D5020U,
///
/// For an animated image, this chunk contains the global parameters of the animation.
///
+ /// ANIM (Single)
AnimationParameter = 0x414E494D,
///
/// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present.
///
+ /// ANMF (Multiple)
Animation = 0x414E4D46,
}
diff --git a/src/ImageSharp/Formats/Webp/WebpConstants.cs b/src/ImageSharp/Formats/Webp/WebpConstants.cs
index d105d8dd62..1433772757 100644
--- a/src/ImageSharp/Formats/Webp/WebpConstants.cs
+++ b/src/ImageSharp/Formats/Webp/WebpConstants.cs
@@ -33,39 +33,6 @@ internal static class WebpConstants
///
public const byte Vp8LHeaderMagicByte = 0x2F;
- ///
- /// Signature bytes identifying a lossy image.
- ///
- public static readonly byte[] Vp8MagicBytes =
- {
- 0x56, // V
- 0x50, // P
- 0x38, // 8
- 0x20 // ' '
- };
-
- ///
- /// Signature bytes identifying a lossless image.
- ///
- public static readonly byte[] Vp8LMagicBytes =
- {
- 0x56, // V
- 0x50, // P
- 0x38, // 8
- 0x4C // L
- };
-
- ///
- /// Signature bytes identifying a VP8X header.
- ///
- public static readonly byte[] Vp8XMagicBytes =
- {
- 0x56, // V
- 0x50, // P
- 0x38, // 8
- 0x58 // X
- };
-
///
/// The header bytes identifying RIFF file.
///
diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
index 8832ac1068..63d3e1aead 100644
--- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
@@ -93,11 +93,6 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
return animationDecoder.Decode(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
}
- if (this.webImageInfo.Features is { Animation: true })
- {
- WebpThrowHelper.ThrowNotSupportedException("Animations are not supported");
- }
-
image = new Image(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, metadata);
Buffer2D pixels = image.GetRootFramePixelBuffer();
if (this.webImageInfo.IsLossless)
diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs
index 9b03a447a9..433b280bc3 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs
@@ -143,7 +143,7 @@ public class YuvConversionTests
};
// act
- YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v);
+ YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame, config, memoryAllocator, y, u, v);
// assert
Assert.True(expectedY.AsSpan().SequenceEqual(y));
@@ -249,7 +249,7 @@ public class YuvConversionTests
};
// act
- YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v);
+ YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame, config, memoryAllocator, y, u, v);
// assert
Assert.True(expectedY.AsSpan().SequenceEqual(y));