Browse Source

Start implementing compressed alpha decoding

pull/1552/head
Brian Popow 6 years ago
parent
commit
7592ebda72
  1. 100
      src/ImageSharp/Formats/WebP/AlphaDecoder.cs
  2. 2
      src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs
  3. 4
      src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs
  4. 4
      src/ImageSharp/Formats/WebP/Vp8Decoder.cs
  5. 34
      src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
  6. 27
      src/ImageSharp/Formats/WebP/Vp8LDecoder.cs
  7. 7
      src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
  8. 5
      src/ImageSharp/Formats/WebP/WebPFeatures.cs
  9. 269
      src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
  10. 51
      src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
  11. 7
      tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
  12. 11
      tests/ImageSharp.Tests/TestImages.cs

100
src/ImageSharp/Formats/WebP/AlphaDecoder.cs

@ -4,19 +4,27 @@
using System;
using SixLabors.ImageSharp.Formats.WebP.Filters;
using SixLabors.ImageSharp.Processing;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.WebP
{
internal class AlphaDecoder
{
public int Width { get; set; }
public int Width { get; }
public int Height { get; set; }
public int Height { get; }
public WebPFilterBase Filter { get; set; }
public WebPFilterBase Filter { get; }
private WebPFilterType FilterType { get; }
public WebPFilterType FilterType { get; }
public int CropTop { get; }
public int LastRow { get; set; }
public Vp8LDecoder Vp8LDec { get; }
public byte[] Alpha { get; }
private int PreProcessing { get; }
@ -24,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
private byte[] Data { get; }
private Vp8LDecoder Vp8LDec { get; set; }
private WebPLosslessDecoder LosslessDecoder { get; }
/// <summary>
/// Although Alpha Channel requires only 1 byte per pixel,
@ -33,14 +41,15 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
public bool Use8BDecode { get; set; }
public AlphaDecoder(int width, int height, byte[] data)
public AlphaDecoder(int width, int height, byte[] data, byte alphaChunkHeader, MemoryAllocator memoryAllocator)
{
this.Width = width;
this.Height = height;
this.Data = data;
this.LastRow = 0;
// Compression method: Either 0 (no compression) or 1 (Compressed using the WebP lossless format)
int method = data[0] & 0x03;
int method = alphaChunkHeader & 0x03;
if (method < 0 || method > 1)
{
WebPThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {method} found");
@ -49,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.Compressed = !(method is 0);
// The filtering method used. Only values between 0 and 3 are valid.
int filter = (data[0] >> 2) & 0x03;
int filter = (alphaChunkHeader >> 2) & 0x03;
if (filter < 0 || filter > 3)
{
WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found");
@ -60,16 +69,49 @@ namespace SixLabors.ImageSharp.Formats.WebP
// These INFORMATIVE bits are used to signal the pre-processing that has been performed during compression.
// The decoder can use this information to e.g. dither the values or smooth the gradients prior to display.
// 0: no pre-processing, 1: level reduction
this.PreProcessing = (data[0] >> 4) & 0x03;
this.PreProcessing = (alphaChunkHeader >> 4) & 0x03;
this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator);
// TODO: use memory allocator
this.Alpha = new byte[width * height];
if (this.Compressed)
{
var bitReader = new Vp8LBitReader(data);
this.LosslessDecoder = new WebPLosslessDecoder(bitReader, memoryAllocator);
this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true);
}
}
private int PrevLineOffset { get; set; }
public void Decode(Vp8Decoder dec, Span<byte> dst)
public void Decode()
{
if (this.Compressed is false)
{
this.Data.AsSpan(1, this.Width * this.Height).CopyTo(dst);
if (this.Data.Length < (this.Width * this.Height))
{
WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk");
}
switch (this.FilterType)
{
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;
}
}
else
{
this.LosslessDecoder.DecodeAlphaData(this);
}
}
@ -82,24 +124,26 @@ namespace SixLabors.ImageSharp.Formats.WebP
int outputOffset,
int stride)
{
if (!(this.Filter is WebPFilterNone))
if (this.Filter is WebPFilterNone)
{
int prevLineOffset = this.PrevLineOffset;
return;
}
for (int y = firstRow; y < lastRow; y++)
{
this.Filter
.Unfilter(
prevLine,
prevLineOffset,
output,
outputOffset,
output,
outputOffset,
stride);
prevLineOffset = outputOffset;
outputOffset += stride;
}
int prevLineOffset = this.PrevLineOffset;
for (int y = firstRow; y < lastRow; y++)
{
this.Filter
.Unfilter(
prevLine,
prevLineOffset,
output,
outputOffset,
output,
outputOffset,
stride);
prevLineOffset = outputOffset;
outputOffset += stride;
}
}
}

2
src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters
// Fast
// }
abstract class WebPFilterBase
internal abstract class WebPFilterBase
{
/// <summary>
/// </summary>

4
src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs

@ -5,7 +5,7 @@ using System;
namespace SixLabors.ImageSharp.Formats.WebP.Filters
{
class WebPFilterHorizontal : WebPFilterBase
internal class WebPFilterHorizontal : WebPFilterBase
{
public override void Unfilter(
Span<byte> prevLine,
@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters
predsOffset = inputOffset;
}
if (row == 0)
if (row is 0)
{
// leftmost pixel is the same as Input for topmost scanline
output[0] = input[0];

4
src/ImageSharp/Formats/WebP/Vp8Decoder.cs

@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.TmpYBuffer = new byte[(width * height) + extraY]; // TODO: figure out min buffer length
this.TmpUBuffer = new byte[(width * height) + extraUv]; // TODO: figure out min buffer length
this.TmpVBuffer = new byte[(width * height) + extraUv]; // TODO: figure out min buffer length
this.Bgr = new byte[width * height * 4];
this.Pixels = new byte[width * height * 4];
for (int i = 0; i < this.YuvBuffer.Length; i++)
{
@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
public byte[] TmpVBuffer { get; }
public byte[] Bgr { get; }
public byte[] Pixels { get; }
/// <summary>
/// Gets or sets filter strength info.

34
src/ImageSharp/Formats/WebP/Vp8LBitReader.cs

@ -61,7 +61,29 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary>
/// True if a bit was read past the end of buffer.
/// </summary>
private bool eos;
public bool Eos;
/// <summary>
/// Initializes a new instance of the <see cref="Vp8LBitReader"/> class.
/// </summary>
/// <param name="data">Lossless compressed image data.</param>
public Vp8LBitReader(byte[] data)
{
this.Data = data;
this.len = data.Length;
this.value = 0;
this.bitPos = 0;
this.Eos = false;
ulong currentValue = 0;
for (int i = 0; i < 8; ++i)
{
currentValue |= (ulong)this.Data[i] << (8 * i);
}
this.value = currentValue;
this.pos = 8;
}
/// <summary>
/// Initializes a new instance of the <see cref="Vp8LBitReader"/> class.
@ -78,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.len = length;
this.value = 0;
this.bitPos = 0;
this.eos = false;
this.Eos = false;
if (length > sizeof(long))
{
@ -104,7 +126,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
if (!this.eos && nBits <= Vp8LMaxNumBitRead)
if (!this.Eos && nBits <= Vp8LMaxNumBitRead)
{
ulong val = this.PrefetchBits() & this.bitMask[nBits];
int newBits = this.bitPos + nBits;
@ -139,7 +161,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary>
/// Return the pre-fetched bits, so they can be looked up.
/// </summary>
/// <returns>Prefetched bits.</returns>
/// <returns>The pre-fetched bits.</returns>
public ulong PrefetchBits()
{
return this.value >> (this.bitPos & (Vp8LLbits - 1));
@ -162,7 +184,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <returns>True, if end of buffer was reached.</returns>
public bool IsEndOfStream()
{
return this.eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits));
return this.Eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits));
}
private void DoFillBitWindow()
@ -191,7 +213,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
private void SetEndOfStream()
{
this.eos = true;
this.Eos = true;
this.bitPos = 0; // To avoid undefined behaviour with shifts.
}
}

27
src/ImageSharp/Formats/WebP/Vp8LDecoder.cs

@ -1,25 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.WebP
{
/// <summary>
/// Holds information for decoding a lossless webp image.
/// </summary>
internal class Vp8LDecoder
internal class Vp8LDecoder : IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="Vp8LDecoder"/> class.
/// </summary>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
public Vp8LDecoder(int width, int height)
/// <param name="memoryAllocator">Used for allocating memory for the pixel data output.</param>
public Vp8LDecoder(int width, int height, MemoryAllocator memoryAllocator)
{
this.Width = width;
this.Height = height;
this.Metadata = new Vp8LMetadata();
this.Pixels = memoryAllocator.Allocate<uint>(width * height, AllocationOptions.Clean);
}
/// <summary>
@ -41,5 +47,22 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// Gets or sets the transformations which needs to be reversed.
/// </summary>
public List<Vp8LTransform> Transforms { get; set; }
/// <summary>
/// Gets the pixel data.
/// </summary>
public IMemoryOwner<uint> Pixels { get; }
///<inheritdoc/>
public void Dispose()
{
this.Pixels.Dispose();
foreach (Vp8LTransform transform in this.Transforms)
{
transform.Data?.Dispose();
}
this.Metadata?.HuffmanImage?.Dispose();
}
}
}

7
src/ImageSharp/Formats/WebP/WebPDecoderCore.cs

@ -242,6 +242,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
byte[] alphaData = null;
byte alphaChunkHeader = 0;
if (isAlphaPresent)
{
chunkType = this.ReadChunkType();
@ -251,8 +252,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
uint alphaChunkSize = this.ReadChunkSize();
alphaData = new byte[alphaChunkSize];
this.currentStream.Read(alphaData, 0, (int)alphaChunkSize);
alphaChunkHeader = (byte)this.currentStream.ReadByte();
alphaData = new byte[alphaChunkSize - 1];
this.currentStream.Read(alphaData, 0, alphaData.Length);
}
var features = new WebPFeatures()
@ -260,6 +262,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
Animation = isAnimationPresent,
Alpha = isAlphaPresent,
AlphaData = alphaData,
AlphaChunkHeader = alphaChunkHeader,
ExifProfile = isExifPresent,
IccProfile = isIccPresent,
XmpMetaData = isXmpPresent

5
src/ImageSharp/Formats/WebP/WebPFeatures.cs

@ -23,6 +23,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
public byte[] AlphaData { get; set; }
/// <summary>
/// Gets or sets the alpha chunk header.
/// </summary>
public byte AlphaChunkHeader { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this image has a EXIF Profile.
/// </summary>

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

@ -5,7 +5,9 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.WebP.Filters;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@ -25,6 +27,8 @@ 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;
@ -82,22 +86,18 @@ namespace SixLabors.ImageSharp.Formats.WebP
public void Decode<TPixel>(Buffer2D<TPixel> pixels, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
var decoder = new Vp8LDecoder(width, height);
IMemoryOwner<uint> pixelData = this.DecodeImageStream(decoder, width, height, true);
this.DecodePixelValues(decoder, pixelData.GetSpan(), pixels);
// Free up allocated memory.
pixelData.Dispose();
foreach (Vp8LTransform transform in decoder.Transforms)
using (var decoder = new Vp8LDecoder(width, height, this.memoryAllocator))
{
transform.Data?.Dispose();
this.DecodeImageStream(decoder, width, height, true);
this.DecodeImageData(decoder, decoder.Pixels.Memory.Span);
this.DecodePixelValues(decoder, pixels);
}
decoder.Metadata?.HuffmanImage?.Dispose();
}
private IMemoryOwner<uint> DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0)
public IMemoryOwner<uint> DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0)
{
int transformXSize = xSize;
int transformYSize = ySize;
int numberOfTransformsPresent = 0;
if (isLevel0)
{
@ -111,7 +111,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
WebPThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebPConstants.MaxNumberOfTransforms} was exceeded");
}
this.ReadTransformation(xSize, ySize, decoder);
this.ReadTransformation(transformXSize, transformYSize, decoder);
if (decoder.Transforms[numberOfTransformsPresent].TransformType == Vp8LTransformType.ColorIndexingTransform)
{
transformXSize = LosslessUtils.SubSampleSize(transformXSize, decoder.Transforms[numberOfTransformsPresent].Bits);
}
numberOfTransformsPresent++;
}
}
@ -135,14 +140,13 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
// Read the Huffman codes (may recurse).
this.ReadHuffmanCodes(decoder, xSize, ySize, colorCacheBits, isLevel0);
this.ReadHuffmanCodes(decoder, transformXSize, transformYSize, colorCacheBits, isLevel0);
decoder.Metadata.ColorCacheSize = colorCacheSize;
// Finish setting up the color-cache
ColorCache colorCache = null;
// Finish setting up the color-cache.
if (colorCachePresent)
{
colorCache = new ColorCache();
decoder.Metadata.ColorCache = new ColorCache();
colorCacheSize = 1 << colorCacheBits;
decoder.Metadata.ColorCacheSize = colorCacheSize;
if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits))
@ -150,24 +154,32 @@ namespace SixLabors.ImageSharp.Formats.WebP
WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found");
}
colorCache.Init(colorCacheBits);
decoder.Metadata.ColorCache.Init(colorCacheBits);
}
else
{
decoder.Metadata.ColorCacheSize = 0;
}
this.UpdateDecoder(decoder, xSize, ySize);
this.UpdateDecoder(decoder, transformXSize, transformYSize);
if (isLevel0)
{
// level 0 complete.
return null;
}
// Use the Huffman trees to decode the LZ77 encoded data.
IMemoryOwner<uint> pixelData = this.memoryAllocator.Allocate<uint>(decoder.Width * decoder.Height, AllocationOptions.Clean);
this.DecodeImageData(decoder, pixelData.GetSpan(), colorCacheSize, colorCache);
this.DecodeImageData(decoder, pixelData.GetSpan());
return pixelData;
}
private void DecodePixelValues<TPixel>(Vp8LDecoder decoder, Span<uint> pixelData, Buffer2D<TPixel> pixels)
private void DecodePixelValues<TPixel>(Vp8LDecoder decoder, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
Span<uint> pixelData = decoder.Pixels.GetSpan();
// Apply reverse transformations, if any are present.
this.ApplyInverseTransforms(decoder, pixelData);
@ -189,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
private void DecodeImageData(Vp8LDecoder decoder, Span<uint> pixelData, int colorCacheSize, ColorCache colorCache)
private void DecodeImageData(Vp8LDecoder decoder, Span<uint> pixelData)
{
int lastPixel = 0;
int width = decoder.Width;
@ -197,6 +209,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
int row = lastPixel / width;
int col = lastPixel % width;
int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes;
int colorCacheSize = decoder.Metadata.ColorCacheSize;
ColorCache colorCache = decoder.Metadata.ColorCache;
int colorCacheLimit = lenCodeLimit + colorCacheSize;
int mask = decoder.Metadata.HuffmanMask;
HTreeGroup[] hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row);
@ -207,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
while (decodedPixels < totalPixels)
{
int code;
if ((col & mask) == 0)
if ((col & mask) is 0)
{
hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row);
}
@ -621,7 +635,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
: (numColors > 4) ? 1
: (numColors > 2) ? 2
: 3;
transform.XSize = LosslessUtils.SubSampleSize(transform.XSize, bits);
transform.Bits = bits;
using (IMemoryOwner<uint> colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false))
{
@ -683,6 +696,208 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
public void DecodeAlphaData(AlphaDecoder dec)
{
Span<uint> pixelData = dec.Vp8LDec.Pixels.Memory.Span;
Span<byte> data = MemoryMarshal.Cast<uint, byte>(pixelData);
int row = 0;
int col = 0;
Vp8LDecoder vp8LDec = dec.Vp8LDec;
int width = vp8LDec.Width;
int height = vp8LDec.Height;
Vp8LMetadata hdr = vp8LDec.Metadata;
int pos = 0; // Current position.
int end = width * height; // End of data.
int last = end; // Last pixel to decode.
int lastRow = height;
int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes;
int mask = hdr.HuffmanMask;
HTreeGroup[] htreeGroup = (pos < last) ? this.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);
}
this.bitReader.FillBitWindow();
int code = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Green]);
if (code < WebPConstants.NumLiteralCodes)
{
// Literal
data[pos] = (byte)code;
++pos;
++col;
if (col >= width)
{
col = 0;
++row;
if (row <= lastRow && (row % NumArgbCacheRows is 0))
{
this.ExtractPalettedAlphaRows(dec, row);
}
}
}
else if (code < lenCodeLimit)
{
// Backward reference
int lengthSym = code - WebPConstants.NumLiteralCodes;
int length = this.GetCopyLength(lengthSym);
int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]);
this.bitReader.FillBitWindow();
int distCode = this.GetCopyDistance(distSymbol);
int dist = this.PlaneCodeToDistance(width, distCode);
if (pos >= dist && end - pos >= length)
{
//CopyBlock8b(data + pos, dist, length);
}
else
{
// TODO: error?
break;
}
pos += length;
col += length;
while (col >= width)
{
col -= width;
++row;
if (row <= lastRow && (row % NumArgbCacheRows is 0))
{
this.ExtractPalettedAlphaRows(dec, row);
}
}
if (pos < last && (col & mask) > 0)
{
htreeGroup = this.GetHTreeGroupForPos(hdr, col, row);
}
}
else
{
WebPThrowHelper.ThrowImageFormatException("bitstream error while parsing alpha data");
}
this.bitReader.Eos = this.bitReader.IsEndOfStream();
}
// Process the remaining rows corresponding to last row-block.
this.ExtractPalettedAlphaRows(dec, row > lastRow ? lastRow : row);
}
private void ExtractPalettedAlphaRows(AlphaDecoder dec, int lastRow)
{
// 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.FilterType is WebPFilterType.None || dec.FilterType is WebPFilterType.Horizontal)
? dec.CropTop
: dec.LastRow;
int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow;
if (lastRow > firstRow)
{
// Special method for paletted alpha data. We only process the cropped area.
Span<byte> output = dec.Alpha.AsSpan();
Span<uint> pixelData = dec.Vp8LDec.Pixels.Memory.Span;
Span<byte> pixelDataAsBytes = MemoryMarshal.Cast<uint, byte>(pixelData);
Span<byte> dst = output.Slice(dec.Width * firstRow);
Span<byte> input = pixelDataAsBytes.Slice(dec.Vp8LDec.Width * firstRow);
// TODO: check if any and the correct transform is present
Vp8LTransform transform = dec.Vp8LDec.Transforms[0];
this.ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst);
//dec.AlphaApplyFilter(firstRow, lastRow, dst, width);
}
dec.LastRow = lastRow;
}
private void ColorIndexInverseTransformAlpha(
Vp8LTransform transform,
int yStart,
int yEnd,
Span<byte> src,
Span<byte> dst)
{
int bitsPerPixel = 8 >> transform.Bits;
int width = transform.XSize;
Span<uint> colorMap = transform.Data.Memory.Span;
int srcOffset = 0;
int dstOffset = 0;
if (bitsPerPixel < 8)
{
int pixelsPerByte = 1 << transform.Bits;
int countMask = pixelsPerByte - 1;
int bitMask = (1 << bitsPerPixel) - 1;
for (int y = yStart; y < yEnd; ++y)
{
int packedPixels = 0;
for (int x = 0; x < width; ++x)
{
if ((x & countMask) is 0)
{
packedPixels = src[srcOffset];
srcOffset++;
}
dst[dstOffset] = GetAlphaValue((int)colorMap[packedPixels & bitMask]);
dstOffset++;
packedPixels >>= bitsPerPixel;
}
}
}
else
{
MapAlpha(src, colorMap, dst, yStart, yEnd, width);
}
}
private static void MapAlpha(Span<byte> src, Span<uint> colorMap, Span<byte> dst, int yStart, int yEnd, int width)
{
int offset = 0;
for (int y = yStart; y < yEnd; ++y)
{
for (int x = 0; x < width; ++x)
{
dst[offset] = GetAlphaValue((int)colorMap[src[offset]]);
offset++;
}
}
}
private static bool Is8bOptimizable(Vp8LMetadata hdr)
{
if (hdr.ColorCacheSize > 0)
{
return false;
}
// When the Huffman tree contains only one symbol, we can skip the
// call to ReadSymbol() for red/blue/alpha channels.
for (int i = 0; i < hdr.NumHTreeGroups; ++i)
{
List<HuffmanCode[]> htrees = hdr.HTreeGroups[i].HTrees;
if (htrees[HuffIndex.Red][0].Value > 0)
{
return false;
}
if (htrees[HuffIndex.Blue][0].Value > 0)
{
return false;
}
if (htrees[HuffIndex.Alpha][0].Value > 0)
{
return false;
}
}
return true;
}
private void UpdateDecoder(Vp8LDecoder decoder, int width, int height)
{
int numBits = decoder.Metadata.HuffmanSubSampleBits;
@ -752,12 +967,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
/// <summary>
/// Decodes the next Huffman code from bit-stream.
/// Decodes the next Huffman code from the bit-stream.
/// FillBitWindow(br) needs to be called at minimum every second call to ReadSymbol, in order to pre-fetch enough bits.
/// </summary>
private uint ReadSymbol(Span<HuffmanCode> table)
{
// TODO: if the bitReader field is moved to this base class we could omit the parameter.
uint val = (uint)this.bitReader.PrefetchBits();
Span<HuffmanCode> tableSpan = table.Slice((int)(val & HuffmanUtils.HuffmanTableMask));
int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits;
@ -832,5 +1046,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
huff.Value |= hCode.Value << shift;
return hCode.BitsUsed;
}
private static byte GetAlphaValue(int val)
{
return (byte)((val >> 8) & 0xff);
}
}
}

51
src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
@ -66,20 +65,19 @@ namespace SixLabors.ImageSharp.Formats.WebP
// Decode image data.
this.ParseFrame(decoder, io);
byte[] decodedAlpha = null;
if (info.Features?.Alpha is true)
{
var alphaDecoder = new AlphaDecoder(width, height, info.Features.AlphaData);
// TODO: use memory allocator.
decodedAlpha = new byte[width * height];
alphaDecoder.Decode(decoder, decodedAlpha.AsSpan());
var alphaDecoder = new AlphaDecoder(width, height, info.Features.AlphaData, info.Features.AlphaChunkHeader, this.memoryAllocator);
alphaDecoder.Decode();
this.DecodePixelValues(width, height, decoder.Pixels, pixels, alphaDecoder.Alpha);
}
else
{
this.DecodePixelValues(width, height, decoder.Pixels, pixels);
}
this.DecodePixelValues(width, height, decoder.Bgr, decodedAlpha, pixels);
}
private void DecodePixelValues<TPixel>(int width, int height, Span<byte> pixelData, byte[] alpha, Buffer2D<TPixel> pixels)
private void DecodePixelValues<TPixel>(int width, int height, Span<byte> pixelData, Buffer2D<TPixel> pixels, byte[] alpha = null)
where TPixel : struct, IPixel<TPixel>
{
TPixel color = default;
@ -628,7 +626,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
private int EmitRgb(Vp8Decoder dec, Vp8Io io)
{
byte[] buf = dec.Bgr;
byte[] buf = dec.Pixels;
int numLinesOut = io.MbH; // a priori guess.
Span<byte> curY = io.Y;
Span<byte> curU = io.U;
@ -1359,37 +1357,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
return io;
}
private static bool Is8bOptimizable(Vp8LMetadata hdr)
{
if (hdr.ColorCacheSize > 0)
{
return false;
}
// When the Huffman tree contains only one symbol, we can skip the
// call to ReadSymbol() for red/blue/alpha channels.
for (int i = 0; i < hdr.NumHTreeGroups; ++i)
{
List<HuffmanCode[]> htrees = hdr.HTreeGroups[i].HTrees;
if (htrees[HuffIndex.Red][0].Value > 0)
{
return false;
}
if (htrees[HuffIndex.Blue][0].Value > 0)
{
return false;
}
if (htrees[HuffIndex.Alpha][0].Value > 0)
{
return false;
}
}
return true;
}
private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz)
{
nzCoeffs <<= 2;

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

@ -169,11 +169,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP
}
[Theory]
[WithFile(Lossy.Alpha1, PixelTypes.Rgba32)]
[WithFile(Lossy.Alpha2, PixelTypes.Rgba32)]
[WithFile(Lossy.Alpha3, PixelTypes.Rgba32)]
[WithFile(Lossy.Alpha4, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaNoCompression, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaNoCompressionNoFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaCompressedNoFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaNoCompressionHorizontalFilter, PixelTypes.Rgba32)]
public void WebpDecoder_CanDecode_Lossy_WithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{

11
tests/ImageSharp.Tests/TestImages.cs

@ -491,10 +491,15 @@ 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 Alpha4 = "WebP/lossy_alpha4.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";
public const string AlphaNoCompressionHorizontalFilter = "WebP/alpha_filter_1_method_0.webp";
public const string AlphaCompressedHorizontalFilter = "WebP/alpha_filter_1_method_1.webp";
public const string AlphaNoCompressionVerticalFilter = "WebP/alpha_filter_2_method_0.webp";
public const string AlphaCompressedVerticalFilter = "WebP/alpha_filter_2_method_1.webp";
public const string AlphaNoCompressionGradientFilter = "WebP/alpha_filter_3_method_0.webp";
public const string AlphaCompressedGradientFilter = "WebP/alpha_filter_3_method_1.webp";
}
}
}

Loading…
Cancel
Save