diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs
new file mode 100644
index 0000000000..4efa1b4126
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Holds information for decoding a lossy webp image.
+ ///
+ internal class Vp8Decoder
+ {
+ public Vp8Decoder()
+ {
+ this.DeQuantMatrices = new Vp8QuantMatrix[WebPConstants.NumMbSegments];
+ }
+
+ public Vp8FrameHeader FrameHeader { get; set; }
+
+ public Vp8PictureHeader PictureHeader { get; set; }
+
+ public Vp8FilterHeader FilterHeader { get; set; }
+
+ public Vp8SegmentHeader SegmentHeader { get; set; }
+
+ public bool Dither { get; set; }
+
+ ///
+ /// Gets or sets dequantization matrices (one set of DC/AC dequant factor per segment).
+ ///
+ public Vp8QuantMatrix[] DeQuantMatrices { get; private set; }
+
+ public Vp8Proba Probabilities { get; set; }
+
+ ///
+ /// Gets or sets the width in macroblock units.
+ ///
+ public int MbWidth { get; set; }
+
+ ///
+ /// Gets or sets the height in macroblock units.
+ ///
+ public int MbHeight { get; set; }
+
+ ///
+ /// Gets or sets the current x position in macroblock units.
+ ///
+ public int MbX { get; set; }
+
+ ///
+ /// Gets or sets the current y position in macroblock units.
+ ///
+ public int MbY { get; set; }
+
+ ///
+ /// Gets or sets the parsed reconstruction data.
+ ///
+ public Vp8MacroBlockData[] MacroBlockData { get; set; }
+
+ ///
+ /// Gets or sets contextual macroblock infos.
+ ///
+ public Vp8MacroBlock[] MacroBlockInfo { get; set; }
+
+ ///
+ /// Gets or sets filter strength info.
+ ///
+ public Vp8FilterInfo FilterInfo { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs
index fa4a570ec6..bf8d949b2b 100644
--- a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs
+++ b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs
@@ -6,10 +6,15 @@ using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.WebP
{
///
- /// Holds information for decoding a lossless image.
+ /// Holds information for decoding a lossless webp image.
///
internal class Vp8LDecoder
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The width of the image.
+ /// The height of the image.
public Vp8LDecoder(int width, int height)
{
this.Width = width;
diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs
index b867893f51..8ecaa2c83c 100644
--- a/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs
+++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs
@@ -4,7 +4,7 @@
namespace SixLabors.ImageSharp.Formats.WebP
{
///
- /// Info about a macro block.
+ /// Contextual macroblock information.
///
internal class Vp8MacroBlock
{
diff --git a/src/ImageSharp/Formats/WebP/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Vp8Proba.cs
index ac635cf48d..a884bd3c77 100644
--- a/src/ImageSharp/Formats/WebP/Vp8Proba.cs
+++ b/src/ImageSharp/Formats/WebP/Vp8Proba.cs
@@ -14,10 +14,13 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
this.Segments = new uint[MbFeatureTreeProbs];
this.Bands = new Vp8BandProbas[WebPConstants.NumTypes, WebPConstants.NumBands];
+ this.BandsPtr = new Vp8BandProbas[WebPConstants.NumTypes, 16 + 1];
}
public uint[] Segments { get; }
public Vp8BandProbas[,] Bands { get; }
+
+ public Vp8BandProbas[,] BandsPtr { get; }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs
index 0e7defd4a8..37ccc358b2 100644
--- a/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs
+++ b/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs
@@ -10,9 +10,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
public Vp8ProbaArray()
{
- this.Probabilities = new uint[WebPConstants.NumProbas];
+ this.Probabilities = new byte[WebPConstants.NumProbas];
}
- public uint[] Probabilities { get; }
+ public byte[] Probabilities { get; }
}
}
diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs
index dfef15260e..fd6fb1d252 100644
--- a/src/ImageSharp/Formats/WebP/WebPConstants.cs
+++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs
@@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
NumDistanceCodes
};
- // VP8 constants from here on
+ // VP8 constants from here on:
public const int NumMbSegments = 4;
public const int MaxNumPartitions = 8;
@@ -169,6 +169,13 @@ namespace SixLabors.ImageSharp.Formats.WebP
249, 254, 259, 264, 269, 274, 279, 284
};
+ // Residual decoding (Paragraph 13.2 / 13.3)
+ public static readonly byte[] Cat3 = { 173, 148, 140 };
+ public static readonly byte[] Cat4 = { 176, 155, 140, 135 };
+ public static readonly byte[] Cat5 = { 180, 157, 141, 134, 130 };
+ public static readonly byte[] Cat6 = { 254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129 };
+ public static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 };
+
// Paragraph 13
public static readonly byte[,,,] CoeffsUpdateProba =
{
diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
index bc4ef5aa99..f5a39c0167 100644
--- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
+++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
@@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.Collections.Generic;
+using System.Linq;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -77,6 +79,260 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
+ private bool ParseResiduals(Vp8Decoder decoder, Vp8MacroBlock mb)
+ {
+ byte tnz, lnz;
+ uint nonZeroY = 0;
+ uint nonZeroUv = 0;
+ int first;
+ var dst = new short[384];
+ var dstOffset = 0;
+ Vp8MacroBlockData block = decoder.MacroBlockData[decoder.MbX];
+ Vp8QuantMatrix q = decoder.DeQuantMatrices[block.Segment];
+ Vp8BandProbas[,] bands = decoder.Probabilities.BandsPtr;
+ Vp8BandProbas[] acProba;
+ Vp8MacroBlock leftMb = null; // TODO: this value needs to be set
+
+ if (!block.IsI4x4)
+ {
+ // Parse DC
+ var dc = new short[16];
+ int ctx = (int)(mb.NoneZeroDcCoeffs + leftMb.NoneZeroDcCoeffs);
+ int nz = this.GetCoeffs(GetBandsRow(bands, 1), ctx, q.Y2Mat, 0, dc);
+ mb.NoneZeroDcCoeffs = leftMb.NoneZeroDcCoeffs = (uint)(nz > 0 ? 1 : 0);
+ if (nz > 0)
+ {
+ // More than just the DC -> perform the full transform.
+ this.TransformWht(dc, dst);
+ }
+ else
+ {
+ int dc0 = (dc[0] + 3) >> 3;
+ for (int i = 0; i < 16 * 16; i += 16)
+ {
+ dst[i] = (short)dc0;
+ }
+ }
+
+ first = 1;
+ acProba = GetBandsRow(bands, 1);
+ }
+ else
+ {
+ first = 0;
+ acProba = GetBandsRow(bands, 3);
+ }
+
+ tnz = (byte)(mb.NoneZeroAcDcCoeffs & 0x0f);
+ lnz = (byte)(leftMb.NoneZeroAcDcCoeffs & 0x0f);
+
+ for (int y = 0; y < 4; ++y)
+ {
+ int l = lnz & 1;
+ uint nzCoeffs = 0;
+ for (int x = 0; x < 4; ++x)
+ {
+ int ctx = l + (tnz & 1);
+ int nz = this.GetCoeffs(acProba, ctx, q.Y1Mat, first, dst.AsSpan(dstOffset));
+ l = (nz > first) ? 1 : 0;
+ tnz = (byte)((tnz >> 1) | (l << 7));
+ nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[0] != 0 ? 1 : 0);
+ dstOffset += 16;
+ }
+
+ tnz >>= 4;
+ lnz = (byte)((lnz >> 1) | (l << 7));
+ nonZeroY = (nonZeroY << 8) | nzCoeffs;
+ }
+
+ uint outTnz = tnz;
+ uint outLnz = (uint)(lnz >> 4);
+
+ for (int ch = 0; ch < 4; ch += 2)
+ {
+ uint nzCoeffs = 0;
+ tnz = (byte)(mb.NoneZeroAcDcCoeffs >> (4 + ch));
+ lnz = (byte)(leftMb.NoneZeroAcDcCoeffs >> (4 + ch));
+ for (int y = 0; y < 2; ++y)
+ {
+ int l = lnz & 1;
+ for (int x = 0; x < 2; ++x)
+ {
+ int ctx = l + (tnz & 1);
+ int nz = this.GetCoeffs(GetBandsRow(bands, 2), ctx, q.UvMat, 0, dst.AsSpan(dstOffset));
+ l = (nz > 0) ? 1 : 0;
+ tnz = (byte)((tnz >> 1) | (l << 3));
+ nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[0] != 0 ? 1 : 0);
+ dstOffset += 16;
+ }
+
+ tnz >>= 2;
+ lnz = (byte)((lnz >> 1) | (l << 5));
+ }
+
+ // Note: we don't really need the per-4x4 details for U/V blocks.
+ nonZeroUv |= nzCoeffs << (4 * ch);
+ outTnz |= (uint)((tnz << 4) << ch);
+ outLnz |= (uint)((lnz & 0xf0) << ch);
+ }
+
+ mb.NoneZeroAcDcCoeffs = outTnz;
+ leftMb.NoneZeroAcDcCoeffs = outLnz;
+
+ block.NonZeroY = nonZeroY;
+ block.NonZeroUv = nonZeroUv;
+
+ // We look at the mode-code of each block and check if some blocks have less
+ // than three non-zero coeffs (code < 2). This is to avoid dithering flat and
+ // empty blocks.
+ block.Dither = (byte)((nonZeroUv & 0xaaaa) > 0 ? 0 : q.Dither);
+
+ return (nonZeroY | nonZeroUv) is 0;
+ }
+
+ private int GetCoeffs(Vp8BandProbas[] prob, int ctx, int[] dq, int n, Span coeffs)
+ {
+ // Returns the position of the last non - zero coeff plus one.
+ Vp8ProbaArray p = prob[n].Probabilities[ctx];
+ for (; n < 16; ++n)
+ {
+ if (this.bitReader.GetBit((int)p.Probabilities[0]) is 0)
+ {
+ // Previous coeff was last non - zero coeff.
+ return n;
+ }
+
+ // Sequence of zero coeffs.
+ while (this.bitReader.GetBit((int)p.Probabilities[1]) is 0)
+ {
+ p = prob[++n].Probabilities[0];
+ if (n is 16)
+ {
+ return 16;
+ }
+ }
+
+ // Non zero coeffs.
+ int v;
+ if (this.bitReader.GetBit((int)p.Probabilities[2]) is 0)
+ {
+ v = 1;
+ p = prob[n + 1].Probabilities[1];
+ }
+ else
+ {
+ v = this.GetLargeValue(p.Probabilities);
+ p = prob[n + 1].Probabilities[2];
+ }
+
+ int idx = n > 0 ? 1 : 0;
+ coeffs[WebPConstants.Zigzag[n]] = (short)(this.bitReader.ReadSignedValue(v) * dq[idx]);
+ }
+
+ return 16;
+ }
+
+ private int GetLargeValue(byte[] p)
+ {
+ // See section 13 - 2: http://tools.ietf.org/html/rfc6386#section-13.2
+ int v;
+ if (this.bitReader.GetBit(p[3]) is 0)
+ {
+ if (this.bitReader.GetBit(p[4]) is 0)
+ {
+ v = 2;
+ }
+ else
+ {
+ v = 3 + this.bitReader.GetBit(p[5]);
+ }
+ }
+ else
+ {
+ if (this.bitReader.GetBit(p[6]) is 0)
+ {
+ if (this.bitReader.GetBit(p[7]) is 0)
+ {
+ v = 5 + this.bitReader.GetBit(159);
+ }
+ else
+ {
+ v = 7 + (2 * this.bitReader.GetBit(165));
+ v += this.bitReader.GetBit(145);
+ }
+ }
+ else
+ {
+ int bit1 = this.bitReader.GetBit(p[8]);
+ int bit0 = this.bitReader.GetBit(p[9] + bit1);
+ int cat = (2 * bit1) + bit0;
+ v = 0;
+ byte[] tab = null;
+ switch (cat)
+ {
+ case 0:
+ tab = WebPConstants.Cat3;
+ break;
+ case 1:
+ tab = WebPConstants.Cat4;
+ break;
+ case 2:
+ tab = WebPConstants.Cat5;
+ break;
+ case 3:
+ tab = WebPConstants.Cat6;
+ break;
+ default:
+ WebPThrowHelper.ThrowImageFormatException("VP8 parsing error");
+ break;
+ }
+
+ for (int i = 0; i < tab.Length; i++)
+ {
+ v += v + this.bitReader.GetBit(tab[i]);
+ }
+
+ v += 3 + (8 << cat);
+ }
+ }
+
+ return v;
+ }
+
+ ///
+ /// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion.
+ ///
+ private void TransformWht(short[] input, short[] output)
+ {
+ var tmp = new int[16];
+ for (int i = 0; i < 4; ++i)
+ {
+ int a0 = input[0 + i] + input[12 + i];
+ int a1 = input[4 + i] + input[8 + i];
+ int a2 = input[4 + i] - input[8 + i];
+ int a3 = input[0 + i] - input[12 + i];
+ tmp[0 + i] = a0 + a1;
+ tmp[8 + i] = a0 - a1;
+ tmp[4 + i] = a3 + a2;
+ tmp[12 + i] = a3 - a2;
+ }
+
+ int outputOffset = 0;
+ for (int i = 0; i < 4; ++i)
+ {
+ int dc = tmp[0 + (i * 4)] + 3;
+ int a0 = dc + tmp[3 + (i * 4)];
+ int a1 = tmp[1 + (i * 4)] + tmp[2 + (i * 4)];
+ int a2 = tmp[1 + (i * 4)] - tmp[2 + (i * 4)];
+ int a3 = dc - tmp[3 + (i * 4)];
+ output[outputOffset + 0] = (short)((a0 + a1) >> 3);
+ output[outputOffset + 16] = (short)((a3 + a2) >> 3);
+ output[outputOffset + 32] = (short)((a0 - a1) >> 3);
+ output[outputOffset + 48] = (short)((a3 - a2) >> 3);
+ outputOffset += 64;
+ }
+ }
+
private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba)
{
var vp8SegmentHeader = new Vp8SegmentHeader
@@ -109,8 +365,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
for (int s = 0; s < proba.Segments.Length; ++s)
{
- hasValue = bitReader.ReadBool();
- proba.Segments[s] = hasValue ? bitReader.ReadValue(8) : 255;
+ hasValue = this.bitReader.ReadBool();
+ proba.Segments[s] = hasValue ? this.bitReader.ReadValue(8) : 255;
}
}
}
@@ -235,15 +491,14 @@ namespace SixLabors.ImageSharp.Formats.WebP
int v = this.bitReader.GetBit(prob) == 0
? (int)this.bitReader.ReadValue(8)
: WebPConstants.DefaultCoeffsProba[t, b, c, p];
- proba.Bands[t, b].Probabilities[c].Probabilities[p] = (uint)v;
+ proba.Bands[t, b].Probabilities[c].Probabilities[p] = (byte)v;
}
}
}
for (int b = 0; b < 16 + 1; ++b)
{
- // TODO: This needs to be reviewed and fixed.
- // proba->bands_ptr_[t][b] = &proba->bands_[t][kBands[b]];
+ proba.BandsPtr[t, b] = proba.Bands[t, WebPConstants.Bands[b]];
}
}
@@ -251,7 +506,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
bool useSkipProba = this.bitReader.ReadBool();
if (useSkipProba)
{
- var skipP = this.bitReader.ReadValue(8);
+ uint skipP = this.bitReader.ReadValue(8);
}
}
@@ -287,6 +542,19 @@ namespace SixLabors.ImageSharp.Formats.WebP
return true;
}
+ private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz)
+ {
+ nzCoeffs <<= 2;
+ nzCoeffs |= (uint)((nz > 3) ? 3 : (nz > 1) ? 2 : dcNz);
+ return nzCoeffs;
+ }
+
+ private static Vp8BandProbas[] GetBandsRow(Vp8BandProbas[,] bands, int rowIdx)
+ {
+ Vp8BandProbas[] bandsRow = Enumerable.Range(0, bands.GetLength(1)).Select(x => bands[rowIdx, x]).ToArray();
+ return bandsRow;
+ }
+
private static int Clip(int value, int max)
{
return value < 0 ? 0 : value > max ? max : value;