From 305d8efba22846ce493738b17c2a8e8cea29a4eb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 31 Jan 2020 15:12:13 +0100 Subject: [PATCH] Add ParseResiduals --- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 68 +++++ src/ImageSharp/Formats/WebP/Vp8LDecoder.cs | 7 +- src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs | 2 +- src/ImageSharp/Formats/WebP/Vp8Proba.cs | 3 + src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs | 4 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 9 +- .../Formats/WebP/WebPLossyDecoder.cs | 280 +++++++++++++++++- 7 files changed, 362 insertions(+), 11 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8Decoder.cs 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;