Implement ATEN AST2100 (0x59) support
Tweak heuristic used to identify iKVM servers; it now correctly detects all of the SuperMicro hardware that we've tested it against. Modify test case for TightVNC to avoid triggering iKVM heuristic.
This commit is contained in:
parent
9ab402d95b
commit
0d048acaca
|
|
@ -141,7 +141,8 @@ WebSockets to TCP socket proxy. There is a python proxy included
|
||||||
* Original Logo : Michael Sersen
|
* Original Logo : Michael Sersen
|
||||||
* tight encoding : Michael Tinglof (Mercuri.ca)
|
* tight encoding : Michael Tinglof (Mercuri.ca)
|
||||||
* pixel format conversion : [Alexander Clouter](http://www.digriz.org.uk/)
|
* pixel format conversion : [Alexander Clouter](http://www.digriz.org.uk/)
|
||||||
* ATEN iKVM support : [Alexander Clouter](http://www.digriz.org.uk/)
|
* ATEN iKVM "HARMON" (0x59) support : [Alexander Clouter](http://www.digriz.org.uk/)
|
||||||
|
* ATEN iKVM "AST2100" (0x57) support : [Kevin Kelley](https://github.com/kelleyk)
|
||||||
|
|
||||||
* Included libraries:
|
* Included libraries:
|
||||||
* as3crypto : Henri Torgemane (code.google.com/p/as3crypto)
|
* as3crypto : Henri Torgemane (code.google.com/p/as3crypto)
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,9 @@ var UI;
|
||||||
WebUtil.load_scripts(
|
WebUtil.load_scripts(
|
||||||
{'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js",
|
{'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js",
|
||||||
"input/xtscancodes.js", "input/util.js", "input/devices.js",
|
"input/xtscancodes.js", "input/util.js", "input/devices.js",
|
||||||
"display.js", "inflator.js", "rfb.js", "input/keysym.js"]});
|
"display.js", "inflator.js", "rfb.js", "input/keysym.js",
|
||||||
|
"ast2100/ast2100const.js", "ast2100/ast2100util.js",
|
||||||
|
"ast2100/ast2100idct.js", "ast2100/ast2100.js"]});
|
||||||
|
|
||||||
window.onscriptsload = function () { UI.load(); };
|
window.onscriptsload = function () { UI.load(); };
|
||||||
/* [end skip-as-module] */
|
/* [end skip-as-module] */
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
# AST2100 (0x57) encoding support
|
||||||
|
|
||||||
|
These are notes to accompany the ATEN iKVM "AST2100" (0x59) encoding implementation that this branch contains.
|
||||||
|
|
||||||
|
This implementation is the product of clean-room reverse engineering (that is, I am not and have never been subject to
|
||||||
|
nondisclosure agreements, nor have I had access to proprietary information, related to the subject matter of this
|
||||||
|
project).
|
||||||
|
|
||||||
|
(c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>
|
||||||
|
|
||||||
|
### Current problems / limitations (aka "TODOs")
|
||||||
|
|
||||||
|
- Especially on lower quality settings, you will notice that the picture is not as clear as what the ATEN iKVM client
|
||||||
|
will show you (using the same settings). I'm aware of this issue and intend to fix it. Disabling chroma
|
||||||
|
subsampling will allow the encoder to use VQ when there are few colors (e.g. when you are looking at a terminal); VQ
|
||||||
|
data doesn't have the same quality issue.
|
||||||
|
|
||||||
|
- The code could stand to be much better-tested.
|
||||||
|
|
||||||
|
- The JavaScript files related to the AST2100 decoder are loaded even when noVNC does not use the decoder. It would
|
||||||
|
be nice to lazy-load them only when they are necessary.
|
||||||
|
|
||||||
|
- Lots of globals (functions, constants, etc.) are exposed. Some quick refactoring could tuck the majority of them
|
||||||
|
away to avoid cluttering the namespace.
|
||||||
|
|
||||||
|
### Profiling
|
||||||
|
|
||||||
|
- For some reason, when I use blitImageData() (with the noVNC render queue disabled), that function shows up as the
|
||||||
|
"heaviest" function in Chrome's CPU profiler, even though the function is doing nothing other than evaluating a
|
||||||
|
branch condition or two and then calling _rgbxImageData(). When I call _rgbxImageData() directly, then
|
||||||
|
putImageData() (the Canvas method that's actually doing the heavy lifting) is correctly shown as the "heaviest"
|
||||||
|
function.
|
||||||
|
|
||||||
|
- Profiler oddness aside, putImageData() is overwhelmingly the dominant cost; it seems to occupy 75-85% of the CPU
|
||||||
|
time that noVNC uses. There are plenty of places that we could get small performance improvements in Ast2100Decoder
|
||||||
|
(and elsewhere in noVNC) but they seem unlikely to have a worthwhile impact, given that fact.
|
||||||
|
|
||||||
|
### About the implementation
|
||||||
|
|
||||||
|
- One large, remaining inefficiency is the several times that image data is copied around before being blitted. The
|
||||||
|
Ast2100Decoder class generates as output 256-element arrays (representing 64 pixels as (R,G,B,A) 4-tuples). This is
|
||||||
|
exactly what winds up in the ImageData object that is eventually passed to putImageData(); we could just have the
|
||||||
|
decoder write its output directly into those arrays if we wanted.
|
||||||
|
|
||||||
|
### Performance questions
|
||||||
|
|
||||||
|
- Is it faster to call putImageData() fewer times with larger buffers? We could collect groups of blocks (or even an
|
||||||
|
entire frame) and then call putImageData() once. (Of course, this would require redrawing unchanged regions every
|
||||||
|
frame, too.)
|
||||||
|
|
@ -0,0 +1,482 @@
|
||||||
|
"use strict";
|
||||||
|
/*global DCTSIZE, DCTSIZE2, JpegHuffmanTable, BitStream, AST2100IDCT, AAN_IDCT_SCALING_FACTORS, ZIGZAG_ORDER */
|
||||||
|
/*global ATEN_QT_LUMA, ATEN_QT_CHROMA, TABLE_CLASS_AC, TABLE_CLASS_DC */
|
||||||
|
/*global BITS_AC_LUMA, BITS_AC_CHROMA, BITS_DC_LUMA, BITS_DC_CHROMA, HUFFVAL_AC_LUMA, HUFFVAL_AC_CHROMA, HUFFVAL_DC_LUMA, HUFFVAL_DC_CHROMA */
|
||||||
|
/*global inRangeIncl, isEmpty */
|
||||||
|
/*global fmt_u8a, fmt_u8, fmt_u16 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
(c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>.
|
||||||
|
|
||||||
|
This implementation is the product of clean-room reverse engineering (that is, I
|
||||||
|
am not and have never been subject to enondisclosure agreements, nor have I had
|
||||||
|
access to proprietary information, related to the subject matter of this
|
||||||
|
project).
|
||||||
|
*/
|
||||||
|
|
||||||
|
var verboseDebug = false;
|
||||||
|
var verboseMcuCount = false;
|
||||||
|
var traceUpdates = false;
|
||||||
|
var verboseStats = false;
|
||||||
|
var verboseVideoSettings = true;
|
||||||
|
|
||||||
|
|
||||||
|
var Ast2100Decoder;
|
||||||
|
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Ast2100Decoder = function (defaults) {
|
||||||
|
|
||||||
|
this._blitCallback = defaults.blitCallback;
|
||||||
|
this._frame_width = defaults.width;
|
||||||
|
this._frame_height = defaults.height;
|
||||||
|
|
||||||
|
if (!this._frame_width || !this._frame_height)
|
||||||
|
throw 'Missing required parameter: width, height';
|
||||||
|
|
||||||
|
// Either 444u or 422u (though the "422" mode is really 4:2:0, where
|
||||||
|
// chroma is subsampled by a factor of two in each direction).. Applies
|
||||||
|
// only to JPEG-ish blocks, not to VQ blocks; but VQ blocks seem to only
|
||||||
|
// appear in 4:4:4 color mode.
|
||||||
|
this.subsamplingMode = -1;
|
||||||
|
|
||||||
|
this.quantTables = [new Int32Array(64), new Int32Array(64)];
|
||||||
|
this._loadedQuantTables = [-1, -1];
|
||||||
|
|
||||||
|
this.huffTables = [
|
||||||
|
[new JpegHuffmanTable({bits: BITS_DC_LUMA, huffval: HUFFVAL_DC_LUMA}),
|
||||||
|
new JpegHuffmanTable({bits: BITS_DC_CHROMA, huffval: HUFFVAL_DC_CHROMA})],
|
||||||
|
[new JpegHuffmanTable({bits: BITS_AC_LUMA, huffval: HUFFVAL_AC_LUMA}),
|
||||||
|
new JpegHuffmanTable({bits: BITS_AC_CHROMA, huffval: HUFFVAL_AC_CHROMA})],
|
||||||
|
];
|
||||||
|
|
||||||
|
this._scan_components = [
|
||||||
|
{huffTableSelectorDC: 0, huffTableSelectorAC: 0}, // Y
|
||||||
|
{huffTableSelectorDC: 1, huffTableSelectorAC: 1}, // Cb
|
||||||
|
{huffTableSelectorDC: 1, huffTableSelectorAC: 1} // Cr
|
||||||
|
];
|
||||||
|
this._scan_prev_dc = [0, 0, 0];
|
||||||
|
|
||||||
|
this._mcuPosX = 0;
|
||||||
|
this._mcuPosY = 0;
|
||||||
|
|
||||||
|
this._initializeVq();
|
||||||
|
|
||||||
|
// Allocate memory here once and then re-use it. These buffers store
|
||||||
|
// data after entropy decoding (Huffman, zigzag) and before we dequant
|
||||||
|
// and apply the IDCT.
|
||||||
|
this._tmpBufY = [
|
||||||
|
new Int16Array(DCTSIZE2),
|
||||||
|
new Int16Array(DCTSIZE2),
|
||||||
|
new Int16Array(DCTSIZE2),
|
||||||
|
new Int16Array(DCTSIZE2)];
|
||||||
|
this._tmpBufCb = new Int16Array(DCTSIZE2);
|
||||||
|
this._tmpBufCr = new Int16Array(DCTSIZE2);
|
||||||
|
|
||||||
|
// These buffers store IDCT output.
|
||||||
|
this._componentBufY = [
|
||||||
|
new Uint8Array(DCTSIZE2),
|
||||||
|
new Uint8Array(DCTSIZE2),
|
||||||
|
new Uint8Array(DCTSIZE2),
|
||||||
|
new Uint8Array(DCTSIZE2)];
|
||||||
|
this._componentBufCb = new Uint8Array(DCTSIZE2);
|
||||||
|
this._componentBufCr = new Uint8Array(DCTSIZE2);
|
||||||
|
|
||||||
|
// The final RGB output goes here; noVNC expects each group of four
|
||||||
|
// elements to represent one RGBA pixel. this._outputBuf = new
|
||||||
|
// Uint8Array(DCTSIZE2 * 4);
|
||||||
|
this._outputBuf = new Uint8Array(DCTSIZE2 * 4);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Ast2100Decoder.prototype = {
|
||||||
|
|
||||||
|
// TODO: Could/should remove this indirection.
|
||||||
|
_idct: function (quant_table, data_unit, dstBuf) {
|
||||||
|
if (!quant_table)
|
||||||
|
throw 'Required argument missing: quant_table';
|
||||||
|
if (!data_unit)
|
||||||
|
throw 'Required argument missing: data_unit';
|
||||||
|
if (!dstBuf)
|
||||||
|
throw 'Required argument missing: dstBuf';
|
||||||
|
|
||||||
|
return AST2100IDCT.idct_fixed_aan(quant_table, data_unit, dstBuf);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getMcuSize: function () {
|
||||||
|
return {444: 8, 422: 16}[this.subsamplingMode];
|
||||||
|
},
|
||||||
|
|
||||||
|
// Bakes in C(u)*C(v) and the cosine terms (from the IDCT formula).
|
||||||
|
_loadQuantTable: function (slot, srcTable) {
|
||||||
|
for (var y = 0; y < 8; ++y) {
|
||||||
|
for (var x = 0; x < 8; ++x) {
|
||||||
|
this.quantTables[slot][y*8+x] = ~~(srcTable[y*8+x] * AAN_IDCT_SCALING_FACTORS[x] * AAN_IDCT_SCALING_FACTORS[y] * 65536.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_initializeVq: function () {
|
||||||
|
// These colors are in YCbCr, so they're just black, white, and two
|
||||||
|
// shades of grey.
|
||||||
|
this._vqCodewordLookup = [0, 1, 2, 3];
|
||||||
|
this._vqCodebook = [
|
||||||
|
[0x00, 0x80, 0x80],
|
||||||
|
[0xFF, 0x80, 0x80],
|
||||||
|
[0x80, 0x80, 0x80],
|
||||||
|
[0xC0, 0x80, 0x80]
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update our position variables to point at the next MCU.
|
||||||
|
_advancePosition: function () {
|
||||||
|
var mcuSize = this._getMcuSize();
|
||||||
|
var widthInMcus = ~~(this._frame_width / mcuSize);
|
||||||
|
if (this._frame_width % mcuSize != 0)
|
||||||
|
widthInMcus += 1;
|
||||||
|
var heightInMcus = ~~(this._frame_height / mcuSize);
|
||||||
|
if (this._frame_height % mcuSize != 0)
|
||||||
|
heightInMcus += 1;
|
||||||
|
|
||||||
|
this._mcuPosX += 1;
|
||||||
|
if (this._mcuPosX >= widthInMcus) {
|
||||||
|
this._mcuPosX = 0;
|
||||||
|
this._mcuPosY += 1;
|
||||||
|
}
|
||||||
|
if (this._mcuPosY >= heightInMcus) {
|
||||||
|
this._mcuPosY = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Change the frame's size.
|
||||||
|
setSize: function (width, height) {
|
||||||
|
if (this._frame_width != width || this._frame_height != height)
|
||||||
|
Util.Debug('Ast2100Decoder: frame height changed to '+width+'x'+height);
|
||||||
|
this._frame_width = width;
|
||||||
|
this._frame_height = height;
|
||||||
|
},
|
||||||
|
|
||||||
|
decode: function (data) {
|
||||||
|
|
||||||
|
var mcuIdx = 0;
|
||||||
|
if (verboseStats) {
|
||||||
|
var blockTypeCounter = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // length 16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset state that must be reset between frames.
|
||||||
|
this._scan_prev_dc = [0, 0, 0];
|
||||||
|
this._mcuPosX = 0;
|
||||||
|
this._mcuPosY = 0;
|
||||||
|
|
||||||
|
// First four bytes.
|
||||||
|
var quantTableSelectorLuma = data[0]; // 0 <= x <= 0xB
|
||||||
|
var quantTableSelectorChroma = data[1]; // 0 <= x <= 0xB
|
||||||
|
var subsamplingMode = (data[2] << 8) | data[3]; // 422u or 444u
|
||||||
|
if (this.subsamplingMode != subsamplingMode) {
|
||||||
|
if (verboseVideoSettings)
|
||||||
|
console.log('decode(): new subsampling mode: '+subsamplingMode);
|
||||||
|
this.subsamplingMode = subsamplingMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The remainder of the stream is byte-swapped in four-byte chunks.
|
||||||
|
// BitStream takes care of this.
|
||||||
|
this._stream = new BitStream({data: data});
|
||||||
|
this._stream.skip(16);
|
||||||
|
this._stream.skip(16); // do this in two parts because bits must be < 32; thanks JavaScript!
|
||||||
|
|
||||||
|
if (quantTableSelectorLuma != this._loadedQuantTables[0]) {
|
||||||
|
if (!inRangeIncl(quantTableSelectorLuma, 0, 0xB))
|
||||||
|
throw 'Out-of-range selector for luma quant table: ' + quantTableSelectorLuma.toString(16);
|
||||||
|
if (verboseVideoSettings)
|
||||||
|
console.log('decode(): loading new luma quant table: '+fmt_u8(quantTableSelectorLuma));
|
||||||
|
this._loadQuantTable(0, ATEN_QT_LUMA[quantTableSelectorLuma]);
|
||||||
|
this._loadedQuantTables[0] = quantTableSelectorLuma;
|
||||||
|
}
|
||||||
|
if (quantTableSelectorChroma != this._loadedQuantTables[1]) {
|
||||||
|
if (!inRangeIncl(quantTableSelectorChroma, 0, 0xB))
|
||||||
|
throw 'Out-of-range selector for chroma quant table: ' + quantTableSelectorChroma.toString(16);
|
||||||
|
if (verboseVideoSettings)
|
||||||
|
console.log('decode(): loading new chroma quant table: '+fmt_u8(quantTableSelectorChroma));
|
||||||
|
this._loadQuantTable(1, ATEN_QT_CHROMA[quantTableSelectorChroma]);
|
||||||
|
this._loadedQuantTables[1] = quantTableSelectorChroma;
|
||||||
|
}
|
||||||
|
if (this.subsamplingMode != 422 && this.subsamplingMode != 444)
|
||||||
|
throw 'Unexpected value for subsamplingMode: 0x' + fmt_u16(this.subsamplingMode);
|
||||||
|
|
||||||
|
// The remainder of the stream is byte-swapped in four-byte chunks. BitStream takes care of this.
|
||||||
|
this._stream = new BitStream({data: data});
|
||||||
|
this._stream.skip(16);
|
||||||
|
this._stream.skip(16); // do this in two parts because bits must be < 32; thanks JavaScript!
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
var controlFlag = this._stream.read(4); // uint4
|
||||||
|
|
||||||
|
if (verboseStats)
|
||||||
|
++blockTypeCounter[controlFlag];
|
||||||
|
|
||||||
|
if (verboseMcuCount) {
|
||||||
|
console.log('MCU #' + mcuIdx + '- Control flag: ' + controlFlag.toString(16));
|
||||||
|
console.log(' stream pos = ' + this._stream.getPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlFlag == 0 || controlFlag == 4 || controlFlag == 8 || controlFlag == 0xC) {
|
||||||
|
// JPEG-ish (DCT-compressed) data.
|
||||||
|
|
||||||
|
if (controlFlag == 8 || controlFlag == 0xC) {
|
||||||
|
this._mcuPosX = this._stream.read(8); // uint8
|
||||||
|
this._mcuPosY = this._stream.read(8); // uint8
|
||||||
|
if (traceUpdates)
|
||||||
|
console.log("decode(): read new MCU pos: (0x"+fmt_u8(this._mcuPosX)+",0x"+fmt_u8(this._mcuPosY)+")");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlFlag == 4 || controlFlag == 0xC) {
|
||||||
|
// Haven't seen traffic where this feature is used yet.
|
||||||
|
throw 'Unexpected control flag: alternate quant table';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we always have 4:2:2 chroma subsampling on, we'll read 6 blocks (4 Y, 1 Cr, 1 Cb) and
|
||||||
|
// produce a block of 16x16 pixels.
|
||||||
|
this._parseMcu();
|
||||||
|
|
||||||
|
} else if (inRangeIncl(controlFlag, 5, 7) || inRangeIncl(controlFlag, 0xD, 0xF)) {
|
||||||
|
// VQ-compressed data.
|
||||||
|
|
||||||
|
if (controlFlag >= 0xD) {
|
||||||
|
this._mcuPosX = this._stream.read(8); // uint8
|
||||||
|
this._mcuPosY = this._stream.read(8); // uint8
|
||||||
|
if (traceUpdates)
|
||||||
|
console.log("decode(): read new MCU pos: (0x"+fmt_u8(this._mcuPosX)+",0x"+fmt_u8(this._mcuPosY)+")");
|
||||||
|
}
|
||||||
|
|
||||||
|
var codewordSize = (controlFlag & 7) - 5; // 0 <= codewordSize <= 2
|
||||||
|
this._parseVqBlock(codewordSize);
|
||||||
|
|
||||||
|
} else if (controlFlag == 9) {
|
||||||
|
// Done with frame!
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
throw 'Unexpected control flag: unknown value 0x'+fmt_u8(controlFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
++mcuIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 0x9 "end-of-frame" block is included in this count.
|
||||||
|
if (traceUpdates)
|
||||||
|
console.log("decode(): finished after "+mcuIdx+" blocks");
|
||||||
|
|
||||||
|
if (verboseStats) {
|
||||||
|
var counts = {};
|
||||||
|
for (var i = 0; i < 16; ++i)
|
||||||
|
if (i != 9 && blockTypeCounter[i] > 0)
|
||||||
|
counts[i] = blockTypeCounter[i];
|
||||||
|
if (!isEmpty(counts))
|
||||||
|
console.log(counts);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// - Always 8x8, since chroma subsampling does not apply to
|
||||||
|
// VQ-compressed data. (As a consequence, VQ blocks only seem to
|
||||||
|
// appear when DCT chroma subsampling is disabled; that is, in "444"
|
||||||
|
// mode.)
|
||||||
|
// - Reads 64 * codewordSize bits from the input stream.
|
||||||
|
_parseVqBlock: function (codewordSize) {
|
||||||
|
|
||||||
|
var mcuSize = this._getMcuSize();
|
||||||
|
if (mcuSize != 8)
|
||||||
|
throw 'Unexpected MCU size for VQ block!';
|
||||||
|
if (!inRangeIncl(codewordSize, 0, 2))
|
||||||
|
throw 'Out-of-range codewordSize!';
|
||||||
|
|
||||||
|
var i;
|
||||||
|
|
||||||
|
var y_buf = this._componentBufY[0];
|
||||||
|
var cb_buf = this._componentBufCb;
|
||||||
|
var cr_buf = this._componentBufCr;
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
var setColor = function (j, codeword) {
|
||||||
|
var color = that._vqCodebook[that._vqCodewordLookup[codeword]];
|
||||||
|
y_buf[j] = color[0];
|
||||||
|
cb_buf[j] = color[1];
|
||||||
|
cr_buf[j] = color[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read new codebook data. (That is: new colors for each of our
|
||||||
|
// codewords (the values we'll read from the input data) to map to.)
|
||||||
|
for (i = 0; i < (1 << codewordSize); ++i) {
|
||||||
|
// Read 1b flag and 2b codebook slot number. if flag is set,
|
||||||
|
// read 24b RGB value and set colors[slot #]. Regardless (?),
|
||||||
|
// set the ith codeowrd to map to this slot.
|
||||||
|
var hasNewColor = this._stream.read(1);
|
||||||
|
var codebookSlotIdx = this._stream.read(2);
|
||||||
|
if (hasNewColor) {
|
||||||
|
var color = [this._stream.read(8), this._stream.read(8), this._stream.read(8)]; // Y, Cb, Cr
|
||||||
|
this._vqCodebook[codebookSlotIdx] = color;
|
||||||
|
}
|
||||||
|
this._vqCodewordLookup[i] = codebookSlotIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a block of image data.
|
||||||
|
if (codewordSize == 0) {
|
||||||
|
// Act as though we've got a single-entry codebook.
|
||||||
|
for (i = 0; i < 64; ++i)
|
||||||
|
setColor(i, 0);
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < 64; ++i)
|
||||||
|
setColor(i, this._stream.read(codewordSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform colorspace conversion and copy into destination image buffer.
|
||||||
|
for (var j = 0; j < 64; ++j)
|
||||||
|
this._ycbcrToRgb(this._outputBuf, j, this._componentBufY[0][j], this._componentBufCb[j], this._componentBufCr[j]);
|
||||||
|
|
||||||
|
this._blitCallback(8 * this._mcuPosX, 8 * this._mcuPosY, 8, 8, this._outputBuf);
|
||||||
|
|
||||||
|
this._advancePosition();
|
||||||
|
},
|
||||||
|
|
||||||
|
_parseMcu: function () {
|
||||||
|
|
||||||
|
var qtLuma = this.quantTables[0];
|
||||||
|
var qtChroma = this.quantTables[1];
|
||||||
|
|
||||||
|
this._parseDataUnit(0, this._tmpBufY[0]);
|
||||||
|
this._idct(qtLuma, this._tmpBufY[0], this._componentBufY[0]);
|
||||||
|
if (this.subsamplingMode != 444) {
|
||||||
|
this._parseDataUnit(0, this._tmpBufY[1]);
|
||||||
|
this._idct(qtLuma, this._tmpBufY[1], this._componentBufY[1]);
|
||||||
|
this._parseDataUnit(0, this._tmpBufY[2]);
|
||||||
|
this._idct(qtLuma, this._tmpBufY[2], this._componentBufY[2]);
|
||||||
|
this._parseDataUnit(0, this._tmpBufY[3]);
|
||||||
|
this._idct(qtLuma, this._tmpBufY[3], this._componentBufY[3]);
|
||||||
|
}
|
||||||
|
this._parseDataUnit(1, this._tmpBufCb);
|
||||||
|
this._idct(qtChroma, this._tmpBufCb, this._componentBufCb);
|
||||||
|
this._parseDataUnit(2, this._tmpBufCr);
|
||||||
|
this._idct(qtChroma, this._tmpBufCr, this._componentBufCr);
|
||||||
|
|
||||||
|
if (this.subsamplingMode != 444) {
|
||||||
|
// 4:2:0 subsampling (x2 in each direction), or what ATEN calls "422" (even though it's not).
|
||||||
|
for (var dy = 0; dy < 2; ++ dy) {
|
||||||
|
for (var dx = 0; dx < 2; ++dx) {
|
||||||
|
// for each of the four blocks in this MCU
|
||||||
|
var componentBufY = this._componentBufY[dx*2+dy];
|
||||||
|
for (var y = 0; y < 8; ++y) {
|
||||||
|
for (var x = 0; x < 8; ++x) {
|
||||||
|
var hy = ~~((8*dx+y)/2);
|
||||||
|
var hx = ~~((8*dy+x)/2);
|
||||||
|
this._ycbcrToRgb(this._outputBuf, y*8+x, componentBufY[y*8+x], this._componentBufCb[hy*8+hx], this._componentBufCr[hy*8+hx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._blitCallback(16 * this._mcuPosX + 8 * dy, 16 * this._mcuPosY + 8 * dx, 8, 8, this._outputBuf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No subsampling.
|
||||||
|
for (var j = 0; j < 64; ++j)
|
||||||
|
this._ycbcrToRgb(this._outputBuf, j, this._componentBufY[0][j], this._componentBufCb[j], this._componentBufCr[j]);
|
||||||
|
this._blitCallback(8 * this._mcuPosX, 8 * this._mcuPosY, 8, 8, this._outputBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._advancePosition();
|
||||||
|
},
|
||||||
|
|
||||||
|
_parseDataUnit: function (componentIdx, buf) {
|
||||||
|
var scanComponent = this._scan_components[componentIdx];
|
||||||
|
var dc_hufftable = this.huffTables[TABLE_CLASS_DC][scanComponent.huffTableSelectorDC];
|
||||||
|
var ac_hufftable = this.huffTables[TABLE_CLASS_AC][scanComponent.huffTableSelectorAC];
|
||||||
|
|
||||||
|
var setValue = function (i, val) {
|
||||||
|
buf[ZIGZAG_ORDER[i]] = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
// First element is the DC component, followed by 63 AC components.
|
||||||
|
// The DC component is encoded slightly differently than the AC
|
||||||
|
// components: it is stored as the delta between the value and the
|
||||||
|
// last DC component from the same component.
|
||||||
|
var dc_delta = this._readEncodedValueDC(dc_hufftable);
|
||||||
|
this._scan_prev_dc[componentIdx] += dc_delta;
|
||||||
|
buf[0] = this._scan_prev_dc[componentIdx];
|
||||||
|
|
||||||
|
// Read the AC components.
|
||||||
|
for (var i = 1; i < 64;) {
|
||||||
|
var x = ac_hufftable.readCode(this._stream);
|
||||||
|
|
||||||
|
// N.B.(kelleyk): Renamed r, s to runlen, size.
|
||||||
|
// See ITU T.81 p89 (e.g. Fig F.1).
|
||||||
|
// r is runlength of zeroes; if s==0, r==0 means EOB and r==15
|
||||||
|
// means ZRL. s is number of bits required to represent the
|
||||||
|
// amplitude that follows the codeword.
|
||||||
|
var runlen = x >>> 4;
|
||||||
|
var size = x & 0x0F;
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
if (runlen == 0) { // special EOB (end-of-block) codeword; fill remainder with zeroes
|
||||||
|
while (i < 64) {
|
||||||
|
setValue(i, 0);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if (runlen == 0xF) { // special ZLE (zero-length-encode) codeword; emit sixteen zeroes
|
||||||
|
for (var j = 0; j < 16; ++j)
|
||||||
|
setValue(i + j, 0);
|
||||||
|
i += 16;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit runlen zero entries.
|
||||||
|
for (var j = 0; j < runlen; ++j)
|
||||||
|
setValue(i + j, 0);
|
||||||
|
i += runlen;
|
||||||
|
|
||||||
|
setValue(i, this._readEncodedValueAC(size)); // category=size
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
},
|
||||||
|
|
||||||
|
_readEncodedValueDC: function (huffTable) {
|
||||||
|
var category = huffTable.readCode(this._stream);
|
||||||
|
return this._readEncodedValueAC(category);
|
||||||
|
},
|
||||||
|
|
||||||
|
_readEncodedValueAC: function (category) {
|
||||||
|
if (category == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var value; // sint32
|
||||||
|
var val_sign = this._stream.read(1);
|
||||||
|
|
||||||
|
if (val_sign == 0) {
|
||||||
|
// Negative (unlike the more common two's-complement
|
||||||
|
// representation).
|
||||||
|
value = -(1 << category) + 1;
|
||||||
|
} else {
|
||||||
|
value = (1 << category - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category > 1) {
|
||||||
|
var more_bits = this._stream.read(category - 1); // uint
|
||||||
|
value += more_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
|
_ycbcrToRgb: function (outputBuf, outputOffset, y, cb, cr) {
|
||||||
|
outputOffset *= 4;
|
||||||
|
outputBuf[outputOffset + 0] = clamp(YUVTORGB_Y_TABLE[y] + YUVTORGB_CR_R_TABLE[cr]);
|
||||||
|
outputBuf[outputOffset + 1] = clamp(YUVTORGB_Y_TABLE[y] + YUVTORGB_CR_G_TABLE[cr] + YUVTORGB_CB_G_TABLE[cb]);
|
||||||
|
outputBuf[outputOffset + 2] = clamp(YUVTORGB_Y_TABLE[y] + YUVTORGB_CB_B_TABLE[cb]);
|
||||||
|
outputBuf[outputOffset + 3] = 0xFF; // noVNC expects alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
@ -0,0 +1,337 @@
|
||||||
|
/* (c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>. */
|
||||||
|
|
||||||
|
|
||||||
|
// JPEG zigzag tables
|
||||||
|
var ZIGZAG_ORDER = [
|
||||||
|
0, 1, 8, 16, 9, 2, 3, 10,
|
||||||
|
17, 24, 32, 25, 18, 11, 4, 5,
|
||||||
|
12, 19, 26, 33, 40, 48, 41, 34,
|
||||||
|
27, 20, 13, 6, 7, 14, 21, 28,
|
||||||
|
35, 42, 49, 56, 57, 50, 43, 36,
|
||||||
|
29, 22, 15, 23, 30, 37, 44, 51,
|
||||||
|
58, 59, 52, 45, 38, 31, 39, 46,
|
||||||
|
53, 60, 61, 54, 47, 55, 62, 63,
|
||||||
|
];
|
||||||
|
var DEZIGZAG_ORDER = [
|
||||||
|
0, 1, 5, 6, 14, 15, 27, 28,
|
||||||
|
2, 4, 7, 13, 16, 26, 29, 42,
|
||||||
|
3, 8, 12, 17, 25, 30, 41, 43,
|
||||||
|
9, 11, 18, 24, 31, 40, 44, 53,
|
||||||
|
10, 19, 23, 32, 39, 45, 52, 54,
|
||||||
|
20, 22, 33, 38, 46, 51, 55, 60,
|
||||||
|
21, 34, 37, 47, 50, 56, 59, 61,
|
||||||
|
35, 36, 48, 49, 57, 58, 62, 63,
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
// Huffman tables
|
||||||
|
var BITS_DC_LUMA = [0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
var HUFFVAL_DC_LUMA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||||
|
var BITS_DC_CHROMA = [0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0];
|
||||||
|
var HUFFVAL_DC_CHROMA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||||
|
var BITS_AC_LUMA = [0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d];
|
||||||
|
var HUFFVAL_AC_LUMA = [
|
||||||
|
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
|
||||||
|
0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
|
||||||
|
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
|
||||||
|
0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
|
||||||
|
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
|
||||||
|
0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
|
||||||
|
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
|
||||||
|
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
|
||||||
|
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
|
||||||
|
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
||||||
|
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
|
||||||
|
0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
|
||||||
|
0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
|
||||||
|
0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
|
||||||
|
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
|
||||||
|
0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
|
||||||
|
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
|
||||||
|
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
|
||||||
|
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
|
||||||
|
0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
||||||
|
0xf9, 0xfa
|
||||||
|
];
|
||||||
|
var BITS_AC_CHROMA = [0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77];
|
||||||
|
var HUFFVAL_AC_CHROMA = [
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
|
||||||
|
0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
|
||||||
|
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
|
||||||
|
0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
|
||||||
|
0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
|
||||||
|
0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
|
||||||
|
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
|
||||||
|
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
|
||||||
|
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
|
||||||
|
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||||
|
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
|
||||||
|
0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||||
|
0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||||
|
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
|
||||||
|
0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
|
||||||
|
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
|
||||||
|
0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
|
||||||
|
0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
|
||||||
|
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
|
||||||
|
0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
||||||
|
0xf9, 0xfa
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
// Quant tables
|
||||||
|
var ATEN_QT_LUMA = [
|
||||||
|
[
|
||||||
|
0x14, 0x0D, 0x0C, 0x14, 0x1E, 0x32, 0x3F, 0x4C,
|
||||||
|
0x0F, 0x0F, 0x11, 0x17, 0x20, 0x48, 0x4B, 0x44,
|
||||||
|
0x11, 0x10, 0x14, 0x1E, 0x32, 0x47, 0x56, 0x46,
|
||||||
|
0x11, 0x15, 0x1B, 0x24, 0x3F, 0x6C, 0x64, 0x4D,
|
||||||
|
0x16, 0x1B, 0x2E, 0x46, 0x55, 0x88, 0x80, 0x60,
|
||||||
|
0x1E, 0x2B, 0x44, 0x50, 0x65, 0x82, 0x8D, 0x73,
|
||||||
|
0x3D, 0x50, 0x61, 0x6C, 0x80, 0x97, 0x96, 0x7E,
|
||||||
|
0x5A, 0x73, 0x76, 0x7A, 0x8C, 0x7D, 0x80, 0x7B,
|
||||||
|
], [
|
||||||
|
0x11, 0x0C, 0x0A, 0x11, 0x1A, 0x2B, 0x37, 0x42,
|
||||||
|
0x0D, 0x0D, 0x0F, 0x14, 0x1C, 0x3F, 0x41, 0x3C,
|
||||||
|
0x0F, 0x0E, 0x11, 0x1A, 0x2B, 0x3E, 0x4B, 0x3D,
|
||||||
|
0x0F, 0x12, 0x18, 0x1F, 0x37, 0x5F, 0x57, 0x43,
|
||||||
|
0x13, 0x18, 0x28, 0x3D, 0x4A, 0x77, 0x70, 0x54,
|
||||||
|
0x1A, 0x26, 0x3C, 0x46, 0x58, 0x71, 0x7B, 0x64,
|
||||||
|
0x35, 0x46, 0x55, 0x5F, 0x70, 0x84, 0x83, 0x6E,
|
||||||
|
0x4E, 0x64, 0x67, 0x6B, 0x7A, 0x6D, 0x70, 0x6C,
|
||||||
|
], [
|
||||||
|
0x0E, 9, 9, 0x0E, 0x15, 0x24, 0x2E, 0x37,
|
||||||
|
0x0A, 0x0A, 0x0C, 0x11, 0x17, 0x34, 0x36, 0x31,
|
||||||
|
0x0C, 0x0B, 0x0E, 0x15, 0x24, 0x33, 0x3E, 0x32,
|
||||||
|
0x0C, 0x0F, 0x13, 0x1A, 0x2E, 0x4E, 0x48, 0x38,
|
||||||
|
0x10, 0x13, 0x21, 0x32, 0x3D, 0x62, 0x5D, 0x45,
|
||||||
|
0x15, 0x1F, 0x31, 0x3A, 0x49, 0x5E, 0x66, 0x53,
|
||||||
|
0x2C, 0x3A, 0x46, 0x4E, 0x5D, 0x6D, 0x6C, 0x5B,
|
||||||
|
0x41, 0x53, 0x56, 0x58, 0x65, 0x5A, 0x5D, 0x59,
|
||||||
|
], [
|
||||||
|
0x0B, 7, 7, 0x0B, 0x11, 0x1C, 0x24, 0x2B,
|
||||||
|
8, 8, 0x0A, 0x0D, 0x12, 0x29, 0x2B, 0x27,
|
||||||
|
0x0A, 9, 0x0B, 0x11, 0x1C, 0x28, 0x31, 0x28,
|
||||||
|
0x0A, 0x0C, 0x0F, 0x14, 0x24, 0x3E, 0x39, 0x2C,
|
||||||
|
0x0C, 0x0F, 0x1A, 0x28, 0x30, 0x4E, 0x4A, 0x37,
|
||||||
|
0x11, 0x19, 0x27, 0x2E, 0x3A, 0x4A, 0x51, 0x42,
|
||||||
|
0x23, 0x2E, 0x38, 0x3E, 0x4A, 0x56, 0x56, 0x48,
|
||||||
|
0x33, 0x42, 0x44, 0x46, 0x50, 0x47, 0x4A, 0x47,
|
||||||
|
], [
|
||||||
|
9, 6, 5, 9, 0x0D, 0x16, 0x1C, 0x22,
|
||||||
|
6, 6, 7, 0x0A, 0x0E, 0x20, 0x21, 0x1E,
|
||||||
|
7, 7, 9, 0x0D, 0x16, 0x20, 0x26, 0x1F,
|
||||||
|
7, 9, 0x0C, 0x10, 0x1C, 0x30, 0x2D, 0x22,
|
||||||
|
0x0A, 0x0C, 0x14, 0x1F, 0x26, 0x3D, 0x39, 0x2B,
|
||||||
|
0x0D, 0x13, 0x1E, 0x24, 0x2D, 0x3A, 0x3F, 0x33,
|
||||||
|
0x1B, 0x24, 0x2B, 0x30, 0x39, 0x44, 0x43, 0x38,
|
||||||
|
0x28, 0x33, 0x35, 0x37, 0x3F, 0x38, 0x39, 0x37,
|
||||||
|
], [
|
||||||
|
6, 4, 3, 6, 9, 0x0F, 0x13, 0x16,
|
||||||
|
4, 4, 5, 7, 9, 0x15, 0x16, 0x14,
|
||||||
|
5, 4, 6, 9, 0x0F, 0x15, 0x19, 0x15,
|
||||||
|
5, 6, 8, 0x0A, 0x13, 0x20, 0x1E, 0x17,
|
||||||
|
6, 8, 0x0D, 0x15, 0x19, 0x28, 0x26, 0x1C,
|
||||||
|
9, 0x0D, 0x14, 0x18, 0x1E, 0x27, 0x2A, 0x22,
|
||||||
|
0x12, 0x18, 0x1D, 0x20, 0x26, 0x2D, 0x2D, 0x25,
|
||||||
|
0x1B, 0x22, 0x23, 0x24, 0x2A, 0x25, 0x26, 0x25,
|
||||||
|
], [
|
||||||
|
3, 2, 1, 3, 4, 7, 9, 0x0B,
|
||||||
|
2, 2, 2, 3, 4, 0x0A, 0x0B, 0x0A,
|
||||||
|
2, 2, 3, 4, 7, 0x0A, 0x0C, 0x0A,
|
||||||
|
2, 3, 4, 5, 9, 0x10, 0x0F, 0x0B,
|
||||||
|
3, 4, 6, 0x0A, 0x0C, 0x14, 0x13, 0x0E,
|
||||||
|
4, 6, 0x0A, 0x0C, 0x0F, 0x13, 0x15, 0x11,
|
||||||
|
9, 0x0C, 0x0E, 0x10, 0x13, 0x16, 0x16, 0x12,
|
||||||
|
0x0D, 0x11, 0x11, 0x12, 0x15, 0x12, 0x13, 0x12,
|
||||||
|
], [
|
||||||
|
2, 1, 1, 2, 3, 5, 6, 7,
|
||||||
|
1, 1, 1, 2, 3, 7, 7, 6,
|
||||||
|
1, 1, 2, 3, 5, 7, 8, 7,
|
||||||
|
1, 2, 2, 3, 6, 0x0A, 0x0A, 7,
|
||||||
|
2, 2, 4, 7, 8, 0x0D, 0x0C, 9,
|
||||||
|
3, 4, 6, 8, 0x0A, 0x0D, 0x0E, 0x0B,
|
||||||
|
6, 8, 9, 0x0A, 0x0C, 0x0F, 0x0F, 0x0C,
|
||||||
|
9, 0x0B, 0x0B, 0x0C, 0x0E, 0x0C, 0x0C, 0x0C,
|
||||||
|
], [
|
||||||
|
2, 1, 1, 2, 3, 5, 6, 7,
|
||||||
|
1, 1, 1, 2, 3, 7, 7, 6,
|
||||||
|
1, 1, 2, 3, 5, 7, 8, 7,
|
||||||
|
1, 2, 2, 3, 6, 0x0A, 0x0A, 7,
|
||||||
|
2, 2, 4, 7, 8, 0x0D, 0x0C, 9,
|
||||||
|
3, 4, 6, 8, 0x0A, 0x0D, 0x0E, 0x0B,
|
||||||
|
6, 8, 9, 0x0A, 0x0C, 0x0F, 0x0F, 0x0C,
|
||||||
|
9, 0x0B, 0x0B, 0x0C, 0x0E, 0x0C, 0x0C, 0x0C,
|
||||||
|
], [
|
||||||
|
1, 1, 1, 1, 2, 3, 4, 5,
|
||||||
|
1, 1, 1, 1, 2, 5, 5, 5,
|
||||||
|
1, 1, 1, 2, 3, 5, 6, 5,
|
||||||
|
1, 1, 2, 2, 4, 8, 7, 5,
|
||||||
|
1, 2, 3, 5, 6, 0x0A, 9, 7,
|
||||||
|
2, 3, 5, 6, 7, 9, 0x0A, 8,
|
||||||
|
4, 6, 7, 8, 9, 0x0B, 0x0B, 9,
|
||||||
|
6, 8, 8, 9, 0x0A, 9, 9, 9,
|
||||||
|
], [
|
||||||
|
1, 1, 1, 1, 1, 2, 3, 3,
|
||||||
|
1, 1, 1, 1, 1, 3, 3, 3,
|
||||||
|
1, 1, 1, 1, 2, 3, 4, 3,
|
||||||
|
1, 1, 1, 1, 3, 5, 5, 3,
|
||||||
|
1, 1, 2, 3, 4, 6, 6, 4,
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 5,
|
||||||
|
3, 4, 4, 5, 6, 7, 7, 6,
|
||||||
|
4, 5, 5, 6, 7, 6, 6, 6,
|
||||||
|
], [
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 2, 1,
|
||||||
|
1, 1, 1, 1, 1, 2, 2, 1,
|
||||||
|
1, 1, 1, 1, 2, 3, 3, 2,
|
||||||
|
1, 1, 1, 2, 2, 3, 3, 2,
|
||||||
|
1, 2, 2, 2, 3, 3, 3, 3,
|
||||||
|
2, 2, 2, 3, 3, 3, 3, 3,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
var ATEN_QT_CHROMA = [
|
||||||
|
[
|
||||||
|
0x1F, 0x21, 0x2D, 0x58, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||||
|
0x21, 0x27, 0x30, 0x7B, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||||
|
0x2D, 0x30, 0x69, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||||
|
0x58, 0x7B, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||||
|
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||||
|
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||||
|
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||||
|
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||||
|
], [
|
||||||
|
0x1B, 0x1D, 0x27, 0x4C, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||||
|
0x1D, 0x22, 0x2A, 0x6B, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||||
|
0x27, 0x2A, 0x5B, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||||
|
0x4C, 0x6B, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||||
|
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||||
|
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||||
|
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||||
|
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||||
|
], [
|
||||||
|
0x16, 0x18, 0x20, 0x3F, 0x85, 0x85, 0x85, 0x85,
|
||||||
|
0x18, 0x1C, 0x22, 0x58, 0x85, 0x85, 0x85, 0x85,
|
||||||
|
0x20, 0x22, 0x4B, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||||
|
0x3F, 0x58, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||||
|
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||||
|
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||||
|
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||||
|
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||||
|
], [
|
||||||
|
0x12, 0x13, 0x1A, 0x33, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||||
|
0x13, 0x16, 0x1C, 0x48, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||||
|
0x1A, 0x1C, 0x3D, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||||
|
0x33, 0x48, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||||
|
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||||
|
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||||
|
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||||
|
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||||
|
], [
|
||||||
|
0x0D, 0x0E, 0x13, 0x26, 0x50, 0x50, 0x50, 0x50,
|
||||||
|
0x0E, 0x11, 0x15, 0x35, 0x50, 0x50, 0x50, 0x50,
|
||||||
|
0x13, 0x15, 0x2D, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||||
|
0x26, 0x35, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||||
|
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||||
|
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||||
|
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||||
|
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||||
|
], [
|
||||||
|
9, 0x0A, 0x0D, 0x1A, 0x37, 0x37, 0x37, 0x37,
|
||||||
|
0x0A, 0x0B, 0x0E, 0x25, 0x37, 0x37, 0x37, 0x37,
|
||||||
|
0x0D, 0x0E, 0x1F, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||||
|
0x1A, 0x25, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||||
|
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||||
|
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||||
|
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||||
|
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||||
|
], [
|
||||||
|
4, 5, 6, 0x0D, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||||
|
5, 5, 7, 0x12, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||||
|
6, 7, 0x0F, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||||
|
0x0D, 0x12, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||||
|
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||||
|
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||||
|
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||||
|
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||||
|
], [
|
||||||
|
3, 3, 4, 8, 0x12, 0x12, 0x12, 0x12,
|
||||||
|
3, 3, 4, 0x0C, 0x12, 0x12, 0x12, 0x12,
|
||||||
|
4, 4, 0x0A, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||||
|
8, 0x0C, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||||
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||||
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||||
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||||
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||||
|
], [
|
||||||
|
2, 2, 3, 7, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||||
|
2, 3, 4, 0x0A, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||||
|
3, 4, 8, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||||
|
7, 0x0A, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||||
|
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||||
|
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||||
|
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||||
|
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||||
|
], [
|
||||||
|
2, 2, 3, 5, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||||
|
2, 2, 3, 8, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||||
|
3, 3, 7, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||||
|
5, 8, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||||
|
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||||
|
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||||
|
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||||
|
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||||
|
], [
|
||||||
|
1, 1, 2, 4, 9, 9, 9, 9,
|
||||||
|
1, 1, 2, 6, 9, 9, 9, 9,
|
||||||
|
2, 2, 5, 9, 9, 9, 9, 9,
|
||||||
|
4, 6, 9, 9, 9, 9, 9, 9,
|
||||||
|
9, 9, 9, 9, 9, 9, 9, 9,
|
||||||
|
9, 9, 9, 9, 9, 9, 9, 9,
|
||||||
|
9, 9, 9, 9, 9, 9, 9, 9,
|
||||||
|
9, 9, 9, 9, 9, 9, 9, 9,
|
||||||
|
], [
|
||||||
|
1, 1, 1, 2, 6, 6, 6, 6,
|
||||||
|
1, 1, 1, 4, 6, 6, 6, 6,
|
||||||
|
1, 1, 3, 6, 6, 6, 6, 6,
|
||||||
|
2, 4, 6, 6, 6, 6, 6, 6,
|
||||||
|
6, 6, 6, 6, 6, 6, 6, 6,
|
||||||
|
6, 6, 6, 6, 6, 6, 6, 6,
|
||||||
|
6, 6, 6, 6, 6, 6, 6, 6,
|
||||||
|
6, 6, 6, 6, 6, 6, 6, 6,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ref.: libjpeg's jddctmgr.c:
|
||||||
|
"For float AA&N IDCT method, multipliers are equal to quantization
|
||||||
|
coefficients scaled by scalefactor[row]*scalefactor[col], where
|
||||||
|
scalefactor[0] = 1
|
||||||
|
scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
|
||||||
|
*/
|
||||||
|
var AAN_IDCT_SCALING_FACTORS = [
|
||||||
|
1.0, 1.387039845, 1.306562965, 1.175875602,
|
||||||
|
1.0, 0.785694958, 0.541196100, 0.275899379,
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
var YUVTORGB_CR_G_TABLE = new Int32Array(256);
|
||||||
|
var YUVTORGB_CR_R_TABLE = new Int32Array(256);
|
||||||
|
var YUVTORGB_CB_G_TABLE = new Int32Array(256);
|
||||||
|
var YUVTORGB_CB_B_TABLE = new Int32Array(256);
|
||||||
|
var YUVTORGB_Y_TABLE = new Int32Array(256);
|
||||||
|
for (var i = 0; i < 256; ++i) {
|
||||||
|
YUVTORGB_CR_G_TABLE[i] = (0x688000 - i * 0xD000) >> 16;
|
||||||
|
YUVTORGB_CR_R_TABLE[i] = (0xFF340000 + i * 0x19900) >> 16;
|
||||||
|
YUVTORGB_CB_G_TABLE[i] = (0x328000 - i * 0x6400) >> 16;
|
||||||
|
YUVTORGB_CB_B_TABLE[i] = (0xFEFE8000 + i * 0x20400) >> 16;
|
||||||
|
YUVTORGB_Y_TABLE[i] = (0xFFEDE040 + i * 0x129FC) >> 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var TABLE_CLASS_DC = 0;
|
||||||
|
var TABLE_CLASS_AC = 1;
|
||||||
|
var DCTSIZE = 8;
|
||||||
|
var DCTSIZE2 = DCTSIZE * DCTSIZE;
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
/* (c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>. */
|
||||||
|
|
||||||
|
|
||||||
|
var AST2100IDCT;
|
||||||
|
|
||||||
|
|
||||||
|
// N.B.: These values assume CONST_BITS == 8
|
||||||
|
var FIX_1_082392200 = 277; // fix(1.082392200)
|
||||||
|
var FIX_1_414213562 = 362; // fix(1.414213562)
|
||||||
|
var FIX_1_847759065 = 473; // fix(1.847759065)
|
||||||
|
var FIX_2_613125930 = 669; // fix(2.613125930)
|
||||||
|
|
||||||
|
|
||||||
|
var MAXJSAMPLE = 255; // largest value of a sample; 8-bit, so 2**8-1
|
||||||
|
var CONST_BITS = 8;
|
||||||
|
var PASS1_BITS = 0;
|
||||||
|
|
||||||
|
// This is like clamp() except that it also adds the MAXJSAMPLE/2 offset.
|
||||||
|
var range_limit = function (x) {
|
||||||
|
// XXX(kelleyk): This offset is baked into the range limit table in the ATEN
|
||||||
|
// stuff and in libjpeg; see commpent above prepare_range_limit_table() in
|
||||||
|
// jdmaster.c.
|
||||||
|
x += 128;
|
||||||
|
|
||||||
|
return Math.max(0, Math.min(255, x));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert float to fixed-point.
|
||||||
|
var fix = function (x) {
|
||||||
|
return ~~(x * (1 << CONST_BITS) + 0.5);
|
||||||
|
};
|
||||||
|
|
||||||
|
var fixed_dequant = function (scaled_quant_table, buf, i) {
|
||||||
|
// N.B.: The data in buf is unscaled; the data in scaled_quant_table has
|
||||||
|
// been scaled by 1<<16.
|
||||||
|
return fixed_mul(scaled_quant_table[i], buf[i]);
|
||||||
|
};
|
||||||
|
|
||||||
|
var descale = function (x, n) {
|
||||||
|
// console.log([x, x>>n, (x>>n)+128]);
|
||||||
|
return x >> n;
|
||||||
|
};
|
||||||
|
|
||||||
|
// if not accurate rounding mode...
|
||||||
|
var idescale = descale;
|
||||||
|
// else use better implementation
|
||||||
|
|
||||||
|
// N.B.(kelleyk): This isn't a libjpeg parameter; it just means that ATEN
|
||||||
|
// downshifts all the way back to "normal ints" at the end of pass 1.
|
||||||
|
var END_PASS1_DESCALE_BITS = CONST_BITS;
|
||||||
|
|
||||||
|
var fixed_mul = function (a, b) {
|
||||||
|
return descale(a * b, CONST_BITS);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
AST2100IDCT = {
|
||||||
|
|
||||||
|
// Uses the 16/16 integer representation that ATEN and libjpeg's
|
||||||
|
// jidctfst.c ("fast, not-so-accurate integer IDCT") do. Note that, for
|
||||||
|
// performance, this routine *also* incorporates the dequantization
|
||||||
|
// step, which is why it takes scaled_quant_table as an argument. (This
|
||||||
|
// argument does not actually contain "just" a scaled quant table; some
|
||||||
|
// constants have been pre-multiplied into it. See the function that
|
||||||
|
// loads quant tables for more details.)
|
||||||
|
idct_fixed_aan: function (scaled_quant_table, buf, dstBuf) {
|
||||||
|
|
||||||
|
// ATEN rounds things off early, at a cost to precision: the int32
|
||||||
|
// values in 'workspace' are not scaled at all.
|
||||||
|
var workspace = new Int32Array(64);
|
||||||
|
|
||||||
|
for (var x = 0; x < 8; ++x)
|
||||||
|
AST2100IDCT._aan_idct_col(scaled_quant_table, buf, workspace, x);
|
||||||
|
|
||||||
|
for (var y = 0; y < 8; ++y)
|
||||||
|
AST2100IDCT._aan_idct_row(scaled_quant_table, dstBuf, workspace, y);
|
||||||
|
|
||||||
|
return dstBuf;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Columns; aka "Pass 1".
|
||||||
|
_aan_idct_col: function(scaled_quant_table, buf, workspace, x) {
|
||||||
|
|
||||||
|
var dequant = function (idx) { return fixed_dequant(scaled_quant_table, buf, idx); };
|
||||||
|
var mul = fixed_mul;
|
||||||
|
|
||||||
|
var y;
|
||||||
|
|
||||||
|
var all_ac_zero = true;
|
||||||
|
for (y = 1; y < 8; ++y) {
|
||||||
|
if (buf[8 * y + x] != 0) {
|
||||||
|
all_ac_zero = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (all_ac_zero) {
|
||||||
|
var raw_dcval = buf[8 * 0 + x];
|
||||||
|
var quant_val = scaled_quant_table[8 * 0 + x];
|
||||||
|
var dcval = idescale(dequant(8 * 0 + x), END_PASS1_DESCALE_BITS); // in total, >> 16
|
||||||
|
for (y = 0; y < 8; ++y)
|
||||||
|
workspace[8 * y + x] = dcval;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even part.
|
||||||
|
var tmp0 = dequant(8 * 0 + x);
|
||||||
|
var tmp1 = dequant(8 * 2 + x);
|
||||||
|
var tmp2 = dequant(8 * 4 + x);
|
||||||
|
var tmp3 = dequant(8 * 6 + x);
|
||||||
|
|
||||||
|
var tmp10 = tmp0 + tmp2; // Phase 3
|
||||||
|
var tmp11 = tmp0 - tmp2;
|
||||||
|
|
||||||
|
var tmp13 = tmp1 + tmp3; // Phases 5-3
|
||||||
|
var tmp12 = mul((tmp1 - tmp3), FIX_1_414213562) - tmp13; // 2 * c4
|
||||||
|
|
||||||
|
tmp0 = tmp10 + tmp13;
|
||||||
|
tmp3 = tmp10 - tmp13;
|
||||||
|
tmp1 = tmp11 + tmp12;
|
||||||
|
tmp2 = tmp11 - tmp12;
|
||||||
|
|
||||||
|
// Odd part.
|
||||||
|
var tmp4 = dequant(8 * 1 + x);
|
||||||
|
var tmp5 = dequant(8 * 3 + x);
|
||||||
|
var tmp6 = dequant(8 * 5 + x);
|
||||||
|
var tmp7 = dequant(8 * 7 + x);
|
||||||
|
|
||||||
|
var z13 = tmp6 + tmp5; // Phase 6
|
||||||
|
var z10 = tmp6 - tmp5;
|
||||||
|
var z11 = tmp4 + tmp7;
|
||||||
|
var z12 = tmp4 - tmp7;
|
||||||
|
|
||||||
|
tmp7 = z11 + z13; // Phase 5
|
||||||
|
tmp11 = mul((z11 - z13), FIX_1_414213562); // 2 * c4
|
||||||
|
|
||||||
|
var z5 = mul((z10 + z12), FIX_1_847759065); // 2 * c2
|
||||||
|
tmp10 = mul(FIX_1_082392200, z12) - z5; // 2 * (c2-c6)
|
||||||
|
tmp12 = mul(-FIX_2_613125930, z10) + z5;
|
||||||
|
|
||||||
|
tmp6 = tmp12 - tmp7;
|
||||||
|
tmp5 = tmp11 - tmp6;
|
||||||
|
tmp4 = tmp10 + tmp5;
|
||||||
|
|
||||||
|
workspace[x + 8 * 0] = idescale(tmp0 + tmp7, END_PASS1_DESCALE_BITS);
|
||||||
|
workspace[x + 8 * 7] = idescale(tmp0 - tmp7, END_PASS1_DESCALE_BITS);
|
||||||
|
workspace[x + 8 * 1] = idescale(tmp1 + tmp6, END_PASS1_DESCALE_BITS);
|
||||||
|
workspace[x + 8 * 6] = idescale(tmp1 - tmp6, END_PASS1_DESCALE_BITS);
|
||||||
|
workspace[x + 8 * 2] = idescale(tmp2 + tmp5, END_PASS1_DESCALE_BITS);
|
||||||
|
workspace[x + 8 * 5] = idescale(tmp2 - tmp5, END_PASS1_DESCALE_BITS);
|
||||||
|
workspace[x + 8 * 4] = idescale(tmp3 + tmp4, END_PASS1_DESCALE_BITS);
|
||||||
|
workspace[x + 8 * 3] = idescale(tmp3 - tmp4, END_PASS1_DESCALE_BITS);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// Rows; aka "Pass 2".
|
||||||
|
_aan_idct_row: function(scaled_quant_table, buf, workspace, y) {
|
||||||
|
|
||||||
|
var wsptr = function (x) { return workspace[8 * y + x]; };
|
||||||
|
var mul = fixed_mul;
|
||||||
|
|
||||||
|
// Even part.
|
||||||
|
var tmp10 = wsptr(0) + wsptr(4);
|
||||||
|
var tmp11 = wsptr(0) - wsptr(4);
|
||||||
|
|
||||||
|
var tmp13 = wsptr(2) + wsptr(6);
|
||||||
|
var tmp12 = mul((wsptr(2) - wsptr(6)), FIX_1_414213562) - tmp13;
|
||||||
|
|
||||||
|
var tmp0 = tmp10 + tmp13;
|
||||||
|
var tmp3 = tmp10 - tmp13;
|
||||||
|
var tmp1 = tmp11 + tmp12;
|
||||||
|
var tmp2 = tmp11 - tmp12;
|
||||||
|
|
||||||
|
// Odd part.
|
||||||
|
var z13 = wsptr(5) + wsptr(3);
|
||||||
|
var z10 = wsptr(5) - wsptr(3);
|
||||||
|
var z11 = wsptr(1) + wsptr(7);
|
||||||
|
var z12 = wsptr(1) - wsptr(7);
|
||||||
|
|
||||||
|
var tmp7 = z11 + z13;
|
||||||
|
tmp11 = mul((z11 - z13), FIX_1_414213562);
|
||||||
|
|
||||||
|
var z5 = mul((z10 + z12), FIX_1_847759065); // 2 * c2
|
||||||
|
tmp10 = mul(FIX_1_082392200, z12) - z5; // 2 * (c2-c6)
|
||||||
|
tmp12 = mul(-FIX_2_613125930, z10) + z5;
|
||||||
|
|
||||||
|
var tmp6 = tmp12 - tmp7;
|
||||||
|
var tmp5 = tmp11 - tmp6;
|
||||||
|
var tmp4 = tmp10 + tmp5;
|
||||||
|
|
||||||
|
var set_out = function (x, val) {
|
||||||
|
// Shift right by PASS1_BITS bits to convert back to a normal
|
||||||
|
// int, and then by another 3 to divide by 8.
|
||||||
|
val = idescale(val, PASS1_BITS + 3);
|
||||||
|
val = range_limit(val); // This also applies the +128 offset.
|
||||||
|
buf[y * 8 + x] = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Final output stage: scale down by a factor of 8 and range-limit
|
||||||
|
set_out(0, tmp0 + tmp7);
|
||||||
|
set_out(7, tmp0 - tmp7);
|
||||||
|
set_out(1, tmp1 + tmp6);
|
||||||
|
set_out(6, tmp0 - tmp6);
|
||||||
|
set_out(2, tmp2 + tmp5);
|
||||||
|
set_out(5, tmp2 - tmp5);
|
||||||
|
set_out(4, tmp3 + tmp4);
|
||||||
|
set_out(3, tmp3 - tmp4);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
@ -0,0 +1,284 @@
|
||||||
|
/* (c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>. */
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
@ -467,6 +467,7 @@
|
||||||
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
||||||
// this probably isn't getting called *nearly* as much
|
// this probably isn't getting called *nearly* as much
|
||||||
var new_arr = new Uint8Array(width * height * 4);
|
var new_arr = new Uint8Array(width * height * 4);
|
||||||
|
// XXX(kelleyk): Why are we allocating *two* new arrays here?
|
||||||
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
|
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
|
||||||
this._renderQ_push({
|
this._renderQ_push({
|
||||||
'type': 'blit',
|
'type': 'blit',
|
||||||
|
|
|
||||||
203
core/rfb.js
203
core/rfb.js
|
|
@ -21,9 +21,11 @@
|
||||||
* import XK2HID from "./input/keysym";
|
* import XK2HID from "./input/keysym";
|
||||||
* import XtScancode from "./input/xtscancodes";
|
* import XtScancode from "./input/xtscancodes";
|
||||||
* import Inflator from "./inflator.mod";
|
* import Inflator from "./inflator.mod";
|
||||||
|
* import Ast2100Decoder from "./ast2100/ast2100";
|
||||||
|
* import arrayEq from "./ast2100/ast2100util";
|
||||||
*/
|
*/
|
||||||
/*jslint white: false, browser: true */
|
/*jslint white: false, browser: true */
|
||||||
/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */
|
/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode, Ast2100Decoder, arrayEq */
|
||||||
|
|
||||||
/* [module] export default */ function RFB(defaults) {
|
/* [module] export default */ function RFB(defaults) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
@ -44,6 +46,7 @@
|
||||||
this._rfb_disconnect_reason = "";
|
this._rfb_disconnect_reason = "";
|
||||||
|
|
||||||
this._rfb_tightvnc = false;
|
this._rfb_tightvnc = false;
|
||||||
|
this._rfb_atenikvm = false;
|
||||||
this._rfb_xvp_ver = 0;
|
this._rfb_xvp_ver = 0;
|
||||||
|
|
||||||
// In preference order
|
// In preference order
|
||||||
|
|
@ -54,7 +57,13 @@
|
||||||
['HEXTILE', 0x05 ],
|
['HEXTILE', 0x05 ],
|
||||||
['RRE', 0x02 ],
|
['RRE', 0x02 ],
|
||||||
['RAW', 0x00 ],
|
['RAW', 0x00 ],
|
||||||
['ATEN', 0x59 ],
|
|
||||||
|
// ATEN iKVM encodings
|
||||||
|
['ATEN_AST2100', 0x57 ],
|
||||||
|
['ATEN_ASTJPEG', 0x58 ],
|
||||||
|
['ATEN_HERMON', 0x59 ],
|
||||||
|
['ATEN_YARKON', 0x60 ],
|
||||||
|
['ATEN_PILOT3', 0x61 ],
|
||||||
|
|
||||||
// Psuedo-encoding settings
|
// Psuedo-encoding settings
|
||||||
|
|
||||||
|
|
@ -369,6 +378,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Tell the ATEN iKVM server to change the quantization tables and/or
|
||||||
|
// type of subsampling that it uses to encode video.
|
||||||
|
atenChangeVideoSettings: function (lumaQt, chromaQt, subsamplingMode) {
|
||||||
|
RFB.messages.atenChangeVideoSettings(this._sock, lumaQt, chromaQt, subsamplingMode);
|
||||||
|
this._sock.flush();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
// Private methods
|
// Private methods
|
||||||
|
|
||||||
|
|
@ -803,6 +819,8 @@
|
||||||
this._rfb_auth_scheme = 0;
|
this._rfb_auth_scheme = 0;
|
||||||
var types = this._sock.rQshiftBytes(num_types);
|
var types = this._sock.rQshiftBytes(num_types);
|
||||||
Util.Debug("Server security types: " + types);
|
Util.Debug("Server security types: " + types);
|
||||||
|
// N.B.(kelleyk): Deliberately copy-constructed, since the underlying array is reused.
|
||||||
|
this._rfb_server_supported_security_types = Array.from(types);
|
||||||
for (var i = 0; i < types.length; i++) {
|
for (var i = 0; i < types.length; i++) {
|
||||||
switch (types[i]) {
|
switch (types[i]) {
|
||||||
case 1: // None
|
case 1: // None
|
||||||
|
|
@ -841,8 +859,9 @@
|
||||||
var aten_sep = this._aten_password_sep;
|
var aten_sep = this._aten_password_sep;
|
||||||
var aten_auth = this._rfb_password.split(aten_sep);
|
var aten_auth = this._rfb_password.split(aten_sep);
|
||||||
if (aten_auth.length < 2) {
|
if (aten_auth.length < 2) {
|
||||||
this._onPasswordRequired(this, 'ATEN iKVM credentials required (user' + aten_sep +
|
this._onPasswordRequired(
|
||||||
'password) -- got only ' + this._rfb_password);
|
this,
|
||||||
|
'ATEN iKVM credentials required (user' + aten_sep + 'password)');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -850,6 +869,7 @@
|
||||||
this._convert_color = true;
|
this._convert_color = true;
|
||||||
|
|
||||||
if (this._rfb_tightvnc) {
|
if (this._rfb_tightvnc) {
|
||||||
|
// N.B.(kelleyk): We've already "skipped" the four bytes that we read into numTunnels.
|
||||||
this._rfb_tightvnc = false;
|
this._rfb_tightvnc = false;
|
||||||
} else {
|
} else {
|
||||||
this._sock.rQskipBytes(4);
|
this._sock.rQskipBytes(4);
|
||||||
|
|
@ -904,6 +924,8 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
_negotiate_tight_tunnels: function (numTunnels) {
|
_negotiate_tight_tunnels: function (numTunnels) {
|
||||||
|
// N.B.(kelleyk): For a full of known tunnel types, see:
|
||||||
|
// https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#tight-security-type
|
||||||
var clientSupportedTunnelTypes = {
|
var clientSupportedTunnelTypes = {
|
||||||
0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
|
0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
|
||||||
};
|
};
|
||||||
|
|
@ -934,16 +956,31 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
_negotiate_tight_auth: function () {
|
_negotiate_tight_auth: function () {
|
||||||
var numTunnels; // NB(directxman12): this is only in scope within the following block,
|
var numTunnels = 0; // NB(directxman12): this is only in scope within the following block,
|
||||||
// or if equal to zero (necessary for ATEN iKVM support)
|
// or if equal to zero (necessary for ATEN iKVM support)
|
||||||
if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
|
if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
|
||||||
if (this._sock.rQwait("num tunnels", 4)) { return false; }
|
if (this._sock.rQwait("num tunnels", 4)) { return false; }
|
||||||
numTunnels = this._sock.rQshift32();
|
numTunnels = this._sock.rQshift32();
|
||||||
if (this._rfb_version === 3.8 && (numTunnels & 0xffff0ff0) >>> 0 === 0xaff90fb0) { return this._negotiate_aten_auth(); }
|
|
||||||
if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
|
|
||||||
|
|
||||||
this._rfb_tightvnc = true;
|
this._rfb_tightvnc = true;
|
||||||
|
|
||||||
|
// N.B.(kelleyk): I can only find about a half-dozen known tunnel
|
||||||
|
// types, so TightVNC should be sending a relatively small number
|
||||||
|
// here, whereas the ATEN servers send four bytes that, when
|
||||||
|
// interpreted as a u32, represent a very large number. (The
|
||||||
|
// condition I've chosen below just checks that the first byte
|
||||||
|
// is nonzero.) Further, TightVNC servers seem to be support
|
||||||
|
// both 0x02 and 0x10 security types, whereas ATEN iKVM servers
|
||||||
|
// advertise only support for 0x10. (Are there *other* VNC
|
||||||
|
// servers that only support 0x10?)
|
||||||
|
if (this._rfb_version === 3.8 &&
|
||||||
|
arrayEq(this._rfb_server_supported_security_types, [0x10]) &&
|
||||||
|
(numTunnels <= 0 || numTunnels > 0x1000000)) {
|
||||||
|
Util.Info('Detected ATEN iKVM server (using heuristic #0 -- ' +
|
||||||
|
'older Winbond/Nuvoton or Renesas BMC?).');
|
||||||
|
return this._negotiate_aten_auth();
|
||||||
|
}
|
||||||
|
if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
|
||||||
|
|
||||||
if (numTunnels > 0) {
|
if (numTunnels > 0) {
|
||||||
this._negotiate_tight_tunnels(numTunnels);
|
this._negotiate_tight_tunnels(numTunnels);
|
||||||
return false; // wait until we receive the sub auth to continue
|
return false; // wait until we receive the sub auth to continue
|
||||||
|
|
@ -960,9 +997,14 @@
|
||||||
|
|
||||||
if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
|
if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
|
||||||
|
|
||||||
// Newer X10 Supermicro motherboards get here
|
// Newer X10 Supermicro motherboards get here.
|
||||||
if (this._rfb_version === 3.8 && numTunnels === 0 && subAuthCount === 0) {
|
// N.B.(kelleyk): If we had trouble with this heuristic matching
|
||||||
Util.Warn("Newer ATEN iKVM detected, you may get an 'unsupported encoding 87'");
|
// non-ATEN servers, we could also add the "only security type 0x10
|
||||||
|
// is supported" condition from above.
|
||||||
|
if (this._rfb_version === 3.8 &&
|
||||||
|
numTunnels === 0 &&
|
||||||
|
(subAuthCount === 0 || (subAuthCount & 0xFFFF) == 0x0100)) {
|
||||||
|
Util.Info('Detected ATEN iKVM server (using heuristic #1 -- newer AST2400 BMC?).');
|
||||||
return this._negotiate_aten_auth();
|
return this._negotiate_aten_auth();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1156,7 +1198,7 @@
|
||||||
// - X9DRL-3F/X9DRL-6F
|
// - X9DRL-3F/X9DRL-6F
|
||||||
// - X10SLD
|
// - X10SLD
|
||||||
//
|
//
|
||||||
// Not supported (uses encoding 87):
|
// Supported using the ATEN "AST2100" encoding (0x57 / 87):
|
||||||
// - X10SL7-F
|
// - X10SL7-F
|
||||||
// - X10SLD-F
|
// - X10SLD-F
|
||||||
// - X10SLM-F
|
// - X10SLM-F
|
||||||
|
|
@ -1177,7 +1219,10 @@
|
||||||
this._fb_width = 10000;
|
this._fb_width = 10000;
|
||||||
this._fb_height = 10000;
|
this._fb_height = 10000;
|
||||||
|
|
||||||
// lies about what it supports
|
// TODO(kelleyk): This message (and this block of code) is part
|
||||||
|
// of the original ATEN "HERMON" (0x59) support. The "AST2100"
|
||||||
|
// (0x57) encoding delivers RGB888 color. I suppose that we should
|
||||||
|
// update this somehow? What effect does it have?
|
||||||
Util.Warn("ATEN iKVM lies and only does 15 bit depth with RGB555");
|
Util.Warn("ATEN iKVM lies and only does 15 bit depth with RGB555");
|
||||||
this._convert_color = true;
|
this._convert_color = true;
|
||||||
this._pixelFormat.bpp = 16;
|
this._pixelFormat.bpp = 16;
|
||||||
|
|
@ -1560,6 +1605,9 @@
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(kelleyk): We should probably modify this condition so
|
||||||
|
// that this can only happen when we are using the ATEN_HERMON
|
||||||
|
// encoding, and not other ATEN encodings. --
|
||||||
// ATEN uses 0x00 even when it is meant to be 0x59
|
// ATEN uses 0x00 even when it is meant to be 0x59
|
||||||
if (this._rfb_atenikvm && this._FBU.encoding === 0x00) {
|
if (this._rfb_atenikvm && this._FBU.encoding === 0x00) {
|
||||||
this._FBU.encoding = 0x59;
|
this._FBU.encoding = 0x59;
|
||||||
|
|
@ -1877,6 +1925,26 @@
|
||||||
sock._sQlen += 18;
|
sock._sQlen += 18;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
atenChangeVideoSettings: function (sock, lumaQt, chromaQt, subsamplingMode) {
|
||||||
|
if (!inRangeIncl(lumaQt, 0, 0xB))
|
||||||
|
throw 'Bad value: must have 0 <= lumaQt <= 0xB';
|
||||||
|
if (!inRangeIncl(chromaQt, 0, 0xB))
|
||||||
|
throw 'Bad value: must have 0 <= chromaQt <= 0xB';
|
||||||
|
if (subsamplingMode != 422 && subsamplingMode != 444)
|
||||||
|
throw 'Bad value: subsamplingMode must be one of 444, 422';
|
||||||
|
|
||||||
|
var buf = sock._sQ;
|
||||||
|
var offset = sock._sQlen;
|
||||||
|
|
||||||
|
buf[offset] = 0x32;
|
||||||
|
buf[offset + 1] = lumaQt;
|
||||||
|
buf[offset + 2] = chromaQt;
|
||||||
|
buf[offset + 3] = subsamplingMode >>> 8;
|
||||||
|
buf[offset + 4] = subsamplingMode;
|
||||||
|
|
||||||
|
sock._sQlen += 5;
|
||||||
|
},
|
||||||
|
|
||||||
pointerEvent: function (sock, x, y, mask) {
|
pointerEvent: function (sock, x, y, mask) {
|
||||||
var buff = sock._sQ;
|
var buff = sock._sQ;
|
||||||
var offset = sock._sQlen;
|
var offset = sock._sQlen;
|
||||||
|
|
@ -2766,12 +2834,12 @@
|
||||||
Util.Error("Server sent compress level pseudo-encoding");
|
Util.Error("Server sent compress level pseudo-encoding");
|
||||||
},
|
},
|
||||||
|
|
||||||
ATEN: function () {
|
ATEN_HERMON: function () {
|
||||||
if (this._FBU.aten_len === -1) {
|
if (this._FBU.aten_len === -1) {
|
||||||
this._FBU.bytes = 8;
|
this._FBU.bytes = 8;
|
||||||
if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; }
|
if (this._sock.rQwait("ATEN_HERMON", this._FBU.bytes)) { return false; }
|
||||||
this._FBU.bytes = 0;
|
this._FBU.bytes = 0;
|
||||||
this._sock.rQskipBytes(4);
|
this._sock.rQskipBytes(4); // N.B.(kelleyk): This is the "mysteryFlag".
|
||||||
this._FBU.aten_len = this._sock.rQshift32();
|
this._FBU.aten_len = this._sock.rQshift32();
|
||||||
|
|
||||||
if (this._FBU.width === 64896 && this._FBU.height === 65056) {
|
if (this._FBU.width === 64896 && this._FBU.height === 65056) {
|
||||||
|
|
@ -2784,25 +2852,25 @@
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this._fb_width !== this._FBU.width && this._fb_height !== this._FBU.height) {
|
if (this._fb_width !== this._FBU.width && this._fb_height !== this._FBU.height) {
|
||||||
Util.Debug(">> ATEN resize desktop");
|
Util.Debug(">> ATEN_HERMON resize desktop");
|
||||||
this._fb_width = this._FBU.width;
|
this._fb_width = this._FBU.width;
|
||||||
this._fb_height = this._FBU.height;
|
this._fb_height = this._FBU.height;
|
||||||
this._onFBResize(this, this._fb_width, this._fb_height);
|
this._onFBResize(this, this._fb_width, this._fb_height);
|
||||||
this._display.resize(this._fb_width, this._fb_height);
|
this._display.resize(this._fb_width, this._fb_height);
|
||||||
Util.Debug("<< ATEN resize desktop");
|
Util.Debug("<< ATEN_HERMON resize desktop");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._FBU.aten_type === -1) {
|
if (this._FBU.aten_type === -1) {
|
||||||
this._FBU.bytes = 10;
|
this._FBU.bytes = 10;
|
||||||
if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; }
|
if (this._sock.rQwait("ATEN_HERMON", this._FBU.bytes)) { return false; }
|
||||||
this._FBU.bytes = 0;
|
this._FBU.bytes = 0;
|
||||||
this._FBU.aten_type = this._sock.rQshift8();
|
this._FBU.aten_type = this._sock.rQshift8();
|
||||||
this._sock.rQskip8();
|
this._sock.rQskip8();
|
||||||
|
|
||||||
this._sock.rQskipBytes(4); // number of subrects
|
this._sock.rQskipBytes(4); // number of subrects
|
||||||
if (this._FBU.aten_len !== this._sock.rQshift32()) {
|
if (this._FBU.aten_len !== this._sock.rQshift32()) {
|
||||||
return this._fail('ATEN RAW len mis-match');
|
return this._fail('ATEN_HERMON RAW len mis-match');
|
||||||
}
|
}
|
||||||
this._FBU.aten_len -= 10;
|
this._FBU.aten_len -= 10;
|
||||||
}
|
}
|
||||||
|
|
@ -2811,7 +2879,7 @@
|
||||||
switch (this._FBU.aten_type) {
|
switch (this._FBU.aten_type) {
|
||||||
case 0: // Subrects
|
case 0: // Subrects
|
||||||
this._FBU.bytes = 6 + (16 * 16 * this._pixelFormat.Bpp); // at least a subrect
|
this._FBU.bytes = 6 + (16 * 16 * this._pixelFormat.Bpp); // at least a subrect
|
||||||
if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; }
|
if (this._sock.rQwait("ATEN_HERMON", this._FBU.bytes)) { return false; }
|
||||||
var a = this._sock.rQshift16();
|
var a = this._sock.rQshift16();
|
||||||
var b = this._sock.rQshift16();
|
var b = this._sock.rQshift16();
|
||||||
var y = this._sock.rQshift8();
|
var y = this._sock.rQshift8();
|
||||||
|
|
@ -2828,7 +2896,7 @@
|
||||||
if (this._FBU.bytes > 0) return false;
|
if (this._FBU.bytes > 0) return false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return this._fail('unknown ATEN type: '+this._FBU.aten_type);
|
return this._fail('unknown ATEN_HERMON type: '+this._FBU.aten_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2843,6 +2911,97 @@
|
||||||
this._FBU.aten_len = -1;
|
this._FBU.aten_len = -1;
|
||||||
this._FBU.aten_type = -1;
|
this._FBU.aten_type = -1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
ATEN_AST2100: function () {
|
||||||
|
|
||||||
|
if (this._FBU.aten_len === -1) {
|
||||||
|
this._FBU.bytes = 8;
|
||||||
|
if (this._sock.rQwait("ATEN_AST2100", this._FBU.bytes)) { return false; }
|
||||||
|
|
||||||
|
// N.B.(kelleyk): I think that the mysteryFlag is 0 when in
|
||||||
|
// "text mode" (at the BIOS, without X started, etc.) and 1 when
|
||||||
|
// running X. Perhaps it's something to do with what mode the
|
||||||
|
// "video card" is being used in?
|
||||||
|
var mysteryFlag = this._sock.rQshift32();
|
||||||
|
// if (mysteryFlag != 0)
|
||||||
|
// console.log('Nonzero mysteryFlag (='+mysteryFlag+')! When does this occur?');
|
||||||
|
this._FBU.aten_len = this._sock.rQshift32();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually read the data.
|
||||||
|
if (this._FBU.aten_len !== 0) {
|
||||||
|
this._FBU.bytes = this._FBU.aten_len;
|
||||||
|
if (this._sock.rQwait("ATEN_AST2100", this._FBU.bytes)) { return false; }
|
||||||
|
var data = this._sock.rQshiftBytes(this._FBU.aten_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without this, the code in _framebufferUpdate() will keep looping
|
||||||
|
// instead of realizing that it's finished and sending out a
|
||||||
|
// FramebufferUpdateRequest. It's important to make sure this code
|
||||||
|
// is called as soon as we have read any data we are going to read:
|
||||||
|
// in particular, it must be called BEFORE we might return true
|
||||||
|
// ("we're done"). Otherwise... infinite loop!
|
||||||
|
this._FBU.rects -= 1;
|
||||||
|
if (this._FBU.rects != 0)
|
||||||
|
throw 'Unexpected number of rects in FramebufferUpdate message; should always be 1!';
|
||||||
|
|
||||||
|
// N.B.(kelleyk): It's also very important for the way that this
|
||||||
|
// function works that aten_len wind up -1 before we ever return
|
||||||
|
// true; otherwise we'll fail to parse the two extra, ATEN-specific
|
||||||
|
// header fields (see above) the next time we get a FramebufferUpdate
|
||||||
|
// message.
|
||||||
|
this._FBU.aten_len = -1;
|
||||||
|
|
||||||
|
// These are -640 and -480 (as int16s), as in the other ATEN encodings.
|
||||||
|
if (this._FBU.width === 64896 && this._FBU.height === 65056) {
|
||||||
|
Util.Debug('Ast2100Decoder: screen is off.');
|
||||||
|
if (this._FBU.aten_len !== 0)
|
||||||
|
Util.Warn('Ast2100Decoder: warning: framebuffer dimensions ' +
|
||||||
|
'indicate that screen is off but data length is nonzero.');
|
||||||
|
return true;
|
||||||
|
} else if (this._FBU.aten_len === 0) {
|
||||||
|
// This seems to happen when the display is off (e.g. when you
|
||||||
|
// tell the machine to restart).
|
||||||
|
// TODO(kelleyk): Is there a way to tell noVNC that the display
|
||||||
|
// is "off"? Should we show a black screen, or otherwise indicate
|
||||||
|
// to the user that this is what is going on?
|
||||||
|
Util.Warn('Ast2100Decoder: warning: data length is zero, but ' +
|
||||||
|
'framebuffer dimensions are not -640x-480 (which is ' +
|
||||||
|
'typically given to indicate that the screen is off).');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._aten_ast2100_dec) {
|
||||||
|
var display = this._display;
|
||||||
|
this._aten_ast2100_dec = new Ast2100Decoder({
|
||||||
|
width: this._FBU.width,
|
||||||
|
height: this._FBU.height,
|
||||||
|
blitCallback: function (x, y, width, height, buf) {
|
||||||
|
// Last arguments here are offset, from_queue. 'from_queue'
|
||||||
|
// means 'should this block be rendered from the queue?', not
|
||||||
|
// 'is this block being rendered from the queue?'. It causes
|
||||||
|
// the block to be enqueued instead of being blitted right
|
||||||
|
// away via a call to _rgbxImageData().
|
||||||
|
display.blitRgbxImage(x, y, width, height, buf, 0, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// N.B.(kelleyk): Copied this block from ATEN_HERMON above.
|
||||||
|
if (this._fb_width !== this._FBU.width && this._fb_height !== this._FBU.height) {
|
||||||
|
Util.Debug(">> ATEN_AST2100 resize desktop");
|
||||||
|
this._fb_width = this._FBU.width;
|
||||||
|
this._fb_height = this._FBU.height;
|
||||||
|
this._onFBResize(this, this._fb_width, this._fb_height);
|
||||||
|
this._display.resize(this._fb_width, this._fb_height);
|
||||||
|
Util.Debug("<< ATEN_AST2100 resize desktop");
|
||||||
|
|
||||||
|
this._aten_ast2100_dec.setSize(this._FBU.width, this._FBU.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._aten_ast2100_dec.decode(data);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,10 @@ module.exports = function(config) {
|
||||||
'core/des.js',
|
'core/des.js',
|
||||||
'core/display.js',
|
'core/display.js',
|
||||||
'core/inflator.js',
|
'core/inflator.js',
|
||||||
|
'core/ast2100/ast2100const.js',
|
||||||
|
'core/ast2100/ast2100util.js',
|
||||||
|
'core/ast2100/ast2100idct.js',
|
||||||
|
'core/ast2100/ast2100.js',
|
||||||
'tests/test.*.js'
|
'tests/test.*.js'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,384 @@
|
||||||
|
// requires local modules: ast2100, ast2100idct, ast2100const, ast2100util
|
||||||
|
/* jshint expr: true */
|
||||||
|
|
||||||
|
var assert = chai.assert;
|
||||||
|
var expect = chai.expect;
|
||||||
|
|
||||||
|
|
||||||
|
// Convert a hex string, optionally containing spaces, into an array of integers representing bytes.
|
||||||
|
var parseHex = function (s) {
|
||||||
|
s = s.replace(/\s/g, '');
|
||||||
|
if (s.length % 2 != 0)
|
||||||
|
throw 'Hex data has uneven length!';
|
||||||
|
for (var bytes = [], i = 0; i < s.length; i += 2)
|
||||||
|
bytes.push(parseInt(s.substr(i, 2), 16));
|
||||||
|
return bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function make_decoder () {
|
||||||
|
return new Ast2100Decoder({
|
||||||
|
width: 0x400,
|
||||||
|
height: 0x300,
|
||||||
|
blitCallback: function () {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap u32 byte order. Mutates input!
|
||||||
|
function buf_swap32 (data) {
|
||||||
|
for (var i = 0; i < data.length; i += 4) {
|
||||||
|
var tmp;
|
||||||
|
|
||||||
|
tmp = data[i+0];
|
||||||
|
data[i+0] = data[i+3];
|
||||||
|
data[i+3] = tmp;
|
||||||
|
|
||||||
|
tmp = data[i+1];
|
||||||
|
data[i+1] = data[i+2];
|
||||||
|
data[i+2] = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
describe('ATEN_AST2100 video encoding', function() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var dec;
|
||||||
|
beforeEach(function () {
|
||||||
|
dec = make_decoder();
|
||||||
|
dec._mcuLimit = 10;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('BitStream', function() {
|
||||||
|
var stream, stream2, stream3;
|
||||||
|
var data = buf_swap32(parseHex('f0f0 cccc cccc cccc cccc cccc cccc cccc'));
|
||||||
|
var data2 = parseHex('F0F1F2F3 F4F5F6F7 F8F9FAFB FCFDFEFF');
|
||||||
|
var data3 = parseHex('0b0b01bc45fbc5e020040101ffff3f20c0ffffff0000240000000000000000005028140a0000000000000000');
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
stream = new BitStream({data: data});
|
||||||
|
stream2 = new BitStream({data: data2});
|
||||||
|
stream3 = new BitStream({data: data3});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass simple sanity checks', function () {
|
||||||
|
expect(stream.read(4)).to.equal(0xF);
|
||||||
|
expect(stream.read(4)).to.equal(0x0);
|
||||||
|
expect(stream.read(4)).to.equal(0xF);
|
||||||
|
expect(stream.read(4)).to.equal(0x0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass simple sanity checks (part II)', function () {
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < 4; ++i)
|
||||||
|
expect(stream.read(1)).to.equal(1);
|
||||||
|
for (i = 0; i < 4; ++i)
|
||||||
|
expect(stream.read(1)).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to properly refill the buffer', function () {
|
||||||
|
expect(stream.read(4)).to.equal(0xF);
|
||||||
|
expect(stream.read(4)).to.equal(0x0);
|
||||||
|
expect(stream.read(4)).to.equal(0xF);
|
||||||
|
expect(stream.read(8)).to.equal(0x0C);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly swap byte order', function () {
|
||||||
|
expect(stream2.read(8)).to.equal(0xF3);
|
||||||
|
expect(stream2.read(8)).to.equal(0xF2);
|
||||||
|
expect(stream2.read(8)).to.equal(0xF1);
|
||||||
|
expect(stream2.read(8)).to.equal(0xF0);
|
||||||
|
expect(stream2.read(8)).to.equal(0xF7);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly handle skipping the full 32-bit read-buffer (data3)', function () {
|
||||||
|
// Must do this in two parts so that bits < 32 each time.
|
||||||
|
stream3.skip(16);
|
||||||
|
stream3.skip(16);
|
||||||
|
expect(stream3.read(4)).to.equal(0xE);
|
||||||
|
});
|
||||||
|
|
||||||
|
// it('issue repro', function () {
|
||||||
|
// var data = parseHex('f0f0 cccc cccc cccc cccc cccc cccc cccc');
|
||||||
|
// var stream = new BitStream({data: data});
|
||||||
|
// expect(stream.read(31)).to.equal(0xF0F0CCCC >>> 1);
|
||||||
|
// expect(stream.read(8)).to.equal(0x66);
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('quant tables', function() {
|
||||||
|
it('quant tables are properly loaded and scaled', function () {
|
||||||
|
// This is luma quant table #4.
|
||||||
|
var expected = Int32Array.from([
|
||||||
|
0x00090000, 0x0008527e, 0x00068866, 0x000a9537, 0x000d0000, 0x00114908, 0x000f274b, 0x0009616d,
|
||||||
|
0x0008527e, 0x000b8b14, 0x000caf8f, 0x00104f53, 0x00136b26, 0x0022df8f, 0x0018c594, 0x000b7b02,
|
||||||
|
0x0009255c, 0x000caf8f, 0x000f5d2c, 0x0013f8fd, 0x001cbe90, 0x0020d994, 0x001adebc, 0x000b2cc4,
|
||||||
|
0x00083b2b, 0x000eadca, 0x00126faf, 0x00161f78, 0x0020ecad, 0x002c58a1, 0x001ca316, 0x000b07c7,
|
||||||
|
0x000a0000, 0x0010a4fc, 0x001a219a, 0x002473bf, 0x00260000, 0x002fed69, 0x001ed922, 0x000bdd19,
|
||||||
|
0x000a36ca, 0x0014b4bd, 0x001ecbfa, 0x00214279, 0x00235b34, 0x0023cdea, 0x001ac9de, 0x000b0e2f,
|
||||||
|
0x000e9cbf, 0x001b0616, 0x001e67d4, 0x001e8bd4, 0x001ed922, 0x001cea24, 0x00139fb4, 0x00085c96,
|
||||||
|
0x000b0935, 0x00138450, 0x00131afd, 0x0011d7e1, 0x001161b4, 0x000c23a7, 0x000882d0, 0x00042fc6
|
||||||
|
]);
|
||||||
|
|
||||||
|
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
|
||||||
|
var luma_quant_table = dec.quantTables[0];
|
||||||
|
expect(luma_quant_table).to.deep.equal(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('IDCT', function () {
|
||||||
|
|
||||||
|
var outputBuf;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
outputBuf = new Uint8Array(DCTSIZE2); // equivalent to one of the componentBufs in Ast2100Decoder.
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Fix typing: variables should be typed arrays.
|
||||||
|
it('test case 0 - DC value only', function () {
|
||||||
|
|
||||||
|
// this is the output of the VLC / entropy coding process
|
||||||
|
// XXX: @KK: Why is this 16-bit?
|
||||||
|
var dataUnit = [ // new Int16Array(
|
||||||
|
0xFF9C,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||||
|
|
||||||
|
var expected_idct_output = Uint8Array.from([ // new Uint8Array(
|
||||||
|
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
||||||
|
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
||||||
|
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF]);
|
||||||
|
|
||||||
|
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
|
||||||
|
var luma_quant_table = dec.quantTables[0];
|
||||||
|
|
||||||
|
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
|
||||||
|
|
||||||
|
// console.log(result);
|
||||||
|
|
||||||
|
console.log('expected:');
|
||||||
|
console.log(fmt_u8a(expected_idct_output));
|
||||||
|
console.log('result:');
|
||||||
|
console.log(fmt_u8a(outputBuf));
|
||||||
|
|
||||||
|
expect(outputBuf).to.deep.equal(expected_idct_output);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test case 1', function () {
|
||||||
|
// this is the output of the VLC / entropy coding process
|
||||||
|
// XXX: @KK: Why is this 16-bit?
|
||||||
|
var dataUnit = Int16Array.from([
|
||||||
|
0xFFBD,0,0,0,0,0,0,0,
|
||||||
|
0xFFC3,0,0,0,0,0,0,0,
|
||||||
|
0x26,0,0,0,0,0,0,0,
|
||||||
|
0xFFED,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,
|
||||||
|
6,0,0,0,0,0,0,0,
|
||||||
|
0xFFFC,0,0,0,0,0,0,0,
|
||||||
|
2,0,0,0,0,0,0,0]);
|
||||||
|
|
||||||
|
var expected = Uint8Array.from([
|
||||||
|
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
||||||
|
0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
|
||||||
|
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
|
||||||
|
0xE,0xE,0xE,0xE,0xE,0xE,0xE,0xE,
|
||||||
|
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
|
||||||
|
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
||||||
|
0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,
|
||||||
|
0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1]);
|
||||||
|
|
||||||
|
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
|
||||||
|
var luma_quant_table = dec.quantTables[0];
|
||||||
|
|
||||||
|
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
console.log('expected:');
|
||||||
|
console.log(fmt_u8a(expected));
|
||||||
|
console.log('result:');
|
||||||
|
console.log(fmt_u8a(outputBuf));
|
||||||
|
*/
|
||||||
|
|
||||||
|
// XXX: TEMPORARY -- figure out this rounding issue
|
||||||
|
// expect(result).to.deep.equal(expected);
|
||||||
|
var maxErr = 0;
|
||||||
|
for (i = 0; i < 64; ++i)
|
||||||
|
maxErr = Math.max(maxErr, Math.abs(outputBuf[i] - expected[i]));
|
||||||
|
if (maxErr > 1)
|
||||||
|
throw 'Error too high!';
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test case 2', function () {
|
||||||
|
// this is the output of the VLC / entropy coding process
|
||||||
|
// XXX: @KK: Why is this 16-bit?
|
||||||
|
|
||||||
|
var dataUnit = Int16Array.from([
|
||||||
|
0xFF9B, 0, 0, 0, 0, 0, 0, 0, 0xFFA4, 0, 0, 0, 0, 0, 0, 0, 0x35, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0xFFE6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0xFFFA,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]);
|
||||||
|
|
||||||
|
var expected = [
|
||||||
|
0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
||||||
|
0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
||||||
|
0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||||
|
0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1];
|
||||||
|
|
||||||
|
// Between IDCT passes, the workspace should look like this:
|
||||||
|
// [-903, 0, 0, 0, 0, 0, 0, 0, -874, 0, 0, 0, 0, 0, 0, 0, -914, 0, 0, 0, 0, 0, 0, 0, -874, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
// -915, 0, 0, 0, 0, 0, 0, 0, -868, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 266, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
|
||||||
|
|
||||||
|
dec._loadQuantTable(0, ATEN_QT_LUMA[5]);
|
||||||
|
var luma_quant_table = dec.quantTables[0];
|
||||||
|
|
||||||
|
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
|
||||||
|
|
||||||
|
// console.log(result);
|
||||||
|
|
||||||
|
console.log('expected:');
|
||||||
|
console.log(fmt_u8a(expected));
|
||||||
|
console.log('result:');
|
||||||
|
console.log(fmt_u8a(outputBuf));
|
||||||
|
|
||||||
|
// XXX: TEMPORARY -- figure out this rounding issue
|
||||||
|
// expect(result).to.deep.equal(expected);
|
||||||
|
var maxErr = 0;
|
||||||
|
for (i = 0; i < 64; ++i)
|
||||||
|
maxErr = Math.max(maxErr, Math.abs(outputBuf[i] - expected[i]));
|
||||||
|
if (maxErr > 1)
|
||||||
|
throw 'Error too high!';
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('VQ', function () {
|
||||||
|
|
||||||
|
it('should successfully load a codebook (colors)', function () {
|
||||||
|
var data = buf_swap32(parseHex('bc010b0b e0c5fb45 01010420 203fffff ffffffc0 00240000 00000000 00000000 0a142850'));
|
||||||
|
data = data.slice(4);
|
||||||
|
|
||||||
|
var stream = new BitStream({data: data}); // Strip off quant table selectors and subsampling mode.
|
||||||
|
var controlFlag = stream.read(4);
|
||||||
|
expect(controlFlag).to.equal(0xE);
|
||||||
|
|
||||||
|
var xMcuPos = stream.read(8), yMcuPos = stream.read(8);
|
||||||
|
expect(xMcuPos).to.equal(0xC);
|
||||||
|
expect(yMcuPos).to.equal(0x5F);
|
||||||
|
|
||||||
|
dec.subsamplingMode = 444; // required or an assertion in the VQ code will fail
|
||||||
|
dec._stream = stream;
|
||||||
|
dec._parseVqBlock(1); // codewordSize=1
|
||||||
|
expect(dec._vqCodewordLookup).to.deep.equal([1, 0, 2, 3]);
|
||||||
|
expect(dec._vqCodebook).to.deep.equal([
|
||||||
|
[0x10, 0x80, 0x80],
|
||||||
|
[0xA2, 0x80, 0x80],
|
||||||
|
[0x80, 0x80, 0x80],
|
||||||
|
[0xC0, 0x80, 0x80]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly handle multiple data blocks', function () {
|
||||||
|
// This data is from a memory dump. It should contain two VQ (0xE-type) blocks and then an 0x9 (end-frame).
|
||||||
|
var data = buf_swap32(parseHex('bc010b0b e0c5fb45 01010420 203fffff ffffffc0 00240000 00000000 00000000 0a142850' + '00000000 00000000'));
|
||||||
|
console.log('VQ multiple block data:');
|
||||||
|
console.log(fmt_u8a(data));
|
||||||
|
dec.decode(data);
|
||||||
|
|
||||||
|
// TODO: improve asserts/etc. beyond just 'finished without error'
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Full JPEG subsampled MCU example 0', function () {
|
||||||
|
// Corresponds to whole-mcu-example / main_mcu_test_2()
|
||||||
|
var data = parseHex('040701a61bff6280f81fbaa2ff4dbc408dfccf405c15ff1f004800f500000000000000002dd4a462237ced2c9c25dca4cef7e6667d6626f3308063c3c7a1b7335badc24aaf0b5e9daf69590fcb96206b59e6f2ccb2bdd386b17dbb72182080d6288a2a1f2f0608a0fe87ae287f132f1023ff33d057c5ff470012403d0000000000000000ec1fb06cdacd2bdf9d79f3cde7f9f1362b7ce914ba521c79524a5bdc212a5ed47c3cbc6c7e072ae3e3c18384785f6ca6d4e22fb1af0c96449d1847a9c7037e966fa3ac8d4990830339463d277f25fc6f30ffe5f4f5cfec9f4ffff8bf00a0f5d358a2ab6e4e6778d929f686bd58ca9c26b8f7338f15105065dd39c718e219a78e');
|
||||||
|
|
||||||
|
|
||||||
|
it('should decode properly', function () {
|
||||||
|
// DOES NOT APPEAR TO correspond to my notes in 'whole-mcu-example.txt'.
|
||||||
|
|
||||||
|
dec._blitCallback = function (x, y, width, height, buf) {
|
||||||
|
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
||||||
|
console.log(fmt_rgb_buf(width, buf));
|
||||||
|
};
|
||||||
|
|
||||||
|
dec.decode(data);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Full JPEG subsampled MCU example 1', function () {
|
||||||
|
|
||||||
|
/* TODO: this looks VERRRRRY SIMILAR to the above example */
|
||||||
|
|
||||||
|
var data = parseHex('040701a61bff6280f81fbaa2ff4dbc408dfccf405c15ff1f004800f500000000000000002dd4a462237ced2c9c25dca4cef7e6667d6626f3308063c3c7a1b7335badc24aaf0b5e9daf69590fcb96206b59e6f2ccb2bdd386b17dbb72182080d6288a2a1f2f0608a0fe87ae287f132f1023ff33d057c5ff470012403d0000000000000000ec1fb06cdacd2bdf9d79f3cde7f9f1362b7ce914ba521c79524a5bdc212a5ed47c3cbc6c7e072ae3e3c18384785f6ca6d4e22fb1af0c96449d1847a9c7037e966fa3ac8d4990830339463d277f25fc6f30ffe5f4f5cfec9f4ffff8bf00a0f5d358a2ab6e4e6778d929f686bd58ca9c26b8f7338f15105065dd39c718');
|
||||||
|
|
||||||
|
/*
|
||||||
|
it('should load quant tables and set subsamplingMode', function () {
|
||||||
|
dec.decode(data);
|
||||||
|
|
||||||
|
expect(dec._loadedQuantTables[0]).to.equal(4);
|
||||||
|
expect(dec._loadedQuantTables[1]).to.equal(7);
|
||||||
|
expect(dec.subsamplingMode).to.equal(422);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
it('should decode properly', function () {
|
||||||
|
// DOES NOT APPEAR TO correspond to my notes in 'whole-mcu-example.txt'.
|
||||||
|
|
||||||
|
dec._blitCallback = function (x, y, width, height, buf) {
|
||||||
|
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
||||||
|
console.log(fmt_rgb_buf(width, buf));
|
||||||
|
};
|
||||||
|
|
||||||
|
dec.decode(data);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: On the 'full-frame decode' and 'frame udpate decode' tests, we are NOT actually asserting anything about the code's output yet.
|
||||||
|
/*
|
||||||
|
describe('Full frame decode test', function () {
|
||||||
|
|
||||||
|
it('should decode properly', function () {
|
||||||
|
|
||||||
|
throw "Won't work without jQuery (or some other way of loading data).";
|
||||||
|
|
||||||
|
dec._blitCallback = function (x, y, width, height, buf) {
|
||||||
|
if (x == 0 && y == 0) {
|
||||||
|
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
||||||
|
console.log(fmt_rgb_buf(width, buf));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: This path won't work for other people, and the test case needs to be made to understand that this
|
||||||
|
// is async; plus, I had to manually add jQuery to the generated HTML.
|
||||||
|
$.get("/novnc-tests/tests/frame4.hex", function (data) {
|
||||||
|
data = parseHex(data);
|
||||||
|
dec.decode(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('Frame update decode test', function () {
|
||||||
|
|
||||||
|
it('should decode properly', function () {
|
||||||
|
var data = parseHex('050501a69a3f6080008aa2a8000000900000000000000000');
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
// this data should NOT be solid-white!
|
||||||
|
|
||||||
|
dec._blitCallback = function (x, y, width, height, buf) {
|
||||||
|
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
||||||
|
console.log(fmt_rgb_buf(width, buf));
|
||||||
|
};
|
||||||
|
|
||||||
|
dec.decode(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -759,8 +759,17 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
client._rfb_init_state = 'Security';
|
client._rfb_init_state = 'Security';
|
||||||
});
|
});
|
||||||
|
|
||||||
function send_security(type, cl) {
|
// N.B.(kelleyk): 'types' may be either a single number or an array of numbers.
|
||||||
cl._sock._websocket._receive_data(new Uint8Array([1, type]));
|
function send_security(types, cl) {
|
||||||
|
if (types.constructor !== Array)
|
||||||
|
types = [types];
|
||||||
|
|
||||||
|
var data = new Uint8Array(1 + types.length);
|
||||||
|
data[0] = types.length;
|
||||||
|
for (var i = 0; i < types.length; ++i)
|
||||||
|
data[i+1] = types[i];
|
||||||
|
|
||||||
|
cl._sock._websocket._receive_data(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
|
it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
|
||||||
|
|
@ -906,7 +915,13 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
client._sock._websocket._open();
|
client._sock._websocket._open();
|
||||||
client._rfb_init_state = 'Security';
|
client._rfb_init_state = 'Security';
|
||||||
client._rfb_version = 3.8;
|
client._rfb_version = 3.8;
|
||||||
send_security(16, client);
|
// N.B.(kelleyk): Actual TightVNC servers support more than
|
||||||
|
// just 16 ("tight"), whereas ATEN iKVM servers advertise only
|
||||||
|
// 16. This is a large part of how we detect that we are
|
||||||
|
// speaking to an iKVM server, so if we want to test the
|
||||||
|
// client's interaction with a TightVNC server, we need to
|
||||||
|
// send more than just the one.
|
||||||
|
send_security([1, 16], client);
|
||||||
client._sock._websocket._get_sent_data(); // skip the security reply
|
client._sock._websocket._get_sent_data(); // skip the security reply
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -995,6 +1010,8 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
client._sock._websocket._open();
|
client._sock._websocket._open();
|
||||||
client._rfb_init_state = 'Security';
|
client._rfb_init_state = 'Security';
|
||||||
client._rfb_version = 3.8;
|
client._rfb_version = 3.8;
|
||||||
|
// N.B.(kelleyk): ATEN iKVM server advertise *only* 16 ("tight"),
|
||||||
|
// which is how we detect them.
|
||||||
send_security(16, client);
|
send_security(16, client);
|
||||||
client._sock._websocket._get_sent_data(); // skip the security reply
|
client._sock._websocket._get_sent_data(); // skip the security reply
|
||||||
client._rfb_password = 'test1:test2';
|
client._rfb_password = 'test1:test2';
|
||||||
|
|
@ -1018,6 +1035,17 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
expect(client._rfb_atenikvm).to.be.true;
|
expect(client._rfb_atenikvm).to.be.true;
|
||||||
expect(client._sock).to.have.sent(auth);
|
expect(client._sock).to.have.sent(auth);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('via new style method', function () {
|
||||||
|
client._sock._websocket._receive_data(new Uint8Array([
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0]));
|
||||||
|
expect(client._rfb_tightvnc).to.be.false;
|
||||||
|
expect(client._rfb_atenikvm).to.be.true;
|
||||||
|
expect(client._sock).to.have.sent(auth);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,9 @@
|
||||||
WebUtil.load_scripts({
|
WebUtil.load_scripts({
|
||||||
'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js",
|
'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js",
|
||||||
"input/xtscancodes.js", "input/util.js", "input/devices.js",
|
"input/xtscancodes.js", "input/util.js", "input/devices.js",
|
||||||
"display.js", "inflator.js", "rfb.js", "input/keysym.js"]});
|
"ast2100/ast2100.js", "ast2100/ast2100idct.js", "ast2100/ast2100util.js", "ast2100/ast2100const.js",
|
||||||
|
"display.js", "inflator.js", "rfb.js", "input/keysym.js"],
|
||||||
|
'app': [WebUtil.getLanguageFileLocation(), "webutil.js"]});
|
||||||
|
|
||||||
var rfb;
|
var rfb;
|
||||||
var resizeTimeout;
|
var resizeTimeout;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue