From 83d82360eb33c3e32c16123af3944b9fe11348df Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 19 Mar 2020 14:19:18 +0100 Subject: [PATCH] Fix issue with DecodeAlphaData when copying a block --- .../Formats/WebP/WebPLosslessDecoder.cs | 130 +++++++++++------- .../Formats/WebP/WebPDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + 3 files changed, 80 insertions(+), 52 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index c25d11f39..6783c3f58 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp.Formats.WebP ColorCache colorCache = decoder.Metadata.ColorCache; int colorCacheLimit = lenCodeLimit + colorCacheSize; int mask = decoder.Metadata.HuffmanMask; - HTreeGroup[] hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); + HTreeGroup[] hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); int totalPixels = width * height; int decodedPixels = 0; @@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int code; if ((col & mask) is 0) { - hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); + hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); } if (hTreeGroup[0].IsTrivialCode) @@ -295,13 +295,13 @@ namespace SixLabors.ImageSharp.Formats.WebP uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); this.bitReader.FillBitWindow(); int distCode = this.GetCopyDistance((int)distSymbol); - int dist = this.PlaneCodeToDistance(width, distCode); + int dist = PlaneCodeToDistance(width, distCode); if (this.bitReader.IsEndOfStream()) { break; } - this.CopyBlock(pixelData, decodedPixels, dist, length); + CopyBlock(pixelData, decodedPixels, dist, length); decodedPixels += length; col += length; while (col >= width) @@ -312,7 +312,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if ((col & mask) != 0) { - hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); + hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); } if (colorCache != null) @@ -701,6 +701,11 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// The alpha channel of a lossy webp image can be compressed using the lossless webp compression. + /// This method will undo the compression. + /// + /// The alpha decoder. public void DecodeAlphaData(AlphaDecoder dec) { Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; @@ -717,13 +722,13 @@ namespace SixLabors.ImageSharp.Formats.WebP int lastRow = height; int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; int mask = hdr.HuffmanMask; - HTreeGroup[] htreeGroup = (pos < last) ? this.GetHTreeGroupForPos(hdr, col, row) : null; + HTreeGroup[] htreeGroup = (pos < last) ? GetHTreeGroupForPos(hdr, col, row) : null; while (!this.bitReader.Eos && pos < last) { // Only update when changing tile. if ((col & mask) is 0) { - htreeGroup = this.GetHTreeGroupForPos(hdr, col, row); + htreeGroup = GetHTreeGroupForPos(hdr, col, row); } this.bitReader.FillBitWindow(); @@ -753,10 +758,10 @@ namespace SixLabors.ImageSharp.Formats.WebP int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]); this.bitReader.FillBitWindow(); int distCode = this.GetCopyDistance(distSymbol); - int dist = this.PlaneCodeToDistance(width, distCode); + int dist = PlaneCodeToDistance(width, distCode); if (pos >= dist && end - pos >= length) { - data.Slice(pos - dist, length).CopyTo(data.Slice(pos)); + CopyBlock8B(data, pos, dist, length); } else { @@ -777,7 +782,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (pos < last && (col & mask) > 0) { - htreeGroup = this.GetHTreeGroupForPos(hdr, col, row); + htreeGroup = GetHTreeGroupForPos(hdr, col, row); } } else @@ -802,14 +807,18 @@ namespace SixLabors.ImageSharp.Formats.WebP int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow; if (lastRow > firstRow) { - // Special method for paletted alpha data. We only process the cropped area. + // Special method for paletted alpha data. Span output = dec.Alpha.Memory.Span; Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); Span dst = output.Slice(dec.Width * firstRow); Span input = pixelDataAsBytes.Slice(dec.Vp8LDec.Width * firstRow); - // TODO: check if any and the correct transform is present + if (dec.Vp8LDec.Transforms.Count is 0 || dec.Vp8LDec.Transforms[0].TransformType != Vp8LTransformType.ColorIndexingTransform) + { + WebPThrowHelper.ThrowImageFormatException("error while decoding alpha channel, expected color index transform data is missing"); + } + Vp8LTransform transform = dec.Vp8LDec.Transforms[0]; this.ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst); dec.AlphaApplyFilter(firstRow, lastRow, dst, dec.Width); @@ -896,25 +905,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return code.Value; } - private void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) - { - if (dist >= length) - { - Span src = pixelData.Slice(decodedPixels - dist, length); - Span dest = pixelData.Slice(decodedPixels); - src.CopyTo(dest); - } - else - { - Span src = pixelData.Slice(decodedPixels - dist); - Span dest = pixelData.Slice(decodedPixels); - for (int i = 0; i < length; ++i) - { - dest[i] = src[i]; - } - } - } - private void BuildPackedTable(HTreeGroup hTreeGroup) { for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; ++code) @@ -931,10 +921,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { huff.BitsUsed = 0; huff.Value = 0; - bits >>= this.AccumulateHCode(hCode, 8, huff); - bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, huff); - bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, huff); - bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, huff); + bits >>= AccumulateHCode(hCode, 8, huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, huff); } } } @@ -962,14 +952,34 @@ namespace SixLabors.ImageSharp.Formats.WebP } [MethodImpl(InliningOptions.ShortMethod)] - private HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) + private int GetCopyLength(int lengthSymbol) + { + // Length and distance prefixes are encoded the same way. + return this.GetCopyDistance(lengthSymbol); + } + + private int GetCopyDistance(int distanceSymbol) { - uint metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); + if (distanceSymbol < 4) + { + return distanceSymbol + 1; + } + + int extraBits = (distanceSymbol - 2) >> 1; + int offset = (2 + (distanceSymbol & 1)) << extraBits; + + return (int)(offset + this.bitReader.ReadValue(extraBits) + 1); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) + { + uint metaIndex = GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); return metadata.HTreeGroups.AsSpan((int)metaIndex).ToArray(); } [MethodImpl(InliningOptions.ShortMethod)] - private uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) + private static uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) { if (bits is 0) { @@ -980,7 +990,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; } - private int PlaneCodeToDistance(int xSize, int planeCode) + private static int PlaneCodeToDistance(int xSize, int planeCode) { if (planeCode > CodeToPlaneCodes) { @@ -996,28 +1006,44 @@ namespace SixLabors.ImageSharp.Formats.WebP return (dist >= 1) ? dist : 1; } - private int GetCopyDistance(int distanceSymbol) + private static void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) { - if (distanceSymbol < 4) + if (dist >= length) { - return distanceSymbol + 1; + Span src = pixelData.Slice(decodedPixels - dist, length); + Span dest = pixelData.Slice(decodedPixels); + src.CopyTo(dest); + } + else + { + Span src = pixelData.Slice(decodedPixels - dist); + Span dest = pixelData.Slice(decodedPixels); + for (int i = 0; i < length; ++i) + { + dest[i] = src[i]; + } } - - int extraBits = (distanceSymbol - 2) >> 1; - int offset = (2 + (distanceSymbol & 1)) << extraBits; - - return (int)(offset + this.bitReader.ReadValue(extraBits) + 1); } - [MethodImpl(InliningOptions.ShortMethod)] - private int GetCopyLength(int lengthSymbol) + private static void CopyBlock8B(Span data, int pos, int dist, int length) { - // Length and distance prefixes are encoded the same way. - return this.GetCopyDistance(lengthSymbol); + if (dist >= length) + { + data.Slice(pos - dist, length).CopyTo(data.Slice(pos)); + } + else + { + Span dst = data.Slice(pos); + Span src = data.Slice(pos - dist); + for (int i = 0; i < length; ++i) + { + dst[i] = src[i]; + } + } } [MethodImpl(InliningOptions.ShortMethod)] - private int AccumulateHCode(HuffmanCode hCode, int shift, HuffmanCode huff) + private static int AccumulateHCode(HuffmanCode hCode, int shift, HuffmanCode huff) { huff.BitsUsed += hCode.BitsUsed; huff.Value |= hCode.Value << shift; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index f2c2d9c2d..60ff49049 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -178,6 +178,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.AlphaCompressedHorizontalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha1, 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 e57d26817..7c2f4ebbf 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -524,6 +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 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";