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