diff --git a/src/ImageSharp/Formats/WebP/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/HTreeGroup.cs index d3c21e690..c9763b69b 100644 --- a/src/ImageSharp/Formats/WebP/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/HTreeGroup.cs @@ -16,10 +16,15 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class HTreeGroup { + public HTreeGroup() + { + HTree = new List(WebPConstants.HuffmanCodesPerMetaCode); + } + /// /// This has a maximum of HuffmanCodesPerMetaCode (5) entrys. /// - public List HTree { get; set; } + public List HTree { get; private set; } /// /// True, if huffman trees for Red, Blue & Alpha Symbols are trivial (have a single code). @@ -29,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// If is_trivial_literal is true, this is the ARGB value of the pixel, with Green channel being set to zero. /// - public int LiteralArb { get; set; } + public uint LiteralArb { get; set; } /// /// True if is_trivial_literal with only one code. diff --git a/src/ImageSharp/Formats/WebP/HuffIndex.cs b/src/ImageSharp/Formats/WebP/HuffIndex.cs new file mode 100644 index 000000000..6d84b86d7 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/HuffIndex.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Five Huffman codes are used at each meta code. + /// + public enum HuffIndex : int + { + /// + /// Green + length prefix codes + color cache codes. + /// + Green = 0, + + /// + /// Red. + /// + Red = 1, + + /// + /// Blue. + /// + Blue = 2, + + /// + /// Alpha. + /// + Alpha = 3, + + /// + /// Distance prefix codes. + /// + Dist = 4 + } +} diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index 0e09d004d..a800f7e85 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -9,9 +9,11 @@ namespace SixLabors.ImageSharp.Formats.WebP { public const int HuffmanTableBits = 8; + public const int HuffmanPackedBits = 6; + public const int HuffmanTableMask = (1 << HuffmanTableBits) - 1; - public static int BuildHuffmanTable(HuffmanCode[] table, int rootBits, int[] codeLengths, int codeLengthsSize) + public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) { Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); Guard.NotNull(codeLengths, nameof(codeLengths)); @@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return 0; } - ++count[codeLengths[symbol]]; + count[codeLengths[symbol]]++; } // Generate offsets into sorted symbol table by code length. @@ -102,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.WebP BitsUsed = len, Value = sorted[symbol++] }; - ReplicateValue(table.AsSpan(key), step, tableSize, huffmanCode); + ReplicateValue(table.Slice(key), step, tableSize, huffmanCode); key = GetNextKey(key, len); } } @@ -118,7 +120,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return 0; } - Span tableSpan = table.AsSpan(); + Span tableSpan = table; for (; count[len] > 0; --count[len]) { if ((key & mask) != low) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 88fd3cad0..ee33af73e 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -25,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private static int FIXED_TABLE_SIZE = 630 * 3 + 410; - private static int[] kTableSize = + private static readonly int[] kTableSize = { FIXED_TABLE_SIZE + 654, FIXED_TABLE_SIZE + 656, @@ -41,6 +42,11 @@ namespace SixLabors.ImageSharp.Formats.WebP FIXED_TABLE_SIZE + 2704 }; + private static readonly byte[] kLiteralMap = + { + 0, 1, 1, 1, 0 + }; + public WebPLosslessDecoder(Vp8LBitReader bitReader, int imageDataSize) { this.bitReader = bitReader; @@ -108,12 +114,16 @@ namespace SixLabors.ImageSharp.Formats.WebP } int tableSize = kTableSize[colorCacheBits]; - var table = new HuffmanCode[numHtreeGroups * tableSize]; + 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(); + HTreeGroup hTreeGroup = hTreeGroups[i]; int size; int totalSize = 0; - int isTrivialLiteral = 1; + bool isTrivialLiteral = true; int maxBits = 0; var codeLengths = new int[maxAlphabetSize]; for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++) @@ -121,24 +131,72 @@ namespace SixLabors.ImageSharp.Formats.WebP int alphabetSize = WebPConstants.kAlphabetSize[j]; if (j == 0 && colorCacheBits > 0) { - if (j == 0 && colorCacheBits > 0) - { - alphabetSize += 1 << colorCacheBits; - } + alphabetSize += 1 << colorCacheBits; + } + + size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); + if (size is 0) + { + WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); + } + hTreeGroup.HTree.Add(huffmanTable.ToArray()); + + if (isTrivialLiteral && kLiteralMap[j] == 1) + { + isTrivialLiteral = huffmanTable[0].BitsUsed == 0; + } + + totalSize += huffmanTable[0].BitsUsed; + huffmanTable = huffmanTable.Slice(size); - size = this.ReadHuffmanCode(alphabetSize, codeLengths, table); - if (size is 0) + if (j <= (int)HuffIndex.Alpha) + { + int localMaxBits = codeLengths[0]; + int k; + for (k = 1; k < alphabetSize; ++k) { - WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); + if (codeLengths[k] > localMaxBits) + { + localMaxBits = codeLengths[k]; + } } + + maxBits += localMaxBits; + } + } + + hTreeGroup.IsTrivialLiteral = isTrivialLiteral; + 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); + if (totalSize == 0 && green < WebPConstants.NumLiteralCodes) + { + hTreeGroup.IsTrivialCode = true; + hTreeGroup.LiteralArb |= (uint)green << 8; } } + + hTreeGroup.UsePackedTable = hTreeGroup.IsTrivialCode && maxBits < HuffmanUtils.HuffmanPackedBits; + if (hTreeGroup.UsePackedTable) + { + throw new NotImplementedException("use packed table is not implemented yet"); + } } } - private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, HuffmanCode[] table) + private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table) { bool simpleCode = this.bitReader.ReadBit(); + for (int i = 0; i < alphabetSize; i++) + { + codeLengths[i] = 0; + } + if (simpleCode) { // (i) Simple Code Length Code. @@ -177,7 +235,7 @@ namespace SixLabors.ImageSharp.Formats.WebP codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadBits(3); } - this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths); + this.ReadHuffmanCodeLengths(table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); } int size = HuffmanUtils.BuildHuffmanTable(table, HuffmanUtils.HuffmanTableBits, codeLengths, alphabetSize);