From d45fe26a69ecae00cfb4a41643b758334eba4b7f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 25 Nov 2019 19:39:50 +0100 Subject: [PATCH] Add additional helper methods for decoding the image, start with decoding the image (still WIP) --- src/ImageSharp/Formats/WebP/ColorCache.cs | 16 + src/ImageSharp/Formats/WebP/HTreeGroup.cs | 15 +- src/ImageSharp/Formats/WebP/HuffIndex.cs | 12 +- src/ImageSharp/Formats/WebP/HuffmanCode.cs | 2 +- src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 8 +- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 11 +- src/ImageSharp/Formats/WebP/Vp8LMetadata.cs | 28 ++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 6 +- .../Formats/WebP/WebPLosslessDecoder.cs | 389 ++++++++++++++++-- 9 files changed, 417 insertions(+), 70 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8LMetadata.cs diff --git a/src/ImageSharp/Formats/WebP/ColorCache.cs b/src/ImageSharp/Formats/WebP/ColorCache.cs index eac1721e8..e8e34e878 100644 --- a/src/ImageSharp/Formats/WebP/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/ColorCache.cs @@ -18,5 +18,21 @@ namespace SixLabors.ImageSharp.Formats.WebP public uint HashShift { get; set; } public uint HashBits { get; set; } + + public void Init(int colorCacheBits) + { + + } + + public void Insert() + { + // TODO: implement VP8LColorCacheInsert + } + + public int ColorCacheLookup() + { + // TODO: implement VP8LColorCacheLookup + return 0; + } } } diff --git a/src/ImageSharp/Formats/WebP/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/HTreeGroup.cs index c9763b69b..99d26844c 100644 --- a/src/ImageSharp/Formats/WebP/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/HTreeGroup.cs @@ -16,15 +16,20 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class HTreeGroup { - public HTreeGroup() + public HTreeGroup(uint packedTableSize) { - HTree = new List(WebPConstants.HuffmanCodesPerMetaCode); + this.HTrees = new List(WebPConstants.HuffmanCodesPerMetaCode); + this.PackedTable = new HuffmanCode[packedTableSize]; + for (int i = 0; i < packedTableSize; i++) + { + this.PackedTable[i] = new HuffmanCode(); + } } /// - /// This has a maximum of HuffmanCodesPerMetaCode (5) entrys. + /// This has a maximum of HuffmanCodesPerMetaCode (5) entry's. /// - public List HTree { get; private set; } + public List HTrees { get; private set; } /// /// True, if huffman trees for Red, Blue & Alpha Symbols are trivial (have a single code). @@ -49,6 +54,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Table mapping input bits to a packed values, or escape case to literal code. /// - public HuffmanCode PackedTable { get; set; } + public HuffmanCode[] PackedTable { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/HuffIndex.cs b/src/ImageSharp/Formats/WebP/HuffIndex.cs index 6d84b86d7..7e2b58a8e 100644 --- a/src/ImageSharp/Formats/WebP/HuffIndex.cs +++ b/src/ImageSharp/Formats/WebP/HuffIndex.cs @@ -6,31 +6,31 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Five Huffman codes are used at each meta code. /// - public enum HuffIndex : int + public static class HuffIndex { /// /// Green + length prefix codes + color cache codes. /// - Green = 0, + public const int Green = 0; /// /// Red. /// - Red = 1, + public const int Red = 1; /// /// Blue. /// - Blue = 2, + public const int Blue = 2; /// /// Alpha. /// - Alpha = 3, + public const int Alpha = 3; /// /// Distance prefix codes. /// - Dist = 4 + public const int Dist = 4; } } diff --git a/src/ImageSharp/Formats/WebP/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/HuffmanCode.cs index b3133786c..b76f41d23 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanCode.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanCode.cs @@ -16,6 +16,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the symbol value or table offset. /// - public int Value { get; set; } + public uint Value { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index a800f7e85..ea549ef26 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -13,6 +13,8 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int HuffmanTableMask = (1 << HuffmanTableBits) - 1; + public const uint HuffmanPackedTableSize = 1u << HuffmanPackedBits; + public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) { Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); @@ -71,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var huffmanCode = new HuffmanCode() { BitsUsed = 0, - Value = sorted[0] + Value = (uint)sorted[0] }; ReplicateValue(table, 1, totalSize, huffmanCode); return totalSize; @@ -102,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var huffmanCode = new HuffmanCode() { BitsUsed = len, - Value = sorted[symbol++] + Value = (uint)sorted[symbol++] }; ReplicateValue(table.Slice(key), step, tableSize, huffmanCode); key = GetNextKey(key, len); @@ -138,7 +140,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var huffmanCode = new HuffmanCode { BitsUsed = len - rootBits, - Value = sorted[symbol++] + Value = (uint)sorted[symbol++] }; ReplicateValue(tableSpan.Slice(key >> rootBits), step, tableSize, huffmanCode); key = GetNextKey(key, len); diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 9bcad284c..cdcca61e9 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.IO; namespace SixLabors.ImageSharp.Formats.WebP @@ -151,6 +150,11 @@ namespace SixLabors.ImageSharp.Formats.WebP this.ShiftBytes(); } + public bool IsEndOfStream() + { + return this.eos || ((this.pos == this.len) && (this.bitPos > VP8L_LBITS)); + } + private void ShiftBytes() { while (this.bitPos >= 8 && this.pos < this.len) @@ -167,11 +171,6 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private bool IsEndOfStream() - { - return this.eos || ((this.pos == this.len) && (this.bitPos > VP8L_LBITS)); - } - private void SetEndOfStream() { this.eos = true; diff --git a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs new file mode 100644 index 000000000..0f9595a41 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class Vp8LMetadata + { + public int ColorCacheSize { get; set; } + + public ColorCache ColorCache { get; set; } + + public ColorCache SavedColorCache { get; set; } + + public int HuffmanMask { get; set; } + + public int HuffmanSubSampleBits { get; set; } + + public int HuffmanXSize { get; set; } + + public int[] HuffmanImage { get; set; } + + public int NumHTreeGroups { get; set; } + + public HTreeGroup[] HTreeGroups { get; set; } + + public HuffmanCode[] HuffmanTables { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index a2930171d..c54f72073 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -86,11 +86,9 @@ namespace SixLabors.ImageSharp.Formats.WebP public static int NumDistanceCodes = 40; - public static int NumCodeLengthCodes = 19; - public static int LengthTableBits = 7; - public static int kCodeLengthLiterals = 16; + public static uint kCodeLengthLiterals = 16; public static int kCodeLengthRepeatCode = 16; @@ -98,8 +96,6 @@ namespace SixLabors.ImageSharp.Formats.WebP public static int[] kCodeLengthRepeatOffsets = { 3, 3, 11 }; - public static byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - public static int[] kAlphabetSize = { NumLiteralCodes + NumLengthCodes, NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 7bf819035..37c6aba55 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using SixLabors.ImageSharp.Memory; @@ -24,25 +23,51 @@ namespace SixLabors.ImageSharp.Formats.WebP private readonly int imageDataSize; - private static int FIXED_TABLE_SIZE = 630 * 3 + 410; + private static readonly int BitsSpecialMarker = 0x100; - private static readonly int[] kTableSize = + private static readonly uint PackedNonLiteralCode = 0; + + private static readonly int NumArgbCacheRows = 16; + + private static readonly int FixedTableSize = (630 * 3) + 410; + + private static readonly int[] KTableSize = { - FIXED_TABLE_SIZE + 654, - FIXED_TABLE_SIZE + 656, - FIXED_TABLE_SIZE + 658, - FIXED_TABLE_SIZE + 662, - FIXED_TABLE_SIZE + 670, - FIXED_TABLE_SIZE + 686, - FIXED_TABLE_SIZE + 718, - FIXED_TABLE_SIZE + 782, - FIXED_TABLE_SIZE + 912, - FIXED_TABLE_SIZE + 1168, - FIXED_TABLE_SIZE + 1680, - FIXED_TABLE_SIZE + 2704 + FixedTableSize + 654, + FixedTableSize + 656, + FixedTableSize + 658, + FixedTableSize + 662, + FixedTableSize + 670, + FixedTableSize + 686, + FixedTableSize + 718, + FixedTableSize + 782, + FixedTableSize + 912, + FixedTableSize + 1168, + FixedTableSize + 1680, + FixedTableSize + 2704 }; - private static readonly byte[] kLiteralMap = + public static int NumCodeLengthCodes = 19; + public static byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + + private static readonly int CodeToPlaneCodes = 120; + private static readonly int[] KCodeToPlane = + { + 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, + 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, + 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, + 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, + 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, + 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, + 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, + 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, + 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, + 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, + 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, + 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 + }; + + private static readonly byte[] KLiteralMap = { 0, 1, 1, 1, 0 }; @@ -51,9 +76,6 @@ namespace SixLabors.ImageSharp.Formats.WebP { this.bitReader = bitReader; this.imageDataSize = imageDataSize; - - // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. - //stream.Skip(imageDataSize + 34); // TODO: Not sure why the additional data starts at offset +34 at the moment. } public void Decode(Buffer2D pixels, int width, int height) @@ -80,10 +102,14 @@ namespace SixLabors.ImageSharp.Formats.WebP colorCache.Colors = new List(hashSize); colorCache.HashBits = (uint)colorCacheBits; colorCache.HashShift = (uint)(32 - colorCacheBits); + colorCache.Init(colorCacheBits); } - this.ReadHuffmanCodes(xsize, ysize, colorCacheBits); - + Vp8LMetadata metadata = this.ReadHuffmanCodes(xsize, ysize, colorCacheBits); + var numBits = 0; // TODO: use huffmanSubsampleBits. + metadata.HuffmanMask = (numBits == 0) ? ~0 : (1 << numBits) - 1; + metadata.ColorCacheSize = colorCacheSize; + int lastPixel = 0; int row = lastPixel / width; int col = lastPixel % width; @@ -91,10 +117,150 @@ namespace SixLabors.ImageSharp.Formats.WebP int colorCacheLimit = lenCodeLimit + colorCacheSize; bool decIsIncremental = false; // TODO: determine correct value for decIsIncremental int nextSyncRow = decIsIncremental ? row : 1 << 24; + int mask = metadata.HuffmanMask; + HTreeGroup[] hTreeGroup = this.GetHtreeGroupForPos(metadata, col, row); + var pixelData = new byte[width * height * 4]; + + int totalPixels = width * height; + int decodedPixels = 0; + while (decodedPixels < totalPixels) + { + int code = 0; + if ((col & mask) == 0) + { + hTreeGroup = this.GetHtreeGroupForPos(metadata, col, row); + } + + this.bitReader.FillBitWindow(); + if (hTreeGroup[0].UsePackedTable) + { + code = (int)this.ReadPackedSymbols(hTreeGroup); + if (this.bitReader.IsEndOfStream()) + { + break; + } + + if (code == PackedNonLiteralCode) + { + this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels); + continue; + } + } + else + { + this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green]); + } + + if (this.bitReader.IsEndOfStream()) + { + break; + } + + // Literal + if (code < WebPConstants.NumLiteralCodes) + { + if (hTreeGroup[0].IsTrivialLiteral) + { + long pixel = hTreeGroup[0].LiteralArb | (code << 8); + } + else + { + uint red = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Red]); + this.bitReader.FillBitWindow(); + uint blue = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Blue]); + uint alpha = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Alpha]); + if (this.bitReader.IsEndOfStream()) + { + break; + } + + int pixelIdx = decodedPixels * 4; + pixelData[pixelIdx] = (byte)alpha; + pixelData[pixelIdx + 1] = (byte)red; + pixelData[pixelIdx + 2] = (byte)code; + pixelData[pixelIdx + 3] = (byte)blue; + } + + this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels); + } + else if (code < lenCodeLimit) + { + // Backward reference is used. + int lengthSym = code - WebPConstants.NumLiteralCodes; + int length = this.GetCopyLength(lengthSym); + uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); + this.bitReader.FillBitWindow(); + int distCode = this.GetCopyDistance((int)distSymbol); + int dist = this.PlaneCodeToDistance(width, distCode); + if (this.bitReader.IsEndOfStream()) + { + break; + } + + this.CopyBlock32b(pixelData, dist, length); + decodedPixels += length; + col += length; + while (col >= width) + { + col -= width; + ++row; + } + + if ((col & mask) != 0) + { + hTreeGroup = this.GetHtreeGroupForPos(metadata, col, row); + } + if (colorCache != null) + { + //while (lastCached < src) + //{ + // colorCache.Insert(lastCached); + //} + } + } + else if (code < colorCacheLimit) + { + // Color cache should be used. + int key = code - lenCodeLimit; + /*while (lastCached < src) + { + colorCache.Insert(lastCached); + }*/ + //pixelData = colorCache.Lookup(key); + this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels); + } + else + { + // Error + } + } } - private void ReadHuffmanCodes(int xsize, int ysize, int colorCacheBits, bool allowRecursion = true) + private void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels) + { + ++col; + decodedPixels++; + if (col >= width) + { + col = 0; + ++row; + /*if (row <= lastRow && (row % NumArgbCacheRows == 0)) + { + this.ProcessRowFunc(row); + }*/ + + if (colorCache != null) + { + /*while (lastCached < src) + { + VP8LColorCacheInsert(color_cache, *last_cached++); + }*/ + } + } + } + + private Vp8LMetadata ReadHuffmanCodes(int xsize, int ysize, int colorCacheBits, bool allowRecursion = true) { int maxAlphabetSize = 0; int numHtreeGroups = 1; @@ -111,7 +277,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int huffmanYSize = SubSampleSize(ysize, (int)huffmanPrecision); // TODO: decode entropy image - return; + return new Vp8LMetadata(); } // Find maximum alphabet size for the htree group. @@ -129,13 +295,13 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - int tableSize = kTableSize[colorCacheBits]; + int tableSize = KTableSize[colorCacheBits]; var huffmanTables = new HuffmanCode[numHtreeGroups * tableSize]; var hTreeGroups = new HTreeGroup[numHtreeGroups]; Span huffmanTable = huffmanTables.AsSpan(); for (int i = 0; i < numHtreeGroupsMax; i++) { - hTreeGroups[i] = new HTreeGroup(); + hTreeGroups[i] = new HTreeGroup(HuffmanUtils.HuffmanPackedTableSize); HTreeGroup hTreeGroup = hTreeGroups[i]; int size; int totalSize = 0; @@ -155,9 +321,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); } - hTreeGroup.HTree.Add(huffmanTable.ToArray()); - if (isTrivialLiteral && kLiteralMap[j] == 1) + hTreeGroup.HTrees.Add(huffmanTable.ToArray()); + + if (isTrivialLiteral && KLiteralMap[j] == 1) { isTrivialLiteral = huffmanTable[0].BitsUsed == 0; } @@ -185,24 +352,34 @@ namespace SixLabors.ImageSharp.Formats.WebP hTreeGroup.IsTrivialCode = false; if (isTrivialLiteral) { - int red = hTreeGroup.HTree[(int)HuffIndex.Red].First().Value; - int blue = hTreeGroup.HTree[(int)HuffIndex.Blue].First().Value; - int green = hTreeGroup.HTree[(int)HuffIndex.Green].First().Value; - int alpha = hTreeGroup.HTree[(int)HuffIndex.Alpha].First().Value; - hTreeGroup.LiteralArb = (uint)((alpha << 24) | (red << 16) | blue); + uint red = hTreeGroup.HTrees[HuffIndex.Red].First().Value; + uint blue = hTreeGroup.HTrees[HuffIndex.Blue].First().Value; + uint green = hTreeGroup.HTrees[HuffIndex.Green].First().Value; + uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha].First().Value; + hTreeGroup.LiteralArb = (alpha << 24) | (red << 16) | blue; if (totalSize == 0 && green < WebPConstants.NumLiteralCodes) { hTreeGroup.IsTrivialCode = true; - hTreeGroup.LiteralArb |= (uint)green << 8; + hTreeGroup.LiteralArb |= green << 8; } } - hTreeGroup.UsePackedTable = hTreeGroup.IsTrivialCode && maxBits < HuffmanUtils.HuffmanPackedBits; + hTreeGroup.UsePackedTable = !hTreeGroup.IsTrivialCode && maxBits < HuffmanUtils.HuffmanPackedBits; if (hTreeGroup.UsePackedTable) { - throw new NotImplementedException("use packed table is not implemented yet"); + this.BuildPackedTable(hTreeGroup); } } + + var metadata = new Vp8LMetadata() + { + // TODO: initialize huffman_image_ + NumHTreeGroups = numHtreeGroups, + HTreeGroups = hTreeGroups, + HuffmanTables = huffmanTables, + }; + + return metadata; } private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table) @@ -239,16 +416,16 @@ namespace SixLabors.ImageSharp.Formats.WebP // (ii)Normal Code Length Code: // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. - var codeLengthCodeLengths = new int[WebPConstants.NumCodeLengthCodes]; + var codeLengthCodeLengths = new int[NumCodeLengthCodes]; uint numCodes = this.bitReader.ReadBits(4) + 4; - if (numCodes > WebPConstants.NumCodeLengthCodes) + if (numCodes > NumCodeLengthCodes) { WebPThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); } for (int i = 0; i < numCodes; i++) { - codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadBits(3); + codeLengthCodeLengths[KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadBits(3); } this.ReadHuffmanCodeLengths(table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); @@ -265,7 +442,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int maxSymbol; int symbol = 0; int prevCodeLen = WebPConstants.DefaultCodeLength; - int size = HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, WebPConstants.NumCodeLengthCodes); + int size = HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, NumCodeLengthCodes); if (size is 0) { WebPThrowHelper.ThrowImageFormatException("Error building huffman table"); @@ -283,7 +460,6 @@ namespace SixLabors.ImageSharp.Formats.WebP while (symbol < numSymbols) { - int codeLen; if (maxSymbol-- == 0) { break; @@ -294,19 +470,19 @@ namespace SixLabors.ImageSharp.Formats.WebP ulong idx = prefetchBits & 127; HuffmanCode huffmanCode = table[idx]; this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); - codeLen = huffmanCode.Value; + uint codeLen = huffmanCode.Value; if (codeLen < WebPConstants.kCodeLengthLiterals) { - codeLengths[symbol++] = codeLen; + codeLengths[symbol++] = (int)codeLen; if (codeLen != 0) { - prevCodeLen = codeLen; + prevCodeLen = (int)codeLen; } } else { bool usePrev = codeLen == WebPConstants.kCodeLengthRepeatCode; - int slot = codeLen - WebPConstants.kCodeLengthLiterals; + uint slot = codeLen - WebPConstants.kCodeLengthLiterals; int extraBits = WebPConstants.kCodeLengthExtraBits[slot]; int repeatOffset = WebPConstants.kCodeLengthRepeatOffsets[slot]; int repeat = (int)(this.bitReader.ReadBits(extraBits) + repeatOffset); @@ -391,5 +567,130 @@ namespace SixLabors.ImageSharp.Formats.WebP { return (size + (1 << samplingBits) - 1) >> samplingBits; } + + /// + /// Decodes the next Huffman code from 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) + { + ulong val = this.bitReader.PrefetchBits(); + Span tableSpan = table.Slice((int)(val & HuffmanUtils.HuffmanTableMask)); + int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; + if (nBits > 0) + { + this.bitReader.AdvanceBitPosition(HuffmanUtils.HuffmanTableBits); + val = this.bitReader.PrefetchBits(); + tableSpan = tableSpan.Slice((int)tableSpan[0].Value); + tableSpan = tableSpan.Slice((int)val & ((1 << nBits) - 1)); + } + + this.bitReader.AdvanceBitPosition(tableSpan[0].BitsUsed); + + return tableSpan[0].Value; + } + + private uint ReadPackedSymbols(HTreeGroup[] group) + { + uint val = (uint)(this.bitReader.PrefetchBits() & (HuffmanUtils.HuffmanPackedTableSize - 1)); + HuffmanCode code = group[0].PackedTable[val]; + if (code.BitsUsed < BitsSpecialMarker) + { + this.bitReader.AdvanceBitPosition(code.BitsUsed); + // dest = (uint)code.Value; + return PackedNonLiteralCode; + } + + this.bitReader.AdvanceBitPosition(code.BitsUsed - BitsSpecialMarker); + + return code.Value; + } + + private void CopyBlock32b(byte[] dest, int dist, int length) + { + + } + + private int GetCopyDistance(int distanceSymbol) + { + if (distanceSymbol < 4) + { + return distanceSymbol + 1; + } + + int extraBits = (distanceSymbol - 2) >> 1; + int offset = (2 + (distanceSymbol & 1)) << extraBits; + + return (int)(offset + this.bitReader.ReadBits(extraBits) + 1); + } + + private int GetCopyLength(int lengthSymbol) + { + // Length and distance prefixes are encoded the same way. + return this.GetCopyDistance(lengthSymbol); + } + + private int PlaneCodeToDistance(int xSize, int planeCode) + { + if (planeCode > CodeToPlaneCodes) + { + return planeCode - CodeToPlaneCodes; + } + + int distCode = KCodeToPlane[planeCode - 1]; + int yOffset = distCode >> 4; + int xOffset = 8 - (distCode & 0xf); + int dist = (yOffset * xSize) + xOffset; + + return (dist >= 1) ? dist : 1; // dist<1 can happen if xsize is very small + } + + private void BuildPackedTable(HTreeGroup hTreeGroup) + { + for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; ++code) + { + uint bits = code; + HuffmanCode huff = hTreeGroup.PackedTable[bits]; + HuffmanCode hCode = hTreeGroup.HTrees[HuffIndex.Green][bits]; + if (hCode.Value >= WebPConstants.NumLiteralCodes) + { + huff.BitsUsed = hCode.BitsUsed + BitsSpecialMarker; + huff.Value = hCode.Value; + } + else + { + huff.BitsUsed = 0; + huff.Value = 0; + bits >>= this.AccumulateHCode(hCode, 8, huff); + bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, huff); + bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, huff); + bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, huff); + } + } + } + + private int AccumulateHCode(HuffmanCode hCode, int shift, HuffmanCode huff) + { + huff.BitsUsed += hCode.BitsUsed; + huff.Value |= hCode.Value << shift; + return hCode.BitsUsed; + } + + private int GetMetaIndex(int[] image, int xSize, int bits, int x, int y) + { + if (bits == 0) + { + return 0; + } + + return image[(xSize * (y >> bits)) + (x >> bits)]; + } + + private HTreeGroup[] GetHtreeGroupForPos(Vp8LMetadata metadata, int x, int y) + { + int metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); + return metadata.HTreeGroups.AsSpan(metaIndex).ToArray(); + } } }