// Huffman Tree Encoding: // - Max 14-bit symbol length = One nibble transmitted per symbol // - 0 means no code // - 15 is our RLE start symbol // - An RLE run would go as 3 nibbles: <Symbol> <RLE Command: 15> <Length of run> // - The lengh of run is transmitted as a nibble. // - This takes a minimum of 2 nibbles so 3 is the shortest run worth encoding. // - Therefore the range of the length of the run symbol is 3..18. // We don't try to do tree elision. The entire chunking thing is entirely an encoder-side // concept. We can emit a New Tree command whenever we want, so a more serious encoder could take // much greater efforts to be fancier with when to emit a new huffman tree. void EncodeHuffTree(HuffmanEncoder huffman) { int lastEmittedLength = -1; for (int i = 0; i < NUM_SYMBOLS;) { if (i < NUM_SYMBOLS - 4 && huffman.Symbols[i].Length == lastEmittedLength && huffman.Symbols[i + 1].Length == lastEmittedLength && huffman.Symbols[i + 2].Length == lastEmittedLength) { Output.WriteBits(15, 4); // Emit an RLE token int runLength = 3; // Determine run length for (; runLength < 18 && i + runLength < NUM_SYMBOLS; runLength++) { if (huffman.Symbols[i + runLength].Length != lastEmittedLength) { break; } } Output.WriteBits(runLength - 3, 4); i += runLength; } else { int length = huffman.Symbols[i].Length; lastEmittedLength = length; Output.WriteBits(length, 4); i++; } } }
void EncodeChunk() { var huff = new HuffmanEncoder(SymbolFreqs, 14); EncodeHuffTree(huff); for (int i = 0; i < ChunkOffset; i++) { int symbol = ChunkBuffer[i]; var sym = huff.Symbols[symbol]; Output.WriteBits(sym.Code, sym.Length); if (symbol >= SYM_MATCH) { var match = MatchEncodes.Dequeue(); Output.WriteBits(match.Distance, match.DistanceBits); EncodeMatchLength(match.Length); } } }