diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
index cbf96a91a..d502fd606 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
@@ -1,8 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-using System.Buffers.Binary;
-using System.Runtime.InteropServices;
+using System.Diagnostics;
+using SixLabors.ImageSharp.Common.Helpers;
+using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
@@ -15,8 +16,6 @@ internal abstract class BitWriterBase
private const ulong MaxCanvasPixels = 4294967295ul;
- protected const uint ExtendedFileChunkSize = WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
-
///
/// Buffer to write to.
///
@@ -79,48 +78,6 @@ internal abstract class BitWriterBase
Array.Resize(ref this.buffer, newSize);
}
- ///
- /// Writes the RIFF header to the stream.
- ///
- /// The stream to write to.
- /// The block length.
- protected static void WriteRiffHeader(Stream stream, uint riffSize)
- {
- stream.Write(WebpConstants.RiffFourCc);
- Span buf = stackalloc byte[4];
- BinaryPrimitives.WriteUInt32LittleEndian(buf, riffSize);
- stream.Write(buf);
- stream.Write(WebpConstants.WebpHeader);
- }
-
- ///
- /// Calculates the chunk size of EXIF, XMP or ICCP metadata.
- ///
- /// The metadata profile bytes.
- /// The metadata chunk size in bytes.
- protected static uint MetadataChunkSize(byte[] metadataBytes)
- {
- uint metaSize = (uint)metadataBytes.Length;
- return WebpConstants.ChunkHeaderSize + metaSize + (metaSize & 1);
- }
-
- ///
- /// Calculates the chunk size of a alpha chunk.
- ///
- /// The alpha chunk bytes.
- /// The alpha data chunk size in bytes.
- protected static uint AlphaChunkSize(Span alphaBytes)
- {
- uint alphaSize = (uint)alphaBytes.Length + 1;
- return WebpConstants.ChunkHeaderSize + alphaSize + (alphaSize & 1);
- }
-
- ///
- /// Overwrites ides the write file size.
- ///
- /// The stream to write to.
- protected static void OverwriteFileSize(Stream stream) => OverwriteFrameSize(stream, 4);
-
///
/// Write the trunks before data trunk.
///
@@ -143,7 +100,9 @@ internal abstract class BitWriterBase
bool hasAnimation)
{
// Write file size later
- WriteRiffHeader(stream, 0);
+ long pos = RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc);
+
+ Debug.Assert(pos is 4, "Stream should be written from position 0.");
// Write VP8X, header if necessary.
bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation;
@@ -153,7 +112,7 @@ internal abstract class BitWriterBase
if (iccProfile != null)
{
- WriteColorProfile(stream, iccProfile.ToByteArray());
+ RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Iccp, iccProfile.ToByteArray());
}
}
}
@@ -177,49 +136,17 @@ internal abstract class BitWriterBase
{
if (exifProfile != null)
{
- WriteMetadataProfile(stream, exifProfile.ToByteArray(), WebpChunkType.Exif);
+ RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Exif, exifProfile.ToByteArray());
}
if (xmpProfile != null)
{
- WriteMetadataProfile(stream, xmpProfile.Data, WebpChunkType.Xmp);
+ RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Xmp, xmpProfile.Data);
}
- OverwriteFileSize(stream);
- }
-
- ///
- /// Writes a metadata profile (EXIF or XMP) to the stream.
- ///
- /// The stream to write to.
- /// The metadata profile's bytes.
- /// The chuck type to write.
- protected static void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpChunkType chunkType)
- {
- DebugGuard.NotNull(metadataBytes, nameof(metadataBytes));
-
- uint size = (uint)metadataBytes.Length;
- Span buf = stackalloc byte[4];
- BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType);
- stream.Write(buf);
- BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
- stream.Write(buf);
- stream.Write(metadataBytes);
-
- // Add padding byte if needed.
- if ((size & 1) == 1)
- {
- stream.WriteByte(0);
- }
+ RiffHelper.EndWriteRiffFile(stream, 4);
}
- ///
- /// Writes the color profile() to the stream.
- ///
- /// The stream to write to.
- /// The color profile bytes.
- protected static void WriteColorProfile(Stream stream, byte[] iccProfileBytes) => WriteMetadataProfile(stream, iccProfileBytes, WebpChunkType.Iccp);
-
///
/// Writes the animation parameter() to the stream.
///
@@ -233,55 +160,8 @@ internal abstract class BitWriterBase
/// The number of times to loop the animation. If it is 0, this means infinitely.
public static void WriteAnimationParameter(Stream stream, Color background, ushort loopCount)
{
- Span buf = stackalloc byte[4];
- BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.AnimationParameter);
- stream.Write(buf);
- BinaryPrimitives.WriteUInt32LittleEndian(buf, sizeof(uint) + sizeof(ushort));
- stream.Write(buf);
- BinaryPrimitives.WriteUInt32LittleEndian(buf, background.ToRgba32().Rgba);
- 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.
- public static long WriteAnimationFrame(Stream stream, WebpFrameData animation)
- {
- Span buf = stackalloc byte[4];
- BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Animation);
- stream.Write(buf);
- long position = stream.Position;
- BinaryPrimitives.WriteUInt32BigEndian(buf, 0);
- 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);
- return position;
- }
-
- ///
- /// Overwrites ides the write frame size.
- ///
- /// The stream to write to.
- /// Previous position.
- public static void OverwriteFrameSize(Stream stream, long prevPosition)
- {
- uint position = (uint)stream.Position;
- stream.Position = prevPosition;
- byte[] buffer = new byte[4];
-
- BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)(position - prevPosition - 4));
- stream.Write(buffer);
- stream.Position = position;
+ WebpAnimationParameter chunk = new(background.ToRgba32().Rgba, loopCount);
+ chunk.WriteTo(stream);
}
///
@@ -292,27 +172,17 @@ internal abstract class BitWriterBase
/// Indicates, if the alpha channel data is compressed.
public static void WriteAlphaChunk(Stream stream, Span dataBytes, bool alphaDataIsCompressed)
{
- uint size = (uint)dataBytes.Length + 1;
- Span buf = stackalloc byte[4];
- BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha);
- stream.Write(buf);
- BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
- stream.Write(buf);
-
+ long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.Alpha);
byte flags = 0;
if (alphaDataIsCompressed)
{
+ // TODO: Filtering and preprocessing
flags = 1;
}
stream.WriteByte(flags);
stream.Write(dataBytes);
-
- // Add padding byte if needed.
- if ((size & 1) == 1)
- {
- stream.WriteByte(0);
- }
+ RiffHelper.EndWriteChunk(stream, pos);
}
///
@@ -328,66 +198,10 @@ internal abstract class BitWriterBase
/// Flag indicating, if an animation parameter is present.
protected static void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha, bool hasAnimation)
{
- if (width > MaxDimension || height > MaxDimension)
- {
- WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {MaxDimension}");
- }
+ WebpVp8X chunk = new(hasAnimation, xmpProfile != null, exifProfile != null, hasAlpha, iccProfile != null, width, height);
- // The spec states that the product of Canvas Width and Canvas Height MUST be at most 2^32 - 1.
- if (width * height > MaxCanvasPixels)
- {
- WebpThrowHelper.ThrowInvalidImageDimensions("The product of image width and height MUST be at most 2^32 - 1");
- }
-
- uint flags = 0;
- if (exifProfile != null)
- {
- // Set exif bit.
- flags |= 8;
- }
-
- if (hasAnimation)
- {
- // Set animated flag.
- flags |= 2;
- }
-
- if (xmpProfile != null)
- {
- // Set xmp bit.
- flags |= 4;
- }
-
- if (hasAlpha)
- {
- // Set alpha bit.
- flags |= 16;
- }
-
- if (iccProfile != null)
- {
- // Set iccp flag.
- flags |= 32;
- }
-
- Span buf = stackalloc byte[4];
- BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Vp8X);
- stream.Write(buf);
- BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
- stream.Write(buf);
- BinaryPrimitives.WriteUInt32LittleEndian(buf, flags);
- stream.Write(buf);
- BinaryPrimitives.WriteUInt32LittleEndian(buf, width - 1);
- stream.Write(buf[..3]);
- BinaryPrimitives.WriteUInt32LittleEndian(buf, height - 1);
- stream.Write(buf[..3]);
- }
-
- private unsafe struct ScratchBuffer
- {
- private const int Size = 4;
- private fixed byte scratch[Size];
+ chunk.Validate(MaxDimension, MaxCanvasPixels);
- public Span Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size);
+ chunk.WriteTo(stream);
}
}
diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpAnimationParameter.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpAnimationParameter.cs
new file mode 100644
index 000000000..3855a293c
--- /dev/null
+++ b/src/ImageSharp/Formats/Webp/Chunks/WebpAnimationParameter.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers.Binary;
+using SixLabors.ImageSharp.Common.Helpers;
+
+namespace SixLabors.ImageSharp.Formats.Webp.Chunks;
+
+internal readonly struct WebpAnimationParameter
+{
+ public WebpAnimationParameter(uint background, ushort loopCount)
+ {
+ this.Background = background;
+ this.LoopCount = loopCount;
+ }
+
+ ///
+ /// Gets 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.
+ ///
+ public uint Background { get; }
+
+ ///
+ /// Gets number of times to loop the animation. If it is 0, this means infinitely.
+ ///
+ public ushort LoopCount { get; }
+
+ public void WriteTo(Stream stream)
+ {
+ Span buffer = stackalloc byte[6];
+ BinaryPrimitives.WriteUInt32LittleEndian(buffer[..4], this.Background);
+ BinaryPrimitives.WriteUInt16LittleEndian(buffer[4..], this.LoopCount);
+ RiffHelper.WriteChunk(stream, (uint)WebpChunkType.AnimationParameter, buffer);
+ }
+}
diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
new file mode 100644
index 000000000..f22a3fd54
--- /dev/null
+++ b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
@@ -0,0 +1,140 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Common.Helpers;
+
+namespace SixLabors.ImageSharp.Formats.Webp.Chunks;
+
+internal readonly struct WebpFrameData
+{
+ ///
+ /// X(3) + Y(3) + Width(3) + Height(3) + Duration(3) + 1 byte for flags.
+ ///
+ public const uint HeaderSize = 16;
+
+ public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, WebpBlendingMethod blendingMethod, WebpDisposalMethod disposalMethod)
+ {
+ this.DataSize = dataSize;
+ this.X = x;
+ this.Y = y;
+ this.Width = width;
+ this.Height = height;
+ this.Duration = duration;
+ this.DisposalMethod = disposalMethod;
+ this.BlendingMethod = blendingMethod;
+ }
+
+ public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, int flags)
+ : this(
+ dataSize,
+ x,
+ y,
+ width,
+ height,
+ duration,
+ (flags & 2) != 0 ? WebpBlendingMethod.DoNotBlend : WebpBlendingMethod.AlphaBlending,
+ (flags & 1) == 1 ? WebpDisposalMethod.Dispose : WebpDisposalMethod.DoNotDispose)
+ {
+ }
+
+ public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, WebpBlendingMethod blendingMethod, WebpDisposalMethod disposalMethod)
+ : this(0, x, y, width, height, duration, blendingMethod, disposalMethod)
+ {
+ }
+
+ ///
+ /// Gets the animation chunk size.
+ ///
+ public uint DataSize { get; }
+
+ ///
+ /// Gets the X coordinate of the upper left corner of the frame is Frame X * 2.
+ ///
+ public uint X { get; }
+
+ ///
+ /// Gets the Y coordinate of the upper left corner of the frame is Frame Y * 2.
+ ///
+ public uint Y { get; }
+
+ ///
+ /// Gets the width of the frame.
+ ///
+ public uint Width { get; }
+
+ ///
+ /// Gets the height of the frame.
+ ///
+ public uint Height { get; }
+
+ ///
+ /// Gets the time to wait before displaying the next frame, in 1 millisecond units.
+ /// Note the interpretation of frame duration of 0 (and often smaller then 10) is implementation defined.
+ ///
+ public uint Duration { get; }
+
+ ///
+ /// Gets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
+ ///
+ public WebpBlendingMethod BlendingMethod { get; }
+
+ ///
+ /// Gets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
+ ///
+ public WebpDisposalMethod DisposalMethod { get; }
+
+ public Rectangle Bounds => new((int)this.X * 2, (int)this.Y * 2, (int)this.Width, (int)this.Height);
+
+ ///
+ /// Writes the animation frame() to the stream.
+ ///
+ /// The stream to write to.
+ public long WriteHeaderTo(Stream stream)
+ {
+ byte flags = 0;
+
+ if (this.BlendingMethod is WebpBlendingMethod.DoNotBlend)
+ {
+ // Set blending flag.
+ flags |= 2;
+ }
+
+ if (this.DisposalMethod is WebpDisposalMethod.Dispose)
+ {
+ // Set disposal flag.
+ flags |= 1;
+ }
+
+ long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.FrameData);
+
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.X);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Y);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Duration);
+ stream.WriteByte(flags);
+
+ return pos;
+ }
+
+ ///
+ /// Reads the animation frame header.
+ ///
+ /// The stream to read from.
+ /// Animation frame data.
+ public static WebpFrameData Parse(Stream stream)
+ {
+ Span buffer = stackalloc byte[4];
+
+ WebpFrameData data = new(
+ dataSize: WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
+ x: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
+ y: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
+ width: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
+ height: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
+ duration: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
+ flags: stream.ReadByte());
+
+ return data;
+ }
+}
diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs
new file mode 100644
index 000000000..70d6870ce
--- /dev/null
+++ b/src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs
@@ -0,0 +1,113 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Common.Helpers;
+
+namespace SixLabors.ImageSharp.Formats.Webp.Chunks;
+
+internal readonly struct WebpVp8X
+{
+ public WebpVp8X(bool hasAnimation, bool hasXmp, bool hasExif, bool hasAlpha, bool hasIcc, uint width, uint height)
+ {
+ this.HasAnimation = hasAnimation;
+ this.HasXmp = hasXmp;
+ this.HasExif = hasExif;
+ this.HasAlpha = hasAlpha;
+ this.HasIcc = hasIcc;
+ this.Width = width;
+ this.Height = height;
+ }
+
+ ///
+ /// Gets a value indicating whether this is an animated image. Data in 'ANIM' and 'ANMF' Chunks should be used to control the animation.
+ ///
+ public bool HasAnimation { get; }
+
+ ///
+ /// Gets a value indicating whether the file contains XMP metadata.
+ ///
+ public bool HasXmp { get; }
+
+ ///
+ /// Gets a value indicating whether the file contains Exif metadata.
+ ///
+ public bool HasExif { get; }
+
+ ///
+ /// Gets a value indicating whether any of the frames of the image contain transparency information ("alpha").
+ ///
+ public bool HasAlpha { get; }
+
+ ///
+ /// Gets a value indicating whether the file contains an 'ICCP' Chunk.
+ ///
+ public bool HasIcc { get; }
+
+ ///
+ /// Gets width of the canvas in pixels. (uint24)
+ ///
+ public uint Width { get; }
+
+ ///
+ /// Gets height of the canvas in pixels. (uint24)
+ ///
+ public uint Height { get; }
+
+ public void Validate(uint maxDimension, ulong maxCanvasPixels)
+ {
+ if (this.Width > maxDimension || this.Height > maxDimension)
+ {
+ WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {maxDimension}");
+ }
+
+ // The spec states that the product of Canvas Width and Canvas Height MUST be at most 2^32 - 1.
+ if (this.Width * this.Height > maxCanvasPixels)
+ {
+ WebpThrowHelper.ThrowInvalidImageDimensions("The product of image width and height MUST be at most 2^32 - 1");
+ }
+ }
+
+ public void WriteTo(Stream stream)
+ {
+ byte flags = 0;
+
+ if (this.HasAnimation)
+ {
+ // Set animated flag.
+ flags |= 2;
+ }
+
+ if (this.HasXmp)
+ {
+ // Set xmp bit.
+ flags |= 4;
+ }
+
+ if (this.HasExif)
+ {
+ // Set exif bit.
+ flags |= 8;
+ }
+
+ if (this.HasAlpha)
+ {
+ // Set alpha bit.
+ flags |= 16;
+ }
+
+ if (this.HasIcc)
+ {
+ // Set icc flag.
+ flags |= 32;
+ }
+
+ long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.Vp8X);
+
+ stream.WriteByte(flags);
+ stream.Position += 3; // Reserved bytes
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1);
+ WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1);
+
+ RiffHelper.EndWriteChunk(stream, pos);
+ }
+}
diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
index 42aa667ac..fe0131a2a 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
@@ -6,7 +6,9 @@ using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.BitWriter;
+using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@@ -308,16 +310,15 @@ internal class Vp8LEncoder : IDisposable
WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata();
// TODO: If we can clip the indexed frame for transparent bounds we can set properties here.
- prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData
- {
- X = 0,
- Y = 0,
- Width = (uint)frame.Width,
- Height = (uint)frame.Height,
- Duration = frameMetadata.FrameDelay,
- BlendingMethod = frameMetadata.BlendMethod,
- DisposalMethod = frameMetadata.DisposalMethod
- });
+ prevPosition = new WebpFrameData(
+ 0,
+ 0,
+ (uint)frame.Width,
+ (uint)frame.Height,
+ frameMetadata.FrameDelay,
+ frameMetadata.BlendMethod,
+ frameMetadata.DisposalMethod)
+ .WriteHeaderTo(stream);
}
// Write bytes from the bitwriter buffer to the stream.
@@ -325,7 +326,7 @@ internal class Vp8LEncoder : IDisposable
if (hasAnimation)
{
- BitWriterBase.OverwriteFrameSize(stream, prevPosition);
+ RiffHelper.EndWriteChunk(stream, prevPosition);
}
}
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index 3b7302306..98e50bb9c 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -4,7 +4,9 @@
using System.Buffers;
using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.BitWriter;
+using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@@ -478,16 +480,15 @@ internal class Vp8Encoder : IDisposable
WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata();
// TODO: If we can clip the indexed frame for transparent bounds we can set properties here.
- prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData
- {
- X = 0,
- Y = 0,
- Width = (uint)frame.Width,
- Height = (uint)frame.Height,
- Duration = frameMetadata.FrameDelay,
- BlendingMethod = frameMetadata.BlendMethod,
- DisposalMethod = frameMetadata.DisposalMethod
- });
+ prevPosition = new WebpFrameData(
+ 0,
+ 0,
+ (uint)frame.Width,
+ (uint)frame.Height,
+ frameMetadata.FrameDelay,
+ frameMetadata.BlendMethod,
+ frameMetadata.DisposalMethod)
+ .WriteHeaderTo(stream);
}
if (hasAlpha)
@@ -501,7 +502,7 @@ internal class Vp8Encoder : IDisposable
if (hasAnimation)
{
- BitWriterBase.OverwriteFrameSize(stream, prevPosition);
+ RiffHelper.EndWriteChunk(stream, prevPosition);
}
}
finally
diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
index fad6ca16c..f0e409319 100644
--- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
+++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
@@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
+using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.IO;
@@ -99,7 +100,7 @@ internal class WebpAnimationDecoder : IDisposable
remainingBytes -= 4;
switch (chunkType)
{
- case WebpChunkType.Animation:
+ case WebpChunkType.FrameData:
Color backgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore
? new Color(new Bgra32(0, 0, 0, 0))
: features.AnimationBackgroundColor!.Value;
diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
index f4e40090c..80ffe8a99 100644
--- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
+++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
@@ -106,14 +106,14 @@ internal static class WebpChunkParsingUtils
WebpThrowHelper.ThrowImageFormatException("bad partition length");
}
- Vp8FrameHeader vp8FrameHeader = new Vp8FrameHeader
+ Vp8FrameHeader vp8FrameHeader = new()
{
KeyFrame = true,
Profile = (sbyte)version,
PartitionLength = partitionLength
};
- Vp8BitReader bitReader = new Vp8BitReader(stream, remaining, memoryAllocator, partitionLength) { Remaining = remaining };
+ Vp8BitReader bitReader = new(stream, remaining, memoryAllocator, partitionLength) { Remaining = remaining };
return new WebpImageInfo
{
@@ -139,7 +139,7 @@ internal static class WebpChunkParsingUtils
// VP8 data size.
uint imageDataSize = ReadChunkSize(stream, buffer);
- Vp8LBitReader bitReader = new Vp8LBitReader(stream, imageDataSize, memoryAllocator);
+ Vp8LBitReader bitReader = new(stream, imageDataSize, memoryAllocator);
// One byte signature, should be 0x2f.
uint signature = bitReader.ReadValue(8);
@@ -231,7 +231,7 @@ internal static class WebpChunkParsingUtils
uint height = ReadUInt24LittleEndian(stream, buffer) + 1;
// Read all the chunks in the order they occur.
- WebpImageInfo info = new WebpImageInfo
+ WebpImageInfo info = new()
{
Width = width,
Height = height,
@@ -247,7 +247,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 ReadUInt24LittleEndian(BufferedReadStream stream, Span buffer)
+ public static uint ReadUInt24LittleEndian(Stream stream, Span buffer)
{
if (stream.Read(buffer, 0, 3) == 3)
{
@@ -286,14 +286,14 @@ internal static class WebpChunkParsingUtils
/// The stream to read the data from.
/// Buffer to store the data read from the stream.
/// The chunk size in bytes.
- public static uint ReadChunkSize(BufferedReadStream stream, Span buffer)
+ public static uint ReadChunkSize(Stream stream, Span buffer)
{
- DebugGuard.IsTrue(buffer.Length == 4, "buffer has wrong length");
+ DebugGuard.IsTrue(buffer.Length is 4, "buffer has wrong length");
- if (stream.Read(buffer) == 4)
+ if (stream.Read(buffer) is 4)
{
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
- return chunkSize % 2 == 0 ? chunkSize : chunkSize + 1;
+ return chunkSize % 2 is 0 ? chunkSize : chunkSize + 1;
}
throw new ImageFormatException("Invalid Webp data, could not read chunk size.");
diff --git a/src/ImageSharp/Formats/Webp/WebpChunkType.cs b/src/ImageSharp/Formats/Webp/WebpChunkType.cs
index 5836dc6c0..12e329777 100644
--- a/src/ImageSharp/Formats/Webp/WebpChunkType.cs
+++ b/src/ImageSharp/Formats/Webp/WebpChunkType.cs
@@ -61,5 +61,5 @@ internal enum WebpChunkType : uint
/// 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,
+ FrameData = 0x414E4D46,
}
diff --git a/src/ImageSharp/Formats/Webp/WebpConstants.cs b/src/ImageSharp/Formats/Webp/WebpConstants.cs
index 143377275..818c843ea 100644
--- a/src/ImageSharp/Formats/Webp/WebpConstants.cs
+++ b/src/ImageSharp/Formats/Webp/WebpConstants.cs
@@ -55,6 +55,11 @@ internal static class WebpConstants
0x50 // P
};
+ ///
+ /// The header bytes identifying a Webp.
+ ///
+ public const string WebpFourCc = "WEBP";
+
///
/// 3 bits reserved for version.
///
@@ -70,11 +75,6 @@ internal static class WebpConstants
///
public const int Vp8FrameHeaderSize = 10;
- ///
- /// Size of a VP8X chunk in bytes.
- ///
- public const int Vp8XChunkSize = 10;
-
///
/// Size of a chunk header.
///
diff --git a/src/ImageSharp/Formats/Webp/WebpFrameData.cs b/src/ImageSharp/Formats/Webp/WebpFrameData.cs
deleted file mode 100644
index 93c5d10dc..000000000
--- a/src/ImageSharp/Formats/Webp/WebpFrameData.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.IO;
-
-namespace SixLabors.ImageSharp.Formats.Webp;
-
-internal struct WebpFrameData
-{
- ///
- /// The animation chunk size.
- ///
- 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.
- ///
- public uint X;
-
- ///
- /// The Y coordinate of the upper left corner of the frame is Frame Y * 2.
- ///
- public uint Y;
-
- ///
- /// The width of the frame.
- ///
- public uint Width;
-
- ///
- /// The height of the frame.
- ///
- public uint Height;
-
- ///
- /// The time to wait before displaying the next frame, in 1 millisecond units.
- /// Note the interpretation of frame duration of 0 (and often smaller then 10) is implementation defined.
- ///
- public uint Duration;
-
- ///
- /// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
- ///
- public WebpBlendingMethod BlendingMethod;
-
- ///
- /// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
- ///
- public WebpDisposalMethod DisposalMethod;
-
- public readonly Rectangle Bounds => new((int)this.X * 2, (int)this.Y * 2, (int)this.Width, (int)this.Height);
-
- ///
- /// Reads the animation frame header.
- ///
- /// The stream to read from.
- /// Animation frame data.
- public static WebpFrameData Parse(BufferedReadStream stream)
- {
- Span buffer = stackalloc byte[4];
-
- WebpFrameData 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 ? WebpDisposalMethod.Dispose : WebpDisposalMethod.DoNotDispose;
- data.BlendingMethod = (flags & (1 << 1)) != 0 ? WebpBlendingMethod.DoNotBlend : WebpBlendingMethod.AlphaBlending;
-
- return data;
- }
-}