Browse Source

Add decoding animated lossy webp

pull/1985/head
Brian Popow 5 years ago
parent
commit
2b8af22d5d
  1. 2
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  2. 13
      src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs
  3. 42
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  4. 2
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  5. 4
      src/ImageSharp/Formats/Webp/WebpDecoder.cs
  6. 36
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  7. 13
      src/ImageSharp/Formats/Webp/WebpFeatures.cs
  8. 1
      src/ImageSharp/Formats/Webp/WebpImageInfo.cs

2
src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs

@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private static ReadOnlySpan<byte> LiteralMap => new byte[] { 0, 1, 1, 1, 0 }; private static ReadOnlySpan<byte> LiteralMap => new byte[] { 0, 1, 1, 1, 0 };
/// <summary> /// <summary>
/// Decodes the image from the stream using the bitreader. /// Decodes the lossless webp image from the stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel buffer to store the decoded data.</param> /// <param name="pixels">The pixel buffer to store the decoded data.</param>

13
src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs

@ -57,7 +57,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.configuration = configuration; this.configuration = configuration;
} }
public void Decode<TPixel>(Buffer2D<TPixel> pixels, int width, int height, WebpImageInfo info) /// <summary>
/// Decodes the lossless webp image from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel buffer to store the decoded data.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="info">Information about the image.</param>
/// <param name="alphaData">The ALPH chunk data.</param>
public void Decode<TPixel>(Buffer2D<TPixel> pixels, int width, int height, WebpImageInfo info, IMemoryOwner<byte> alphaData)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// Paragraph 9.2: color space and clamp type follow. // Paragraph 9.2: color space and clamp type follow.
@ -105,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
using (var alphaDecoder = new AlphaDecoder( using (var alphaDecoder = new AlphaDecoder(
width, width,
height, height,
info.Features.AlphaData, alphaData,
info.Features.AlphaChunkHeader, info.Features.AlphaChunkHeader,
this.memoryAllocator, this.memoryAllocator,
this.configuration)) this.configuration))

42
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
@ -14,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <summary> /// <summary>
/// Decoder for animated webp images. /// Decoder for animated webp images.
/// </summary> /// </summary>
internal class WebpAnimationDecoder internal class WebpAnimationDecoder : IDisposable
{ {
/// <summary> /// <summary>
/// Reusable buffer. /// Reusable buffer.
@ -47,6 +48,11 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.configuration = configuration; this.configuration = configuration;
} }
/// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary>
public IMemoryOwner<byte> AlphaData { get; set; }
/// <summary> /// <summary>
/// Decodes the animated webp image from the specified stream. /// Decodes the animated webp image from the specified stream.
/// </summary> /// </summary>
@ -108,12 +114,12 @@ namespace SixLabors.ImageSharp.Formats.Webp
long streamStartPosition = stream.Position; long streamStartPosition = stream.Position;
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
bool hasAlpha = false;
byte alphaChunkHeader = 0;
if (chunkType is WebpChunkType.Alpha) if (chunkType is WebpChunkType.Alpha)
{ {
// TODO: ignore alpha for now. alphaChunkHeader = this.ReadAlphaData(stream);
stream.Skip(4); hasAlpha = true;
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
stream.Skip((int)alphaChunkSize);
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
} }
@ -123,6 +129,8 @@ namespace SixLabors.ImageSharp.Formats.Webp
{ {
case WebpChunkType.Vp8: case WebpChunkType.Vp8:
webpInfo = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features); webpInfo = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features);
features.Alpha = hasAlpha;
features.AlphaChunkHeader = alphaChunkHeader;
break; break;
case WebpChunkType.Vp8L: case WebpChunkType.Vp8L:
webpInfo = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features); webpInfo = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features);
@ -171,6 +179,25 @@ namespace SixLabors.ImageSharp.Formats.Webp
return (uint)(stream.Position - streamStartPosition); return (uint)(stream.Position - streamStartPosition);
} }
/// <summary>
/// Reads the ALPH chunk data.
/// </summary>
/// <param name="stream">The stream to read from.</param>
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<byte>(alphaDataSize);
byte alphaChunkHeader = (byte)stream.ReadByte();
Span<byte> alphaData = this.AlphaData.GetSpan();
stream.Read(alphaData, 0, alphaDataSize);
return alphaChunkHeader;
}
/// <summary> /// <summary>
/// Decodes the either lossy or lossless webp image data. /// Decodes the either lossy or lossless webp image data.
/// </summary> /// </summary>
@ -191,7 +218,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
else else
{ {
var lossyDecoder = new WebpLossyDecoder(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration); 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; return decodedImage;
@ -326,5 +353,8 @@ namespace SixLabors.ImageSharp.Formats.Webp
return data; return data;
} }
/// <inheritdoc/>
public void Dispose() => this.AlphaData?.Dispose();
} }
} }

2
src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
stream.Read(buffer, 0, 4); stream.Read(buffer, 0, 4);
uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
// remaining counts the available image data payload. // Remaining counts the available image data payload.
uint remaining = dataSize; uint remaining = dataSize;
// Paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30 // Paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30

4
src/ImageSharp/Formats/Webp/WebpDecoder.cs

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new WebpDecoderCore(configuration, this); using var decoder = new WebpDecoderCore(configuration, this);
try try
{ {
@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new WebpDecoderCore(configuration, this); using var decoder = new WebpDecoderCore(configuration, this);
try try
{ {

36
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
@ -19,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <summary> /// <summary>
/// Performs the webp decoding operation. /// Performs the webp decoding operation.
/// </summary> /// </summary>
internal sealed class WebpDecoderCore : IImageDecoderInternals internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
{ {
/// <summary> /// <summary>
/// Reusable buffer. /// Reusable buffer.
@ -76,6 +78,11 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary> /// </summary>
public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height);
/// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary>
public IMemoryOwner<byte> AlphaData { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -89,7 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
{ {
if (this.webImageInfo.Features is { Animation: true }) 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<TPixel>(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize); return animationDecoder.Decode<TPixel>(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
} }
@ -104,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
else else
{ {
var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); 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. // 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.currentStream = stream;
this.ReadImageHeader(); 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); 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
/// <summary> /// <summary>
/// Reads information present in the image header, about the image content and how to decode the image. /// Reads information present in the image header, about the image content and how to decode the image.
/// </summary> /// </summary>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <returns>Information about the webp image.</returns> /// <returns>Information about the webp image.</returns>
private WebpImageInfo ReadVp8Info() private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false)
{ {
this.Metadata = new ImageMetadata(); this.Metadata = new ImageMetadata();
this.webpMetadata = this.Metadata.GetFormatMetadata(WebpFormat.Instance); this.webpMetadata = this.Metadata.GetFormatMetadata(WebpFormat.Instance);
@ -183,7 +191,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
} }
else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType)) else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType))
{ {
bool isAnimationChunk = this.ParseOptionalExtendedChunks(chunkType, features); bool isAnimationChunk = this.ParseOptionalExtendedChunks(chunkType, features, ignoreAlpha);
if (isAnimationChunk) if (isAnimationChunk)
{ {
return webpInfos; return webpInfos;
@ -207,8 +215,9 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary> /// </summary>
/// <param name="chunkType">The chunk type.</param> /// <param name="chunkType">The chunk type.</param>
/// <param name="features">The webp image features.</param> /// <param name="features">The webp image features.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <returns>true, if animation chunk was found.</returns> /// <returns>true, if animation chunk was found.</returns>
private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features) private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features, bool ignoreAlpha)
{ {
int bytesRead; int bytesRead;
switch (chunkType) switch (chunkType)
@ -293,10 +302,16 @@ namespace SixLabors.ImageSharp.Formats.Webp
case WebpChunkType.Alpha: case WebpChunkType.Alpha:
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
if (ignoreAlpha)
{
this.currentStream.Skip((int)alphaChunkSize);
}
features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); features.AlphaChunkHeader = (byte)this.currentStream.ReadByte();
int alphaDataSize = (int)(alphaChunkSize - 1); int alphaDataSize = (int)(alphaChunkSize - 1);
features.AlphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize); this.AlphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
this.currentStream.Read(features.AlphaData.Memory.Span, 0, alphaDataSize); Span<byte> alphaData = this.AlphaData.GetSpan();
this.currentStream.Read(alphaData, 0, alphaDataSize);
break; break;
default: default:
WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); 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); WebpChunkParsingUtils.ParseOptionalChunks(this.currentStream, chunkType, this.Metadata, this.IgnoreMetadata, this.buffer);
} }
} }
/// <inheritdoc/>
public void Dispose() => this.AlphaData?.Dispose();
} }
} }

13
src/ImageSharp/Formats/Webp/WebpFeatures.cs

@ -1,15 +1,12 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
namespace SixLabors.ImageSharp.Formats.Webp namespace SixLabors.ImageSharp.Formats.Webp
{ {
/// <summary> /// <summary>
/// Image features of a VP8X image. /// Image features of a VP8X image.
/// </summary> /// </summary>
internal class WebpFeatures : IDisposable internal class WebpFeatures
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this image has an ICC Profile. /// Gets or sets a value indicating whether this image has an ICC Profile.
@ -21,11 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary> /// </summary>
public bool Alpha { get; set; } public bool Alpha { get; set; }
/// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary>
public IMemoryOwner<byte> AlphaData { get; set; }
/// <summary> /// <summary>
/// Gets or sets the alpha chunk header. /// Gets or sets the alpha chunk header.
/// </summary> /// </summary>
@ -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.. /// 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..
/// </summary> /// </summary>
public Color? AnimationBackgroundColor { get; set; } public Color? AnimationBackgroundColor { get; set; }
/// <inheritdoc/>
public void Dispose() => this.AlphaData?.Dispose();
} }
} }

1
src/ImageSharp/Formats/Webp/WebpImageInfo.cs

@ -63,7 +63,6 @@ namespace SixLabors.ImageSharp.Formats.Webp
{ {
this.Vp8BitReader?.Dispose(); this.Vp8BitReader?.Dispose();
this.Vp8LBitReader?.Dispose(); this.Vp8LBitReader?.Dispose();
this.Features?.AlphaData?.Dispose();
} }
} }
} }

Loading…
Cancel
Save