Browse Source

Use correct alpha blending

pull/2569/head
James Jackson-South 3 years ago
parent
commit
a477ac13bc
  1. 64
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  2. 10
      tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
  3. 1
      tests/ImageSharp.Tests/TestImages.cs
  4. 3
      tests/Images/Input/Webp/landscape.webp

64
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -200,13 +200,10 @@ internal class WebpAnimationDecoder : IDisposable
this.RestoreToBackground(imageFrame, backgroundColor);
}
using Buffer2D<TPixel> decodedImage = this.DecodeImageData<TPixel>(frameData, webpInfo);
DrawDecodedImageOnCanvas(decodedImage, imageFrame, regionRectangle);
using Buffer2D<TPixel> decodedImageFrame = this.DecodeImageFrameData<TPixel>(frameData, webpInfo);
if (previousFrame != null && frameData.BlendingMethod is WebpBlendingMethod.AlphaBlending)
{
this.AlphaBlend(previousFrame, imageFrame, regionRectangle);
}
bool blend = previousFrame != null && frameData.BlendingMethod == WebpBlendingMethod.AlphaBlending;
DrawDecodedImageFrameOnCanvas(decodedImageFrame, imageFrame, regionRectangle, blend);
previousFrame = currentFrame ?? image.Frames.RootFrame;
this.restoreArea = regionRectangle;
@ -253,7 +250,7 @@ internal class WebpAnimationDecoder : IDisposable
/// <param name="frameData">The frame data.</param>
/// <param name="webpInfo">The webp information.</param>
/// <returns>A decoded image.</returns>
private Buffer2D<TPixel> DecodeImageData<TPixel>(WebpFrameData frameData, WebpImageInfo webpInfo)
private Buffer2D<TPixel> DecodeImageFrameData<TPixel>(WebpFrameData frameData, WebpImageInfo webpInfo)
where TPixel : unmanaged, IPixel<TPixel>
{
ImageFrame<TPixel> decodedFrame = new(Configuration.Default, (int)frameData.Width, (int)frameData.Height);
@ -291,42 +288,43 @@ internal class WebpAnimationDecoder : IDisposable
/// Draws the decoded image on canvas. The decoded image can be smaller the canvas.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="decodedImage">The decoded image.</param>
/// <param name="decodedImageFrame">The decoded image.</param>
/// <param name="imageFrame">The image frame to draw into.</param>
/// <param name="restoreArea">The area of the frame.</param>
private static void DrawDecodedImageOnCanvas<TPixel>(Buffer2D<TPixel> decodedImage, ImageFrame<TPixel> imageFrame, Rectangle restoreArea)
/// <param name="blend">Whether to blend the decoded frame data onto the target frame.</param>
private static void DrawDecodedImageFrameOnCanvas<TPixel>(
Buffer2D<TPixel> decodedImageFrame,
ImageFrame<TPixel> imageFrame,
Rectangle restoreArea,
bool blend)
where TPixel : unmanaged, IPixel<TPixel>
{
// Trim the destination frame to match the restore area. The source frame is already trimmed.
Buffer2DRegion<TPixel> imageFramePixels = imageFrame.PixelBuffer.GetRegion(restoreArea);
int decodedRowIdx = 0;
for (int y = 0; y < restoreArea.Height; y++)
if (blend)
{
Span<TPixel> framePixelRow = imageFramePixels.DangerousGetRowSpan(y);
Span<TPixel> decodedPixelRow = decodedImage.DangerousGetRowSpan(decodedRowIdx++)[..restoreArea.Width];
decodedPixelRow.TryCopyTo(framePixelRow);
// The destination frame has already been prepopulated with the pixel data from the previous frame
// so blending will leave the desired result which takes into consideration restoration to the
// background color within the restore area.
PixelBlender<TPixel> blender =
PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
for (int y = 0; y < restoreArea.Height; y++)
{
Span<TPixel> framePixelRow = imageFramePixels.DangerousGetRowSpan(y);
Span<TPixel> decodedPixelRow = decodedImageFrame.DangerousGetRowSpan(y)[..restoreArea.Width];
blender.Blend<TPixel>(imageFrame.Configuration, framePixelRow, framePixelRow, decodedPixelRow, 1f);
}
return;
}
}
/// <summary>
/// After disposing of the previous frame, render the current frame on the canvas using alpha-blending.
/// If the current frame does not have an alpha channel, assume alpha value of 255, effectively replacing the rectangle.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="src">The source image.</param>
/// <param name="dst">The destination image.</param>
/// <param name="restoreArea">The area of the frame.</param>
private void AlphaBlend<TPixel>(ImageFrame<TPixel> src, ImageFrame<TPixel> dst, Rectangle restoreArea)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2DRegion<TPixel> srcPixels = src.PixelBuffer.GetRegion(restoreArea);
Buffer2DRegion<TPixel> dstPixels = dst.PixelBuffer.GetRegion(restoreArea);
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
for (int y = 0; y < restoreArea.Height; y++)
{
Span<TPixel> srcPixelRow = srcPixels.DangerousGetRowSpan(y);
Span<TPixel> dstPixelRow = dstPixels.DangerousGetRowSpan(y);
blender.Blend<TPixel>(this.configuration, dstPixelRow, srcPixelRow, dstPixelRow, 1f);
Span<TPixel> framePixelRow = imageFramePixels.DangerousGetRowSpan(y);
Span<TPixel> decodedPixelRow = decodedImageFrame.DangerousGetRowSpan(y)[..restoreArea.Width];
decodedPixelRow.CopyTo(framePixelRow);
}
}

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

@ -357,6 +357,16 @@ public class WebpDecoderTests
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
[WithFile(Lossy.AnimatedLandscape, PixelTypes.Rgba32)]
public void Decode_AnimatedLossy_AlphaBlending_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(WebpDecoder.Instance);
image.DebugSaveMultiFrame(provider);
image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact);
}
[Theory]
[WithFile(Lossless.LossLessCorruptImage1, PixelTypes.Rgba32)]
[WithFile(Lossless.LossLessCorruptImage2, PixelTypes.Rgba32)]

1
tests/ImageSharp.Tests/TestImages.cs

@ -681,6 +681,7 @@ public static class TestImages
public static class Lossy
{
public const string AnimatedLandscape = "Webp/landscape.webp";
public const string Earth = "Webp/earth_lossy.webp";
public const string WithExif = "Webp/exif_lossy.webp";
public const string WithExifNotEnoughData = "Webp/exif_lossy_not_enough_data.webp";

3
tests/Images/Input/Webp/landscape.webp

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