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