Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add webp decoder option to handle the background color in ANIM chunk #2547

Merged
merged 7 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public WebpTiffCompression(DecoderOptions options, MemoryAllocator memoryAllocat
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
using WebpDecoderCore decoder = new(this.options);
using WebpDecoderCore decoder = new(new WebpDecoderOptions());
using Image<Rgb24> image = decoder.Decode<Rgb24>(stream, cancellationToken);
CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Memory;

namespace SixLabors.ImageSharp.Formats.Tiff.Compression;
Expand Down
21 changes: 21 additions & 0 deletions src/ImageSharp/Formats/Webp/BackgroundColorHandling.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.ImageSharp.Formats.Webp;

/// <summary>
/// Enum to decide how to handle the background color of the Animation chunk during decoding.
/// </summary>
public enum BackgroundColorHandling
{
/// <summary>
/// The background color of the ANIM chunk will be used to initialize the canvas to fill the unused space on the canvas around the frame.
/// Also, if AnimationDisposalMethod.Dispose is used, this color will be used to restore the canvas background.
/// </summary>
Standard = 0,

/// <summary>
/// The background color of the ANIM chunk is ignored and instead the canvas is initialized with transparent, BGRA(0, 0, 0, 0).
/// </summary>
Ignore = 1
}
14 changes: 12 additions & 2 deletions src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,24 @@ internal class WebpAnimationDecoder : IDisposable
/// </summary>
private IMemoryOwner<byte>? alphaData;

/// <summary>
/// The flag to decide how to handle the background color in the Animation Chunk.
/// </summary>
private readonly BackgroundColorHandling backgroundColorHandling;

/// <summary>
/// Initializes a new instance of the <see cref="WebpAnimationDecoder"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="configuration">The global configuration.</param>
/// <param name="maxFrames">The maximum number of frames to decode. Inclusive.</param>
public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames)
/// <param name="backgroundColorHandling">The flag to decide how to handle the background color in the Animation Chunk.</param>
public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames, BackgroundColorHandling backgroundColorHandling)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.maxFrames = maxFrames;
this.backgroundColorHandling = backgroundColorHandling;
}

/// <summary>
Expand Down Expand Up @@ -94,7 +101,10 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, WebpFeatures feat
switch (chunkType)
{
case WebpChunkType.Animation:
uint dataSize = this.ReadFrame(stream, ref image, ref previousFrame, width, height, features.AnimationBackgroundColor!.Value);
Color backgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore
? new Color(new Bgra32(0, 0, 0, 0))
: features.AnimationBackgroundColor!.Value;
uint dataSize = this.ReadFrame(stream, ref image, ref previousFrame, width, height, backgroundColor);
remainingBytes -= (int)dataSize;
break;
case WebpChunkType.Xmp:
Expand Down
18 changes: 13 additions & 5 deletions src/ImageSharp/Formats/Webp/WebpDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
/// Image decoder for generating an image out of a webp stream.
/// </summary>
public sealed class WebpDecoder : ImageDecoder
public sealed class WebpDecoder : SpecializedImageDecoder<WebpDecoderOptions>
{
private WebpDecoder()
{
Expand All @@ -25,25 +25,33 @@ protected override ImageInfo Identify(DecoderOptions options, Stream stream, Can
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));

using WebpDecoderCore decoder = new(options);
using WebpDecoderCore decoder = new(new WebpDecoderOptions() { GeneralOptions = options });
return decoder.Identify(options.Configuration, stream, cancellationToken);
}

/// <inheritdoc/>
protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
protected override Image<TPixel> Decode<TPixel>(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));

using WebpDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
Image<TPixel> image = decoder.Decode<TPixel>(options.GeneralOptions.Configuration, stream, cancellationToken);

ScaleToTargetSize(options, image);
ScaleToTargetSize(options.GeneralOptions, image);

return image;
}

/// <inheritdoc/>
protected override Image Decode(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);

/// <inheritdoc/>
protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);

/// <inheritdoc/>
protected override WebpDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
=> new() { GeneralOptions = options };
}
18 changes: 12 additions & 6 deletions src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,22 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// </summary>
private WebpImageInfo? webImageInfo;

/// <summary>
/// The flag to decide how to handle the background color in the Animation Chunk.
/// </summary>
private BackgroundColorHandling backgroundColorHandling;

/// <summary>
/// Initializes a new instance of the <see cref="WebpDecoderCore"/> class.
/// </summary>
/// <param name="options">The decoder options.</param>
public WebpDecoderCore(DecoderOptions options)
public WebpDecoderCore(WebpDecoderOptions options)
{
this.Options = options;
this.configuration = options.Configuration;
this.skipMetadata = options.SkipMetadata;
this.maxFrames = options.MaxFrames;
this.Options = options.GeneralOptions;
this.backgroundColorHandling = options.BackgroundColorHandling;
this.configuration = options.GeneralOptions.Configuration;
this.skipMetadata = options.GeneralOptions.SkipMetadata;
this.maxFrames = options.GeneralOptions.MaxFrames;
this.memoryAllocator = this.configuration.MemoryAllocator;
}

Expand All @@ -83,7 +89,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
{
if (this.webImageInfo.Features is { Animation: true })
{
using WebpAnimationDecoder animationDecoder = new(this.memoryAllocator, this.configuration, this.maxFrames);
using WebpAnimationDecoder animationDecoder = new(this.memoryAllocator, this.configuration, this.maxFrames, this.backgroundColorHandling);
return animationDecoder.Decode<TPixel>(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
}

Expand Down
21 changes: 21 additions & 0 deletions src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.ImageSharp.Formats.Webp;

/// <summary>
/// Configuration options for decoding webp images.
/// </summary>
public sealed class WebpDecoderOptions : ISpecializedDecoderOptions
{
/// <inheritdoc/>
public DecoderOptions GeneralOptions { get; init; } = new();

/// <summary>
/// Gets the flag to decide how to handle the background color Animation Chunk.
/// The specification is vague on how to handle the background color of the animation chunk.
/// This option let's the user choose how to deal with it.
/// </summary>
/// <see href="https://developers.google.com/speed/webp/docs/riff_container#animation"/>
public BackgroundColorHandling BackgroundColorHandling { get; init; } = BackgroundColorHandling.Standard;
}
19 changes: 18 additions & 1 deletion tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Webp;
Expand Down Expand Up @@ -340,6 +339,24 @@ public void Decode_AnimatedLossless_WithFrameDecodingModeFirst_OnlyDecodesOneFra
Assert.Equal(1, image.Frames.Count);
}

[Theory]
[WithFile(Lossy.AnimatedIssue2528, PixelTypes.Rgba32)]
public void Decode_AnimatedLossy_IgnoreBackgroundColor_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
WebpDecoderOptions options = new()
{
BackgroundColorHandling = BackgroundColorHandling.Ignore,
GeneralOptions = new DecoderOptions()
{
MaxFrames = 1
}
};
using Image<TPixel> image = provider.GetImage(WebpDecoder.Instance, options);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}

[Theory]
[WithFile(Lossless.LossLessCorruptImage1, PixelTypes.Rgba32)]
[WithFile(Lossless.LossLessCorruptImage2, PixelTypes.Rgba32)]
Expand Down
1 change: 1 addition & 0 deletions tests/ImageSharp.Tests/TestImages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ public static class Lossy
public const string WithXmp = "Webp/xmp_lossy.webp";
public const string BikeSmall = "Webp/bike_lossy_small.webp";
public const string Animated = "Webp/leo_animated_lossy.webp";
public const string AnimatedIssue2528 = "Webp/issues/Issue2528.webp";

// Lossy images without macroblock filtering.
public const string BikeWithExif = "Webp/bike_lossy_with_exif.webp";
Expand Down
3 changes: 3 additions & 0 deletions tests/Images/Input/Webp/issues/Issue2528.webp
Git LFS file not shown