From 45bf1064e34a575d77ddbfafc6d7ffb935d172d7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 21 Mar 2020 13:03:07 +0100 Subject: [PATCH] alpha_color_cache.webp now decodes correctly --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 64 ++++++++++++++++--- src/ImageSharp/Formats/WebP/WebPConstants.cs | 2 + .../Formats/WebP/WebPLosslessDecoder.cs | 19 +++--- .../Formats/WebP/WebPDecoderTests.cs | 2 + tests/ImageSharp.Tests/TestImages.cs | 2 +- 5 files changed, 70 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 9d0b68e79..0163d74c0 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.WebP @@ -14,6 +15,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class AlphaDecoder : IDisposable { + private readonly MemoryAllocator memoryAllocator; + /// /// Initializes a new instance of the class. /// @@ -28,7 +31,9 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Width = width; this.Height = height; this.Data = data; + this.memoryAllocator = memoryAllocator; this.LastRow = 0; + int totalPixels = width * height; var compression = (AlphaCompressionMethod)(alphaChunkHeader & 0x03); if (compression != AlphaCompressionMethod.NoCompression && compression != AlphaCompressionMethod.WebPLosslessCompression) @@ -45,8 +50,8 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); } + this.Alpha = memoryAllocator.Allocate(totalPixels); this.AlphaFilterType = (WebPAlphaFilterType)filter; - this.Alpha = memoryAllocator.Allocate(width * height); this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); if (this.Compressed) @@ -54,6 +59,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var bitReader = new Vp8LBitReader(data); this.LosslessDecoder = new WebPLosslessDecoder(bitReader, memoryAllocator, configuration); this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); + this.Use8BDecode = Is8BOptimizable(this.Vp8LDec.Metadata); } } @@ -110,11 +116,11 @@ namespace SixLabors.ImageSharp.Formats.WebP private WebPLosslessDecoder LosslessDecoder { get; } /// - /// Gets or sets a value indicating whether the decoding needs 1 byte per pixel for decoding. + /// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding. /// Although Alpha Channel requires only 1 byte per pixel, sometimes Vp8LDecoder may need to allocate /// 4 bytes per pixel internally during decode. /// - public bool Use8BDecode { get; set; } + public bool Use8BDecode { get; } /// /// Decodes and filters the maybe compressed alpha data. @@ -162,7 +168,15 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - this.LosslessDecoder.DecodeAlphaData(this); + if (this.Use8BDecode) + { + this.LosslessDecoder.DecodeAlphaData(this); + } + else + { + this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span); + this.ExtractAlphaRows(this.Vp8LDec); + } } } @@ -204,6 +218,25 @@ namespace SixLabors.ImageSharp.Formats.WebP this.PrevRow = lastRow - 1; } + /// + /// Once the image-stream is decoded into ARGB color values, the transparency information will be extracted from the green channel of the ARGB quadruplet. + /// + /// The VP8L decoder. + private void ExtractAlphaRows(Vp8LDecoder dec) + { + int numRowsToProcess = dec.Height; + int width = dec.Width; + Span pixels = dec.Pixels.Memory.Span; + Span input = pixels; + Span output = this.Alpha.Memory.Span; + + // Extract alpha (which is stored in the green plane). + int pixelCount = width * numRowsToProcess; + WebPLosslessDecoder.ApplyInverseTransforms(dec, input, this.memoryAllocator); + this.AlphaApplyFilter(0, numRowsToProcess, output, width); + ExtractGreen(input, output, pixelCount); + } + private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) { byte pred = (byte)(prev == null ? 0 : prev[0]); @@ -252,7 +285,13 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static bool Is8bOptimizable(Vp8LMetadata hdr) + /// + /// Row-processing for the special case when alpha data contains only one + /// transform (color indexing), and trivial non-green literals. + /// + /// The VP8L meta data. + /// True, if alpha channel has one byte per pixel, otherwise 4. + private static bool Is8BOptimizable(Vp8LMetadata hdr) { if (hdr.ColorCacheSize > 0) { @@ -264,17 +303,17 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < hdr.NumHTreeGroups; ++i) { List htrees = hdr.HTreeGroups[i].HTrees; - if (htrees[HuffIndex.Red][0].Value > 0) + if (htrees[HuffIndex.Red][0].BitsUsed > 0) { return false; } - if (htrees[HuffIndex.Blue][0].Value > 0) + if (htrees[HuffIndex.Blue][0].BitsUsed > 0) { return false; } - if (htrees[HuffIndex.Alpha][0].Value > 0) + if (htrees[HuffIndex.Alpha][0].BitsUsed > 0) { return false; } @@ -290,6 +329,15 @@ namespace SixLabors.ImageSharp.Formats.WebP return ((g & ~0xff) is 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit } + [MethodImpl(InliningOptions.ShortMethod)] + private static void ExtractGreen(Span argb, Span alpha, int size) + { + for (int i = 0; i < size; ++i) + { + alpha[i] = (byte)(argb[i] >> 8); + } + } + /// public void Dispose() { diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 4908c133b..78519da82 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -85,6 +85,8 @@ namespace SixLabors.ImageSharp.Formats.WebP public const uint ArgbBlack = 0xff000000; + public const int NumArgbCacheRows = 16; + public const int NumLiteralCodes = 256; public const int NumLengthCodes = 24; diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 6783c3f58..2c0b69911 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -39,8 +39,6 @@ namespace SixLabors.ImageSharp.Formats.WebP private static readonly int BitsSpecialMarker = 0x100; - private static readonly int NumArgbCacheRows = 16; - private static readonly uint PackedNonLiteralCode = 0; private static readonly int CodeToPlaneCodes = WebPLookupTables.CodeToPlane.Length; @@ -191,7 +189,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int width = decoder.Width; // Apply reverse transformations, if any are present. - this.ApplyInverseTransforms(decoder, pixelData); + ApplyInverseTransforms(decoder, pixelData, this.memoryAllocator); Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); for (int y = 0; y < decoder.Height; y++) @@ -206,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private void DecodeImageData(Vp8LDecoder decoder, Span pixelData) + public void DecodeImageData(Vp8LDecoder decoder, Span pixelData) { int lastPixel = 0; int width = decoder.Width; @@ -673,7 +671,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// The decoder holding the transformation infos. /// The pixel data to apply the transformation. - private void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData) + /// The memory allocator is needed to allocate memory during the predictor transform. + public static void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData, MemoryAllocator memoryAllocator) { List transforms = decoder.Transforms; for (int i = transforms.Count - 1; i >= 0; i--) @@ -682,7 +681,7 @@ namespace SixLabors.ImageSharp.Formats.WebP switch (transformType) { case Vp8LTransformType.PredictorTransform: - using (IMemoryOwner output = this.memoryAllocator.Allocate(pixelData.Length, AllocationOptions.Clean)) + using (IMemoryOwner output = memoryAllocator.Allocate(pixelData.Length, AllocationOptions.Clean)) { LosslessUtils.PredictorInverseTransform(transforms[i], pixelData, output.GetSpan()); } @@ -744,7 +743,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { col = 0; ++row; - if (row <= lastRow && (row % NumArgbCacheRows is 0)) + if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows is 0)) { this.ExtractPalettedAlphaRows(dec, row); } @@ -774,7 +773,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { col -= width; ++row; - if (row <= lastRow && (row % NumArgbCacheRows is 0)) + if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows is 0)) { this.ExtractPalettedAlphaRows(dec, row); } @@ -802,8 +801,8 @@ namespace SixLabors.ImageSharp.Formats.WebP // For vertical and gradient filtering, we need to decode the part above the // cropTop row, in order to have the correct spatial predictors. int topRow = (dec.AlphaFilterType is WebPAlphaFilterType.None || dec.AlphaFilterType is WebPAlphaFilterType.Horizontal) - ? dec.CropTop - : dec.LastRow; + ? dec.CropTop + : dec.LastRow; int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow; if (lastRow > firstRow) { diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 60ff49049..4c96b1e41 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -179,6 +179,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)] [WithFile(Lossy.Alpha1, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha2, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha3, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 7c2f4ebbf..55a9e5e94 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -524,7 +524,7 @@ namespace SixLabors.ImageSharp.Tests // Lossy images with an alpha channel. public const string Alpha1 = "WebP/lossy_alpha1.webp"; public const string Alpha2 = "WebP/lossy_alpha2.webp"; - public const string Alpha3 = "WebP/lossy_alpha3.webp"; + public const string Alpha3 = "WebP/alpha_color_cache.webp"; public const string AlphaNoCompression = "WebP/alpha_no_compression.webp"; public const string AlphaNoCompressionNoFilter = "WebP/alpha_filter_0_method_0.webp"; public const string AlphaCompressedNoFilter = "WebP/alpha_filter_0_method_1.webp";