Browse Source

Move huffman code into separate class, introduce HTreeGroup

pull/1147/head
Brian Popow 7 years ago
parent
commit
83cdeb54ff
  1. 49
      src/ImageSharp/Formats/WebP/HTreeGroup.cs
  2. 3
      src/ImageSharp/Formats/WebP/HuffmanCode.cs
  3. 146
      src/ImageSharp/Formats/WebP/HuffmanUtils.cs
  4. 120
      src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs

49
src/ImageSharp/Formats/WebP/HTreeGroup.cs

@ -0,0 +1,49 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.WebP
{
/// <summary>
/// Huffman table group.
/// Includes special handling for the following cases:
/// - is_trivial_literal: one common literal base for RED/BLUE/ALPHA (not GREEN)
/// - is_trivial_code: only 1 code (no bit is read from bitstream)
/// - use_packed_table: few enough literal symbols, so all the bit codes
/// can fit into a small look-up table packed_table[]
/// The common literal base, if applicable, is stored in 'literal_arb'.
/// </summary>
internal class HTreeGroup
{
/// <summary>
/// This has a maximum of HuffmanCodesPerMetaCode (5) entrys.
/// </summary>
public List<HuffmanCode> HTree { get; set; }
/// <summary>
/// True, if huffman trees for Red, Blue & Alpha Symbols are trivial (have a single code).
/// </summary>
public bool IsTrivialLiteral { get; set; }
/// <summary>
/// If is_trivial_literal is true, this is the ARGB value of the pixel, with Green channel being set to zero.
/// </summary>
public int LiteralArb { get; set; }
/// <summary>
/// True if is_trivial_literal with only one code.
/// </summary>
public bool IsTrivialCode { get; set; }
/// <summary>
/// use packed table below for short literal code
/// </summary>
public bool UsePackedTable { get; set; }
/// <summary>
/// Table mapping input bits to a packed values, or escape case to literal code.
/// </summary>
public HuffmanCode PackedTable { get; set; }
}
}

3
src/ImageSharp/Formats/WebP/HuffmanCode.cs

@ -1,8 +1,11 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Diagnostics;
namespace SixLabors.ImageSharp.Formats.WebP namespace SixLabors.ImageSharp.Formats.WebP
{ {
[DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")]
internal class HuffmanCode internal class HuffmanCode
{ {
/// <summary> /// <summary>

146
src/ImageSharp/Formats/WebP/HuffmanUtils.cs

@ -0,0 +1,146 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.WebP
{
internal static class HuffmanUtils
{
public static List<HuffmanCode> BuildHuffmanTable(HuffmanCode[] table, int rootBits, int[] codeLengths, int codeLengthsSize)
{
Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits));
Guard.NotNull(codeLengths, nameof(codeLengths));
Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize));
// TODO: not sure yet howto store the codes properly
var huffmanCodes = new List<HuffmanCode>();
// sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length.
var sorted = new int[codeLengthsSize];
// total size root table + 2nd level table
int totalSize = 1 << rootBits;
// current code length
int len;
// symbol index in original or sorted table
int symbol;
// number of codes of each length:
var count = new int[WebPConstants.MaxAllowedCodeLength + 1];
// offsets in sorted table for each length
var offset = new int[WebPConstants.MaxAllowedCodeLength + 1];
// Build histogram of code lengths.
for (symbol = 0; symbol < codeLengthsSize; ++symbol)
{
if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength)
{
return huffmanCodes;
}
++count[codeLengths[symbol]];
}
// Generate offsets into sorted symbol table by code length.
offset[1] = 0;
for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len)
{
if (count[len] > (1 << len))
{
return huffmanCodes;
}
offset[len + 1] = offset[len] + count[len];
}
// Sort symbols by length, by symbol order within each length.
for (symbol = 0; symbol < codeLengthsSize; ++symbol)
{
int symbolCodeLength = codeLengths[symbol];
if (codeLengths[symbol] > 0)
{
sorted[offset[symbolCodeLength]++] = symbol;
}
}
// Special case code with only one value.
if (offset[WebPConstants.MaxAllowedCodeLength] is 1)
{
var huffmanCode = new HuffmanCode()
{
BitsUsed = 0,
Value = sorted[0]
};
huffmanCodes.Add(huffmanCode);
return huffmanCodes;
}
int step; // step size to replicate values in current table
int low = -1; // low bits for current root entry
int mask = totalSize - 1; // mask for low bits
int key = 0; // reversed prefix code
int numNodes = 1; // number of Huffman tree nodes
int numOpen = 1; // number of open branches in current tree level
int tableBits = rootBits; // key length of current table
int tableSize = 1 << tableBits; // size of current table
symbol = 0;
// Fill in root table.
for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1)
{
numOpen <<= 1;
numNodes += numOpen;
numOpen -= count[len];
if (numOpen < 0)
{
return huffmanCodes;
}
for (; count[len] > 0; --count[len])
{
var huffmanCode = new HuffmanCode()
{
BitsUsed = len,
Value = sorted[symbol++]
};
huffmanCodes.Add(huffmanCode);
ReplicateValue(table, step, tableSize, huffmanCode);
key = GetNextKey(key, len);
}
}
return huffmanCodes;
}
/// <summary>
/// Stores code in table[0], table[step], table[2*step], ..., table[end].
/// Assumes that end is an integer multiple of step.
/// </summary>
private static void ReplicateValue(HuffmanCode[] table, int step, int end, HuffmanCode code)
{
Guard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step");
do
{
end -= step;
table[end] = code;
}
while (end > 0);
}
/// <summary>
/// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the
/// bit-wise reversal of the len least significant bits of key.
/// </summary>
private static int GetNextKey(int key, int len)
{
int step = 1 << (len - 1);
while ((key & step) != 0)
{
step >>= 1;
}
return step != 0 ? (key & (step - 1)) + step : key;
}
}
}

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -87,6 +88,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
} }
} }
// TODO: not sure about the correct tabelSize here. Harcoded for now.
//int tableSize = kTableSize[colorCacheBits];
int tableSize = 2970;
var table = new HuffmanCode[numHtreeGroups * tableSize];
for (int i = 0; i < numHtreeGroupsMax; i++) for (int i = 0; i < numHtreeGroupsMax; i++)
{ {
int size; int size;
@ -99,18 +104,22 @@ namespace SixLabors.ImageSharp.Formats.WebP
int alphabetSize = WebPConstants.kAlphabetSize[j]; int alphabetSize = WebPConstants.kAlphabetSize[j];
if (j == 0 && colorCacheBits > 0) if (j == 0 && colorCacheBits > 0)
{ {
alphabetSize += 1 << colorCacheBits; if (j == 0 && colorCacheBits > 0)
size = this.ReadHuffmanCode(alphabetSize, codeLengths); {
/*if (size is 0) alphabetSize += 1 << colorCacheBits;
}
size = this.ReadHuffmanCode(alphabetSize, codeLengths, table);
if (size is 0)
{ {
WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero");
}*/ }
} }
} }
} }
} }
private int ReadHuffmanCode(int alphabetSize, int[] codeLengths) private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, HuffmanCode[] table)
{ {
bool simpleCode = this.bitReader.ReadBit(); bool simpleCode = this.bitReader.ReadBit();
if (simpleCode) if (simpleCode)
@ -139,7 +148,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
// (ii)Normal Code Length Code: // (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 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. // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros.
var codeLengthCodeLengths = new int[WebPConstants.NumLengthCodes]; var codeLengthCodeLengths = new int[WebPConstants.NumCodeLengthCodes];
uint numCodes = this.bitReader.Read(4) + 4; uint numCodes = this.bitReader.Read(4) + 4;
if (numCodes > WebPConstants.NumCodeLengthCodes) if (numCodes > WebPConstants.NumCodeLengthCodes)
{ {
@ -151,7 +160,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.Read(3); codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.Read(3);
} }
this.ReadHuffmanCodeLengths(codeLengthCodeLengths, alphabetSize, codeLengths); this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths);
} }
int size = 0; int size = 0;
@ -160,12 +169,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
return size; return size;
} }
private void ReadHuffmanCodeLengths(int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths)
{ {
int maxSymbol; int maxSymbol;
int symbol = 0; int symbol = 0;
int prevCodeLen = WebPConstants.DefaultCodeLength; int prevCodeLen = WebPConstants.DefaultCodeLength;
BuildHuffmanTable(WebPConstants.LengthTableBits, codeLengthCodeLengths, WebPConstants.NumCodeLengthCodes); HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, WebPConstants.NumCodeLengthCodes);
if (this.bitReader.ReadBit()) if (this.bitReader.ReadBit())
{ {
int lengthNBits = 2 + (2 * (int)this.bitReader.Read(3)); int lengthNBits = 2 + (2 * (int)this.bitReader.Read(3));
@ -216,99 +225,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
} }
} }
private int BuildHuffmanTable(int rootBits, int[] codeLengths, int codeLengthsSize)
{
// sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length.
var sorted = new int[codeLengthsSize];
// total size root table + 2nd level table
int totalSize = 1 << rootBits;
// current code length
int len;
// symbol index in original or sorted table
int symbol;
// number of codes of each length:
var count = new int[WebPConstants.MaxAllowedCodeLength + 1];
// offsets in sorted table for each length
var offset = new int[WebPConstants.MaxAllowedCodeLength + 1];
// Build histogram of code lengths.
for (symbol = 0; symbol < codeLengthsSize; ++symbol)
{
if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength)
{
return 0;
}
++count[codeLengths[symbol]];
}
// Generate offsets into sorted symbol table by code length.
offset[1] = 0;
for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len)
{
if (count[len] > (1 << len))
{
return 0;
}
offset[len + 1] = offset[len] + count[len];
}
// Sort symbols by length, by symbol order within each length.
for (symbol = 0; symbol < codeLengthsSize; ++symbol)
{
int symbolCodeLength = codeLengths[symbol];
if (codeLengths[symbol] > 0)
{
sorted[offset[symbolCodeLength]++] = symbol;
}
}
// Special case code with only one value.
if (offset[WebPConstants.MaxAllowedCodeLength] is 1)
{
var huffmanCode = new HuffmanCode()
{
BitsUsed = 0,
Value = sorted[0]
};
return totalSize;
}
int step; // step size to replicate values in current table
int low = -1; // low bits for current root entry
int mask = totalSize - 1; // mask for low bits
int key = 0; // reversed prefix code
int numNodes = 1; // number of Huffman tree nodes
int numOpen = 1; // number of open branches in current tree level
int tableBits = rootBits; // key length of current table
int tableSize = 1 << tableBits; // size of current table
symbol = 0;
// Fill in root table.
for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1)
{
numOpen <<= 1;
numNodes += numOpen;
numOpen -= count[len];
if (numOpen < 0)
{
return 0;
}
for (; count[len] > 0; --count[len])
{
var code = new HuffmanCode()
{
BitsUsed = len,
Value = sorted[symbol++]
};
}
}
return 0;
}
private void ReadTransformations() private void ReadTransformations()
{ {
// Next bit indicates, if a transformation is present. // Next bit indicates, if a transformation is present.

Loading…
Cancel
Save