diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs
index f6ee5ec16..bf382c425 100644
--- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs
+++ b/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; }
///
/// Although Alpha Channel requires only 1 byte per pixel,
@@ -33,14 +41,15 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
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 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;
}
}
}
diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs
index 4ff2ae568..74b69f43a 100644
--- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs
+++ b/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
{
///
///
diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs
index 4328332a5..fdec37e5d 100644
--- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs
+++ b/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 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];
diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs
index 7dab51e33..8de3144f6 100644
--- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs
+++ b/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; }
///
/// Gets or sets filter strength info.
diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
index 9dba6c91d..f4114f5be 100644
--- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
+++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
@@ -61,7 +61,29 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
/// True if a bit was read past the end of buffer.
///
- private bool eos;
+ public bool Eos;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Lossless compressed image data.
+ 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;
+ }
///
/// Initializes a new instance of the 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
///
/// Return the pre-fetched bits, so they can be looked up.
///
- /// Prefetched bits.
+ /// The pre-fetched bits.
public ulong PrefetchBits()
{
return this.value >> (this.bitPos & (Vp8LLbits - 1));
@@ -162,7 +184,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// True, if end of buffer was reached.
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.
}
}
diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs
index bf8d949b2..968d98b9d 100644
--- a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs
+++ b/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
{
///
/// Holds information for decoding a lossless webp image.
///
- internal class Vp8LDecoder
+ internal class Vp8LDecoder : IDisposable
{
///
/// Initializes a new instance of the class.
///
/// The width of the image.
/// The height of the image.
- public Vp8LDecoder(int width, int height)
+ /// Used for allocating memory for the pixel data output.
+ public Vp8LDecoder(int width, int height, MemoryAllocator memoryAllocator)
{
this.Width = width;
this.Height = height;
this.Metadata = new Vp8LMetadata();
+ this.Pixels = memoryAllocator.Allocate(width * height, AllocationOptions.Clean);
}
///
@@ -41,5 +47,22 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// Gets or sets the transformations which needs to be reversed.
///
public List Transforms { get; set; }
+
+ ///
+ /// Gets the pixel data.
+ ///
+ public IMemoryOwner Pixels { get; }
+
+ ///
+ public void Dispose()
+ {
+ this.Pixels.Dispose();
+ foreach (Vp8LTransform transform in this.Transforms)
+ {
+ transform.Data?.Dispose();
+ }
+
+ this.Metadata?.HuffmanImage?.Dispose();
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
index f55bc0ed0..a9312f19c 100644
--- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
+++ b/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
diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs
index f0d728402..3fd035076 100644
--- a/src/ImageSharp/Formats/WebP/WebPFeatures.cs
+++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs
@@ -23,6 +23,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
public byte[] AlphaData { get; set; }
+ ///
+ /// Gets or sets the alpha chunk header.
+ ///
+ public byte AlphaChunkHeader { get; set; }
+
///
/// Gets or sets a value indicating whether this image has a EXIF Profile.
///
diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
index 98fe46cbc..b64cc0a17 100644
--- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
+++ b/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(Buffer2D pixels, int width, int height)
where TPixel : struct, IPixel
{
- var decoder = new Vp8LDecoder(width, height);
- IMemoryOwner 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 DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0)
+ public IMemoryOwner 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 pixelData = this.memoryAllocator.Allocate(decoder.Width * decoder.Height, AllocationOptions.Clean);
- this.DecodeImageData(decoder, pixelData.GetSpan(), colorCacheSize, colorCache);
+ this.DecodeImageData(decoder, pixelData.GetSpan());
return pixelData;
}
- private void DecodePixelValues(Vp8LDecoder decoder, Span pixelData, Buffer2D pixels)
+ private void DecodePixelValues(Vp8LDecoder decoder, Buffer2D pixels)
where TPixel : struct, IPixel
{
+ Span 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 pixelData, int colorCacheSize, ColorCache colorCache)
+ private void DecodeImageData(Vp8LDecoder decoder, Span 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 colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false))
{
@@ -683,6 +696,208 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
+ public void DecodeAlphaData(AlphaDecoder dec)
+ {
+ Span pixelData = dec.Vp8LDec.Pixels.Memory.Span;
+ Span data = MemoryMarshal.Cast(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 output = dec.Alpha.AsSpan();
+ 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
+ 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 src,
+ Span dst)
+ {
+ int bitsPerPixel = 8 >> transform.Bits;
+ int width = transform.XSize;
+ Span 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 src, Span colorMap, Span 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 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
}
///
- /// 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.
///
private uint ReadSymbol(Span table)
{
- // TODO: if the bitReader field is moved to this base class we could omit the parameter.
uint val = (uint)this.bitReader.PrefetchBits();
Span 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);
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
index 34ddade1e..9c906a828 100644
--- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
+++ b/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(int width, int height, Span pixelData, byte[] alpha, Buffer2D pixels)
+ private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels, byte[] alpha = null)
where TPixel : struct, IPixel
{
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 curY = io.Y;
Span 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 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;
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
index a6898093a..1d9c41660 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
+++ b/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(TestImageProvider provider)
where TPixel : struct, IPixel
{
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index e892d87af..32ee475a7 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/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";
}
}
}