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 System;
using SixLabors.ImageSharp.Formats.WebP.Filters; using SixLabors.ImageSharp.Formats.WebP.Filters;
using SixLabors.ImageSharp.Processing; using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.WebP namespace SixLabors.ImageSharp.Formats.WebP
{ {
internal class AlphaDecoder 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; } private int PreProcessing { get; }
@ -24,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
private byte[] Data { get; } private byte[] Data { get; }
private Vp8LDecoder Vp8LDec { get; set; } private WebPLosslessDecoder LosslessDecoder { get; }
/// <summary> /// <summary>
/// Although Alpha Channel requires only 1 byte per pixel, /// Although Alpha Channel requires only 1 byte per pixel,
@ -33,14 +41,15 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary> /// </summary>
public bool Use8BDecode { get; set; } 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.Width = width;
this.Height = height; this.Height = height;
this.Data = data; this.Data = data;
this.LastRow = 0;
// Compression method: Either 0 (no compression) or 1 (Compressed using the WebP lossless format) // 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) if (method < 0 || method > 1)
{ {
WebPThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {method} found"); WebPThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {method} found");
@ -49,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.Compressed = !(method is 0); this.Compressed = !(method is 0);
// The filtering method used. Only values between 0 and 3 are valid. // 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) if (filter < 0 || filter > 3)
{ {
WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); 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. // 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. // 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 // 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; } private int PrevLineOffset { get; set; }
public void Decode(Vp8Decoder dec, Span<byte> dst) public void Decode()
{ {
if (this.Compressed is false) 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 outputOffset,
int stride) int stride)
{ {
if (!(this.Filter is WebPFilterNone)) if (this.Filter is WebPFilterNone)
{ {
int prevLineOffset = this.PrevLineOffset; return;
}
for (int y = firstRow; y < lastRow; y++) int prevLineOffset = this.PrevLineOffset;
{
this.Filter for (int y = firstRow; y < lastRow; y++)
.Unfilter( {
prevLine, this.Filter
prevLineOffset, .Unfilter(
output, prevLine,
outputOffset, prevLineOffset,
output, output,
outputOffset, outputOffset,
stride); output,
prevLineOffset = outputOffset; outputOffset,
outputOffset += stride; stride);
} prevLineOffset = outputOffset;
outputOffset += stride;
} }
} }
} }

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

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

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

@ -5,7 +5,7 @@ using System;
namespace SixLabors.ImageSharp.Formats.WebP.Filters namespace SixLabors.ImageSharp.Formats.WebP.Filters
{ {
class WebPFilterHorizontal : WebPFilterBase internal class WebPFilterHorizontal : WebPFilterBase
{ {
public override void Unfilter( public override void Unfilter(
Span<byte> prevLine, Span<byte> prevLine,
@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters
predsOffset = inputOffset; predsOffset = inputOffset;
} }
if (row == 0) if (row is 0)
{ {
// leftmost pixel is the same as Input for topmost scanline // leftmost pixel is the same as Input for topmost scanline
output[0] = input[0]; 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.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.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.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++) for (int i = 0; i < this.YuvBuffer.Length; i++)
{ {
@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
public byte[] TmpVBuffer { get; } public byte[] TmpVBuffer { get; }
public byte[] Bgr { get; } public byte[] Pixels { get; }
/// <summary> /// <summary>
/// Gets or sets filter strength info. /// Gets or sets filter strength info.

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

@ -61,7 +61,29 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary> /// <summary>
/// True if a bit was read past the end of buffer. /// True if a bit was read past the end of buffer.
/// </summary> /// </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> /// <summary>
/// Initializes a new instance of the <see cref="Vp8LBitReader"/> class. /// Initializes a new instance of the <see cref="Vp8LBitReader"/> class.
@ -78,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.len = length; this.len = length;
this.value = 0; this.value = 0;
this.bitPos = 0; this.bitPos = 0;
this.eos = false; this.Eos = false;
if (length > sizeof(long)) if (length > sizeof(long))
{ {
@ -104,7 +126,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
{ {
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
if (!this.eos && nBits <= Vp8LMaxNumBitRead) if (!this.Eos && nBits <= Vp8LMaxNumBitRead)
{ {
ulong val = this.PrefetchBits() & this.bitMask[nBits]; ulong val = this.PrefetchBits() & this.bitMask[nBits];
int newBits = this.bitPos + nBits; int newBits = this.bitPos + nBits;
@ -139,7 +161,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary> /// <summary>
/// Return the pre-fetched bits, so they can be looked up. /// Return the pre-fetched bits, so they can be looked up.
/// </summary> /// </summary>
/// <returns>Prefetched bits.</returns> /// <returns>The pre-fetched bits.</returns>
public ulong PrefetchBits() public ulong PrefetchBits()
{ {
return this.value >> (this.bitPos & (Vp8LLbits - 1)); 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> /// <returns>True, if end of buffer was reached.</returns>
public bool IsEndOfStream() 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() private void DoFillBitWindow()
@ -191,7 +213,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
private void SetEndOfStream() private void SetEndOfStream()
{ {
this.eos = true; this.Eos = true;
this.bitPos = 0; // To avoid undefined behaviour with shifts. 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.WebP namespace SixLabors.ImageSharp.Formats.WebP
{ {
/// <summary> /// <summary>
/// Holds information for decoding a lossless webp image. /// Holds information for decoding a lossless webp image.
/// </summary> /// </summary>
internal class Vp8LDecoder internal class Vp8LDecoder : IDisposable
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Vp8LDecoder"/> class. /// Initializes a new instance of the <see cref="Vp8LDecoder"/> class.
/// </summary> /// </summary>
/// <param name="width">The width of the image.</param> /// <param name="width">The width of the image.</param>
/// <param name="height">The height 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.Width = width;
this.Height = height; this.Height = height;
this.Metadata = new Vp8LMetadata(); this.Metadata = new Vp8LMetadata();
this.Pixels = memoryAllocator.Allocate<uint>(width * height, AllocationOptions.Clean);
} }
/// <summary> /// <summary>
@ -41,5 +47,22 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// Gets or sets the transformations which needs to be reversed. /// Gets or sets the transformations which needs to be reversed.
/// </summary> /// </summary>
public List<Vp8LTransform> Transforms { get; set; } 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[] alphaData = null;
byte alphaChunkHeader = 0;
if (isAlphaPresent) if (isAlphaPresent)
{ {
chunkType = this.ReadChunkType(); chunkType = this.ReadChunkType();
@ -251,8 +252,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
} }
uint alphaChunkSize = this.ReadChunkSize(); uint alphaChunkSize = this.ReadChunkSize();
alphaData = new byte[alphaChunkSize]; alphaChunkHeader = (byte)this.currentStream.ReadByte();
this.currentStream.Read(alphaData, 0, (int)alphaChunkSize); alphaData = new byte[alphaChunkSize - 1];
this.currentStream.Read(alphaData, 0, alphaData.Length);
} }
var features = new WebPFeatures() var features = new WebPFeatures()
@ -260,6 +262,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
Animation = isAnimationPresent, Animation = isAnimationPresent,
Alpha = isAlphaPresent, Alpha = isAlphaPresent,
AlphaData = alphaData, AlphaData = alphaData,
AlphaChunkHeader = alphaChunkHeader,
ExifProfile = isExifPresent, ExifProfile = isExifPresent,
IccProfile = isIccPresent, IccProfile = isIccPresent,
XmpMetaData = isXmpPresent XmpMetaData = isXmpPresent

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

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

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

@ -5,7 +5,9 @@ using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.WebP.Filters;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -25,6 +27,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
private static readonly int BitsSpecialMarker = 0x100; private static readonly int BitsSpecialMarker = 0x100;
private static readonly int NumArgbCacheRows = 16;
private static readonly uint PackedNonLiteralCode = 0; private static readonly uint PackedNonLiteralCode = 0;
private static readonly int CodeToPlaneCodes = WebPLookupTables.CodeToPlane.Length; 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) public void Decode<TPixel>(Buffer2D<TPixel> pixels, int width, int height)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var decoder = new Vp8LDecoder(width, height); using (var decoder = new Vp8LDecoder(width, height, this.memoryAllocator))
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)
{ {
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; int numberOfTransformsPresent = 0;
if (isLevel0) if (isLevel0)
{ {
@ -111,7 +111,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
WebPThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebPConstants.MaxNumberOfTransforms} was exceeded"); 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++; numberOfTransformsPresent++;
} }
} }
@ -135,14 +140,13 @@ namespace SixLabors.ImageSharp.Formats.WebP
} }
// Read the Huffman codes (may recurse). // Read the Huffman codes (may recurse).
this.ReadHuffmanCodes(decoder, xSize, ySize, colorCacheBits, isLevel0); this.ReadHuffmanCodes(decoder, transformXSize, transformYSize, colorCacheBits, isLevel0);
decoder.Metadata.ColorCacheSize = colorCacheSize; decoder.Metadata.ColorCacheSize = colorCacheSize;
// Finish setting up the color-cache // Finish setting up the color-cache.
ColorCache colorCache = null;
if (colorCachePresent) if (colorCachePresent)
{ {
colorCache = new ColorCache(); decoder.Metadata.ColorCache = new ColorCache();
colorCacheSize = 1 << colorCacheBits; colorCacheSize = 1 << colorCacheBits;
decoder.Metadata.ColorCacheSize = colorCacheSize; decoder.Metadata.ColorCacheSize = colorCacheSize;
if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits)) if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits))
@ -150,24 +154,32 @@ namespace SixLabors.ImageSharp.Formats.WebP
WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found");
} }
colorCache.Init(colorCacheBits); decoder.Metadata.ColorCache.Init(colorCacheBits);
} }
else else
{ {
decoder.Metadata.ColorCacheSize = 0; 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); 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; 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> where TPixel : struct, IPixel<TPixel>
{ {
Span<uint> pixelData = decoder.Pixels.GetSpan();
// Apply reverse transformations, if any are present. // Apply reverse transformations, if any are present.
this.ApplyInverseTransforms(decoder, pixelData); 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 lastPixel = 0;
int width = decoder.Width; int width = decoder.Width;
@ -197,6 +209,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
int row = lastPixel / width; int row = lastPixel / width;
int col = lastPixel % width; int col = lastPixel % width;
int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes;
int colorCacheSize = decoder.Metadata.ColorCacheSize;
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 = this.GetHTreeGroupForPos(decoder.Metadata, col, row);
@ -207,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
while (decodedPixels < totalPixels) while (decodedPixels < totalPixels)
{ {
int code; int code;
if ((col & mask) == 0) if ((col & mask) is 0)
{ {
hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row);
} }
@ -621,7 +635,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
: (numColors > 4) ? 1 : (numColors > 4) ? 1
: (numColors > 2) ? 2 : (numColors > 2) ? 2
: 3; : 3;
transform.XSize = LosslessUtils.SubSampleSize(transform.XSize, bits);
transform.Bits = bits; transform.Bits = bits;
using (IMemoryOwner<uint> colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false)) 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) private void UpdateDecoder(Vp8LDecoder decoder, int width, int height)
{ {
int numBits = decoder.Metadata.HuffmanSubSampleBits; int numBits = decoder.Metadata.HuffmanSubSampleBits;
@ -752,12 +967,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
} }
/// <summary> /// <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. /// FillBitWindow(br) needs to be called at minimum every second call to ReadSymbol, in order to pre-fetch enough bits.
/// </summary> /// </summary>
private uint ReadSymbol(Span<HuffmanCode> table) 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(); uint val = (uint)this.bitReader.PrefetchBits();
Span<HuffmanCode> tableSpan = table.Slice((int)(val & HuffmanUtils.HuffmanTableMask)); Span<HuffmanCode> tableSpan = table.Slice((int)(val & HuffmanUtils.HuffmanTableMask));
int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits;
@ -832,5 +1046,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
huff.Value |= hCode.Value << shift; huff.Value |= hCode.Value << shift;
return hCode.BitsUsed; 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -66,20 +65,19 @@ namespace SixLabors.ImageSharp.Formats.WebP
// Decode image data. // Decode image data.
this.ParseFrame(decoder, io); this.ParseFrame(decoder, io);
byte[] decodedAlpha = null;
if (info.Features?.Alpha is true) if (info.Features?.Alpha is true)
{ {
var alphaDecoder = new AlphaDecoder(width, height, info.Features.AlphaData); var alphaDecoder = new AlphaDecoder(width, height, info.Features.AlphaData, info.Features.AlphaChunkHeader, this.memoryAllocator);
alphaDecoder.Decode();
// TODO: use memory allocator. this.DecodePixelValues(width, height, decoder.Pixels, pixels, alphaDecoder.Alpha);
decodedAlpha = new byte[width * height]; }
alphaDecoder.Decode(decoder, decodedAlpha.AsSpan()); 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> where TPixel : struct, IPixel<TPixel>
{ {
TPixel color = default; TPixel color = default;
@ -628,7 +626,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
private int EmitRgb(Vp8Decoder dec, Vp8Io io) private int EmitRgb(Vp8Decoder dec, Vp8Io io)
{ {
byte[] buf = dec.Bgr; byte[] buf = dec.Pixels;
int numLinesOut = io.MbH; // a priori guess. int numLinesOut = io.MbH; // a priori guess.
Span<byte> curY = io.Y; Span<byte> curY = io.Y;
Span<byte> curU = io.U; Span<byte> curU = io.U;
@ -1359,37 +1357,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
return io; 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) private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz)
{ {
nzCoeffs <<= 2; nzCoeffs <<= 2;

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

@ -169,11 +169,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP
} }
[Theory] [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.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) public void WebpDecoder_CanDecode_Lossy_WithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel> 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. // 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 Alpha4 = "WebP/lossy_alpha4.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 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