From 2b8af22d5d2defb21432d58b9d2e10e4011dbe87 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 9 Feb 2022 15:28:30 +0100 Subject: [PATCH] Add decoding animated lossy webp --- .../Webp/Lossless/WebpLosslessDecoder.cs | 2 +- .../Formats/Webp/Lossy/WebpLossyDecoder.cs | 13 +++++- .../Formats/Webp/WebpAnimationDecoder.cs | 42 ++++++++++++++++--- .../Formats/Webp/WebpChunkParsingUtils.cs | 2 +- src/ImageSharp/Formats/Webp/WebpDecoder.cs | 4 +- .../Formats/Webp/WebpDecoderCore.cs | 36 ++++++++++++---- src/ImageSharp/Formats/Webp/WebpFeatures.cs | 13 +----- src/ImageSharp/Formats/Webp/WebpImageInfo.cs | 1 - 8 files changed, 79 insertions(+), 34 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index f517ad520..2d2396f1a 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static ReadOnlySpan LiteralMap => new byte[] { 0, 1, 1, 1, 0 }; /// - /// Decodes the image from the stream using the bitreader. + /// Decodes the lossless webp image from the stream. /// /// The pixel format. /// The pixel buffer to store the decoded data. diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs index b74f6969e..d374393e9 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs @@ -57,7 +57,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.configuration = configuration; } - public void Decode(Buffer2D pixels, int width, int height, WebpImageInfo info) + /// + /// Decodes the lossless webp image from the stream. + /// + /// The pixel format. + /// The pixel buffer to store the decoded data. + /// The width of the image. + /// The height of the image. + /// Information about the image. + /// The ALPH chunk data. + public void Decode(Buffer2D pixels, int width, int height, WebpImageInfo info, IMemoryOwner alphaData) where TPixel : unmanaged, IPixel { // Paragraph 9.2: color space and clamp type follow. @@ -105,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy using (var alphaDecoder = new AlphaDecoder( width, height, - info.Features.AlphaData, + alphaData, info.Features.AlphaChunkHeader, this.memoryAllocator, this.configuration)) diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index b3cf4b00f..90a5142eb 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.IO; @@ -14,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Decoder for animated webp images. /// - internal class WebpAnimationDecoder + internal class WebpAnimationDecoder : IDisposable { /// /// Reusable buffer. @@ -47,6 +48,11 @@ namespace SixLabors.ImageSharp.Formats.Webp this.configuration = configuration; } + /// + /// Gets or sets the alpha data, if an ALPH chunk is present. + /// + public IMemoryOwner AlphaData { get; set; } + /// /// Decodes the animated webp image from the specified stream. /// @@ -108,12 +114,12 @@ namespace SixLabors.ImageSharp.Formats.Webp long streamStartPosition = stream.Position; WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); + bool hasAlpha = false; + byte alphaChunkHeader = 0; if (chunkType is WebpChunkType.Alpha) { - // TODO: ignore alpha for now. - stream.Skip(4); - uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); - stream.Skip((int)alphaChunkSize); + alphaChunkHeader = this.ReadAlphaData(stream); + hasAlpha = true; chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); } @@ -123,6 +129,8 @@ namespace SixLabors.ImageSharp.Formats.Webp { case WebpChunkType.Vp8: webpInfo = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features); + features.Alpha = hasAlpha; + features.AlphaChunkHeader = alphaChunkHeader; break; case WebpChunkType.Vp8L: webpInfo = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features); @@ -171,6 +179,25 @@ namespace SixLabors.ImageSharp.Formats.Webp return (uint)(stream.Position - streamStartPosition); } + /// + /// Reads the ALPH chunk data. + /// + /// The stream to read from. + private byte ReadAlphaData(BufferedReadStream stream) + { + this.AlphaData?.Dispose(); + + uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); + int alphaDataSize = (int)(alphaChunkSize - 1); + this.AlphaData = this.memoryAllocator.Allocate(alphaDataSize); + + byte alphaChunkHeader = (byte)stream.ReadByte(); + Span alphaData = this.AlphaData.GetSpan(); + stream.Read(alphaData, 0, alphaDataSize); + + return alphaChunkHeader; + } + /// /// Decodes the either lossy or lossless webp image data. /// @@ -191,7 +218,7 @@ namespace SixLabors.ImageSharp.Formats.Webp else { var lossyDecoder = new WebpLossyDecoder(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration); - lossyDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo); + lossyDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.AlphaData); } return decodedImage; @@ -326,5 +353,8 @@ namespace SixLabors.ImageSharp.Formats.Webp return data; } + + /// + public void Dispose() => this.AlphaData?.Dispose(); } } diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs index b93b4d66a..5b8e1857c 100644 --- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs +++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Webp stream.Read(buffer, 0, 4); uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); - // remaining counts the available image data payload. + // Remaining counts the available image data payload. uint remaining = dataSize; // Paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30 diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs index b4e6cecd0..1736e97ce 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { Guard.NotNull(stream, nameof(stream)); - var decoder = new WebpDecoderCore(configuration, this); + using var decoder = new WebpDecoderCore(configuration, this); try { @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { Guard.NotNull(stream, nameof(stream)); - var decoder = new WebpDecoderCore(configuration, this); + using var decoder = new WebpDecoderCore(configuration, this); try { diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 447f7f781..cde8f612b 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers; using System.Buffers.Binary; using System.IO; using System.Threading; @@ -19,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Performs the webp decoding operation. /// - internal sealed class WebpDecoderCore : IImageDecoderInternals + internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable { /// /// Reusable buffer. @@ -76,6 +78,11 @@ namespace SixLabors.ImageSharp.Formats.Webp /// public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); + /// + /// Gets or sets the alpha data, if an ALPH chunk is present. + /// + public IMemoryOwner AlphaData { get; set; } + /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel @@ -89,7 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { if (this.webImageInfo.Features is { Animation: true }) { - var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.Configuration); + using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.Configuration); return animationDecoder.Decode(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize); } @@ -104,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Webp else { var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); - lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo); + lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.AlphaData); } // There can be optional chunks after the image data, like EXIF and XMP. @@ -120,7 +127,7 @@ namespace SixLabors.ImageSharp.Formats.Webp this.currentStream = stream; this.ReadImageHeader(); - using (this.webImageInfo = this.ReadVp8Info()) + using (this.webImageInfo = this.ReadVp8Info(true)) { return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata); } @@ -149,8 +156,9 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Reads information present in the image header, about the image content and how to decode the image. /// + /// For identify, the alpha data should not be read. /// Information about the webp image. - private WebpImageInfo ReadVp8Info() + private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false) { this.Metadata = new ImageMetadata(); this.webpMetadata = this.Metadata.GetFormatMetadata(WebpFormat.Instance); @@ -183,7 +191,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType)) { - bool isAnimationChunk = this.ParseOptionalExtendedChunks(chunkType, features); + bool isAnimationChunk = this.ParseOptionalExtendedChunks(chunkType, features, ignoreAlpha); if (isAnimationChunk) { return webpInfos; @@ -207,8 +215,9 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// The chunk type. /// The webp image features. + /// For identify, the alpha data should not be read. /// true, if animation chunk was found. - private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features) + private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features, bool ignoreAlpha) { int bytesRead; switch (chunkType) @@ -293,10 +302,16 @@ namespace SixLabors.ImageSharp.Formats.Webp case WebpChunkType.Alpha: uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); + if (ignoreAlpha) + { + this.currentStream.Skip((int)alphaChunkSize); + } + features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); int alphaDataSize = (int)(alphaChunkSize - 1); - features.AlphaData = this.memoryAllocator.Allocate(alphaDataSize); - this.currentStream.Read(features.AlphaData.Memory.Span, 0, alphaDataSize); + this.AlphaData = this.memoryAllocator.Allocate(alphaDataSize); + Span alphaData = this.AlphaData.GetSpan(); + this.currentStream.Read(alphaData, 0, alphaDataSize); break; default: WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); @@ -324,5 +339,8 @@ namespace SixLabors.ImageSharp.Formats.Webp WebpChunkParsingUtils.ParseOptionalChunks(this.currentStream, chunkType, this.Metadata, this.IgnoreMetadata, this.buffer); } } + + /// + public void Dispose() => this.AlphaData?.Dispose(); } } diff --git a/src/ImageSharp/Formats/Webp/WebpFeatures.cs b/src/ImageSharp/Formats/Webp/WebpFeatures.cs index b0131e07a..398514d5b 100644 --- a/src/ImageSharp/Formats/Webp/WebpFeatures.cs +++ b/src/ImageSharp/Formats/Webp/WebpFeatures.cs @@ -1,15 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Buffers; - namespace SixLabors.ImageSharp.Formats.Webp { /// /// Image features of a VP8X image. /// - internal class WebpFeatures : IDisposable + internal class WebpFeatures { /// /// Gets or sets a value indicating whether this image has an ICC Profile. @@ -21,11 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Webp /// public bool Alpha { get; set; } - /// - /// Gets or sets the alpha data, if an ALPH chunk is present. - /// - public IMemoryOwner AlphaData { get; set; } - /// /// Gets or sets the alpha chunk header. /// @@ -56,8 +48,5 @@ namespace SixLabors.ImageSharp.Formats.Webp /// 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.. /// public Color? AnimationBackgroundColor { get; set; } - - /// - public void Dispose() => this.AlphaData?.Dispose(); } } diff --git a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs b/src/ImageSharp/Formats/Webp/WebpImageInfo.cs index 530f5c0a5..aa11d38c3 100644 --- a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs +++ b/src/ImageSharp/Formats/Webp/WebpImageInfo.cs @@ -63,7 +63,6 @@ namespace SixLabors.ImageSharp.Formats.Webp { this.Vp8BitReader?.Dispose(); this.Vp8LBitReader?.Dispose(); - this.Features?.AlphaData?.Dispose(); } } }