Browse Source

Add decoding animated lossy webp

pull/1985/head
Brian Popow 4 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 };
/// <summary>
/// Decodes the image from the stream using the bitreader.
/// 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>

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

@ -57,7 +57,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
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>
{
// 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))

42
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
/// <summary>
/// Decoder for animated webp images.
/// </summary>
internal class WebpAnimationDecoder
internal class WebpAnimationDecoder : IDisposable
{
/// <summary>
/// Reusable buffer.
@ -47,6 +48,11 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.configuration = configuration;
}
/// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary>
public IMemoryOwner<byte> AlphaData { get; set; }
/// <summary>
/// Decodes the animated webp image from the specified stream.
/// </summary>
@ -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);
}
/// <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>
/// Decodes the either lossy or lossless webp image data.
/// </summary>
@ -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;
}
/// <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);
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

4
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
{

36
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
/// <summary>
/// Performs the webp decoding operation.
/// </summary>
internal sealed class WebpDecoderCore : IImageDecoderInternals
internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
{
/// <summary>
/// Reusable buffer.
@ -76,6 +78,11 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary>
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 />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
@ -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<TPixel>(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
/// <summary>
/// Reads information present in the image header, about the image content and how to decode the image.
/// </summary>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <returns>Information about the webp image.</returns>
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
/// </summary>
/// <param name="chunkType">The chunk type.</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>
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<byte>(alphaDataSize);
this.currentStream.Read(features.AlphaData.Memory.Span, 0, alphaDataSize);
this.AlphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
Span<byte> 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);
}
}
/// <inheritdoc/>
public void Dispose() => this.AlphaData?.Dispose();
}
}

13
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
{
/// <summary>
/// Image features of a VP8X image.
/// </summary>
internal class WebpFeatures : IDisposable
internal class WebpFeatures
{
/// <summary>
/// Gets or sets a value indicating whether this image has an ICC Profile.
@ -21,11 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary>
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>
/// Gets or sets the alpha chunk header.
/// </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..
/// </summary>
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.Vp8LBitReader?.Dispose();
this.Features?.AlphaData?.Dispose();
}
}
}

Loading…
Cancel
Save