UNPKG

vox-core

Version:

Runtime de aplicaciones multiplataforma

515 lines (399 loc) 13.4 kB
/* Adaptive Huffman code, using Vitter's algorithm ported from * vitter.c at http://code.google.com/p/compression-code/downloads/list * The original code was placed in the public domain, and so I * also place this JavaScript port in the public domain. * -- C. Scott Ananian <cscott@cscott.net>, 2013 * ps. some truly grotty C code in the originally, faithfully ported to * evil comma-operator-using, assignment-in-if-condition JavaScript. */ if (typeof define !== 'function') { var define = require('amdefine')(module); } define(['./BitStream','./Util'],function(BitStream,Util) { // This code is adapted from Professor Vitter's // article, Design and Analysis of Dynamic Huffman Codes, // which appeared in JACM October 1987 // A design trade-off has been made to simplify the // code: a node's block is determined dynamically, // and the implicit tree structure is maintained, // e.g. explicit node numbers are also implicit. // Dynamic Huffman table weight ranking // is maintained per Professor Vitter's // invariant (*) for algorithm FGK: // leaves precede internal nodes of the // same weight in a non-decreasing ranking // of weights using implicit node numbers: // 1) leaves slide over internal nodes, internal nodes // swap over groups of leaves, leaves are swapped // into group leader position, but two internal // nodes never change positions relative // to one another. // 2) weights are incremented by 2: // leaves always have even weight values; // internal nodes always have odd values. // 3) even node numbers are always right children; // odd numbers are left children in the tree. // node 2 * HuffSize - 1 is always the tree root; // node HuffEsc is the escape node; // the tree is initialized by creating an // escape node as the root. // each new leaf symbol is paired with a new escape // node into the previous escape node in the tree, // until the last symbol which takes over the // tree position of the escape node, and // HuffEsc is left at zero. // overall table size: 2 * HuffSize // huff_init(alphabet_size, potential symbols used) // huff_encode(next_symbol) // next_symbol = huff_decode() // huff_scale(by_bits) -- scale weights and re-balance tree var HTable = function(up, down, symbol, weight) { this.up = up; // next node up the tree this.down = down; // pair of down nodes this.symbol = symbol; // node symbol value this.weight = weight; // node weight }; HTable.prototype.clone = function() { return new HTable(this.up, this.down, this.symbol, this.weight); }; HTable.prototype.set = function(htable) { this.up = htable.up; this.down = htable.down; this.symbol = htable.symbol; this.weight = htable.weight; }; // initialize an adaptive coder // for alphabet size, and count // of nodes to be used var Huffman = function(size, root, bitstream, max_weight) { var i; // default: all alphabet symbols are used console.assert(size && typeof(size)==='number'); if( !root || root > size ) root = size; // create the initial escape node // at the tree root if ( root <<= 1 ) { root--; } // create root+1 htables (coding table) // XXX this could be views on a backing Uint32 array? this.table = []; for (i=0; i<=root; i++) { this.table[i] = new HTable(0,0,0,0); } // this.map => mapping for symbols to nodes this.map = []; // this.size => the alphabet size if( this.size = size ) { for (i=0; i<size; i++) { this.map[i] = 0; } } // this.esc => the current tree height // this.root => the root of the tree this.esc = this.root = root; if (bitstream) { this.readBit = bitstream.readBit.bind(bitstream); this.writeBit = bitstream.writeBit.bind(bitstream); } this.max_weight = max_weight; // may be null or undefined } // factory interface Huffman.factory = function(bitstream, max_weight) { return function(size) { return new Huffman(size, size, bitstream, max_weight); }; }; // split escape node to incorporate new symbol Huffman.prototype.split = function(symbol) { var pair, node; // is the tree already full??? if( pair = this.esc ) { this.esc--; } else { console.assert(false); return 0; } // if this is the last symbol, it moves into // the escape node's old position, and // this.esc is set to zero. // otherwise, the escape node is promoted to // parent a new escape node and the new symbol. if( node = this.esc ) { this.table[pair].down = node; this.table[pair].weight = 1; this.table[node].up = pair; this.esc--; } else { pair = 0; node = 1; } // initialize the new symbol node this.table[node].symbol = symbol; this.table[node].weight = 0; this.table[node].down = 0; this.map[symbol] = node; // initialize a new escape node. this.table[this.esc].weight = 0; this.table[this.esc].down = 0; this.table[this.esc].up = pair; return node; }; // swap leaf to group leader position // return symbol's new node Huffman.prototype.leader = function(node) { var weight = this.table[node].weight; var leader = node, prev, symbol; while( weight === this.table[leader + 1].weight ) { leader++; } if( leader === node ) { return node; } // swap the leaf nodes symbol = this.table[node].symbol; prev = this.table[leader].symbol; this.table[leader].symbol = symbol; this.table[node].symbol = prev; this.map[symbol] = leader; this.map[prev] = node; return leader; }; // slide internal node up over all leaves of equal weight; // or exchange leaf with next smaller weight internal node // return node's new position Huffman.prototype.slide = function(node) { var next = node; var swap; swap = this.table[next++].clone(); // if we're sliding an internal node, find the // highest possible leaf to exchange with if( swap.weight & 1 ) { while( swap.weight > this.table[next + 1].weight ) { next++; } } // swap the two nodes this.table[node].set(this.table[next]); this.table[next].set(swap); this.table[next].up = this.table[node].up; this.table[node].up = swap.up; // repair the symbol map and tree structure if( swap.weight & 1 ) { this.table[swap.down].up = next; this.table[swap.down - 1].up = next; this.map[this.table[node].symbol] = node; } else { this.table[this.table[node].down - 1].up = node; this.table[this.table[node].down].up = node; this.map[swap.symbol] = next; } return next; }; // increment symbol weight and re balance the tree. Huffman.prototype.increment = function(node) { var up; // obviate swapping a parent with its child: // increment the leaf and proceed // directly to its parent. // otherwise, promote leaf to group leader position in the tree if( this.table[node].up === node + 1 ) { this.table[node].weight += 2; node++; } else { node = this.leader (node); } // increase the weight of each node and slide // over any smaller weights ahead of it // until reaching the root // internal nodes work upwards from // their initial positions; while // symbol nodes slide over first, // then work up from their final // positions. while( this.table[node].weight += 2, up = this.table[node].up ) { while( this.table[node].weight > this.table[node + 1].weight ) { node = this.slide (node); } if( this.table[node].weight & 1 ) { node = up; } else { node = this.table[node].up; } } /* Re-scale if necessary. */ if (this.max_weight) { if (this.table[this.root].weight >= this.max_weight) { this.scale(1); } } }; // scale all weights and re-balance the tree // zero weight nodes are removed from the tree // by sliding them out the left of the rank list Huffman.prototype.scale = function(bits) { var node = this.esc, weight, prev; // work up the tree from the escape node // scaling weights by the value of bits while( ++node <= this.root ) { // recompute the weight of internal nodes; // slide down and out any unused ones if( this.table[node].weight & 1 ) { if( weight = this.table[this.table[node].down].weight & ~1 ) { weight += this.table[this.table[node].down - 1].weight | 1; } // remove zero weight leaves by incrementing HuffEsc // and removing them from the symbol map. take care } else if( !(weight = this.table[node].weight >> bits & ~1) ) { if( this.map[this.table[node].symbol] = 0, this.esc++ ) { this.esc++; } } // slide the scaled node back down over any // previous nodes with larger weights this.table[node].weight = weight; prev = node; while( weight < this.table[--prev].weight ) { this.slide(prev); } } // prepare a new escape node this.table[this.esc].down = 0; }; // send the bits for an escaped symbol Huffman.prototype.sendid = function(symbol) { var empty = 0, max; // count the number of empty symbols // before the symbol in the table while( symbol-- ) { if( !this.map[symbol] ) { empty++; } } // send LSB of this count first, using // as many bits as are required for // the maximum possible count if( max = this.size - Math.floor((this.root - this.esc) / 2) - 1 ) { do { this.writeBit(empty & 1); empty >>= 1; } while( max >>= 1 ); } }; // encode the next symbol Huffman.prototype.encode = function(symbol) { var emit = 1, bit; var up, idx, node; if( symbol < this.size ) { node = this.map[symbol]; } else { console.assert(false); return; } // for a new symbol, direct the receiver to the escape node // but refuse input if table is already full. if( !(idx = node) ) { if( !(idx = this.esc) ) { return; } } // accumulate the code bits by // working up the tree from // the node to the root while( up = this.table[idx].up ) { emit <<= 1; emit |= idx & 1; idx = up; } // send the code, root selector bit first while( bit = emit & 1, emit >>= 1 ) { this.writeBit(bit); } // send identification and incorporate // new symbols into the tree if( !node ) { this.sendid(symbol); node = this.split(symbol); } // adjust and re-balance the tree this.increment(node); }; // read the identification bits // for an escaped symbol Huffman.prototype.readid = function() { var empty = 0, bit = 1, max, symbol; // receive the symbol, LSB first, reading // only the number of bits necessary to // transmit the maximum possible symbol value if( max = this.size - Math.floor((this.root - this.esc) / 2) - 1 ) { do { empty |= this.readBit() ? bit : 0; bit <<= 1; } while( max >>= 1 ); } // the count is of unmapped symbols // in the table before the new one for( symbol = 0; symbol < this.size; symbol++ ) { if( !this.map[symbol] ) { if( !empty-- ) { return symbol; } } } // oops! our count is too big, either due // to a bit error, or a short node count // given to huff_init. console.assert(false); return 0; }; // decode the next symbol Huffman.prototype.decode = function() { var node = this.root; var symbol, down; // work down the tree from the root // until reaching either a leaf // or the escape node. A one // bit means go left, a zero // means go right. while( down = this.table[node].down ) { if( this.readBit() ) { node = down - 1; // the left child precedes the right child } else { node = down; } } // sent to the escape node??? // refuse to add to a full tree if( node === this.esc ) { if( this.esc ) { symbol = this.readid (); node = this.split (symbol); } else { console.assert(false); return 0; } } else { symbol = this.table[node].symbol; } // increment weights and re-balance // the coding tree this.increment (node); return symbol; }; // stand alone compressor, mostly for testing Huffman.MAGIC = 'huff'; Huffman.compressFile = Util.compressFileHelper(Huffman.MAGIC, function(input, output, size, props) { var bitstream = new BitStream(output); var alphabetSize = 256; if (size < 0) { alphabetSize++; } var huff = new Huffman(257, alphabetSize, bitstream, 8191); Util.compressWithModel(input, size, huff); bitstream.flush(); }); // stand alone decompresser, again for testing Huffman.decompressFile = Util.decompressFileHelper(Huffman.MAGIC, function(input, output, size) { var bitstream = new BitStream(input); var alphabetSize = 256; if (size < 0) { alphabetSize++; } var huff = new Huffman(257, alphabetSize, bitstream, 8191); Util.decompressWithModel(output, size, huff); }); return Huffman; });