Browse Source

Add filtering for compressed alpha

pull/1552/head
Brian Popow 6 years ago
parent
commit
08c4278ba4
  1. 233
      src/ImageSharp/Formats/WebP/AlphaDecoder.cs
  2. 104
      src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs
  3. 116
      src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs
  4. 115
      src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs
  5. 33
      src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs
  6. 16
      src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs
  7. 100
      src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs
  8. 9
      src/ImageSharp/Formats/WebP/Vp8BitReader.cs
  9. 11
      src/ImageSharp/Formats/WebP/Vp8Io.cs
  10. 24
      src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
  11. 2
      src/ImageSharp/Formats/WebP/Vp8LDecoder.cs
  12. 31
      src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs
  13. 46
      src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
  14. 45
      src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
  15. 3
      tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs

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

@ -2,45 +2,26 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.WebP.Filters;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.WebP
{
internal class AlphaDecoder
/// <summary>
/// Implements decoding for lossy alpha chunks which may be compressed.
/// </summary>
internal class AlphaDecoder : IDisposable
{
public int Width { get; }
public int Height { get; }
public WebPFilterBase Filter { 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 bool Compressed { get; }
private byte[] Data { get; }
private WebPLosslessDecoder LosslessDecoder { get; }
/// <summary>
/// Although Alpha Channel requires only 1 byte per pixel,
/// sometimes Vp8LDecoder may need to allocate
/// 4 bytes per pixel internally during decode.
/// Initializes a new instance of the <see cref="AlphaDecoder"/> class.
/// </summary>
public bool Use8BDecode { get; set; }
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="data">The (maybe compressed) alpha data.</param>
/// <param name="alphaChunkHeader">The first byte of the alpha image stream contains information on ow to decode the stream.</param>
/// <param name="memoryAllocator">Used for allocating memory during decoding.</param>
public AlphaDecoder(int width, int height, byte[] data, byte alphaChunkHeader, MemoryAllocator memoryAllocator)
{
this.Width = width;
@ -64,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found");
}
this.FilterType = (WebPFilterType)filter;
this.AlphaFilterType = (WebPAlphaFilterType)filter;
// 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.
@ -73,8 +54,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator);
// TODO: use memory allocator
this.Alpha = new byte[width * height];
this.Alpha = memoryAllocator.Allocate<byte>(width * height);
if (this.Compressed)
{
@ -84,8 +64,74 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
private int PrevLineOffset { get; set; }
/// <summary>
/// Gets the the width of the image.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height of the image.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the used filter type.
/// </summary>
public WebPAlphaFilterType AlphaFilterType { get; }
/// <summary>
/// Gets or sets the last decoded row.
/// </summary>
public int LastRow { get; set; }
/// <summary>
/// Gets or sets the row before the last decoded row.
/// </summary>
public int PrevRow { get; set; }
/// <summary>
/// Gets information for decoding Vp8L compressed alpha data.
/// </summary>
public Vp8LDecoder Vp8LDec { get; }
/// <summary>
/// Gets the decoded alpha data.
/// </summary>
public IMemoryOwner<byte> Alpha { get; }
public int CropTop { get; }
/// <summary>
/// Gets a value indicating whether pre-processing was used during compression.
/// 0: no pre-processing, 1: level reduction.
/// </summary>
private int PreProcessing { get; }
/// <summary>
/// Gets a value indicating whether the alpha channel uses compression.
/// </summary>
private bool Compressed { get; }
/// <summary>
/// Gets the (maybe compressed) alpha data.
/// </summary>
private byte[] Data { get; }
/// <summary>
/// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed.
/// </summary>
private WebPLosslessDecoder LosslessDecoder { get; }
/// <summary>
/// Gets or sets a value indicating whether the decoding needs 1 byte per pixel for decoding.
/// Although Alpha Channel requires only 1 byte per pixel, sometimes Vp8LDecoder may need to allocate
/// 4 bytes per pixel internally during decode.
/// </summary>
public bool Use8BDecode { get; set; }
/// <summary>
/// Decodes and filters the maybe compressed alpha data.
/// </summary>
public void Decode()
{
if (this.Compressed is false)
@ -95,26 +141,27 @@ namespace SixLabors.ImageSharp.Formats.WebP
WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk");
}
if (this.FilterType == WebPFilterType.None)
Span<byte> alphaSpan = this.Alpha.Memory.Span;
if (this.AlphaFilterType == WebPAlphaFilterType.None)
{
this.Data.AsSpan(0, this.Width * this.Height).CopyTo(this.Alpha);
this.Data.AsSpan(0, this.Width * this.Height).CopyTo(alphaSpan);
return;
}
Span<byte> deltas = this.Data.AsSpan();
Span<byte> dst = this.Alpha.AsSpan();
Span<byte> dst = alphaSpan;
Span<byte> prev = null;
for (int y = 0; y < this.Height; ++y)
{
switch (this.FilterType)
switch (this.AlphaFilterType)
{
case WebPFilterType.Horizontal:
case WebPAlphaFilterType.Horizontal:
HorizontalUnfilter(prev, deltas, dst, this.Width);
break;
case WebPFilterType.Vertical:
case WebPAlphaFilterType.Vertical:
VerticalUnfilter(prev, deltas, dst, this.Width);
break;
case WebPFilterType.Gradient:
case WebPAlphaFilterType.Gradient:
GradientUnfilter(prev, deltas, dst, this.Width);
break;
}
@ -130,6 +177,44 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
/// <summary>
/// Applies filtering to a set of rows.
/// </summary>
/// <param name="firstRow">The first row index to start filtering.</param>
/// <param name="lastRow">The last row index for filtering.</param>
/// <param name="dst">The destination to store the filtered data.</param>
/// <param name="stride">The stride to use.</param>
public void AlphaApplyFilter(int firstRow, int lastRow, Span<byte> dst, int stride)
{
if (this.AlphaFilterType is WebPAlphaFilterType.None)
{
return;
}
Span<byte> alphaSpan = this.Alpha.Memory.Span;
Span<byte> prev = this.PrevRow == 0 ? null : alphaSpan.Slice(this.Width * this.PrevRow);
for (int y = firstRow; y < lastRow; ++y)
{
switch (this.AlphaFilterType)
{
case WebPAlphaFilterType.Horizontal:
HorizontalUnfilter(prev, dst, dst, this.Width);
break;
case WebPAlphaFilterType.Vertical:
VerticalUnfilter(prev, dst, dst, this.Width);
break;
case WebPAlphaFilterType.Gradient:
GradientUnfilter(prev, dst, dst, this.Width);
break;
}
prev = dst;
dst = dst.Slice(stride);
}
this.PrevRow = lastRow - 1;
}
private static void HorizontalUnfilter(Span<byte> prev, Span<byte> input, Span<byte> dst, int width)
{
byte pred = (byte)(prev == null ? 0 : prev[0]);
@ -177,42 +262,48 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
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 int GradientPredictor(byte a, byte b, byte c)
{
int g = a + b - c;
return ((g & ~0xff) is 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit
}
// Taken from vp8l_dec.c AlphaApplyFilter
public void AlphaApplyFilter(
int firstRow,
int lastRow,
Span<byte> prevLine,
Span<byte> output,
int outputOffset,
int stride)
/// <inheritdoc/>
public void Dispose()
{
if (this.Filter is WebPFilterNone)
{
return;
}
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;
}
this.Vp8LDec?.Dispose();
this.Alpha?.Dispose();
}
}
}

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

@ -1,104 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics;
namespace SixLabors.ImageSharp.Formats.WebP.Filters
{
// TODO from dsp.h
// public enum WebPFilterType
// {
// None = 0,
// Horizontal,
// Vertical,
// Gradient,
// Last = Gradient + 1, // end marker
// Best, // meta types
// Fast
// }
internal abstract class WebPFilterBase
{
/// <summary>
/// </summary>
/// <param name="prevLine"></param>
/// <param name="prevLineOffset">nullable as prevLine is nullable in the original but Span'T can't be null.</param>
/// <param name="preds"></param>
/// <param name="predsOffset"></param>
/// <param name="currentLine"></param>
/// <param name="currentLineOffset"></param>
/// <param name="width"></param>
public abstract void Unfilter(
Span<byte> prevLine,
int? prevLineOffset,
Span<byte> preds,
int predsOffset,
Span<byte> currentLine,
int currentLineOffset,
int width);
public abstract void Filter(
Span<byte> input,
int inputOffset,
int width,
int height,
int stride,
Span<byte> output,
int outputOffset);
protected static void SanityCheck(
Span<byte> input, Span<byte> output, int width, int numRows, int height, int stride, int row)
{
Debug.Assert(input != null);
Debug.Assert(output != null);
Debug.Assert(width > 0);
Debug.Assert(height > 0);
Debug.Assert(stride > width);
Debug.Assert(row >= 0);
Debug.Assert(height > 0);
Debug.Assert(row + numRows <= height);
}
protected static void PredictLine(
Span<byte> src,
int srcOffset,
Span<byte> pred,
int predOffset,
Span<byte> dst,
int dstOffset,
int length,
bool inverse)
{
if (inverse)
{
for (int i = 0; i < length; i++)
{
dst[i] = (byte)(src[i] + pred[i]);
}
}
else
{
for (int i = 0; i < length; i++)
{
dst[i] = (byte)(src[i] - pred[i]);
}
}
}
protected void UnfilterHorizontalOrVerticalCore(
byte pred,
Span<byte> input,
int inputOffset,
Span<byte> output,
int outputOffset,
int width)
{
for (int i = 0; i < width; i++)
{
output[outputOffset + i] = (byte)(pred + input[inputOffset + i]);
pred = output[i];
}
}
}
}

116
src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs

@ -1,116 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.WebP.Filters
{
class WebPFilterGradient : WebPFilterBase
{
public override void Unfilter(
Span<byte> prevLine,
int? prevLineOffsetNullable,
Span<byte> input,
int inputOffset,
Span<byte> output,
int outputOffset,
int width)
{
if (prevLineOffsetNullable is int prevLineOffset)
{
byte top = prevLine[prevLineOffset];
byte topLeft = top;
byte left = top;
for (int i = 0; i < width; i++)
{
top = prevLine[prevLineOffset + i]; // need to read this first in case prev==out
left = (byte)(input[inputOffset + i] + GradientPredictor(left, top, topLeft));
topLeft = top;
output[outputOffset + i] = left;
}
}
else
{
this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width);
}
}
public override void Filter(
Span<byte> input,
int inputOffset,
int width,
int height,
int stride,
Span<byte> output,
int outputOffset)
{
// calling (input, width, height, stride, 0, height, 0, output
int row = 0;
int numRows = height;
bool inverse = false;
int startOffset = row * stride;
int lastRow = row + numRows;
SanityCheck(input, output, width, numRows, height, stride, row);
inputOffset += startOffset;
outputOffset += startOffset;
Span<byte> preds;
int predsOffset;
if (inverse)
{
preds = output;
predsOffset = outputOffset;
}
else
{
preds = input;
predsOffset = inputOffset;
}
if (row == 0)
{
output[outputOffset] = input[inputOffset];
PredictLine(
input,
inputOffset + 1,
preds,
predsOffset,
output,
outputOffset + 1,
width - 1,
inverse);
}
while (row < lastRow)
{
PredictLine(
input,
inputOffset,
preds,
predsOffset - stride,
output,
outputOffset,
1,
inverse);
for (int w = 1; w < width; w++)
{
int pred = GradientPredictor(preds[w - 1], preds[w - stride], preds[w - stride - 1]);
int signedPred = inverse ? pred : -pred;
output[outputOffset + w] = (byte)(input[inputOffset + w] + signedPred);
}
row++;
predsOffset += stride;
inputOffset += stride;
outputOffset += stride;
}
}
private static int GradientPredictor(byte a, byte b, byte c)
{
int g = a + b + c;
return (g & ~0xff) == 0 ? g : (g < 0) ? 0 : 255;
}
}
}

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

@ -1,115 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.WebP.Filters
{
internal class WebPFilterHorizontal : WebPFilterBase
{
public override void Unfilter(
Span<byte> prevLine,
int? prevLineOffsetNullable,
Span<byte> input,
int inputOffset,
Span<byte> output,
int outputOffset,
int width)
{
byte pred = prevLineOffsetNullable is int prevLineOffset
? prevLine[prevLineOffset]
: (byte)0;
this.UnfilterHorizontalOrVerticalCore(
pred,
input,
inputOffset,
output,
outputOffset,
width);
}
public override void Filter(
Span<byte> input,
int inputOffset,
int width,
int height,
int stride,
Span<byte> output,
int outputOffset)
{
int numRows = height;
int row = 0;
const bool inverse = false;
int startOffset = row * stride;
int lastRow = row + height;
SanityCheck(input, output, width, height, numRows, stride, row);
inputOffset += startOffset;
outputOffset += startOffset;
Span<byte> preds;
int predsOffset;
if (inverse)
{
preds = output;
predsOffset = outputOffset;
}
else
{
preds = input;
predsOffset = inputOffset;
}
if (row is 0)
{
// leftmost pixel is the same as Input for topmost scanline
output[0] = input[0];
PredictLine(
input,
inputOffset + 1,
preds,
predsOffset,
output,
outputOffset + 1,
width - 1,
inverse);
row = 1;
predsOffset += stride;
inputOffset += stride;
outputOffset += stride;
}
// Filter line by line.
while (row < lastRow)
{
PredictLine(
input,
inputOffset,
preds,
predsOffset - stride,
output,
0,
1,
inverse);
PredictLine(
input,
inputOffset,
preds,
predsOffset,
output,
outputOffset + 1,
width - 1,
inverse);
row++;
predsOffset += stride;
inputOffset += stride;
outputOffset += stride;
}
}
}
}

33
src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs

@ -1,33 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.WebP.Filters
{
// TODO: check if this is a filter or just a placeholder from the C implementation details
class WebPFilterNone : WebPFilterBase
{
public override void Unfilter(
Span<byte> prevLine,
int? prevLineOffset,
Span<byte> input,
int inputOffset,
Span<byte> output,
int outputOffset,
int width)
{
}
public override void Filter(
Span<byte> input,
int inputOffset,
int width,
int height,
int stride,
Span<byte> output,
int outputOffset)
{
}
}
}

16
src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs

@ -1,16 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.WebP.Filters
{
internal enum WebPFilterType
{
None = 0,
Horizontal = 1,
Vertical = 2,
Gradient = 3,
}
}

100
src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs

@ -1,100 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.WebP.Filters
{
class WebPFilterVertical : WebPFilterBase
{
public override void Unfilter(Span<byte> prevLine, int? prevLineOffsetNullable, Span<byte> input, int inputOffset, Span<byte> output, int outputOffset, int width)
{
if (prevLineOffsetNullable is int prevLineOffset)
{
for (int i = 0; i < width; i++)
{
output[outputOffset + i] = (byte)(prevLine[prevLineOffset + i] + input[inputOffset + i]);
}
}
else
{
this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width);
}
}
public override void Filter(
Span<byte> input,
int inputOffset,
int width,
int height,
int stride,
Span<byte> output,
int outputOffset)
{
int row = 0;
bool inverse = false;
// TODO: DoVerticalFilter_C with parameters after stride and after height set to 0
int startOffset = row * stride;
int lastRow = row + height;
SanityCheck(input, output, width, height, height, stride, row);
inputOffset += startOffset;
outputOffset += startOffset;
Span<byte> preds;
int predsOffset;
if (inverse)
{
preds = output;
predsOffset = outputOffset;
}
else
{
preds = input;
predsOffset = inputOffset;
}
if (row == 0)
{
// Very first top-left pixel is copied.
output[0] = input[0];
// Rest of top scan-line is left-predicted:
PredictLine(
input,
inputOffset + 1,
preds,
predsOffset,
output,
outputOffset + 1,
width - 1,
inverse);
row = 1;
inputOffset += stride;
outputOffset += stride;
}
else
{
predsOffset -= stride;
}
// Filter line-by-line.
while (row < lastRow)
{
PredictLine(
input,
inputOffset,
preds,
predsOffset,
output,
outputOffset,
width,
inverse);
row++;
predsOffset += stride;
inputOffset += stride;
outputOffset += stride;
}
}
}
}

9
src/ImageSharp/Formats/WebP/Vp8BitReader.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
private int bits;
/// <summary>
/// Max packed-read position on buffer.
/// Max packed-read position of the buffer.
/// </summary>
private uint bufferMax;
@ -54,6 +54,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <param name="inputStream">The input stream to read from.</param>
/// <param name="imageDataSize">The raw image data size in bytes.</param>
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
/// <param name="partitionLength">The partition length.</param>
/// <param name="startPos">Start index in the data array. Defaults to 0.</param>
public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0)
{
@ -63,6 +64,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.InitBitreader(partitionLength, startPos);
}
/// <summary>
/// Initializes a new instance of the <see cref="Vp8BitReader"/> class.
/// </summary>
/// <param name="imageData">The raw encoded image data.</param>
/// <param name="partitionLength">The partition length.</param>
/// <param name="startPos">Start index in the data array. Defaults to 0.</param>
public Vp8BitReader(byte[] imageData, uint partitionLength, int startPos = 0)
{
this.Data = imageData;

11
src/ImageSharp/Formats/WebP/Vp8Io.cs

@ -37,17 +37,17 @@ namespace SixLabors.ImageSharp.Formats.WebP
public int MbH { get; set; }
/// <summary>
/// Rows to copy (in YUV format).
/// Gets or sets the luma component.
/// </summary>
public Span<byte> Y { get; set; }
/// <summary>
/// Rows to copy (in YUV format).
/// Gets or sets the U chroma component.
/// </summary>
public Span<byte> U { get; set; }
/// <summary>
/// Rows to copy (in YUV format).
/// Gets or sets the V chroma component.
/// </summary>
public Span<byte> V { get; set; }
@ -76,10 +76,5 @@ namespace SixLabors.ImageSharp.Formats.WebP
public int ScaledWidth { get; set; }
public int ScaledHeight { get; set; }
/// <summary>
/// User data
/// </summary>
private object Opaque { get; set; }
}
}

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

@ -8,7 +8,7 @@ using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.WebP
{
/// <summary>
/// A bit reader for VP8L streams.
/// A bit reader for reading lossless webp streams.
/// </summary>
internal class Vp8LBitReader : BitReaderBase
{
@ -20,12 +20,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary>
/// Number of bits prefetched.
/// </summary>
private const int Vp8LLbits = 64;
private const int Lbits = 64;
/// <summary>
/// Minimum number of bytes ready after VP8LFillBitWindow.
/// </summary>
private const int Vp8LWbits = 32;
private const int Wbits = 32;
private readonly uint[] bitMask =
{
@ -58,11 +58,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
private int bitPos;
/// <summary>
/// True if a bit was read past the end of buffer.
/// </summary>
public bool Eos;
/// <summary>
/// Initializes a new instance of the <see cref="Vp8LBitReader"/> class.
/// </summary>
@ -117,6 +112,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.pos = length;
}
/// <summary>
/// Gets or sets a value indicating whether a bit was read past the end of buffer.
/// </summary>
public bool Eos { get; set; }
/// <summary>
/// Reads a unsigned short value from the buffer. The bits of each byte are read in least-significant-bit-first order.
/// </summary>
@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <returns>The pre-fetched bits.</returns>
public ulong PrefetchBits()
{
return this.value >> (this.bitPos & (Vp8LLbits - 1));
return this.value >> (this.bitPos & (Lbits - 1));
}
/// <summary>
@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
public void FillBitWindow()
{
if (this.bitPos >= Vp8LWbits)
if (this.bitPos >= Wbits)
{
this.DoFillBitWindow();
}
@ -184,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 > Lbits));
}
private void DoFillBitWindow()
@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
while (this.bitPos >= 8 && this.pos < this.len)
{
this.value >>= 8;
this.value |= (ulong)this.Data[this.pos] << (Vp8LLbits - 8);
this.value |= (ulong)this.Data[this.pos] << (Lbits - 8);
++this.pos;
this.bitPos -= 8;
}

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

@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
public IMemoryOwner<uint> Pixels { get; }
///<inheritdoc/>
/// <inheritdoc/>
public void Dispose()
{
this.Pixels.Dispose();

31
src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.WebP
{
/// <summary>
/// Enum for the different alpha filter types.
/// </summary>
internal enum WebPAlphaFilterType
{
/// <summary>
/// No filtering.
/// </summary>
None = 0,
/// <summary>
/// Horizontal filter.
/// </summary>
Horizontal = 1,
/// <summary>
/// Vertical filter.
/// </summary>
Vertical = 2,
/// <summary>
/// Gradient filter.
/// </summary>
Gradient = 3,
}
}

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

@ -7,7 +7,6 @@ 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;
@ -23,6 +22,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </remarks>
internal sealed class WebPLosslessDecoder
{
/// <summary>
/// A bit reader for reading lossless webp streams.
/// </summary>
private readonly Vp8LBitReader bitReader;
private static readonly int BitsSpecialMarker = 0x100;
@ -751,12 +753,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
int dist = this.PlaneCodeToDistance(width, distCode);
if (pos >= dist && end - pos >= length)
{
//CopyBlock8b(data + pos, dist, length);
data.Slice(pos - dist, length).CopyTo(data.Slice(pos));
}
else
{
// TODO: error?
break;
WebPThrowHelper.ThrowImageFormatException("error while decoding alpha data");
}
pos += length;
@ -792,14 +793,14 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
// 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)
int topRow = (dec.AlphaFilterType is WebPAlphaFilterType.None || dec.AlphaFilterType is WebPAlphaFilterType.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<byte> output = dec.Alpha.Memory.Span;
Span<uint> pixelData = dec.Vp8LDec.Pixels.Memory.Span;
Span<byte> pixelDataAsBytes = MemoryMarshal.Cast<uint, byte>(pixelData);
Span<byte> dst = output.Slice(dec.Width * firstRow);
@ -808,7 +809,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
// 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.AlphaApplyFilter(firstRow, lastRow, dst, dec.Width);
}
dec.LastRow = lastRow;
@ -867,37 +868,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
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;

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Linq;
using System.Runtime.InteropServices;
@ -13,10 +14,21 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
internal sealed class WebPLossyDecoder
{
/// <summary>
/// A bit reader for reading lossy webp streams.
/// </summary>
private readonly Vp8BitReader bitReader;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// Initializes a new instance of the <see cref="WebPLossyDecoder"/> class.
/// </summary>
/// <param name="bitReader">Bitreader to read from the stream.</param>
/// <param name="memoryAllocator">Used for allocating memory during processing operations.</param>
public WebPLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator)
{
this.memoryAllocator = memoryAllocator;
@ -77,11 +89,18 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
private void DecodePixelValues<TPixel>(int width, int height, Span<byte> pixelData, Buffer2D<TPixel> pixels, byte[] alpha = null)
private void DecodePixelValues<TPixel>(int width, int height, Span<byte> pixelData, Buffer2D<TPixel> pixels, IMemoryOwner<byte> alpha = null)
where TPixel : struct, IPixel<TPixel>
{
TPixel color = default;
bool hasAlpha = alpha != null;
bool hasAlpha = false;
Span<byte> alphaSpan = null;
if (alpha != null)
{
hasAlpha = true;
alphaSpan = alpha.Memory.Span;
}
for (int y = 0; y < height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y);
@ -96,7 +115,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
// TODO: use bulk conversion here.
if (hasAlpha)
{
byte a = alpha[offset];
byte a = alphaSpan[offset];
color.FromBgra32(new Bgra32(r, g, b, a));
}
else
@ -781,26 +800,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
private Vp8Profile DecodeProfile(int version)
{
switch (version)
{
case 0:
return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bicubic, LoopFilter = LoopFilter.Complex };
case 1:
return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bilinear, LoopFilter = LoopFilter.Simple };
case 2:
return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bilinear, LoopFilter = LoopFilter.None };
case 3:
return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.None, LoopFilter = LoopFilter.None };
default:
// Reserved for future use in Spec.
// https://tools.ietf.org/html/rfc6386#page-30
WebPThrowHelper.ThrowNotSupportedException($"unsupported VP8 version {version} found");
return new Vp8Profile();
}
}
private void DecodeMacroBlock(Vp8Decoder dec, Vp8BitReader bitreader)
{
Vp8MacroBlock left = dec.LeftMacroBlock;

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

@ -175,6 +175,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP
[WithFile(Lossy.AlphaNoCompressionHorizontalFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaNoCompressionVerticalFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaNoCompressionGradientFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaCompressedHorizontalFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)]
public void WebpDecoder_CanDecode_Lossy_WithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{

Loading…
Cancel
Save