Skip to content

Commit a477ac1

Browse files
Use correct alpha blending
1 parent 8e9473e commit a477ac1

File tree

4 files changed

+45
-33
lines changed

4 files changed

+45
-33
lines changed

src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -200,13 +200,10 @@ private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
200200
this.RestoreToBackground(imageFrame, backgroundColor);
201201
}
202202

203-
using Buffer2D<TPixel> decodedImage = this.DecodeImageData<TPixel>(frameData, webpInfo);
204-
DrawDecodedImageOnCanvas(decodedImage, imageFrame, regionRectangle);
203+
using Buffer2D<TPixel> decodedImageFrame = this.DecodeImageFrameData<TPixel>(frameData, webpInfo);
205204

206-
if (previousFrame != null && frameData.BlendingMethod is WebpBlendingMethod.AlphaBlending)
207-
{
208-
this.AlphaBlend(previousFrame, imageFrame, regionRectangle);
209-
}
205+
bool blend = previousFrame != null && frameData.BlendingMethod == WebpBlendingMethod.AlphaBlending;
206+
DrawDecodedImageFrameOnCanvas(decodedImageFrame, imageFrame, regionRectangle, blend);
210207

211208
previousFrame = currentFrame ?? image.Frames.RootFrame;
212209
this.restoreArea = regionRectangle;
@@ -253,7 +250,7 @@ private byte ReadAlphaData(BufferedReadStream stream)
253250
/// <param name="frameData">The frame data.</param>
254251
/// <param name="webpInfo">The webp information.</param>
255252
/// <returns>A decoded image.</returns>
256-
private Buffer2D<TPixel> DecodeImageData<TPixel>(WebpFrameData frameData, WebpImageInfo webpInfo)
253+
private Buffer2D<TPixel> DecodeImageFrameData<TPixel>(WebpFrameData frameData, WebpImageInfo webpInfo)
257254
where TPixel : unmanaged, IPixel<TPixel>
258255
{
259256
ImageFrame<TPixel> decodedFrame = new(Configuration.Default, (int)frameData.Width, (int)frameData.Height);
@@ -291,42 +288,43 @@ private Buffer2D<TPixel> DecodeImageData<TPixel>(WebpFrameData frameData, WebpIm
291288
/// Draws the decoded image on canvas. The decoded image can be smaller the canvas.
292289
/// </summary>
293290
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
294-
/// <param name="decodedImage">The decoded image.</param>
291+
/// <param name="decodedImageFrame">The decoded image.</param>
295292
/// <param name="imageFrame">The image frame to draw into.</param>
296293
/// <param name="restoreArea">The area of the frame.</param>
297-
private static void DrawDecodedImageOnCanvas<TPixel>(Buffer2D<TPixel> decodedImage, ImageFrame<TPixel> imageFrame, Rectangle restoreArea)
294+
/// <param name="blend">Whether to blend the decoded frame data onto the target frame.</param>
295+
private static void DrawDecodedImageFrameOnCanvas<TPixel>(
296+
Buffer2D<TPixel> decodedImageFrame,
297+
ImageFrame<TPixel> imageFrame,
298+
Rectangle restoreArea,
299+
bool blend)
298300
where TPixel : unmanaged, IPixel<TPixel>
299301
{
302+
// Trim the destination frame to match the restore area. The source frame is already trimmed.
300303
Buffer2DRegion<TPixel> imageFramePixels = imageFrame.PixelBuffer.GetRegion(restoreArea);
301-
int decodedRowIdx = 0;
302-
for (int y = 0; y < restoreArea.Height; y++)
304+
if (blend)
303305
{
304-
Span<TPixel> framePixelRow = imageFramePixels.DangerousGetRowSpan(y);
305-
Span<TPixel> decodedPixelRow = decodedImage.DangerousGetRowSpan(decodedRowIdx++)[..restoreArea.Width];
306-
decodedPixelRow.TryCopyTo(framePixelRow);
306+
// The destination frame has already been prepopulated with the pixel data from the previous frame
307+
// so blending will leave the desired result which takes into consideration restoration to the
308+
// background color within the restore area.
309+
PixelBlender<TPixel> blender =
310+
PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
311+
312+
for (int y = 0; y < restoreArea.Height; y++)
313+
{
314+
Span<TPixel> framePixelRow = imageFramePixels.DangerousGetRowSpan(y);
315+
Span<TPixel> decodedPixelRow = decodedImageFrame.DangerousGetRowSpan(y)[..restoreArea.Width];
316+
317+
blender.Blend<TPixel>(imageFrame.Configuration, framePixelRow, framePixelRow, decodedPixelRow, 1f);
318+
}
319+
320+
return;
307321
}
308-
}
309322

310-
/// <summary>
311-
/// After disposing of the previous frame, render the current frame on the canvas using alpha-blending.
312-
/// If the current frame does not have an alpha channel, assume alpha value of 255, effectively replacing the rectangle.
313-
/// </summary>
314-
/// <typeparam name="TPixel">The pixel format.</typeparam>
315-
/// <param name="src">The source image.</param>
316-
/// <param name="dst">The destination image.</param>
317-
/// <param name="restoreArea">The area of the frame.</param>
318-
private void AlphaBlend<TPixel>(ImageFrame<TPixel> src, ImageFrame<TPixel> dst, Rectangle restoreArea)
319-
where TPixel : unmanaged, IPixel<TPixel>
320-
{
321-
Buffer2DRegion<TPixel> srcPixels = src.PixelBuffer.GetRegion(restoreArea);
322-
Buffer2DRegion<TPixel> dstPixels = dst.PixelBuffer.GetRegion(restoreArea);
323-
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
324323
for (int y = 0; y < restoreArea.Height; y++)
325324
{
326-
Span<TPixel> srcPixelRow = srcPixels.DangerousGetRowSpan(y);
327-
Span<TPixel> dstPixelRow = dstPixels.DangerousGetRowSpan(y);
328-
329-
blender.Blend<TPixel>(this.configuration, dstPixelRow, srcPixelRow, dstPixelRow, 1f);
325+
Span<TPixel> framePixelRow = imageFramePixels.DangerousGetRowSpan(y);
326+
Span<TPixel> decodedPixelRow = decodedImageFrame.DangerousGetRowSpan(y)[..restoreArea.Width];
327+
decodedPixelRow.CopyTo(framePixelRow);
330328
}
331329
}
332330

tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,16 @@ public void Decode_AnimatedLossy_IgnoreBackgroundColor_Works<TPixel>(TestImagePr
357357
image.CompareToOriginal(provider, ReferenceDecoder);
358358
}
359359

360+
[Theory]
361+
[WithFile(Lossy.AnimatedLandscape, PixelTypes.Rgba32)]
362+
public void Decode_AnimatedLossy_AlphaBlending_Works<TPixel>(TestImageProvider<TPixel> provider)
363+
where TPixel : unmanaged, IPixel<TPixel>
364+
{
365+
using Image<TPixel> image = provider.GetImage(WebpDecoder.Instance);
366+
image.DebugSaveMultiFrame(provider);
367+
image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact);
368+
}
369+
360370
[Theory]
361371
[WithFile(Lossless.LossLessCorruptImage1, PixelTypes.Rgba32)]
362372
[WithFile(Lossless.LossLessCorruptImage2, PixelTypes.Rgba32)]

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ public static class Lossless
681681

682682
public static class Lossy
683683
{
684+
public const string AnimatedLandscape = "Webp/landscape.webp";
684685
public const string Earth = "Webp/earth_lossy.webp";
685686
public const string WithExif = "Webp/exif_lossy.webp";
686687
public const string WithExifNotEnoughData = "Webp/exif_lossy_not_enough_data.webp";
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:1e9f8b7ee87ecb59d8cee5e84320da7670eb5e274e1c0a7dd5f13fe3675be62a
3+
size 26892

0 commit comments

Comments
 (0)