Browse Source

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

* Add webp decoder option BackgroundColorHandling to decide howto handle the background color in the ANIM chunk

* Add test for Issue 2528

* Update src/ImageSharp/Formats/Webp/BackgroundColorHandling.cs

Co-authored-by: James Jackson-South <james_south@hotmail.com>

* Fix path to test file

* Another attempt fixing the file path: path should be lower case

* Revert fa4febf7

---------

Co-authored-by: James Jackson-South <james_south@hotmail.com>
pull/2550/head
Brian Popow 2 years ago
committed by GitHub
parent
commit
c5eed0e537
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs
  2. 1
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  3. 21
      src/ImageSharp/Formats/Webp/BackgroundColorHandling.cs
  4. 14
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  5. 18
      src/ImageSharp/Formats/Webp/WebpDecoder.cs
  6. 18
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  7. 21
      src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs
  8. 19
      tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
  9. 1
      tests/ImageSharp.Tests/TestImages.cs
  10. 3
      tests/Images/Input/Webp/issues/Issue2528.webp

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs

@ -32,7 +32,7 @@ internal class WebpTiffCompression : TiffBaseDecompressor
/// <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);
}

1
src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs

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

21
src/ImageSharp/Formats/Webp/BackgroundColorHandling.cs

@ -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
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -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>
@ -94,7 +101,10 @@ internal class WebpAnimationDecoder : IDisposable
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:

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

@ -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()
{
@ -25,25 +25,33 @@ public sealed class WebpDecoder : ImageDecoder
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
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -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;
}
@ -83,7 +89,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
{
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);
}

21
src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs

@ -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
tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs

@ -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;
@ -340,6 +339,24 @@ public class WebpDecoderTests
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)]

1
tests/ImageSharp.Tests/TestImages.cs

@ -677,6 +677,7 @@ public static class TestImages
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";

3
tests/Images/Input/Webp/issues/Issue2528.webp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b4aa2ba2e6ef0263b5b657e4d15241d497721a0461250b1d942751812b96de71
size 60214
Loading…
Cancel
Save