/* (c) Copyright 2015-2017 Kevin Kelley . */ function fmt_rgb_buf (n, buf) { var s = ""; for (var y = 0; y < n; ++y) { for (var x = 0; x < n; ++x) { var offset = ((y*n)+x)*4; s += fmt_u8(buf[offset+0]) + fmt_u8(buf[offset+1]) + fmt_u8(buf[offset+2]) + fmt_u8(buf[offset+3]) + ' '; } s += '\n'; } return s; } var fmt_u8 = function (x) { var pad = '00'; var s = x.toString(16); return pad.substring(0, pad.length - s.length) + s; }; var fmt_u16 = function (x) { var pad = '0000'; var s = x.toString(16); return pad.substring(0, pad.length - s.length) + s; }; var fmt_u32 = function (x) { // cheesy way to ger around the sign bit return fmt_u16(x >>> 16) + fmt_u16(x & 0xFFFF); }; var fmt_s8 = function (x) { if (x < 0) x += (1 << 8); return fmt_u8(x); }; var fmt_s16 = function (x) { if (x < 0) x += (1 << 16); return fmt_u16(x); }; var fmt_s32 = function (x) { if (x < 0) x += (1 << 32); return fmt_u32(x); }; var fmt_array = function(f, xx) { xx = Array.from(xx); // if xx is a typed array, the strings fmt_u8 returns will be silently coerced back to numbers return '[' + xx.map(f).join(', ') + ']'; }; var fmt_u8a = function (xx) { return fmt_array(fmt_u8, xx); }; var fmt_u16a = function (xx) { return fmt_array(fmt_u16, xx); }; var fmt_u32a = function (xx) { return fmt_array(fmt_u32, xx); }; var fmt_s8a = function (xx) { return fmt_array(fmt_s8, xx); }; var fmt_s16a = function (xx) { return fmt_array(fmt_s16, xx); }; var fmt_s32a = function (xx) { return fmt_array(fmt_s32, xx); }; var inRangeIncl = function (x, a, b) { return (x >= a && x <= b); }; var inRange = function (x, a, b) { return (x >= a && x < b); }; var swap32 = function (val) { return ((val & 0xFF) << 24) | ((val & 0xFF00) << 8) | ((val >> 8) & 0xFF00) | ((val >> 24) & 0xFF); }; var arrayEq = function (a, b) { if (a.length != b.length) return false; for (var i = 0; i < a.length; ++i) if (a[i] != b[i]) return false; return true; }; var isEmpty = function (obj) { for(var prop in obj) if(obj.hasOwnProperty(prop)) return false; return true; }; var clamp = function (x) { x = ~~x; if (x <= 0) return 0; if (x >= 255) return 255; return x; }; var BitStream; var JpegHuffmanTable; (function () { "use strict"; BitStream = function (defaults) { // ATEN scan data does not treat 0xFF bytes specially. this._interpretMarkers = false; // True iff we've encountered "end-of-image". this._eoi = false; this._data = defaults.data; // uint8[] if (this._data === undefined) throw 'BitStream constructor requires argument "data"!'; this._nextDword = 0; // uint8 // Fill the readbuf and reservoir with the first two dwords in the data // buffer. this._reservoir = 0; this._bitsInReservoir = 0; this._refill(); this._readbuf = this._reservoir; this._reservoir = 0; this._bitsInReservoir = 0; this._refill(); }; BitStream.prototype = { getPos: function () { return (this._nextDword * 32) - (32 + this._bitsInReservoir); }, skip: function (bits) { while (bits > 0) { var n = Math.min(32, bits); this.read(n); bits -= n; } }, read: function (bits) { // JavaScript's bitwise operators have this limitation. if (bits >= 32) throw 'Number of bits must be less than 32.'; // this.checkInvariants(); var that = this; var moveFromReservoir = function (n) { that._readbuf = (that._readbuf << n) | (that._reservoir >>> (32 - n)); that._reservoir <<= n; that._bitsInReservoir -= n; }; var retval = this._readbuf >>> (32 - bits); // same as this.peek(bits) if (bits > this._bitsInReservoir) { bits -= this._bitsInReservoir; moveFromReservoir(this._bitsInReservoir); this._refill(); } moveFromReservoir(bits); return retval; }, _refill: function () { // this.checkInvariants(); // N.B.: Must use >>> for unsigned shift; >> is sra. if (this._bitsInReservoir != 0) throw 'Oops: in _refill(), bitsInReservoir='+this._bitsInReservoir; for (var i = 0; i < 4; ++i) { var x = this._data[(4 * this._nextDword) + i]; if (x === undefined) throw 'BitStream overran available data!'; // TODO: if interpretMarkers ... this._reservoir = (this._reservoir << 8) | x; this._bitsInReservoir += 8; } this._nextDword += 1; this._reservoir = swap32(this._reservoir); // this.checkInvariants(); }, peek: function (bits) { if (bits >= 32) // due to the fact that the bitwise operators have this limitation throw 'Number of bits must be less than 32.'; // this.checkInvariants(); return this._readbuf >>> (32 - bits); }, showDebug: function () { console.log('stream readbuf='+fmt_u32(this._readbuf)+' reservoir='+fmt_u32(this._reservoir)+' bitsLeft='+this._bitsInReservoir+'d nextDword='+fmt_u16(this._nextDword)); }, checkInvariants: function () { var err = null; if (this._bitsInReservoir < 0 || this._bitsInReservoir > 32) err = 'Oops: BitStream invariant violated: bitsInReservoir='+this._bitsInReservoir; if (err) { console.log(err); this.showDebug(); throw err; } } }; JpegHuffmanTable = function (defaults) { if (!defaults) defaults = {}; if (!defaults.bits || !defaults.huffval) throw 'Arguments bits, huffval are required.'; this._huffsize = new Uint8Array(1<<16); this._huffval_lookup = new Uint8Array(1<<16); // 1 to 16, plus a useless 0th element to make indexing nicer. this._bits = new Uint8Array(17); this._buildTables(defaults.bits, defaults.huffval); }; JpegHuffmanTable.prototype = { _buildTables: function (bits, huffval) { // First, copy BITS. for (var i = 0; i < 17; ++i) { this._bits[i] = bits[i]; // This follows from the fact that the all-ones codeword of each // length is reserved as a prefix for longer codewords, which // implies that there may not be more than (2**i)-1 codewords of // length i bits. if ((1 << i) <= bits[i]) throw 'Oops: bad BITS.'; } // Calculate Huffman codewords. The best explanation for this is a // graphical one. Refer to 'Binary Tree Expansin of DHT' figure // here: // http://www.impulseadventure.com/photo/jpeg-huffman-coding.html. var next_codeword = 0; var codeword_qty = 0; var codeword_idx = 0; // index into HUFFVAL for (var code_len = 1; code_len < 17; ++code_len) { for (var i = 0; i < this._bits[code_len]; ++i) { for (var j = 0; j < (1 << (16 - code_len)); ++j) { this._huffsize[next_codeword + j] = code_len; this._huffval_lookup[next_codeword + j] = huffval[codeword_idx]; } next_codeword += (1 << (16 - code_len)); codeword_idx += 1; } } if (codeword_idx != huffval.length) throw 'Oops: all codewords should be used!'; }, // Reads the next code from the stream. Consumes up to 16 bits. // Returns the corresponding codeword (the value that the code // represents, which is always exactly one byte). readCode: function (stream) { // Some number of bits, 16 < x <= 0, on the least-significant end of // this 16b value are not relevant, depoending on how long the code // actually is. However, we've built our lookup tables so that, // regardless of what value the irrelevant bits have, we will get // the same result. var fixedlenCode = stream.peek(16); // uint16 var codeLen = this._huffsize[fixedlenCode]; stream.skip(codeLen); var codeword = this._huffval_lookup[fixedlenCode]; return codeword; } }; })();