This commit is contained in:
Kevin Kelley 2017-01-20 09:20:05 +00:00 committed by GitHub
commit 7b4a6729dd
20 changed files with 3127 additions and 212 deletions

View File

@ -140,6 +140,9 @@ WebSockets to TCP socket proxy. There is a python proxy included
* UI and Icons : Pierre Ossman, Chris Gordon
* Original Logo : Michael Sersen
* tight encoding : Michael Tinglof (Mercuri.ca)
* pixel format conversion : [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:
* as3crypto : Henri Torgemane (code.google.com/p/as3crypto)

View File

@ -147,6 +147,11 @@ input[type=button]:active, select:active {
background: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
}
/* Settings slider: video quality (quantization table selector) in AST2100 mode. */
#noVNC_settings #noVNC_setting_ast2100_quality {
margin: 0 10px;
}
/* ----------------------------------------
* WebKit centering hacks
* ----------------------------------------

116
app/ui.js
View File

@ -78,7 +78,9 @@ var UI;
WebUtil.load_scripts(
{'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.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(); };
/* [end skip-as-module] */
@ -106,6 +108,12 @@ var UI;
lastKeyboardinput: null,
defaultKeyboardinputLen: 100,
// True if we are connected to an ATEN iKVM server speaking the AST2100 video encoding.
// This variable tracks whether the extra UI elements used to configure video settings
// for the AST2100 encoding have been shown yet; it's set on the first FramebufferUpdate
// message that we receive.
_ast2100_videoSettingsInitialized: false,
// Setup rfb object, load settings from browser storage, then call
// UI.init to setup the UI/menus
load: function(callback) {
@ -205,7 +213,6 @@ var UI;
UI.initSetting('host', window.location.hostname);
UI.initSetting('port', port);
UI.initSetting('encrypt', (window.location.protocol === "https:"));
UI.initSetting('true_color', true);
UI.initSetting('cursor', !Util.isTouchDevice);
UI.initSetting('resize', 'off');
UI.initSetting('shared', true);
@ -399,7 +406,9 @@ var UI;
'onBell': UI.bell,
'onFBUComplete': UI.initialResize,
'onFBResize': UI.updateSessionSize,
'onDesktopName': UI.updateDesktopName});
'onDesktopName': UI.updateDesktopName,
'ast2100_onVideoSettingsChanged': UI.ast2100_handleVideoSettingsChanged
});
return true;
} catch (exc) {
var msg = "Unable to create RFB client -- " + exc;
@ -443,7 +452,9 @@ var UI;
document.documentElement.classList.add("noVNC_disconnecting");
break;
case 'disconnected':
UI.connected = false;
UI.showStatus(_("Disconnected"));
UI.ast2100_reset();
break;
default:
msg = "Invalid UI state";
@ -459,7 +470,6 @@ var UI;
updateVisualState: function() {
//Util.Debug(">> updateVisualState");
document.getElementById('noVNC_setting_encrypt').disabled = UI.connected;
document.getElementById('noVNC_setting_true_color').disabled = UI.connected;
if (Util.browserSupportsCursorURIs()) {
document.getElementById('noVNC_setting_cursor').disabled = UI.connected;
} else {
@ -833,7 +843,6 @@ var UI;
settingsApply: function() {
//Util.Debug(">> settingsApply");
UI.saveSetting('encrypt');
UI.saveSetting('true_color');
if (Util.browserSupportsCursorURIs()) {
UI.saveSetting('cursor');
}
@ -853,6 +862,15 @@ var UI;
UI.saveSetting('repeaterID');
UI.saveSetting('logging');
if (UI._ast2100_videoSettingsInitialized) {
var videoSettings = UI.ast2100_getConfiguredSettings();
if (videoSettings != UI._ast2100_serverVideoSettings)
UI.rfb.atenChangeVideoSettings(
videoSettings.quantTableSelectorLuma,
videoSettings.quantTableSelectorChroma,
videoSettings.subsamplingMode);
}
// Settings with immediate (non-connected related) effect
WebUtil.init_logging(UI.getSetting('logging'));
UI.updateViewClip();
@ -884,7 +902,6 @@ var UI;
UI.openControlbar();
UI.updateSetting('encrypt');
UI.updateSetting('true_color');
if (Util.browserSupportsCursorURIs()) {
UI.updateSetting('cursor');
} else {
@ -973,9 +990,93 @@ var UI;
/* ------^-------
* /XVP
* ==============
* CLIPBOARD
* AST2100
* ------v------*/
ast2100_handleVideoSettingsChanged: function (settings) {
if (!UI._ast2100_videoSettingsInitialized)
UI.ast2100_setDefaultSettings(settings);
UI.ast2100_updateVideoSettings(settings);
},
// Called the first time we receive a FramebufferUpdate object. Responsible
// for telling the server about any configured default settings.
ast2100_setDefaultSettings: function (settings) {
// Convert the settings that noVNC has been configured to set for all
// AST2100 servers to the familiar videoSettings format.
var defaultQuality = parseInt(UI.ast2100_quality);
var defaultSettings = {
quantTableSelectorLuma: defaultQuality,
quantTableSelectorChroma: defaultQuality,
subsamplingMode: parseInt(UI.ast2100_subsamplingMode)
};
// If defaults were not given or were invalid, stick with what the
// server is already using.
if (!(defaultSettings.subsamplingMode == 422 || defaultSettings.subsamplingMode == 444))
defaultSettings.subsamplingMode = settings.subsamplingMode;
if (!inRangeIncl(defaultQuality, 0x0, 0xB)) {
defaultSettings.quantTableSelectorLuma = settings.quantTableSelectorLuma;
defaultSettings.quantTableSelectorChroma = settings.quantTableSelectorChroma;
}
if (defaultSettings != settings)
UI.rfb.atenChangeVideoSettings(
defaultSettings.quantTableSelectorLuma,
defaultSettings.quantTableSelectorChroma,
defaultSettings.subsamplingMode);
},
// Should be called at init time and after disconnects. This is sort
// of the opposite of _init().
ast2100_reset: function() {
document.getElementById("noVNC_ast2100_settings").classList.add('noVNC_hidden');
UI._ast2100_videoSettingsInitialized = false;
UI._ast2100_serverVideoSettings = undefined;
},
// Updates the UI to reflect values received from the server.
ast2100_updateVideoSettings: function (videoSettings) {
Util.Info("AST2100 video settings changed:");
Util.Info(videoSettings);
// We use this to tell if the user changed anything when they apply
// settings.
UI._ast2100_serverVideoSettings = videoSettings;
// First run: tell UI to show video quality controls, now that we
// know we are on a machine that supports them, and we know their
// current values.
if (!UI._ast2100_videoSettingsInitialized) {
document.getElementById("noVNC_ast2100_settings").classList.remove('noVNC_hidden');
UI._ast2100_videoSettingsInitialized = true;
}
// Average the two quant table selectors as a poor way of dealing
// with the fact that they can, technically, be different.
var quality = ~~((videoSettings.quantTableSelectorLuma + videoSettings.quantTableSelectorChroma) / 2);
document.getElementById("noVNC_setting_ast2100_quality").value = quality;
// Either 444 or 422 (which is really 4:2:0).
document.getElementById("noVNC_setting_ast2100_subsampling").value = videoSettings.subsamplingMode;
},
// Returns the current state of the UI.
ast2100_getConfiguredSettings: function () {
var quality = +document.getElementById("noVNC_setting_ast2100_quality").value;
return {
quantTableSelectorLuma: quality,
quantTableSelectorChroma: quality,
subsamplingMode: +document.getElementById("noVNC_setting_ast2100_subsampling").value
};
},
/* ------^-------
* /AST2100
* ==============
* CLIPBOARD
* ------v------*/
openClipboardPanel: function() {
UI.closeAllPanels();
UI.openControlbar();
@ -1068,7 +1169,6 @@ var UI;
UI.closeConnectPanel();
UI.rfb.set_encrypt(UI.getSetting('encrypt'));
UI.rfb.set_true_color(UI.getSetting('true_color'));
UI.rfb.set_local_cursor(UI.getSetting('cursor'));
UI.rfb.set_shared(UI.getSetting('shared'));
UI.rfb.set_view_only(UI.getSetting('view_only'));

49
core/ast2100/README.md Normal file
View File

@ -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.)

504
core/ast2100/ast2100.js Normal file
View File

@ -0,0 +1,504 @@
"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._videoSettingsChangedCallback = defaults.videoSettingsChangedCallback;
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;
},
// Each quant table selector is between 0x0 (lowest quality) and 0xB (highest quality). The ATEN client shows a
// single quality slider, which changes both values in tandem. The server sends all three values with each
// FramebufferUpdate message, so these values are updated with every call to decode(). They will be -1 before
// the first frame is decoded.
getVideoSettings: function () {
return {
quantTableSelectorLuma: this._loadedQuantTables[0],
quantTableSelectorChroma: this._loadedQuantTables[1],
subsamplingMode: this.subsamplingMode
};
},
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
var changedSettings = false;
if (this.subsamplingMode != subsamplingMode) {
if (verboseVideoSettings)
console.log('decode(): new subsampling mode: '+subsamplingMode);
this.subsamplingMode = subsamplingMode;
changedSettings = true;
}
// 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;
changedSettings = true;
}
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;
changedSettings = true;
}
if (this.subsamplingMode != 422 && this.subsamplingMode != 444)
throw 'Unexpected value for subsamplingMode: 0x' + fmt_u16(this.subsamplingMode);
if (changedSettings && this._videoSettingsChangedCallback)
this._videoSettingsChangedCallback(this.getVideoSettings());
// 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
}
};
})();

View File

@ -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;

214
core/ast2100/ast2100idct.js Normal file
View File

@ -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);
}
};
})();

284
core/ast2100/ast2100util.js Normal file
View File

@ -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;
}
};
})();

View File

@ -456,12 +456,18 @@
// else: No-op -- already done by setSubTile
},
blitImage: function (x, y, width, height, arr, offset, from_queue) {
// N.B.: You *cannot* call this during a test in RGBX-order true-color
// mode, or PhantomJS dies with a Uint8ClampeddArray error.
// N.B.: If you know that your data will always be RGB (that is, isRgb==true
// and this._true_color==true), then you should be calling blitRgbxImage()
// instead of blitImage() to avoid the extra branches.
blitImage: function (x, y, width, height, arr, offset, isRgb, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// 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
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));
this._renderQ_push({
'type': 'blit',
@ -470,9 +476,14 @@
'y': y,
'width': width,
'height': height,
'isRgb': isRgb,
});
} else if (this._true_color) {
if (isRgb) {
this._rgbxImageData(x, y, width, height, arr, offset);
} else {
this._bgrxImageData(x, y, width, height, arr, offset);
}
} else {
this._cmapImageData(x, y, width, height, arr, offset);
}
@ -501,6 +512,8 @@
}
},
// this is different from the above in that it assumes the data is always rgbx format, instead
// of dealing with the possibility of cmap data
blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
@ -716,10 +729,7 @@
this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
break;
case 'blit':
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break;
case 'blitRgb':
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, a.rgb, true);
break;
case 'blitRgbx':
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);

View File

@ -379,4 +379,82 @@ var KeyTable = {
XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */
};
var XK2HID = {};
// F{1..12}
for (var i = KeyTable.XK_F1; i <= KeyTable.XK_F12; i++) {
XK2HID[i] = 0x3a + (i - KeyTable.XK_F1);
}
// A-Za-z
for (var i = KeyTable.XK_A; i <= KeyTable.XK_Z; i++) {
XK2HID[i] = 0x04 + (i - KeyTable.XK_A);
XK2HID[i + (KeyTable.XK_a - KeyTable.XK_A)] = 0x04 + (i - KeyTable.XK_A);
}
// 1-9
for (var i = KeyTable.XK_1; i <= KeyTable.XK_9; i++) {
XK2HID[i] = 0x1e + (i - KeyTable.XK_1);
}
XK2HID[KeyTable.XK_0] = 0x27;
XK2HID[KeyTable.XK_Return] = 0x28;
XK2HID[KeyTable.XK_Escape] = 0x29;
XK2HID[KeyTable.XK_BackSpace] = 0x2a;
XK2HID[KeyTable.XK_Tab] = 0x2b;
XK2HID[KeyTable.XK_space] = 0x2c;
XK2HID[KeyTable.XK_minus] = 0x2d;
XK2HID[KeyTable.XK_equal] = 0x2e;
XK2HID[KeyTable.XK_bracketleft] = 0x2f;
XK2HID[KeyTable.XK_bracketright] = 0x30;
XK2HID[KeyTable.XK_backslash] = 0x31;
XK2HID[KeyTable.XK_semicolon] = 0x33;
XK2HID[KeyTable.XK_apostrophe] = 0x34;
XK2HID[KeyTable.XK_grave] = 0x35;
XK2HID[KeyTable.XK_comma] = 0x36;
XK2HID[KeyTable.XK_period] = 0x37;
XK2HID[KeyTable.XK_slash] = 0x38;
XK2HID[KeyTable.XK_Print] = 0x46;
XK2HID[KeyTable.XK_Scroll_Lock] = 0x47;
XK2HID[KeyTable.XK_Pause] = 0x48;
XK2HID[KeyTable.XK_Insert] = 0x49;
XK2HID[KeyTable.XK_Home] = 0x4a;
XK2HID[KeyTable.XK_Page_Up] = 0x4b;
XK2HID[KeyTable.XK_Delete] = 0x4c;
XK2HID[KeyTable.XK_End] = 0x4d;
XK2HID[KeyTable.XK_Page_Down] = 0x4e;
XK2HID[KeyTable.XK_Right] = 0x4f;
XK2HID[KeyTable.XK_Left] = 0x50;
XK2HID[KeyTable.XK_Down] = 0x51;
XK2HID[KeyTable.XK_Up] = 0x52;
XK2HID[KeyTable.XK_Control_L] = 0xe0;
XK2HID[KeyTable.XK_Control_R] = XK2HID[KeyTable.XK_Control_L];
XK2HID[KeyTable.XK_Shift_L] = 0xe1;
XK2HID[KeyTable.XK_Shift_R] = XK2HID[KeyTable.XK_Shift_L];
XK2HID[KeyTable.XK_Alt_L] = 0xe2;
XK2HID[KeyTable.XK_Alt_R] = XK2HID[KeyTable.XK_Alt_L];
XK2HID[KeyTable.XK_Super_L] = 0xe3;
XK2HID[KeyTable.XK_Super_R] = XK2HID[KeyTable.XK_Super_L];
XK2HID[KeyTable.XK_Caps_Lock] = 0x39;
// locale hardcoded hack
XK2HID[KeyTable.XK_less] = XK2HID[KeyTable.XK_comma];
XK2HID[KeyTable.XK_greater] = XK2HID[KeyTable.XK_period];
XK2HID[KeyTable.XK_exclam] = XK2HID[KeyTable.XK_1];
XK2HID[KeyTable.XK_at] = XK2HID[KeyTable.XK_2];
XK2HID[KeyTable.XK_numbersign] = XK2HID[KeyTable.XK_3];
XK2HID[KeyTable.XK_dollar] = XK2HID[KeyTable.XK_4];
XK2HID[KeyTable.XK_percent] = XK2HID[KeyTable.XK_5];
XK2HID[KeyTable.XK_asciicircum] = XK2HID[KeyTable.XK_6];
XK2HID[KeyTable.XK_ampersand] = XK2HID[KeyTable.XK_7];
XK2HID[KeyTable.XK_asterisk] = XK2HID[KeyTable.XK_8];
XK2HID[KeyTable.XK_parenleft] = XK2HID[KeyTable.XK_9];
XK2HID[KeyTable.XK_parenright] = XK2HID[KeyTable.XK_0];
XK2HID[KeyTable.XK_underscore] = XK2HID[KeyTable.XK_minus];
XK2HID[KeyTable.XK_bar] = XK2HID[KeyTable.XK_backslash];
XK2HID[KeyTable.XK_quotedbl] = XK2HID[KeyTable.XK_apostrophe];
XK2HID[KeyTable.XK_asciitilde] = XK2HID[KeyTable.XK_grave];
/* [module] export default KeyTable; */

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,32 @@
var Util = {};
var addClassFunc = function (cl, name, func) {
if (!cl[name]) {
Object.defineProperty(cl, name, { enumerable: false, value: func });
}
};
// TODO(kelleyk): There's probably a better way to do this, but TypedArray isn't
// directly accessible.
// N.B.(kelleyk): PhantomJS 1.x does not support Uint8ClampedArray or Float64Array,
// so those are left out.
[Array, Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array].forEach(
function (cls) {
var thatCls = cls;
addClassFunc(cls, 'from', function (arrayLike, mapFn, thisArg) {
if (typeof(mapFn) !== 'undefined' || typeof(thisArg) !== 'undefined')
throw new Error("This version of Array.from() does not support " +
"mapFn or thisArg arguments.");
var result = new thatCls(arrayLike.length);
for (var i = 0; i < arrayLike.length; ++i)
result[i] = arrayLike[i];
return result;
});
});
/*
* ------------------------------------------------------
* Namespaced in Util

View File

@ -123,6 +123,10 @@ module.exports = function(config) {
'core/des.js',
'core/display.js',
'core/inflator.js',
'core/ast2100/ast2100const.js',
'core/ast2100/ast2100util.js',
'core/ast2100/ast2100idct.js',
'core/ast2100/ast2100.js',
'tests/test.*.js'
],

1
tests/frame4.hex Normal file

File diff suppressed because one or more lines are too long

384
tests/test.ast2100.js Normal file
View File

@ -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);
});
});
});

View File

@ -392,19 +392,14 @@ describe('Display/Canvas Helper', function () {
data[i * 4 + 2] = checked_data[i * 4];
data[i * 4 + 3] = checked_data[i * 4 + 3];
}
display.blitImage(0, 0, 4, 4, data, 0);
display.flip();
expect(display).to.have.displayed(checked_data);
});
it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
var data = [];
for (var i = 0; i < 16; i++) {
data[i * 3] = checked_data[i * 4];
data[i * 3 + 1] = checked_data[i * 4 + 1];
data[i * 3 + 2] = checked_data[i * 4 + 2];
}
display.blitRgbImage(0, 0, 4, 4, data, 0);
it('should support drawing RGBX blit images with true color via #blitImage', function () {
display.blitImage(0, 0, 4, 4, checked_data, 0, true);
display.flip();
expect(display).to.have.displayed(checked_data);
});
@ -490,18 +485,19 @@ describe('Display/Canvas Helper', function () {
expect(display.get_onFlush()).to.have.been.calledOnce;
});
it('should draw a blit image on type "blit"', function () {
it('should draw a blit image on type "blit" with "rgb" set to false', function () {
display.blitImage = sinon.spy();
display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9], rgb: false });
expect(display.blitImage).to.have.been.calledOnce;
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0, false);
});
it('should draw a blit RGB image on type "blitRgb"', function () {
display.blitRgbImage = sinon.spy();
display._renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
expect(display.blitRgbImage).to.have.been.calledOnce;
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
it('should draw a blit RGBX image on type "blit" with "rgb" set to true', function () {
display.blitImage = sinon.spy();
display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9, 10], rgb: true });
expect(display.blitImage).to.have.been.calledOnce;
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9, 10], 0, true);
});
it('should copy a region on type "copy"', function () {

View File

@ -759,8 +759,17 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._rfb_init_state = 'Security';
});
function send_security(type, cl) {
cl._sock._websocket._receive_data(new Uint8Array([1, type]));
// N.B.(kelleyk): 'types' may be either a single number or an array of numbers.
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 () {
@ -906,7 +915,13 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._sock._websocket._open();
client._rfb_init_state = 'Security';
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
});
@ -985,6 +1000,53 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._fail).to.have.been.calledOnce;
});
});
describe('ATEN iKVM Authentication Handler', function () {
var client;
beforeEach(function () {
client = make_rfb();
client.connect('host', 8675);
client._sock._websocket._open();
client._rfb_init_state = 'Security';
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);
client._sock._websocket._get_sent_data(); // skip the security reply
client._rfb_password = 'test1:test2';
});
var auth = [
116, 101, 115, 116, 49, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
116, 101, 115, 116, 50, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0];
it('via old style method', function () {
client._sock._websocket._receive_data(new Uint8Array([
0xaf, 0xf9, 0x0f, 0xb0, 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);
});
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);
});
});
});
describe('SecurityResult', function () {
@ -1103,8 +1165,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._fb_height).to.equal(84);
});
// NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
it('should set the framebuffer name and call the callback', function () {
client.set_onDesktopName(sinon.spy());
send_server_init({ name: 'some name' }, client);
@ -1134,12 +1194,23 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._rfb_connection_state).to.equal('connected');
});
it('should set the true color mode on the display to the configuration variable', function () {
client.set_true_color(false);
sinon.spy(client._display, 'set_true_color');
send_server_init({ true_color: 1 }, client);
expect(client._display.set_true_color).to.have.been.calledOnce;
expect(client._display.set_true_color).to.have.been.calledWith(false);
it('should handle an ATEN iKVM server initialization', function () {
RFB.ATEN_INIT_WIDTH = 1;
RFB.ATEN_INIT_HEIGHT = 1;
client._rfb_atenikvm = true;
send_server_init({ true_color: 1, bpp: 32 }, client);
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
expect(client._pixelFormat.bpp).to.equal(16);
expect(client._pixelFormat.depth).to.equal(15);
expect(client._pixelFormat.red_max).to.equal(31);
expect(client._pixelFormat.green_max).to.equal(31);
expect(client._pixelFormat.blue_max).to.equal(31);
expect(client._pixelFormat.red_shift).to.equal(10);
expect(client._pixelFormat.green_shift).to.equal(5);
expect(client._pixelFormat.blue_shift).to.equal(0);
expect(client._pixelFormat.Bpp).to.equal(2);
expect(client._pixelFormat.Bdepth).to.equal(2);
expect(client._rfb_connection_state).to.equal('connected');
});
it('should call the resize callback and resize the display', function () {
@ -1163,29 +1234,35 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._mouse.grab).to.have.been.calledOnce;
});
it('should set the BPP and depth to 4 and 3 respectively if in true color mode', function () {
client.set_true_color(true);
send_server_init({}, client);
expect(client._fb_Bpp).to.equal(4);
expect(client._fb_depth).to.equal(3);
it('should set the BPP and depth to 4 and 3 respectively if server can send native (true color)', function () {
send_server_init({ true_color: 1, bpp: 8, depth: 8 }, client);
expect(client._pixelFormat.Bpp).to.equal(4);
expect(client._pixelFormat.Bdepth).to.equal(3);
});
it('should set the BPP and depth to 1 and 1 respectively if not in true color mode', function () {
client.set_true_color(false);
send_server_init({}, client);
expect(client._fb_Bpp).to.equal(1);
expect(client._fb_depth).to.equal(1);
it('should set the BPP and depth to 2 and 2 respectively if server cannot send native (true color)', function () {
client.set_convert_color(true);
send_server_init({ true_color: 1, bpp: 16, depth: 15 }, client);
expect(client._pixelFormat.Bpp).to.equal(2);
expect(client._pixelFormat.Bdepth).to.equal(2);
});
it('should set the BPP and depth to 1 and 1 respectively if server cannot send native (not true color)', function () {
client.set_convert_color(true);
send_server_init({ true_color: 0, bpp: 8, depth: 8 }, client);
expect(client._pixelFormat.Bpp).to.equal(1);
expect(client._pixelFormat.Bdepth).to.equal(1);
});
// TODO(directxman12): test the various options in this configuration matrix
it('should reply with the pixel format, client encodings, and initial update request', function () {
client.set_true_color(true);
client.set_local_cursor(false);
// we skip the cursor encoding
var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)),
_sQlen: 0,
flush: function () {}};
RFB.messages.pixelFormat(expected, 4, 3, true);
var pf = { bpp: 32, depth: 24, big_endian: false, true_color: true, red_max: 255, green_max: 255, blue_max: 255, red_shift: 16, green_shift: 8, blue_shift: 0 };
RFB.messages.pixelFormat(expected, pf);
RFB.messages.clientEncodings(expected, client._encodings, false, true);
RFB.messages.fbUpdateRequest(expected, false, 0, 0, 27, 32);
@ -1197,6 +1274,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
send_server_init({}, client);
expect(client._rfb_connection_state).to.equal('connected');
});
});
});
@ -1224,13 +1302,14 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._fb_name = 'some device';
client._fb_width = 640;
client._fb_height = 20;
client._pixelFormat.Bpp = 4;
});
var target_data_arr = [
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
0xf8, 0x00, 0x00, 255, 0x00, 0xf8, 0x00, 255, 0x00, 0x00, 0xf8, 255, 0x00, 0x00, 0xf8, 255,
0x00, 0xf8, 0x00, 255, 0xf8, 0x00, 0x00, 255, 0x00, 0x00, 0xf8, 255, 0x00, 0x00, 0xf8, 255,
0xe8, 0x00, 0xf8, 255, 0x00, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255,
0xe8, 0x00, 0xf8, 255, 0x00, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255
];
var target_data;
@ -1379,24 +1458,98 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._fb_width = 4;
client._fb_height = 4;
client._display.resize(4, 4);
client._fb_Bpp = 4;
client._pixelFormat.Bpp = 4;
client._destBuff = new Uint8Array(client._fb_width * client._fb_height * 4);
});
it('should handle the RAW encoding', function () {
// warning: the fbupdates *overlap* so you have to send all rects for the numbers
// to even make sense; this means (ironically) no iterative building of your tests
describe('should handle the RAW encoding', function () {
it('should handle 24bit depth (RGBX888) @ 32bpp [native]', function () {
client._convert_color = true;
client._pixelFormat.big_endian = false;
client._pixelFormat.red_shift = 0;
client._pixelFormat.red_max = 255;
client._pixelFormat.green_shift = 8;
client._pixelFormat.green_max = 255;
client._pixelFormat.blue_shift = 16;
client._pixelFormat.blue_max = 255;
var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
{ x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
// data is in bgrx
var rects = [
[0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
[0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
[0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
[0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
[0xf8, 0x00, 0x00, 0, 0x00, 0xf8, 0x00, 0, 0x00, 0xf8, 0x00, 0, 0xf8, 0x00, 0x00, 0],
[0x00, 0x00, 0xf8, 0, 0x00, 0x00, 0xf8, 0, 0x00, 0x00, 0xf8, 0, 0x00, 0x00, 0xf8, 0],
[0xe8, 0x00, 0xf8, 0, 0x00, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0],
[0xe8, 0x00, 0xf8, 0, 0x00, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0]];
send_fbu_msg(info, rects, client);
expect(client._display).to.have.displayed(target_data);
});
it('should handle 24bit depth (BGRX888) @ 32bpp', function () {
var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
{ x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
var rects = [
[0x00, 0x00, 0xf8, 0, 0x00, 0xf8, 0x00, 0, 0x00, 0xf8, 0x00, 0, 0x00, 0x00, 0xf8, 0],
[0xf8, 0x00, 0x00, 0, 0xf8, 0x00, 0x00, 0, 0xf8, 0x00, 0x00, 0, 0xf8, 0x00, 0x00, 0],
[0xf8, 0x00, 0xe8, 0, 0xf8, 0xe8, 0x00, 0, 0xf8, 0xe8, 0xa8, 0, 0xf8, 0xe8, 0xa8, 0],
[0xf8, 0x00, 0xe8, 0, 0xf8, 0xe8, 0x00, 0, 0xf8, 0xe8, 0xa8, 0, 0xf8, 0xe8, 0xa8, 0]];
send_fbu_msg(info, rects, client);
expect(client._display).to.have.displayed(target_data);
});
// for wisdom: perl -e '($w, $r, $g, $b) = @ARGV; $W=2**$w; $nb = $b*($W/256); $ng = $g*($W/256); $nr = $r*($W/256); printf "%f:%f:%f %04x\n", $nr, $ng, $nb, unpack("S", pack("n", ($nr << (2*$w)) | ($ng << (1*$w)) | ($nb << (0*$w))))' 5 0 248 0
it('should handle 15bit depth (BGR555) @ 16bpp', function () {
client._convert_color = true;
client._pixelFormat.big_endian = false;
client._pixelFormat.Bpp = 2;
client._pixelFormat.red_shift = 10;
client._pixelFormat.red_max = 31;
client._pixelFormat.green_shift = 5;
client._pixelFormat.green_max = 31;
client._pixelFormat.blue_shift = 0;
client._pixelFormat.blue_max = 31;
var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
{ x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
var rects = [
[0x00, 0x7c, 0xe0, 0x03, 0xe0, 0x03, 0x00, 0x7c],
[0x1f, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x1f, 0x00],
[0x1f, 0x74, 0xbf, 0x03, 0xbf, 0x57, 0xbf, 0x57],
[0x1f, 0x74, 0xbf, 0x03, 0xbf, 0x57, 0xbf, 0x57]];
send_fbu_msg(info, rects, client);
expect(client._display).to.have.displayed(target_data);
});
it('should handle 15bit depth (BGR555) @ 16bpp big-endian', function () {
client._convert_color = true;
client._pixelFormat.big_endian = false;
client._pixelFormat.Bpp = 2;
client._pixelFormat.big_endian = true;
client._pixelFormat.red_shift = 10;
client._pixelFormat.red_max = 31;
client._pixelFormat.green_shift = 5;
client._pixelFormat.green_max = 31;
client._pixelFormat.blue_shift = 0;
client._pixelFormat.blue_max = 31;
var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
{ x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
var rects = [
[0x7c, 0x00, 0x03, 0xe0, 0x03, 0xe0, 0x7c, 0x00],
[0x00, 0x1f, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x1f],
[0x74, 0x1f, 0x03, 0xbf, 0x57, 0xbf, 0x57, 0xbf],
[0x74, 0x1f, 0x03, 0xbf, 0x57, 0xbf, 0x57, 0xbf]];
send_fbu_msg(info, rects, client);
expect(client._display).to.have.displayed(target_data);
});
});
it('should handle the COPYRECT encoding', function () {
// seed some initial data to copy
client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0);
@ -1426,7 +1579,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
push16(rect, 2); // width: 2
push16(rect, 2); // height: 2
rect.push(0xff); // becomes ff0000ff --> #0000FF color
rect.push(0x00);
rect.push(0x00); // becomes 0000ffff --> #0000FF color
rect.push(0x00);
rect.push(0xff);
push16(rect, 2); // x: 2
@ -1450,7 +1603,8 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._fb_width = 4;
client._fb_height = 4;
client._display.resize(4, 4);
client._fb_Bpp = 4;
client._pixelFormat.Bpp = 4;
client._destBuff = new Uint8Array(client._fb_width * client._fb_height * 4);
});
it('should handle a tile with fg, bg specified, normal subrects', function () {
@ -1594,6 +1748,97 @@ describe('Remote Frame Buffer Protocol Client', function() {
// TODO(directxman12): test this
});
describe('the ATEN encoding handler', function () {
var client;
beforeEach(function () {
client = make_rfb();
client.connect('host', 8675);
client._sock._websocket._open();
client._rfb_connection_state = 'connected';
client._fb_name = 'some device';
client._rfb_atenikvm = true;
// start large, then go small
client._fb_width = 10000;
client._fb_height = 10000;
client._display._fb_width = 10000;
client._display._fb_height = 10000;
client._display._viewportLoc.w = 10000;
client._display._viewportLoc.h = 10000;
client._convert_color = true;
client._pixelFormat.Bpp = 2;
client._pixelFormat.big_endian = false;
client._pixelFormat.red_shift = 10;
client._pixelFormat.red_max = 31;
client._pixelFormat.green_shift = 5;
client._pixelFormat.green_max = 31;
client._pixelFormat.blue_shift = 0;
client._pixelFormat.blue_max = 31;
client._destBuff = new Uint8Array(client._fb_width * client._fb_height * 4);
});
var aten_target_data_arr = [0xa8, 0xe8, 0xf8, 0xff];
var aten_target_data;
before(function () {
for (var i = 0; i < 10; i++) {
aten_target_data_arr = aten_target_data_arr.concat(aten_target_data_arr);
}
aten_target_data = new Uint8Array(aten_target_data_arr);
});
it('should handle subtype subrect encoding', function () {
var info = [{ x: 0, y: 0, width: 32, height: 32, encoding: 0x59 }];
var rect = [];
push32(rect, 0); // padding
push32(rect, 2082); // 10 + 32x32x2Bpp + 6*(num of subrects)
push8(rect, 0); // type
push8(rect, 0); // padding
push32(rect, 4); // num of subrects (32/16)*(32/16)
push32(rect, 2082); // length (again)
for (var y = 0; y < 2; y++) {
for (var x = 0; x < 2; x++) {
push16(rect, 0); // a
push16(rect, 0); // b
push8(rect, y);
push8(rect, x);
for (var i = 0; i < 16*16; i++) {
push16(rect, 0xbf57);
}
}
}
send_fbu_msg(info, [rect], client);
expect(client._display).to.have.displayed(aten_target_data);
});
it('should handle subtype RAW encoding', function () {
// do not use encoding=0x59 here, as rfb.js should override it
var info = [{ x: 0, y: 0, width: 32, height: 32, encoding: 0x00 }];
var rect = [];
push32(rect, 0); // padding
push32(rect, 2058); // 10 + 32x32x2Bpp
push8(rect, 1); // type
push8(rect, 0); // padding
push32(rect, 0); // padding
push32(rect, 2058); // length (again)
for (var i = 0; i < 32*32; i++) {
push16(rect, 0xbf57);
}
send_fbu_msg(info, [rect], client);
expect(client._display).to.have.displayed(aten_target_data);
});
});
it('should handle the DesktopSize pseduo-encoding', function () {
client.set_onFBResize(sinon.spy());
sinon.spy(client._display, 'resize');

View File

@ -7,6 +7,45 @@ var expect = chai.expect;
describe('Utils', function() {
"use strict";
describe('Array class methods', function () {
var expectTypedArrayEq = function (cls, arr) {
var other = cls.from(arr);
expect(other).to.be.instanceof(cls);
// N.B.: We might be tempted to say 'expect(other).to.deep.equal(arr);',
// but that would be incorrect in the situation where
// 'arr' is not an instance of 'cls'.
expect(other.length).to.equal(arr.length);
for (var i = 0; i < arr.length; ++i)
expect(other[i]).to.equal(arr[i]);
// TODO: Test deep/shallow copy behavior?
};
describe('Array.from', function () {
it('should create a new object with the same type and contents', function () {
var arr = [5, 4, 3];
expectTypedArrayEq(Array, arr);
});
});
// As a stand-in for all of the TypedArray classes
describe('Int32Array.from', function () {
it('should create a new object with the same type and contents', function () {
var arr = new Int32Array(3);
arr[0] = 5; arr[1] = 4; arr[2] = 3; // want to do this without from(), obviously
expectTypedArrayEq(Int32Array, arr);
});
it('should return an Int32Array even if the argument is a different type of array', function () {
var arr = [5, 4, 3];
expectTypedArrayEq(Int32Array, arr);
});
});
});
describe('logging functions', function () {
beforeEach(function () {
sinon.spy(console, 'log');

View File

@ -11,9 +11,9 @@
This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
Connect parameters are provided in query string:
http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1
http://example.com/?host=HOST&port=PORT&encrypt=1
or the fragment:
http://example.com/#host=HOST&port=PORT&encrypt=1&true_color=1
http://example.com/#host=HOST&port=PORT&encrypt=1
-->
<title>noVNC</title>
@ -199,9 +199,6 @@
<li>
<div class="noVNC_expander">Advanced</div>
<div><ul>
<li>
<label><input id="noVNC_setting_true_color" type="checkbox" checked /> True Color</label>
</li>
<li>
<label><input id="noVNC_setting_cursor" type="checkbox" /> Local Cursor</label>
</li>
@ -240,6 +237,38 @@
</li>
</ul></div>
</li>
<!--
AST2100 (0x57) video encoding only:
- Video quality slider (quantization table selector)
- Subsampling mode
-->
<div id="noVNC_ast2100_settings" class="">
<li><hr></li>
<li>
<div class="noVNC_expander">AST2100 Settings</div>
<div><ul>
<li>
<label>Video quality</label>
<div class="slider-area">
low
<input type="range" min="0" max="11" class="slider-bar" id="noVNC_setting_ast2100_quality" />
high
</div>
</li>
<li><hr></li>
<li>
<label>Subsampling mode</label>
<select id="noVNC_setting_ast2100_subsampling">
<option value="444">4:4:4 (no subsampling; higher quality)</option>
<option value="422">4:2:0 (chroma subsampling; lower quality)</option>
</select>
</li>
</ul></div>
</li>
</div>
<li><hr></li>
<li>
<input type="button" id="noVNC_settings_apply" value="Apply" class="noVNC_submit" />

View File

@ -10,9 +10,9 @@
This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
Connect parameters are provided in query string:
http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1
http://example.com/?host=HOST&port=PORT&encrypt=1
or the fragment:
http://example.com/#host=HOST&port=PORT&encrypt=1&true_color=1
http://example.com/#host=HOST&port=PORT&encrypt=1
-->
<title>noVNC</title>
@ -81,7 +81,9 @@
WebUtil.load_scripts({
'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.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 resizeTimeout;
@ -265,11 +267,12 @@
'encrypt': WebUtil.getConfigVar('encrypt',
(window.location.protocol === "https:")),
'repeaterID': WebUtil.getConfigVar('repeaterID', ''),
'true_color': WebUtil.getConfigVar('true_color', true),
'local_cursor': WebUtil.getConfigVar('cursor', true),
'shared': WebUtil.getConfigVar('shared', true),
'view_only': WebUtil.getConfigVar('view_only', false),
'onNotification': notification,
'ast2100_quality': WebUtil.getConfigVar('ast2100_quality', -1),
'ast2100_subsamplingMode': WebUtil.getConfigVar('ast2100_subsamplingMode', -1),
'onUpdateState': updateState,
'onDisconnected': disconnected,
'onXvpInit': xvpInit,
@ -280,7 +283,6 @@
status('Unable to create RFB client -- ' + exc, 'error');
return; // don't continue trying to connect
}
rfb.connect(host, port, password, path);
};
</script>