UNPKG

vox-core

Version:

Runtime de aplicaciones multiplataforma

242 lines (222 loc) 9.28 kB
/* Tweaked version of LZJB, using range coder. */ if (typeof define !== 'function') { var define = require('amdefine')(module); } define(['./Context1Model','./FenwickModel','./LogDistanceModel','./NoModel','./RangeCoder','./Stream','./Util'],function(Context1Model,FenwickModel,LogDistanceModel,NoModel,RangeCoder,Stream,Util){ var LzjbR = Object.create(null); LzjbR.MAGIC = 'lzjR'; // Constants was used for compress/decompress function. var NBBY = 8, MATCH_BITS = 6, MATCH_MIN = 3, MATCH_MAX = ((1 << MATCH_BITS) + (MATCH_MIN - 1)), OFFSET_MASK = ((1 << (16 - MATCH_BITS)) - 1), LEMPEL_SIZE_BASE = 1024; var LENGTH_MODEL_CUTOFF = 32; /** * Compress using modified LZJB algorithm. Instead of using the simple * 9-bit literal / 17-bit match format of the original, use a range * coder for the literal/match bit and for the offset and length. */ LzjbR.compressFile = Util.compressFileHelper(LzjbR.MAGIC, function(inStream, outStream, fileSize, props, finalByte) { var sstart, dstart = [], slen, src = 0, dst = 0, cpy, copymap, mlen, offset, hash, hp, lempel, i, j; // in an improvement over the original C implementation of LZJB, we expand // the hash table to track a number of potential matches, not just the // most recent. This doesn't require any changes to the decoder. var LEMPEL_SIZE = LEMPEL_SIZE_BASE; var EXPAND = 1; // default to original C impl if (typeof(props)==='number') { LEMPEL_SIZE *= 2; props = Math.max(1, Math.min(9, props)) - 1; EXPAND = 1<<Math.floor(props/2); if (props&1) EXPAND = Math.round(EXPAND * 1.5); if (props >=2 && props <= 4) EXPAND++; } var encoder = new RangeCoder(outStream); encoder.encodeStart(finalByte, 1); // use Uint16Array if available (zero-filled) lempel = Util.makeU16Buffer(LEMPEL_SIZE * EXPAND); var window = Util.makeU8Buffer(OFFSET_MASK+1); var windowpos = 0; var winput = function(_byte) { window[windowpos++] = _byte; if (windowpos >= window.length) { windowpos = 0; } return _byte; }; var unbuffer = []; var get = function() { if (unbuffer.length) return unbuffer.pop(); return inStream.readByte(); }; var unget = function(_byte) { unbuffer.push(_byte); }; var matchpossibility = []; var MATCH = 256; var EOF_SYM = 257; var noModelFactory = NoModel.factory(encoder); var modelFactory = FenwickModel.factory(encoder, 0xFF00, 0x100); var literalModel = new Context1Model(modelFactory, 256, ((fileSize<0) ? EOF_SYM : MATCH) + 1); var sparseModelFactory = function(size) { if (size <= LENGTH_MODEL_CUTOFF) { return modelFactory(size); } return noModelFactory(size); }; var lenModel = new LogDistanceModel((MATCH_MAX-MATCH_MIN)+1, 0, modelFactory, sparseModelFactory); var posModel = new LogDistanceModel(OFFSET_MASK+1, 1, modelFactory, sparseModelFactory); var lastChar = 0x20, lastOffset = 0; while (true) { var initialPos = windowpos; var c1 = get(); if (c1 === Stream.EOF) break; var c2 = get(); if (c2 === Stream.EOF) { literalModel.encode(winput(c1), lastChar); // literal, not a match break; } var c3 = get(); if (c3 === Stream.EOF) { literalModel.encode(winput(c1), lastChar); // literal, not a match unget(c2); lastChar = c1; continue; } hash = (c1 << 16) + (c2 << 8) + c3; hash ^= (hash >> 9); hash += (hash >> 5); hash ^= c1; hp = (hash & (LEMPEL_SIZE - 1)) * EXPAND; matchpossibility.length = 0; for (j=0; j<EXPAND; j++) { offset = (windowpos - lempel[hp+j]) & OFFSET_MASK; cpy = window.length + windowpos - offset; var w1 = window[cpy & OFFSET_MASK]; var w2 = window[(cpy+1) & OFFSET_MASK]; var w3 = window[(cpy+2) & OFFSET_MASK]; // if offset is small, we might not have copied the tentative // bytes into the window yet. (Note that offset=0 really means // offset=(OFFSET_MASK+1).) if (offset==1) { w2 = c1; w3 = c2; } else if (offset==2) { w3 = c1; } if (c1 === w1 && c2 === w2 && c3 === w3) { matchpossibility.push(offset); } } // store this location in the hash, move the others over to make room // oldest match drops off for (j=EXPAND-1; j>0; j--) lempel[hp+j] = lempel[hp+j-1]; lempel[hp] = windowpos; // did we find any matches? if (matchpossibility.length === 0) { literalModel.encode(winput(c1), lastChar); // literal, not a match unget(c3); unget(c2); lastChar = c1; } else { literalModel.encode(MATCH, lastChar); // a match! // find the longest of the possible matches winput(c1); winput(c2); winput(c3); lastChar = c3; var c4 = get(), last = matchpossibility[0]; var base = window.length + windowpos; for (mlen = MATCH_MIN; mlen < MATCH_MAX; mlen++, base++) { if (c4 === Stream.EOF) break; for (j=0; j < matchpossibility.length; ) { var w4 = window[(base - matchpossibility[j]) & OFFSET_MASK]; if (c4 !== w4) { last = matchpossibility[j]; matchpossibility.splice(j, 1); } else { j++; } } if (matchpossibility.length===0) break; // no more matches winput(c4); lastChar = c4; c4 = get(); } if (matchpossibility.length !== 0) { // maximum length match, rock on! last = matchpossibility[0]; } unget(c4); // encode match length // XXX we could get a bit more compression if we allowed // the length to predict the offset (or vice-versa) lenModel.encode(mlen - MATCH_MIN); offset = (initialPos - last) & OFFSET_MASK; if (offset === lastOffset) { posModel.encode(-1); // common case! } else { posModel.encode(offset); lastOffset = offset; } } } if (fileSize < 0) { literalModel.encode(EOF_SYM, lastChar); // end of file (streaming) } encoder.encodeFinish(); }, true); /** * Decompress using modified LZJB algorithm. */ LzjbR.decompressFile = Util.decompressFileHelper(LzjbR.MAGIC, function(inStream, outStream, outSize) { var sstart, dstart = [], slen, src = 0, dst = 0, cpy, copymap, mlen, offset, i, c; var window = Util.makeU8Buffer(OFFSET_MASK+1); var windowpos = 0; var decoder = new RangeCoder(inStream); decoder.decodeStart(true/* we already read the 'free' byte*/); var MATCH = 256; var EOF_SYM = 257; var noModelFactory = NoModel.factory(decoder); var modelFactory = FenwickModel.factory(decoder, 0xFF00, 0x100); var literalModel = new Context1Model(modelFactory, 256, ((outSize<0) ? EOF_SYM : MATCH) + 1); var sparseModelFactory = function(size) { if (size <= LENGTH_MODEL_CUTOFF) { return modelFactory(size); } return noModelFactory(size); }; var lenModel = new LogDistanceModel((MATCH_MAX-MATCH_MIN)+1, 0, modelFactory, sparseModelFactory); var posModel = new LogDistanceModel(OFFSET_MASK+1, 1, modelFactory, sparseModelFactory); var lastChar = 0x20, lastOffset = 0; while (outSize !== 0) { c = literalModel.decode(lastChar); if (c === EOF_SYM) { break; } else if (c === MATCH) { mlen = lenModel.decode() + MATCH_MIN; cpy = posModel.decode(); if (cpy<0) { cpy = lastOffset; } else { lastOffset = cpy; } if (outSize >= 0) outSize -= mlen; while (--mlen >= 0) { c = lastChar = window[windowpos++] = window[cpy++]; outStream.writeByte(c); if (windowpos >= window.length) { windowpos=0; } if (cpy >= window.length) { cpy = 0; } } } else { outStream.writeByte(c); window[windowpos++] = lastChar = c; if (windowpos >= window.length) { windowpos=0; } if (outSize >= 0) outSize--; } } decoder.decodeFinish(); }); return LzjbR; });