diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index bf382c425..cf92e3876 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -95,18 +95,33 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); } - switch (this.FilterType) + if (this.FilterType == WebPFilterType.None) { - case WebPFilterType.None: - this.Data.AsSpan(0, this.Width * this.Height).CopyTo(this.Alpha); - break; - case WebPFilterType.Horizontal: - - break; - case WebPFilterType.Vertical: - break; - case WebPFilterType.Gradient: - break; + this.Data.AsSpan(0, this.Width * this.Height).CopyTo(this.Alpha); + return; + } + + Span deltas = this.Data.AsSpan(); + Span dst = this.Alpha.AsSpan(); + Span prev = null; + for (int y = 0; y < this.Height; ++y) + { + switch (this.FilterType) + { + case WebPFilterType.Horizontal: + HorizontalUnfilter(prev, deltas, dst, this.Width); + break; + case WebPFilterType.Vertical: + VerticalUnfilter(prev, deltas, dst, this.Width); + break; + case WebPFilterType.Gradient: + GradientUnfilter(prev, deltas, dst, this.Width); + break; + } + + prev = dst; + deltas = deltas.Slice(this.Width); + dst = dst.Slice(this.Width); } } else @@ -115,6 +130,59 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) + { + byte pred = (byte)(prev == null ? 0 : prev[0]); + + for (int i = 0; i < width; ++i) + { + dst[i] = (byte)(pred + input[i]); + pred = dst[i]; + } + } + + private static void VerticalUnfilter(Span prev, Span input, Span dst, int width) + { + if (prev == null) + { + HorizontalUnfilter(null, input, dst, width); + } + else + { + for (int i = 0; i < width; ++i) + { + dst[i] = (byte)(prev[i] + input[i]); + } + } + } + + private static void GradientUnfilter(Span prev, Span input, Span dst, int width) + { + if (prev == null) + { + HorizontalUnfilter(null, input, dst, width); + } + else + { + byte top = prev[0]; + byte topLeft = top; + byte left = top; + for (int i = 0; i < width; ++i) + { + top = prev[i]; + left = (byte)(input[i] + GradientPredictor(left, top, topLeft)); + topLeft = top; + dst[i] = left; + } + } + } + + private static int GradientPredictor(byte a, byte b, byte c) + { + int g = a + b - c; + return ((g & ~0xff) is 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit + } + // Taken from vp8l_dec.c AlphaApplyFilter public void AlphaApplyFilter( int firstRow, diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 1d9c41660..5041f35ae 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -173,6 +173,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.AlphaNoCompressionNoFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaCompressedNoFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaNoCompressionHorizontalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaNoCompressionVerticalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaNoCompressionGradientFilter, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : struct, IPixel {