Browse Source

Fix issue with DecodeAlphaData when copying a block

pull/1552/head
Brian Popow 6 years ago
parent
commit
83d82360eb
  1. 130
      src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
  2. 1
      tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
  3. 1
      tests/ImageSharp.Tests/TestImages.cs

130
src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs

@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
ColorCache colorCache = decoder.Metadata.ColorCache; ColorCache colorCache = decoder.Metadata.ColorCache;
int colorCacheLimit = lenCodeLimit + colorCacheSize; int colorCacheLimit = lenCodeLimit + colorCacheSize;
int mask = decoder.Metadata.HuffmanMask; 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 totalPixels = width * height;
int decodedPixels = 0; int decodedPixels = 0;
@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
int code; int code;
if ((col & mask) is 0) if ((col & mask) is 0)
{ {
hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row);
} }
if (hTreeGroup[0].IsTrivialCode) if (hTreeGroup[0].IsTrivialCode)
@ -295,13 +295,13 @@ namespace SixLabors.ImageSharp.Formats.WebP
uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]);
this.bitReader.FillBitWindow(); this.bitReader.FillBitWindow();
int distCode = this.GetCopyDistance((int)distSymbol); int distCode = this.GetCopyDistance((int)distSymbol);
int dist = this.PlaneCodeToDistance(width, distCode); int dist = PlaneCodeToDistance(width, distCode);
if (this.bitReader.IsEndOfStream()) if (this.bitReader.IsEndOfStream())
{ {
break; break;
} }
this.CopyBlock(pixelData, decodedPixels, dist, length); CopyBlock(pixelData, decodedPixels, dist, length);
decodedPixels += length; decodedPixels += length;
col += length; col += length;
while (col >= width) while (col >= width)
@ -312,7 +312,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
if ((col & mask) != 0) if ((col & mask) != 0)
{ {
hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row);
} }
if (colorCache != null) if (colorCache != null)
@ -701,6 +701,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
} }
} }
/// <summary>
/// The alpha channel of a lossy webp image can be compressed using the lossless webp compression.
/// This method will undo the compression.
/// </summary>
/// <param name="dec">The alpha decoder.</param>
public void DecodeAlphaData(AlphaDecoder dec) public void DecodeAlphaData(AlphaDecoder dec)
{ {
Span<uint> pixelData = dec.Vp8LDec.Pixels.Memory.Span; Span<uint> pixelData = dec.Vp8LDec.Pixels.Memory.Span;
@ -717,13 +722,13 @@ namespace SixLabors.ImageSharp.Formats.WebP
int lastRow = height; int lastRow = height;
int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes;
int mask = hdr.HuffmanMask; 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) while (!this.bitReader.Eos && pos < last)
{ {
// Only update when changing tile. // Only update when changing tile.
if ((col & mask) is 0) if ((col & mask) is 0)
{ {
htreeGroup = this.GetHTreeGroupForPos(hdr, col, row); htreeGroup = GetHTreeGroupForPos(hdr, col, row);
} }
this.bitReader.FillBitWindow(); this.bitReader.FillBitWindow();
@ -753,10 +758,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]); int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]);
this.bitReader.FillBitWindow(); this.bitReader.FillBitWindow();
int distCode = this.GetCopyDistance(distSymbol); int distCode = this.GetCopyDistance(distSymbol);
int dist = this.PlaneCodeToDistance(width, distCode); int dist = PlaneCodeToDistance(width, distCode);
if (pos >= dist && end - pos >= length) if (pos >= dist && end - pos >= length)
{ {
data.Slice(pos - dist, length).CopyTo(data.Slice(pos)); CopyBlock8B(data, pos, dist, length);
} }
else else
{ {
@ -777,7 +782,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
if (pos < last && (col & mask) > 0) if (pos < last && (col & mask) > 0)
{ {
htreeGroup = this.GetHTreeGroupForPos(hdr, col, row); htreeGroup = GetHTreeGroupForPos(hdr, col, row);
} }
} }
else else
@ -802,14 +807,18 @@ namespace SixLabors.ImageSharp.Formats.WebP
int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow; int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow;
if (lastRow > firstRow) if (lastRow > firstRow)
{ {
// Special method for paletted alpha data. We only process the cropped area. // Special method for paletted alpha data.
Span<byte> output = dec.Alpha.Memory.Span; Span<byte> output = dec.Alpha.Memory.Span;
Span<uint> pixelData = dec.Vp8LDec.Pixels.Memory.Span; Span<uint> pixelData = dec.Vp8LDec.Pixels.Memory.Span;
Span<byte> pixelDataAsBytes = MemoryMarshal.Cast<uint, byte>(pixelData); Span<byte> pixelDataAsBytes = MemoryMarshal.Cast<uint, byte>(pixelData);
Span<byte> dst = output.Slice(dec.Width * firstRow); Span<byte> dst = output.Slice(dec.Width * firstRow);
Span<byte> input = pixelDataAsBytes.Slice(dec.Vp8LDec.Width * firstRow); Span<byte> 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]; Vp8LTransform transform = dec.Vp8LDec.Transforms[0];
this.ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst); this.ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst);
dec.AlphaApplyFilter(firstRow, lastRow, dst, dec.Width); dec.AlphaApplyFilter(firstRow, lastRow, dst, dec.Width);
@ -896,25 +905,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
return code.Value; return code.Value;
} }
private void CopyBlock(Span<uint> pixelData, int decodedPixels, int dist, int length)
{
if (dist >= length)
{
Span<uint> src = pixelData.Slice(decodedPixels - dist, length);
Span<uint> dest = pixelData.Slice(decodedPixels);
src.CopyTo(dest);
}
else
{
Span<uint> src = pixelData.Slice(decodedPixels - dist);
Span<uint> dest = pixelData.Slice(decodedPixels);
for (int i = 0; i < length; ++i)
{
dest[i] = src[i];
}
}
}
private void BuildPackedTable(HTreeGroup hTreeGroup) private void BuildPackedTable(HTreeGroup hTreeGroup)
{ {
for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; ++code) for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; ++code)
@ -931,10 +921,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
{ {
huff.BitsUsed = 0; huff.BitsUsed = 0;
huff.Value = 0; huff.Value = 0;
bits >>= this.AccumulateHCode(hCode, 8, huff); bits >>= AccumulateHCode(hCode, 8, huff);
bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, huff); bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, huff);
bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, huff); bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, huff);
bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, huff); bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, huff);
} }
} }
} }
@ -962,14 +952,34 @@ namespace SixLabors.ImageSharp.Formats.WebP
} }
[MethodImpl(InliningOptions.ShortMethod)] [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(); return metadata.HTreeGroups.AsSpan((int)metaIndex).ToArray();
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private uint GetMetaIndex(IMemoryOwner<uint> huffmanImage, int xSize, int bits, int x, int y) private static uint GetMetaIndex(IMemoryOwner<uint> huffmanImage, int xSize, int bits, int x, int y)
{ {
if (bits is 0) if (bits is 0)
{ {
@ -980,7 +990,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; 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) if (planeCode > CodeToPlaneCodes)
{ {
@ -996,28 +1006,44 @@ namespace SixLabors.ImageSharp.Formats.WebP
return (dist >= 1) ? dist : 1; return (dist >= 1) ? dist : 1;
} }
private int GetCopyDistance(int distanceSymbol) private static void CopyBlock(Span<uint> pixelData, int decodedPixels, int dist, int length)
{ {
if (distanceSymbol < 4) if (dist >= length)
{ {
return distanceSymbol + 1; Span<uint> src = pixelData.Slice(decodedPixels - dist, length);
Span<uint> dest = pixelData.Slice(decodedPixels);
src.CopyTo(dest);
}
else
{
Span<uint> src = pixelData.Slice(decodedPixels - dist);
Span<uint> 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 static void CopyBlock8B(Span<byte> data, int pos, int dist, int length)
private int GetCopyLength(int lengthSymbol)
{ {
// Length and distance prefixes are encoded the same way. if (dist >= length)
return this.GetCopyDistance(lengthSymbol); {
data.Slice(pos - dist, length).CopyTo(data.Slice(pos));
}
else
{
Span<byte> dst = data.Slice(pos);
Span<byte> src = data.Slice(pos - dist);
for (int i = 0; i < length; ++i)
{
dst[i] = src[i];
}
}
} }
[MethodImpl(InliningOptions.ShortMethod)] [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.BitsUsed += hCode.BitsUsed;
huff.Value |= hCode.Value << shift; huff.Value |= hCode.Value << shift;

1
tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs

@ -178,6 +178,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP
[WithFile(Lossy.AlphaCompressedHorizontalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaCompressedHorizontalFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.Alpha1, PixelTypes.Rgba32)]
public void WebpDecoder_CanDecode_Lossy_WithAlpha<TPixel>(TestImageProvider<TPixel> provider) public void WebpDecoder_CanDecode_Lossy_WithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {

1
tests/ImageSharp.Tests/TestImages.cs

@ -524,6 +524,7 @@ namespace SixLabors.ImageSharp.Tests
// Lossy images with an alpha channel. // Lossy images with an alpha channel.
public const string Alpha1 = "WebP/lossy_alpha1.webp"; public const string Alpha1 = "WebP/lossy_alpha1.webp";
public const string Alpha2 = "WebP/lossy_alpha2.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 AlphaNoCompression = "WebP/alpha_no_compression.webp";
public const string AlphaNoCompressionNoFilter = "WebP/alpha_filter_0_method_0.webp"; public const string AlphaNoCompressionNoFilter = "WebP/alpha_filter_0_method_0.webp";
public const string AlphaCompressedNoFilter = "WebP/alpha_filter_0_method_1.webp"; public const string AlphaCompressedNoFilter = "WebP/alpha_filter_0_method_1.webp";

Loading…
Cancel
Save