diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs
index a5ce4f842..416472e83 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs
@@ -32,7 +32,7 @@ internal class WebpTiffCompression : TiffBaseDecompressor
///
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken)
{
- using WebpDecoderCore decoder = new(this.options);
+ using WebpDecoderCore decoder = new(new WebpDecoderOptions());
using Image image = decoder.Decode(stream, cancellationToken);
CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer);
}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
index b9a1f3155..720e376b9 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
+++ b/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;
diff --git a/src/ImageSharp/Formats/Webp/BackgroundColorHandling.cs b/src/ImageSharp/Formats/Webp/BackgroundColorHandling.cs
new file mode 100644
index 000000000..5be8f6a29
--- /dev/null
+++ b/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;
+
+///
+/// Enum to decide how to handle the background color of the Animation chunk during decoding.
+///
+public enum BackgroundColorHandling
+{
+ ///
+ /// 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.
+ ///
+ Standard = 0,
+
+ ///
+ /// The background color of the ANIM chunk is ignored and instead the canvas is initialized with transparent, BGRA(0, 0, 0, 0).
+ ///
+ Ignore = 1
+}
diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
index 21337ce6c..90c9c70b2 100644
--- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
+++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
@@ -52,17 +52,24 @@ internal class WebpAnimationDecoder : IDisposable
///
private IMemoryOwner? alphaData;
+ ///
+ /// The flag to decide how to handle the background color in the Animation Chunk.
+ ///
+ private readonly BackgroundColorHandling backgroundColorHandling;
+
///
/// Initializes a new instance of the class.
///
/// The memory allocator.
/// The global configuration.
/// The maximum number of frames to decode. Inclusive.
- public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames)
+ /// The flag to decide how to handle the background color in the Animation Chunk.
+ public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames, BackgroundColorHandling backgroundColorHandling)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.maxFrames = maxFrames;
+ this.backgroundColorHandling = backgroundColorHandling;
}
///
@@ -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:
diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs
index daa5eaf4f..e23b817cc 100644
--- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs
+++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs
@@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
///
/// Image decoder for generating an image out of a webp stream.
///
-public sealed class WebpDecoder : ImageDecoder
+public sealed class WebpDecoder : SpecializedImageDecoder
{
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);
}
///
- protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
+ protected override Image Decode(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
using WebpDecoderCore decoder = new(options);
- Image image = decoder.Decode(options.Configuration, stream, cancellationToken);
+ Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken);
- ScaleToTargetSize(options, image);
+ ScaleToTargetSize(options.GeneralOptions, image);
return image;
}
+ ///
+ protected override Image Decode(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
+ => this.Decode(options, stream, cancellationToken);
+
///
protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode(options, stream, cancellationToken);
+
+ ///
+ protected override WebpDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
+ => new() { GeneralOptions = options };
}
diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
index 223e15a0e..8832ac106 100644
--- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
@@ -48,16 +48,22 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
///
private WebpImageInfo? webImageInfo;
+ ///
+ /// The flag to decide how to handle the background color in the Animation Chunk.
+ ///
+ private BackgroundColorHandling backgroundColorHandling;
+
///
/// Initializes a new instance of the class.
///
/// The decoder options.
- 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(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
}
diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs b/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs
new file mode 100644
index 000000000..6fb15acbb
--- /dev/null
+++ b/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;
+
+///
+/// Configuration options for decoding webp images.
+///
+public sealed class WebpDecoderOptions : ISpecializedDecoderOptions
+{
+ ///
+ public DecoderOptions GeneralOptions { get; init; } = new();
+
+ ///
+ /// 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.
+ ///
+ ///
+ public BackgroundColorHandling BackgroundColorHandling { get; init; } = BackgroundColorHandling.Standard;
+}
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
index f95b003d0..c0fc00b82 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
+++ b/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(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ WebpDecoderOptions options = new()
+ {
+ BackgroundColorHandling = BackgroundColorHandling.Ignore,
+ GeneralOptions = new DecoderOptions()
+ {
+ MaxFrames = 1
+ }
+ };
+ using Image image = provider.GetImage(WebpDecoder.Instance, options);
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider, ReferenceDecoder);
+ }
+
[Theory]
[WithFile(Lossless.LossLessCorruptImage1, PixelTypes.Rgba32)]
[WithFile(Lossless.LossLessCorruptImage2, PixelTypes.Rgba32)]
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 180a8594c..470a670a4 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/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";
diff --git a/tests/Images/Input/Webp/issues/Issue2528.webp b/tests/Images/Input/Webp/issues/Issue2528.webp
new file mode 100644
index 000000000..c7ff62ec3
--- /dev/null
+++ b/tests/Images/Input/Webp/issues/Issue2528.webp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b4aa2ba2e6ef0263b5b657e4d15241d497721a0461250b1d942751812b96de71
+size 60214