Merge eb386b7672 into a7619faf18
This commit is contained in:
commit
7b4a6729dd
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
116
app/ui.js
|
|
@ -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'));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
# AST2100 (0x57) encoding support
|
||||
|
||||
These are notes to accompany the ATEN iKVM "AST2100" (0x59) encoding implementation that this branch contains.
|
||||
|
||||
This implementation is the product of clean-room reverse engineering (that is, I am not and have never been subject to
|
||||
nondisclosure agreements, nor have I had access to proprietary information, related to the subject matter of this
|
||||
project).
|
||||
|
||||
(c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>
|
||||
|
||||
### Current problems / limitations (aka "TODOs")
|
||||
|
||||
- Especially on lower quality settings, you will notice that the picture is not as clear as what the ATEN iKVM client
|
||||
will show you (using the same settings). I'm aware of this issue and intend to fix it. Disabling chroma
|
||||
subsampling will allow the encoder to use VQ when there are few colors (e.g. when you are looking at a terminal); VQ
|
||||
data doesn't have the same quality issue.
|
||||
|
||||
- The code could stand to be much better-tested.
|
||||
|
||||
- The JavaScript files related to the AST2100 decoder are loaded even when noVNC does not use the decoder. It would
|
||||
be nice to lazy-load them only when they are necessary.
|
||||
|
||||
- Lots of globals (functions, constants, etc.) are exposed. Some quick refactoring could tuck the majority of them
|
||||
away to avoid cluttering the namespace.
|
||||
|
||||
### Profiling
|
||||
|
||||
- For some reason, when I use blitImageData() (with the noVNC render queue disabled), that function shows up as the
|
||||
"heaviest" function in Chrome's CPU profiler, even though the function is doing nothing other than evaluating a
|
||||
branch condition or two and then calling _rgbxImageData(). When I call _rgbxImageData() directly, then
|
||||
putImageData() (the Canvas method that's actually doing the heavy lifting) is correctly shown as the "heaviest"
|
||||
function.
|
||||
|
||||
- Profiler oddness aside, putImageData() is overwhelmingly the dominant cost; it seems to occupy 75-85% of the CPU
|
||||
time that noVNC uses. There are plenty of places that we could get small performance improvements in Ast2100Decoder
|
||||
(and elsewhere in noVNC) but they seem unlikely to have a worthwhile impact, given that fact.
|
||||
|
||||
### About the implementation
|
||||
|
||||
- One large, remaining inefficiency is the several times that image data is copied around before being blitted. The
|
||||
Ast2100Decoder class generates as output 256-element arrays (representing 64 pixels as (R,G,B,A) 4-tuples). This is
|
||||
exactly what winds up in the ImageData object that is eventually passed to putImageData(); we could just have the
|
||||
decoder write its output directly into those arrays if we wanted.
|
||||
|
||||
### Performance questions
|
||||
|
||||
- Is it faster to call putImageData() fewer times with larger buffers? We could collect groups of blocks (or even an
|
||||
entire frame) and then call putImageData() once. (Of course, this would require redrawing unchanged regions every
|
||||
frame, too.)
|
||||
|
|
@ -0,0 +1,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
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
/* (c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>. */
|
||||
|
||||
|
||||
// JPEG zigzag tables
|
||||
var ZIGZAG_ORDER = [
|
||||
0, 1, 8, 16, 9, 2, 3, 10,
|
||||
17, 24, 32, 25, 18, 11, 4, 5,
|
||||
12, 19, 26, 33, 40, 48, 41, 34,
|
||||
27, 20, 13, 6, 7, 14, 21, 28,
|
||||
35, 42, 49, 56, 57, 50, 43, 36,
|
||||
29, 22, 15, 23, 30, 37, 44, 51,
|
||||
58, 59, 52, 45, 38, 31, 39, 46,
|
||||
53, 60, 61, 54, 47, 55, 62, 63,
|
||||
];
|
||||
var DEZIGZAG_ORDER = [
|
||||
0, 1, 5, 6, 14, 15, 27, 28,
|
||||
2, 4, 7, 13, 16, 26, 29, 42,
|
||||
3, 8, 12, 17, 25, 30, 41, 43,
|
||||
9, 11, 18, 24, 31, 40, 44, 53,
|
||||
10, 19, 23, 32, 39, 45, 52, 54,
|
||||
20, 22, 33, 38, 46, 51, 55, 60,
|
||||
21, 34, 37, 47, 50, 56, 59, 61,
|
||||
35, 36, 48, 49, 57, 58, 62, 63,
|
||||
];
|
||||
|
||||
|
||||
// Huffman tables
|
||||
var BITS_DC_LUMA = [0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0];
|
||||
var HUFFVAL_DC_LUMA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
var BITS_DC_CHROMA = [0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0];
|
||||
var HUFFVAL_DC_CHROMA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
var BITS_AC_LUMA = [0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d];
|
||||
var HUFFVAL_AC_LUMA = [
|
||||
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
|
||||
0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
|
||||
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
|
||||
0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
|
||||
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
|
||||
0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
|
||||
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
|
||||
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
|
||||
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
||||
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
|
||||
0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
|
||||
0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
|
||||
0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
|
||||
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
|
||||
0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
|
||||
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
|
||||
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
|
||||
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
|
||||
0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
||||
0xf9, 0xfa
|
||||
];
|
||||
var BITS_AC_CHROMA = [0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77];
|
||||
var HUFFVAL_AC_CHROMA = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
|
||||
0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
|
||||
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
|
||||
0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
|
||||
0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
|
||||
0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
|
||||
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
|
||||
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
|
||||
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
|
||||
0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
|
||||
0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
|
||||
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
|
||||
0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
|
||||
0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
|
||||
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
|
||||
0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
||||
0xf9, 0xfa
|
||||
];
|
||||
|
||||
|
||||
// Quant tables
|
||||
var ATEN_QT_LUMA = [
|
||||
[
|
||||
0x14, 0x0D, 0x0C, 0x14, 0x1E, 0x32, 0x3F, 0x4C,
|
||||
0x0F, 0x0F, 0x11, 0x17, 0x20, 0x48, 0x4B, 0x44,
|
||||
0x11, 0x10, 0x14, 0x1E, 0x32, 0x47, 0x56, 0x46,
|
||||
0x11, 0x15, 0x1B, 0x24, 0x3F, 0x6C, 0x64, 0x4D,
|
||||
0x16, 0x1B, 0x2E, 0x46, 0x55, 0x88, 0x80, 0x60,
|
||||
0x1E, 0x2B, 0x44, 0x50, 0x65, 0x82, 0x8D, 0x73,
|
||||
0x3D, 0x50, 0x61, 0x6C, 0x80, 0x97, 0x96, 0x7E,
|
||||
0x5A, 0x73, 0x76, 0x7A, 0x8C, 0x7D, 0x80, 0x7B,
|
||||
], [
|
||||
0x11, 0x0C, 0x0A, 0x11, 0x1A, 0x2B, 0x37, 0x42,
|
||||
0x0D, 0x0D, 0x0F, 0x14, 0x1C, 0x3F, 0x41, 0x3C,
|
||||
0x0F, 0x0E, 0x11, 0x1A, 0x2B, 0x3E, 0x4B, 0x3D,
|
||||
0x0F, 0x12, 0x18, 0x1F, 0x37, 0x5F, 0x57, 0x43,
|
||||
0x13, 0x18, 0x28, 0x3D, 0x4A, 0x77, 0x70, 0x54,
|
||||
0x1A, 0x26, 0x3C, 0x46, 0x58, 0x71, 0x7B, 0x64,
|
||||
0x35, 0x46, 0x55, 0x5F, 0x70, 0x84, 0x83, 0x6E,
|
||||
0x4E, 0x64, 0x67, 0x6B, 0x7A, 0x6D, 0x70, 0x6C,
|
||||
], [
|
||||
0x0E, 9, 9, 0x0E, 0x15, 0x24, 0x2E, 0x37,
|
||||
0x0A, 0x0A, 0x0C, 0x11, 0x17, 0x34, 0x36, 0x31,
|
||||
0x0C, 0x0B, 0x0E, 0x15, 0x24, 0x33, 0x3E, 0x32,
|
||||
0x0C, 0x0F, 0x13, 0x1A, 0x2E, 0x4E, 0x48, 0x38,
|
||||
0x10, 0x13, 0x21, 0x32, 0x3D, 0x62, 0x5D, 0x45,
|
||||
0x15, 0x1F, 0x31, 0x3A, 0x49, 0x5E, 0x66, 0x53,
|
||||
0x2C, 0x3A, 0x46, 0x4E, 0x5D, 0x6D, 0x6C, 0x5B,
|
||||
0x41, 0x53, 0x56, 0x58, 0x65, 0x5A, 0x5D, 0x59,
|
||||
], [
|
||||
0x0B, 7, 7, 0x0B, 0x11, 0x1C, 0x24, 0x2B,
|
||||
8, 8, 0x0A, 0x0D, 0x12, 0x29, 0x2B, 0x27,
|
||||
0x0A, 9, 0x0B, 0x11, 0x1C, 0x28, 0x31, 0x28,
|
||||
0x0A, 0x0C, 0x0F, 0x14, 0x24, 0x3E, 0x39, 0x2C,
|
||||
0x0C, 0x0F, 0x1A, 0x28, 0x30, 0x4E, 0x4A, 0x37,
|
||||
0x11, 0x19, 0x27, 0x2E, 0x3A, 0x4A, 0x51, 0x42,
|
||||
0x23, 0x2E, 0x38, 0x3E, 0x4A, 0x56, 0x56, 0x48,
|
||||
0x33, 0x42, 0x44, 0x46, 0x50, 0x47, 0x4A, 0x47,
|
||||
], [
|
||||
9, 6, 5, 9, 0x0D, 0x16, 0x1C, 0x22,
|
||||
6, 6, 7, 0x0A, 0x0E, 0x20, 0x21, 0x1E,
|
||||
7, 7, 9, 0x0D, 0x16, 0x20, 0x26, 0x1F,
|
||||
7, 9, 0x0C, 0x10, 0x1C, 0x30, 0x2D, 0x22,
|
||||
0x0A, 0x0C, 0x14, 0x1F, 0x26, 0x3D, 0x39, 0x2B,
|
||||
0x0D, 0x13, 0x1E, 0x24, 0x2D, 0x3A, 0x3F, 0x33,
|
||||
0x1B, 0x24, 0x2B, 0x30, 0x39, 0x44, 0x43, 0x38,
|
||||
0x28, 0x33, 0x35, 0x37, 0x3F, 0x38, 0x39, 0x37,
|
||||
], [
|
||||
6, 4, 3, 6, 9, 0x0F, 0x13, 0x16,
|
||||
4, 4, 5, 7, 9, 0x15, 0x16, 0x14,
|
||||
5, 4, 6, 9, 0x0F, 0x15, 0x19, 0x15,
|
||||
5, 6, 8, 0x0A, 0x13, 0x20, 0x1E, 0x17,
|
||||
6, 8, 0x0D, 0x15, 0x19, 0x28, 0x26, 0x1C,
|
||||
9, 0x0D, 0x14, 0x18, 0x1E, 0x27, 0x2A, 0x22,
|
||||
0x12, 0x18, 0x1D, 0x20, 0x26, 0x2D, 0x2D, 0x25,
|
||||
0x1B, 0x22, 0x23, 0x24, 0x2A, 0x25, 0x26, 0x25,
|
||||
], [
|
||||
3, 2, 1, 3, 4, 7, 9, 0x0B,
|
||||
2, 2, 2, 3, 4, 0x0A, 0x0B, 0x0A,
|
||||
2, 2, 3, 4, 7, 0x0A, 0x0C, 0x0A,
|
||||
2, 3, 4, 5, 9, 0x10, 0x0F, 0x0B,
|
||||
3, 4, 6, 0x0A, 0x0C, 0x14, 0x13, 0x0E,
|
||||
4, 6, 0x0A, 0x0C, 0x0F, 0x13, 0x15, 0x11,
|
||||
9, 0x0C, 0x0E, 0x10, 0x13, 0x16, 0x16, 0x12,
|
||||
0x0D, 0x11, 0x11, 0x12, 0x15, 0x12, 0x13, 0x12,
|
||||
], [
|
||||
2, 1, 1, 2, 3, 5, 6, 7,
|
||||
1, 1, 1, 2, 3, 7, 7, 6,
|
||||
1, 1, 2, 3, 5, 7, 8, 7,
|
||||
1, 2, 2, 3, 6, 0x0A, 0x0A, 7,
|
||||
2, 2, 4, 7, 8, 0x0D, 0x0C, 9,
|
||||
3, 4, 6, 8, 0x0A, 0x0D, 0x0E, 0x0B,
|
||||
6, 8, 9, 0x0A, 0x0C, 0x0F, 0x0F, 0x0C,
|
||||
9, 0x0B, 0x0B, 0x0C, 0x0E, 0x0C, 0x0C, 0x0C,
|
||||
], [
|
||||
2, 1, 1, 2, 3, 5, 6, 7,
|
||||
1, 1, 1, 2, 3, 7, 7, 6,
|
||||
1, 1, 2, 3, 5, 7, 8, 7,
|
||||
1, 2, 2, 3, 6, 0x0A, 0x0A, 7,
|
||||
2, 2, 4, 7, 8, 0x0D, 0x0C, 9,
|
||||
3, 4, 6, 8, 0x0A, 0x0D, 0x0E, 0x0B,
|
||||
6, 8, 9, 0x0A, 0x0C, 0x0F, 0x0F, 0x0C,
|
||||
9, 0x0B, 0x0B, 0x0C, 0x0E, 0x0C, 0x0C, 0x0C,
|
||||
], [
|
||||
1, 1, 1, 1, 2, 3, 4, 5,
|
||||
1, 1, 1, 1, 2, 5, 5, 5,
|
||||
1, 1, 1, 2, 3, 5, 6, 5,
|
||||
1, 1, 2, 2, 4, 8, 7, 5,
|
||||
1, 2, 3, 5, 6, 0x0A, 9, 7,
|
||||
2, 3, 5, 6, 7, 9, 0x0A, 8,
|
||||
4, 6, 7, 8, 9, 0x0B, 0x0B, 9,
|
||||
6, 8, 8, 9, 0x0A, 9, 9, 9,
|
||||
], [
|
||||
1, 1, 1, 1, 1, 2, 3, 3,
|
||||
1, 1, 1, 1, 1, 3, 3, 3,
|
||||
1, 1, 1, 1, 2, 3, 4, 3,
|
||||
1, 1, 1, 1, 3, 5, 5, 3,
|
||||
1, 1, 2, 3, 4, 6, 6, 4,
|
||||
1, 2, 3, 4, 5, 6, 7, 5,
|
||||
3, 4, 4, 5, 6, 7, 7, 6,
|
||||
4, 5, 5, 6, 7, 6, 6, 6,
|
||||
], [
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 2, 1,
|
||||
1, 1, 1, 1, 1, 2, 2, 1,
|
||||
1, 1, 1, 1, 2, 3, 3, 2,
|
||||
1, 1, 1, 2, 2, 3, 3, 2,
|
||||
1, 2, 2, 2, 3, 3, 3, 3,
|
||||
2, 2, 2, 3, 3, 3, 3, 3,
|
||||
]
|
||||
];
|
||||
|
||||
var ATEN_QT_CHROMA = [
|
||||
[
|
||||
0x1F, 0x21, 0x2D, 0x58, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||
0x21, 0x27, 0x30, 0x7B, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||
0x2D, 0x30, 0x69, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||
0x58, 0x7B, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||
0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9, 0x0B9,
|
||||
], [
|
||||
0x1B, 0x1D, 0x27, 0x4C, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||
0x1D, 0x22, 0x2A, 0x6B, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||
0x27, 0x2A, 0x5B, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||
0x4C, 0x6B, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||
0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0, 0x0A0,
|
||||
], [
|
||||
0x16, 0x18, 0x20, 0x3F, 0x85, 0x85, 0x85, 0x85,
|
||||
0x18, 0x1C, 0x22, 0x58, 0x85, 0x85, 0x85, 0x85,
|
||||
0x20, 0x22, 0x4B, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||
0x3F, 0x58, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||
0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
|
||||
], [
|
||||
0x12, 0x13, 0x1A, 0x33, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||
0x13, 0x16, 0x1C, 0x48, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||
0x1A, 0x1C, 0x3D, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||
0x33, 0x48, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||
0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C,
|
||||
], [
|
||||
0x0D, 0x0E, 0x13, 0x26, 0x50, 0x50, 0x50, 0x50,
|
||||
0x0E, 0x11, 0x15, 0x35, 0x50, 0x50, 0x50, 0x50,
|
||||
0x13, 0x15, 0x2D, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||
0x26, 0x35, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||
], [
|
||||
9, 0x0A, 0x0D, 0x1A, 0x37, 0x37, 0x37, 0x37,
|
||||
0x0A, 0x0B, 0x0E, 0x25, 0x37, 0x37, 0x37, 0x37,
|
||||
0x0D, 0x0E, 0x1F, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||
0x1A, 0x25, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||
0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
|
||||
], [
|
||||
4, 5, 6, 0x0D, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||
5, 5, 7, 0x12, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||
6, 7, 0x0F, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||
0x0D, 0x12, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||
0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B,
|
||||
], [
|
||||
3, 3, 4, 8, 0x12, 0x12, 0x12, 0x12,
|
||||
3, 3, 4, 0x0C, 0x12, 0x12, 0x12, 0x12,
|
||||
4, 4, 0x0A, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||
8, 0x0C, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||
], [
|
||||
2, 2, 3, 7, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||
2, 3, 4, 0x0A, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||
3, 4, 8, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||
7, 0x0A, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||
], [
|
||||
2, 2, 3, 5, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
2, 2, 3, 8, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
3, 3, 7, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
5, 8, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
], [
|
||||
1, 1, 2, 4, 9, 9, 9, 9,
|
||||
1, 1, 2, 6, 9, 9, 9, 9,
|
||||
2, 2, 5, 9, 9, 9, 9, 9,
|
||||
4, 6, 9, 9, 9, 9, 9, 9,
|
||||
9, 9, 9, 9, 9, 9, 9, 9,
|
||||
9, 9, 9, 9, 9, 9, 9, 9,
|
||||
9, 9, 9, 9, 9, 9, 9, 9,
|
||||
9, 9, 9, 9, 9, 9, 9, 9,
|
||||
], [
|
||||
1, 1, 1, 2, 6, 6, 6, 6,
|
||||
1, 1, 1, 4, 6, 6, 6, 6,
|
||||
1, 1, 3, 6, 6, 6, 6, 6,
|
||||
2, 4, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6,
|
||||
]
|
||||
];
|
||||
|
||||
/*
|
||||
Ref.: libjpeg's jddctmgr.c:
|
||||
"For float AA&N IDCT method, multipliers are equal to quantization
|
||||
coefficients scaled by scalefactor[row]*scalefactor[col], where
|
||||
scalefactor[0] = 1
|
||||
scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
|
||||
*/
|
||||
var AAN_IDCT_SCALING_FACTORS = [
|
||||
1.0, 1.387039845, 1.306562965, 1.175875602,
|
||||
1.0, 0.785694958, 0.541196100, 0.275899379,
|
||||
];
|
||||
|
||||
|
||||
var YUVTORGB_CR_G_TABLE = new Int32Array(256);
|
||||
var YUVTORGB_CR_R_TABLE = new Int32Array(256);
|
||||
var YUVTORGB_CB_G_TABLE = new Int32Array(256);
|
||||
var YUVTORGB_CB_B_TABLE = new Int32Array(256);
|
||||
var YUVTORGB_Y_TABLE = new Int32Array(256);
|
||||
for (var i = 0; i < 256; ++i) {
|
||||
YUVTORGB_CR_G_TABLE[i] = (0x688000 - i * 0xD000) >> 16;
|
||||
YUVTORGB_CR_R_TABLE[i] = (0xFF340000 + i * 0x19900) >> 16;
|
||||
YUVTORGB_CB_G_TABLE[i] = (0x328000 - i * 0x6400) >> 16;
|
||||
YUVTORGB_CB_B_TABLE[i] = (0xFEFE8000 + i * 0x20400) >> 16;
|
||||
YUVTORGB_Y_TABLE[i] = (0xFFEDE040 + i * 0x129FC) >> 16;
|
||||
}
|
||||
|
||||
|
||||
var TABLE_CLASS_DC = 0;
|
||||
var TABLE_CLASS_AC = 1;
|
||||
var DCTSIZE = 8;
|
||||
var DCTSIZE2 = DCTSIZE * DCTSIZE;
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
/* (c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>. */
|
||||
|
||||
|
||||
var AST2100IDCT;
|
||||
|
||||
|
||||
// N.B.: These values assume CONST_BITS == 8
|
||||
var FIX_1_082392200 = 277; // fix(1.082392200)
|
||||
var FIX_1_414213562 = 362; // fix(1.414213562)
|
||||
var FIX_1_847759065 = 473; // fix(1.847759065)
|
||||
var FIX_2_613125930 = 669; // fix(2.613125930)
|
||||
|
||||
|
||||
var MAXJSAMPLE = 255; // largest value of a sample; 8-bit, so 2**8-1
|
||||
var CONST_BITS = 8;
|
||||
var PASS1_BITS = 0;
|
||||
|
||||
// This is like clamp() except that it also adds the MAXJSAMPLE/2 offset.
|
||||
var range_limit = function (x) {
|
||||
// XXX(kelleyk): This offset is baked into the range limit table in the ATEN
|
||||
// stuff and in libjpeg; see commpent above prepare_range_limit_table() in
|
||||
// jdmaster.c.
|
||||
x += 128;
|
||||
|
||||
return Math.max(0, Math.min(255, x));
|
||||
};
|
||||
|
||||
// Convert float to fixed-point.
|
||||
var fix = function (x) {
|
||||
return ~~(x * (1 << CONST_BITS) + 0.5);
|
||||
};
|
||||
|
||||
var fixed_dequant = function (scaled_quant_table, buf, i) {
|
||||
// N.B.: The data in buf is unscaled; the data in scaled_quant_table has
|
||||
// been scaled by 1<<16.
|
||||
return fixed_mul(scaled_quant_table[i], buf[i]);
|
||||
};
|
||||
|
||||
var descale = function (x, n) {
|
||||
// console.log([x, x>>n, (x>>n)+128]);
|
||||
return x >> n;
|
||||
};
|
||||
|
||||
// if not accurate rounding mode...
|
||||
var idescale = descale;
|
||||
// else use better implementation
|
||||
|
||||
// N.B.(kelleyk): This isn't a libjpeg parameter; it just means that ATEN
|
||||
// downshifts all the way back to "normal ints" at the end of pass 1.
|
||||
var END_PASS1_DESCALE_BITS = CONST_BITS;
|
||||
|
||||
var fixed_mul = function (a, b) {
|
||||
return descale(a * b, CONST_BITS);
|
||||
};
|
||||
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
AST2100IDCT = {
|
||||
|
||||
// Uses the 16/16 integer representation that ATEN and libjpeg's
|
||||
// jidctfst.c ("fast, not-so-accurate integer IDCT") do. Note that, for
|
||||
// performance, this routine *also* incorporates the dequantization
|
||||
// step, which is why it takes scaled_quant_table as an argument. (This
|
||||
// argument does not actually contain "just" a scaled quant table; some
|
||||
// constants have been pre-multiplied into it. See the function that
|
||||
// loads quant tables for more details.)
|
||||
idct_fixed_aan: function (scaled_quant_table, buf, dstBuf) {
|
||||
|
||||
// ATEN rounds things off early, at a cost to precision: the int32
|
||||
// values in 'workspace' are not scaled at all.
|
||||
var workspace = new Int32Array(64);
|
||||
|
||||
for (var x = 0; x < 8; ++x)
|
||||
AST2100IDCT._aan_idct_col(scaled_quant_table, buf, workspace, x);
|
||||
|
||||
for (var y = 0; y < 8; ++y)
|
||||
AST2100IDCT._aan_idct_row(scaled_quant_table, dstBuf, workspace, y);
|
||||
|
||||
return dstBuf;
|
||||
},
|
||||
|
||||
// Columns; aka "Pass 1".
|
||||
_aan_idct_col: function(scaled_quant_table, buf, workspace, x) {
|
||||
|
||||
var dequant = function (idx) { return fixed_dequant(scaled_quant_table, buf, idx); };
|
||||
var mul = fixed_mul;
|
||||
|
||||
var y;
|
||||
|
||||
var all_ac_zero = true;
|
||||
for (y = 1; y < 8; ++y) {
|
||||
if (buf[8 * y + x] != 0) {
|
||||
all_ac_zero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_ac_zero) {
|
||||
var raw_dcval = buf[8 * 0 + x];
|
||||
var quant_val = scaled_quant_table[8 * 0 + x];
|
||||
var dcval = idescale(dequant(8 * 0 + x), END_PASS1_DESCALE_BITS); // in total, >> 16
|
||||
for (y = 0; y < 8; ++y)
|
||||
workspace[8 * y + x] = dcval;
|
||||
return;
|
||||
}
|
||||
|
||||
// Even part.
|
||||
var tmp0 = dequant(8 * 0 + x);
|
||||
var tmp1 = dequant(8 * 2 + x);
|
||||
var tmp2 = dequant(8 * 4 + x);
|
||||
var tmp3 = dequant(8 * 6 + x);
|
||||
|
||||
var tmp10 = tmp0 + tmp2; // Phase 3
|
||||
var tmp11 = tmp0 - tmp2;
|
||||
|
||||
var tmp13 = tmp1 + tmp3; // Phases 5-3
|
||||
var tmp12 = mul((tmp1 - tmp3), FIX_1_414213562) - tmp13; // 2 * c4
|
||||
|
||||
tmp0 = tmp10 + tmp13;
|
||||
tmp3 = tmp10 - tmp13;
|
||||
tmp1 = tmp11 + tmp12;
|
||||
tmp2 = tmp11 - tmp12;
|
||||
|
||||
// Odd part.
|
||||
var tmp4 = dequant(8 * 1 + x);
|
||||
var tmp5 = dequant(8 * 3 + x);
|
||||
var tmp6 = dequant(8 * 5 + x);
|
||||
var tmp7 = dequant(8 * 7 + x);
|
||||
|
||||
var z13 = tmp6 + tmp5; // Phase 6
|
||||
var z10 = tmp6 - tmp5;
|
||||
var z11 = tmp4 + tmp7;
|
||||
var z12 = tmp4 - tmp7;
|
||||
|
||||
tmp7 = z11 + z13; // Phase 5
|
||||
tmp11 = mul((z11 - z13), FIX_1_414213562); // 2 * c4
|
||||
|
||||
var z5 = mul((z10 + z12), FIX_1_847759065); // 2 * c2
|
||||
tmp10 = mul(FIX_1_082392200, z12) - z5; // 2 * (c2-c6)
|
||||
tmp12 = mul(-FIX_2_613125930, z10) + z5;
|
||||
|
||||
tmp6 = tmp12 - tmp7;
|
||||
tmp5 = tmp11 - tmp6;
|
||||
tmp4 = tmp10 + tmp5;
|
||||
|
||||
workspace[x + 8 * 0] = idescale(tmp0 + tmp7, END_PASS1_DESCALE_BITS);
|
||||
workspace[x + 8 * 7] = idescale(tmp0 - tmp7, END_PASS1_DESCALE_BITS);
|
||||
workspace[x + 8 * 1] = idescale(tmp1 + tmp6, END_PASS1_DESCALE_BITS);
|
||||
workspace[x + 8 * 6] = idescale(tmp1 - tmp6, END_PASS1_DESCALE_BITS);
|
||||
workspace[x + 8 * 2] = idescale(tmp2 + tmp5, END_PASS1_DESCALE_BITS);
|
||||
workspace[x + 8 * 5] = idescale(tmp2 - tmp5, END_PASS1_DESCALE_BITS);
|
||||
workspace[x + 8 * 4] = idescale(tmp3 + tmp4, END_PASS1_DESCALE_BITS);
|
||||
workspace[x + 8 * 3] = idescale(tmp3 - tmp4, END_PASS1_DESCALE_BITS);
|
||||
|
||||
},
|
||||
|
||||
// Rows; aka "Pass 2".
|
||||
_aan_idct_row: function(scaled_quant_table, buf, workspace, y) {
|
||||
|
||||
var wsptr = function (x) { return workspace[8 * y + x]; };
|
||||
var mul = fixed_mul;
|
||||
|
||||
// Even part.
|
||||
var tmp10 = wsptr(0) + wsptr(4);
|
||||
var tmp11 = wsptr(0) - wsptr(4);
|
||||
|
||||
var tmp13 = wsptr(2) + wsptr(6);
|
||||
var tmp12 = mul((wsptr(2) - wsptr(6)), FIX_1_414213562) - tmp13;
|
||||
|
||||
var tmp0 = tmp10 + tmp13;
|
||||
var tmp3 = tmp10 - tmp13;
|
||||
var tmp1 = tmp11 + tmp12;
|
||||
var tmp2 = tmp11 - tmp12;
|
||||
|
||||
// Odd part.
|
||||
var z13 = wsptr(5) + wsptr(3);
|
||||
var z10 = wsptr(5) - wsptr(3);
|
||||
var z11 = wsptr(1) + wsptr(7);
|
||||
var z12 = wsptr(1) - wsptr(7);
|
||||
|
||||
var tmp7 = z11 + z13;
|
||||
tmp11 = mul((z11 - z13), FIX_1_414213562);
|
||||
|
||||
var z5 = mul((z10 + z12), FIX_1_847759065); // 2 * c2
|
||||
tmp10 = mul(FIX_1_082392200, z12) - z5; // 2 * (c2-c6)
|
||||
tmp12 = mul(-FIX_2_613125930, z10) + z5;
|
||||
|
||||
var tmp6 = tmp12 - tmp7;
|
||||
var tmp5 = tmp11 - tmp6;
|
||||
var tmp4 = tmp10 + tmp5;
|
||||
|
||||
var set_out = function (x, val) {
|
||||
// Shift right by PASS1_BITS bits to convert back to a normal
|
||||
// int, and then by another 3 to divide by 8.
|
||||
val = idescale(val, PASS1_BITS + 3);
|
||||
val = range_limit(val); // This also applies the +128 offset.
|
||||
buf[y * 8 + x] = val;
|
||||
};
|
||||
|
||||
// Final output stage: scale down by a factor of 8 and range-limit
|
||||
set_out(0, tmp0 + tmp7);
|
||||
set_out(7, tmp0 - tmp7);
|
||||
set_out(1, tmp1 + tmp6);
|
||||
set_out(6, tmp0 - tmp6);
|
||||
set_out(2, tmp2 + tmp5);
|
||||
set_out(5, tmp2 - tmp5);
|
||||
set_out(4, tmp3 + tmp4);
|
||||
set_out(3, tmp3 - tmp4);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
/* (c) Copyright 2015-2017 Kevin Kelley <kelleyk@kelleyk.net>. */
|
||||
|
||||
|
||||
function fmt_rgb_buf (n, buf) {
|
||||
var s = "";
|
||||
for (var y = 0; y < n; ++y) {
|
||||
for (var x = 0; x < n; ++x) {
|
||||
var offset = ((y*n)+x)*4;
|
||||
s += fmt_u8(buf[offset+0]) + fmt_u8(buf[offset+1]) + fmt_u8(buf[offset+2]) + fmt_u8(buf[offset+3]) + ' ';
|
||||
}
|
||||
s += '\n';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
var fmt_u8 = function (x) {
|
||||
var pad = '00';
|
||||
var s = x.toString(16);
|
||||
return pad.substring(0, pad.length - s.length) + s;
|
||||
};
|
||||
var fmt_u16 = function (x) {
|
||||
var pad = '0000';
|
||||
var s = x.toString(16);
|
||||
return pad.substring(0, pad.length - s.length) + s;
|
||||
};
|
||||
var fmt_u32 = function (x) { // cheesy way to ger around the sign bit
|
||||
return fmt_u16(x >>> 16) + fmt_u16(x & 0xFFFF);
|
||||
};
|
||||
|
||||
var fmt_s8 = function (x) {
|
||||
if (x < 0)
|
||||
x += (1 << 8);
|
||||
return fmt_u8(x);
|
||||
};
|
||||
|
||||
var fmt_s16 = function (x) {
|
||||
if (x < 0)
|
||||
x += (1 << 16);
|
||||
return fmt_u16(x);
|
||||
};
|
||||
|
||||
var fmt_s32 = function (x) {
|
||||
if (x < 0)
|
||||
x += (1 << 32);
|
||||
return fmt_u32(x);
|
||||
};
|
||||
|
||||
var fmt_array = function(f, xx) {
|
||||
xx = Array.from(xx); // if xx is a typed array, the strings fmt_u8 returns will be silently coerced back to numbers
|
||||
return '[' + xx.map(f).join(', ') + ']';
|
||||
};
|
||||
|
||||
|
||||
var fmt_u8a = function (xx) { return fmt_array(fmt_u8, xx); };
|
||||
var fmt_u16a = function (xx) { return fmt_array(fmt_u16, xx); };
|
||||
var fmt_u32a = function (xx) { return fmt_array(fmt_u32, xx); };
|
||||
var fmt_s8a = function (xx) { return fmt_array(fmt_s8, xx); };
|
||||
var fmt_s16a = function (xx) { return fmt_array(fmt_s16, xx); };
|
||||
var fmt_s32a = function (xx) { return fmt_array(fmt_s32, xx); };
|
||||
|
||||
|
||||
var inRangeIncl = function (x, a, b) {
|
||||
return (x >= a && x <= b);
|
||||
};
|
||||
var inRange = function (x, a, b) {
|
||||
return (x >= a && x < b);
|
||||
};
|
||||
|
||||
var swap32 = function (val) {
|
||||
return ((val & 0xFF) << 24)
|
||||
| ((val & 0xFF00) << 8)
|
||||
| ((val >> 8) & 0xFF00)
|
||||
| ((val >> 24) & 0xFF);
|
||||
};
|
||||
|
||||
var arrayEq = function (a, b) {
|
||||
if (a.length != b.length)
|
||||
return false;
|
||||
for (var i = 0; i < a.length; ++i)
|
||||
if (a[i] != b[i])
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
var isEmpty = function (obj) {
|
||||
for(var prop in obj)
|
||||
if(obj.hasOwnProperty(prop))
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
var clamp = function (x) {
|
||||
x = ~~x;
|
||||
if (x <= 0)
|
||||
return 0;
|
||||
if (x >= 255)
|
||||
return 255;
|
||||
return x;
|
||||
};
|
||||
|
||||
|
||||
var BitStream;
|
||||
var JpegHuffmanTable;
|
||||
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
BitStream = function (defaults) {
|
||||
|
||||
// ATEN scan data does not treat 0xFF bytes specially.
|
||||
this._interpretMarkers = false;
|
||||
// True iff we've encountered "end-of-image".
|
||||
this._eoi = false;
|
||||
|
||||
this._data = defaults.data; // uint8[]
|
||||
if (this._data === undefined)
|
||||
throw 'BitStream constructor requires argument "data"!';
|
||||
|
||||
this._nextDword = 0; // uint8
|
||||
|
||||
// Fill the readbuf and reservoir with the first two dwords in the data
|
||||
// buffer.
|
||||
this._reservoir = 0;
|
||||
this._bitsInReservoir = 0;
|
||||
this._refill();
|
||||
this._readbuf = this._reservoir;
|
||||
this._reservoir = 0;
|
||||
this._bitsInReservoir = 0;
|
||||
this._refill();
|
||||
};
|
||||
|
||||
BitStream.prototype = {
|
||||
getPos: function () {
|
||||
return (this._nextDword * 32) - (32 + this._bitsInReservoir);
|
||||
},
|
||||
skip: function (bits) {
|
||||
while (bits > 0) {
|
||||
var n = Math.min(32, bits);
|
||||
this.read(n);
|
||||
bits -= n;
|
||||
}
|
||||
},
|
||||
read: function (bits) {
|
||||
// JavaScript's bitwise operators have this limitation.
|
||||
if (bits >= 32)
|
||||
throw 'Number of bits must be less than 32.';
|
||||
|
||||
// this.checkInvariants();
|
||||
|
||||
var that = this;
|
||||
var moveFromReservoir = function (n) {
|
||||
that._readbuf = (that._readbuf << n) | (that._reservoir >>> (32 - n));
|
||||
that._reservoir <<= n;
|
||||
that._bitsInReservoir -= n;
|
||||
};
|
||||
|
||||
var retval = this._readbuf >>> (32 - bits); // same as this.peek(bits)
|
||||
|
||||
if (bits > this._bitsInReservoir) {
|
||||
bits -= this._bitsInReservoir;
|
||||
moveFromReservoir(this._bitsInReservoir);
|
||||
this._refill();
|
||||
}
|
||||
moveFromReservoir(bits);
|
||||
|
||||
return retval;
|
||||
},
|
||||
_refill: function () {
|
||||
// this.checkInvariants();
|
||||
|
||||
// N.B.: Must use >>> for unsigned shift; >> is sra.
|
||||
if (this._bitsInReservoir != 0)
|
||||
throw 'Oops: in _refill(), bitsInReservoir='+this._bitsInReservoir;
|
||||
for (var i = 0; i < 4; ++i) {
|
||||
var x = this._data[(4 * this._nextDword) + i];
|
||||
if (x === undefined)
|
||||
throw 'BitStream overran available data!';
|
||||
// TODO: if interpretMarkers ...
|
||||
this._reservoir = (this._reservoir << 8) | x;
|
||||
this._bitsInReservoir += 8;
|
||||
}
|
||||
this._nextDword += 1;
|
||||
|
||||
this._reservoir = swap32(this._reservoir);
|
||||
|
||||
// this.checkInvariants();
|
||||
},
|
||||
peek: function (bits) {
|
||||
if (bits >= 32) // due to the fact that the bitwise operators have this limitation
|
||||
throw 'Number of bits must be less than 32.';
|
||||
|
||||
// this.checkInvariants();
|
||||
return this._readbuf >>> (32 - bits);
|
||||
},
|
||||
showDebug: function () {
|
||||
console.log('stream readbuf='+fmt_u32(this._readbuf)+' reservoir='+fmt_u32(this._reservoir)+' bitsLeft='+this._bitsInReservoir+'d nextDword='+fmt_u16(this._nextDword));
|
||||
},
|
||||
checkInvariants: function () {
|
||||
var err = null;
|
||||
|
||||
if (this._bitsInReservoir < 0 || this._bitsInReservoir > 32)
|
||||
err = 'Oops: BitStream invariant violated: bitsInReservoir='+this._bitsInReservoir;
|
||||
|
||||
if (err) {
|
||||
console.log(err);
|
||||
this.showDebug();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
JpegHuffmanTable = function (defaults) {
|
||||
if (!defaults)
|
||||
defaults = {};
|
||||
if (!defaults.bits || !defaults.huffval)
|
||||
throw 'Arguments bits, huffval are required.';
|
||||
|
||||
this._huffsize = new Uint8Array(1<<16);
|
||||
this._huffval_lookup = new Uint8Array(1<<16);
|
||||
// 1 to 16, plus a useless 0th element to make indexing nicer.
|
||||
this._bits = new Uint8Array(17);
|
||||
|
||||
this._buildTables(defaults.bits, defaults.huffval);
|
||||
};
|
||||
|
||||
JpegHuffmanTable.prototype = {
|
||||
|
||||
_buildTables: function (bits, huffval) {
|
||||
// First, copy BITS.
|
||||
for (var i = 0; i < 17; ++i) {
|
||||
this._bits[i] = bits[i];
|
||||
|
||||
// This follows from the fact that the all-ones codeword of each
|
||||
// length is reserved as a prefix for longer codewords, which
|
||||
// implies that there may not be more than (2**i)-1 codewords of
|
||||
// length i bits.
|
||||
if ((1 << i) <= bits[i])
|
||||
throw 'Oops: bad BITS.';
|
||||
}
|
||||
|
||||
// Calculate Huffman codewords. The best explanation for this is a
|
||||
// graphical one. Refer to 'Binary Tree Expansin of DHT' figure
|
||||
// here:
|
||||
// http://www.impulseadventure.com/photo/jpeg-huffman-coding.html.
|
||||
var next_codeword = 0;
|
||||
var codeword_qty = 0;
|
||||
var codeword_idx = 0; // index into HUFFVAL
|
||||
for (var code_len = 1; code_len < 17; ++code_len) {
|
||||
for (var i = 0; i < this._bits[code_len]; ++i) {
|
||||
for (var j = 0; j < (1 << (16 - code_len)); ++j) {
|
||||
this._huffsize[next_codeword + j] = code_len;
|
||||
this._huffval_lookup[next_codeword + j] = huffval[codeword_idx];
|
||||
}
|
||||
next_codeword += (1 << (16 - code_len));
|
||||
codeword_idx += 1;
|
||||
}
|
||||
}
|
||||
if (codeword_idx != huffval.length)
|
||||
throw 'Oops: all codewords should be used!';
|
||||
},
|
||||
|
||||
// Reads the next code from the stream. Consumes up to 16 bits.
|
||||
// Returns the corresponding codeword (the value that the code
|
||||
// represents, which is always exactly one byte).
|
||||
readCode: function (stream) {
|
||||
// Some number of bits, 16 < x <= 0, on the least-significant end of
|
||||
// this 16b value are not relevant, depoending on how long the code
|
||||
// actually is. However, we've built our lookup tables so that,
|
||||
// regardless of what value the irrelevant bits have, we will get
|
||||
// the same result.
|
||||
var fixedlenCode = stream.peek(16); // uint16
|
||||
|
||||
var codeLen = this._huffsize[fixedlenCode];
|
||||
stream.skip(codeLen);
|
||||
var codeword = this._huffval_lookup[fixedlenCode];
|
||||
return codeword;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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; */
|
||||
|
|
|
|||
865
core/rfb.js
865
core/rfb.js
File diff suppressed because it is too large
Load Diff
26
core/util.js
26
core/util.js
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
],
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,384 @@
|
|||
// requires local modules: ast2100, ast2100idct, ast2100const, ast2100util
|
||||
/* jshint expr: true */
|
||||
|
||||
var assert = chai.assert;
|
||||
var expect = chai.expect;
|
||||
|
||||
|
||||
// Convert a hex string, optionally containing spaces, into an array of integers representing bytes.
|
||||
var parseHex = function (s) {
|
||||
s = s.replace(/\s/g, '');
|
||||
if (s.length % 2 != 0)
|
||||
throw 'Hex data has uneven length!';
|
||||
for (var bytes = [], i = 0; i < s.length; i += 2)
|
||||
bytes.push(parseInt(s.substr(i, 2), 16));
|
||||
return bytes;
|
||||
};
|
||||
|
||||
|
||||
function make_decoder () {
|
||||
return new Ast2100Decoder({
|
||||
width: 0x400,
|
||||
height: 0x300,
|
||||
blitCallback: function () {}
|
||||
});
|
||||
}
|
||||
|
||||
// Swap u32 byte order. Mutates input!
|
||||
function buf_swap32 (data) {
|
||||
for (var i = 0; i < data.length; i += 4) {
|
||||
var tmp;
|
||||
|
||||
tmp = data[i+0];
|
||||
data[i+0] = data[i+3];
|
||||
data[i+3] = tmp;
|
||||
|
||||
tmp = data[i+1];
|
||||
data[i+1] = data[i+2];
|
||||
data[i+2] = tmp;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
describe('ATEN_AST2100 video encoding', function() {
|
||||
"use strict";
|
||||
|
||||
var dec;
|
||||
beforeEach(function () {
|
||||
dec = make_decoder();
|
||||
dec._mcuLimit = 10;
|
||||
});
|
||||
|
||||
describe('BitStream', function() {
|
||||
var stream, stream2, stream3;
|
||||
var data = buf_swap32(parseHex('f0f0 cccc cccc cccc cccc cccc cccc cccc'));
|
||||
var data2 = parseHex('F0F1F2F3 F4F5F6F7 F8F9FAFB FCFDFEFF');
|
||||
var data3 = parseHex('0b0b01bc45fbc5e020040101ffff3f20c0ffffff0000240000000000000000005028140a0000000000000000');
|
||||
|
||||
beforeEach(function () {
|
||||
stream = new BitStream({data: data});
|
||||
stream2 = new BitStream({data: data2});
|
||||
stream3 = new BitStream({data: data3});
|
||||
});
|
||||
|
||||
it('should pass simple sanity checks', function () {
|
||||
expect(stream.read(4)).to.equal(0xF);
|
||||
expect(stream.read(4)).to.equal(0x0);
|
||||
expect(stream.read(4)).to.equal(0xF);
|
||||
expect(stream.read(4)).to.equal(0x0);
|
||||
});
|
||||
|
||||
it('should pass simple sanity checks (part II)', function () {
|
||||
var i;
|
||||
for (i = 0; i < 4; ++i)
|
||||
expect(stream.read(1)).to.equal(1);
|
||||
for (i = 0; i < 4; ++i)
|
||||
expect(stream.read(1)).to.equal(0);
|
||||
});
|
||||
|
||||
it('should be able to properly refill the buffer', function () {
|
||||
expect(stream.read(4)).to.equal(0xF);
|
||||
expect(stream.read(4)).to.equal(0x0);
|
||||
expect(stream.read(4)).to.equal(0xF);
|
||||
expect(stream.read(8)).to.equal(0x0C);
|
||||
});
|
||||
|
||||
it('should properly swap byte order', function () {
|
||||
expect(stream2.read(8)).to.equal(0xF3);
|
||||
expect(stream2.read(8)).to.equal(0xF2);
|
||||
expect(stream2.read(8)).to.equal(0xF1);
|
||||
expect(stream2.read(8)).to.equal(0xF0);
|
||||
expect(stream2.read(8)).to.equal(0xF7);
|
||||
});
|
||||
|
||||
it('should properly handle skipping the full 32-bit read-buffer (data3)', function () {
|
||||
// Must do this in two parts so that bits < 32 each time.
|
||||
stream3.skip(16);
|
||||
stream3.skip(16);
|
||||
expect(stream3.read(4)).to.equal(0xE);
|
||||
});
|
||||
|
||||
// it('issue repro', function () {
|
||||
// var data = parseHex('f0f0 cccc cccc cccc cccc cccc cccc cccc');
|
||||
// var stream = new BitStream({data: data});
|
||||
// expect(stream.read(31)).to.equal(0xF0F0CCCC >>> 1);
|
||||
// expect(stream.read(8)).to.equal(0x66);
|
||||
// });
|
||||
});
|
||||
|
||||
describe('quant tables', function() {
|
||||
it('quant tables are properly loaded and scaled', function () {
|
||||
// This is luma quant table #4.
|
||||
var expected = Int32Array.from([
|
||||
0x00090000, 0x0008527e, 0x00068866, 0x000a9537, 0x000d0000, 0x00114908, 0x000f274b, 0x0009616d,
|
||||
0x0008527e, 0x000b8b14, 0x000caf8f, 0x00104f53, 0x00136b26, 0x0022df8f, 0x0018c594, 0x000b7b02,
|
||||
0x0009255c, 0x000caf8f, 0x000f5d2c, 0x0013f8fd, 0x001cbe90, 0x0020d994, 0x001adebc, 0x000b2cc4,
|
||||
0x00083b2b, 0x000eadca, 0x00126faf, 0x00161f78, 0x0020ecad, 0x002c58a1, 0x001ca316, 0x000b07c7,
|
||||
0x000a0000, 0x0010a4fc, 0x001a219a, 0x002473bf, 0x00260000, 0x002fed69, 0x001ed922, 0x000bdd19,
|
||||
0x000a36ca, 0x0014b4bd, 0x001ecbfa, 0x00214279, 0x00235b34, 0x0023cdea, 0x001ac9de, 0x000b0e2f,
|
||||
0x000e9cbf, 0x001b0616, 0x001e67d4, 0x001e8bd4, 0x001ed922, 0x001cea24, 0x00139fb4, 0x00085c96,
|
||||
0x000b0935, 0x00138450, 0x00131afd, 0x0011d7e1, 0x001161b4, 0x000c23a7, 0x000882d0, 0x00042fc6
|
||||
]);
|
||||
|
||||
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
|
||||
var luma_quant_table = dec.quantTables[0];
|
||||
expect(luma_quant_table).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('IDCT', function () {
|
||||
|
||||
var outputBuf;
|
||||
|
||||
beforeEach(function () {
|
||||
outputBuf = new Uint8Array(DCTSIZE2); // equivalent to one of the componentBufs in Ast2100Decoder.
|
||||
});
|
||||
|
||||
// TODO: Fix typing: variables should be typed arrays.
|
||||
it('test case 0 - DC value only', function () {
|
||||
|
||||
// this is the output of the VLC / entropy coding process
|
||||
// XXX: @KK: Why is this 16-bit?
|
||||
var dataUnit = [ // new Int16Array(
|
||||
0xFF9C,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
|
||||
var expected_idct_output = Uint8Array.from([ // new Uint8Array(
|
||||
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
||||
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
||||
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF]);
|
||||
|
||||
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
|
||||
var luma_quant_table = dec.quantTables[0];
|
||||
|
||||
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
|
||||
|
||||
// console.log(result);
|
||||
|
||||
console.log('expected:');
|
||||
console.log(fmt_u8a(expected_idct_output));
|
||||
console.log('result:');
|
||||
console.log(fmt_u8a(outputBuf));
|
||||
|
||||
expect(outputBuf).to.deep.equal(expected_idct_output);
|
||||
|
||||
});
|
||||
|
||||
it('test case 1', function () {
|
||||
// this is the output of the VLC / entropy coding process
|
||||
// XXX: @KK: Why is this 16-bit?
|
||||
var dataUnit = Int16Array.from([
|
||||
0xFFBD,0,0,0,0,0,0,0,
|
||||
0xFFC3,0,0,0,0,0,0,0,
|
||||
0x26,0,0,0,0,0,0,0,
|
||||
0xFFED,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,
|
||||
6,0,0,0,0,0,0,0,
|
||||
0xFFFC,0,0,0,0,0,0,0,
|
||||
2,0,0,0,0,0,0,0]);
|
||||
|
||||
var expected = Uint8Array.from([
|
||||
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
||||
0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
|
||||
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
|
||||
0xE,0xE,0xE,0xE,0xE,0xE,0xE,0xE,
|
||||
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
|
||||
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
||||
0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,
|
||||
0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1]);
|
||||
|
||||
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
|
||||
var luma_quant_table = dec.quantTables[0];
|
||||
|
||||
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
|
||||
|
||||
/*
|
||||
console.log('expected:');
|
||||
console.log(fmt_u8a(expected));
|
||||
console.log('result:');
|
||||
console.log(fmt_u8a(outputBuf));
|
||||
*/
|
||||
|
||||
// XXX: TEMPORARY -- figure out this rounding issue
|
||||
// expect(result).to.deep.equal(expected);
|
||||
var maxErr = 0;
|
||||
for (i = 0; i < 64; ++i)
|
||||
maxErr = Math.max(maxErr, Math.abs(outputBuf[i] - expected[i]));
|
||||
if (maxErr > 1)
|
||||
throw 'Error too high!';
|
||||
|
||||
});
|
||||
|
||||
it('test case 2', function () {
|
||||
// this is the output of the VLC / entropy coding process
|
||||
// XXX: @KK: Why is this 16-bit?
|
||||
|
||||
var dataUnit = Int16Array.from([
|
||||
0xFF9B, 0, 0, 0, 0, 0, 0, 0, 0xFFA4, 0, 0, 0, 0, 0, 0, 0, 0x35, 0, 0, 0, 0, 0, 0, 0,
|
||||
0xFFE6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0xFFFA,
|
||||
0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
var expected = [
|
||||
0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
||||
0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
||||
0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1];
|
||||
|
||||
// Between IDCT passes, the workspace should look like this:
|
||||
// [-903, 0, 0, 0, 0, 0, 0, 0, -874, 0, 0, 0, 0, 0, 0, 0, -914, 0, 0, 0, 0, 0, 0, 0, -874, 0, 0, 0, 0, 0, 0, 0,
|
||||
// -915, 0, 0, 0, 0, 0, 0, 0, -868, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 266, 0, 0, 0, 0, 0, 0, 0]
|
||||
|
||||
|
||||
dec._loadQuantTable(0, ATEN_QT_LUMA[5]);
|
||||
var luma_quant_table = dec.quantTables[0];
|
||||
|
||||
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
|
||||
|
||||
// console.log(result);
|
||||
|
||||
console.log('expected:');
|
||||
console.log(fmt_u8a(expected));
|
||||
console.log('result:');
|
||||
console.log(fmt_u8a(outputBuf));
|
||||
|
||||
// XXX: TEMPORARY -- figure out this rounding issue
|
||||
// expect(result).to.deep.equal(expected);
|
||||
var maxErr = 0;
|
||||
for (i = 0; i < 64; ++i)
|
||||
maxErr = Math.max(maxErr, Math.abs(outputBuf[i] - expected[i]));
|
||||
if (maxErr > 1)
|
||||
throw 'Error too high!';
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('VQ', function () {
|
||||
|
||||
it('should successfully load a codebook (colors)', function () {
|
||||
var data = buf_swap32(parseHex('bc010b0b e0c5fb45 01010420 203fffff ffffffc0 00240000 00000000 00000000 0a142850'));
|
||||
data = data.slice(4);
|
||||
|
||||
var stream = new BitStream({data: data}); // Strip off quant table selectors and subsampling mode.
|
||||
var controlFlag = stream.read(4);
|
||||
expect(controlFlag).to.equal(0xE);
|
||||
|
||||
var xMcuPos = stream.read(8), yMcuPos = stream.read(8);
|
||||
expect(xMcuPos).to.equal(0xC);
|
||||
expect(yMcuPos).to.equal(0x5F);
|
||||
|
||||
dec.subsamplingMode = 444; // required or an assertion in the VQ code will fail
|
||||
dec._stream = stream;
|
||||
dec._parseVqBlock(1); // codewordSize=1
|
||||
expect(dec._vqCodewordLookup).to.deep.equal([1, 0, 2, 3]);
|
||||
expect(dec._vqCodebook).to.deep.equal([
|
||||
[0x10, 0x80, 0x80],
|
||||
[0xA2, 0x80, 0x80],
|
||||
[0x80, 0x80, 0x80],
|
||||
[0xC0, 0x80, 0x80]
|
||||
]);
|
||||
});
|
||||
|
||||
it('should correctly handle multiple data blocks', function () {
|
||||
// This data is from a memory dump. It should contain two VQ (0xE-type) blocks and then an 0x9 (end-frame).
|
||||
var data = buf_swap32(parseHex('bc010b0b e0c5fb45 01010420 203fffff ffffffc0 00240000 00000000 00000000 0a142850' + '00000000 00000000'));
|
||||
console.log('VQ multiple block data:');
|
||||
console.log(fmt_u8a(data));
|
||||
dec.decode(data);
|
||||
|
||||
// TODO: improve asserts/etc. beyond just 'finished without error'
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Full JPEG subsampled MCU example 0', function () {
|
||||
// Corresponds to whole-mcu-example / main_mcu_test_2()
|
||||
var data = parseHex('040701a61bff6280f81fbaa2ff4dbc408dfccf405c15ff1f004800f500000000000000002dd4a462237ced2c9c25dca4cef7e6667d6626f3308063c3c7a1b7335badc24aaf0b5e9daf69590fcb96206b59e6f2ccb2bdd386b17dbb72182080d6288a2a1f2f0608a0fe87ae287f132f1023ff33d057c5ff470012403d0000000000000000ec1fb06cdacd2bdf9d79f3cde7f9f1362b7ce914ba521c79524a5bdc212a5ed47c3cbc6c7e072ae3e3c18384785f6ca6d4e22fb1af0c96449d1847a9c7037e966fa3ac8d4990830339463d277f25fc6f30ffe5f4f5cfec9f4ffff8bf00a0f5d358a2ab6e4e6778d929f686bd58ca9c26b8f7338f15105065dd39c718e219a78e');
|
||||
|
||||
|
||||
it('should decode properly', function () {
|
||||
// DOES NOT APPEAR TO correspond to my notes in 'whole-mcu-example.txt'.
|
||||
|
||||
dec._blitCallback = function (x, y, width, height, buf) {
|
||||
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
||||
console.log(fmt_rgb_buf(width, buf));
|
||||
};
|
||||
|
||||
dec.decode(data);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Full JPEG subsampled MCU example 1', function () {
|
||||
|
||||
/* TODO: this looks VERRRRRY SIMILAR to the above example */
|
||||
|
||||
var data = parseHex('040701a61bff6280f81fbaa2ff4dbc408dfccf405c15ff1f004800f500000000000000002dd4a462237ced2c9c25dca4cef7e6667d6626f3308063c3c7a1b7335badc24aaf0b5e9daf69590fcb96206b59e6f2ccb2bdd386b17dbb72182080d6288a2a1f2f0608a0fe87ae287f132f1023ff33d057c5ff470012403d0000000000000000ec1fb06cdacd2bdf9d79f3cde7f9f1362b7ce914ba521c79524a5bdc212a5ed47c3cbc6c7e072ae3e3c18384785f6ca6d4e22fb1af0c96449d1847a9c7037e966fa3ac8d4990830339463d277f25fc6f30ffe5f4f5cfec9f4ffff8bf00a0f5d358a2ab6e4e6778d929f686bd58ca9c26b8f7338f15105065dd39c718');
|
||||
|
||||
/*
|
||||
it('should load quant tables and set subsamplingMode', function () {
|
||||
dec.decode(data);
|
||||
|
||||
expect(dec._loadedQuantTables[0]).to.equal(4);
|
||||
expect(dec._loadedQuantTables[1]).to.equal(7);
|
||||
expect(dec.subsamplingMode).to.equal(422);
|
||||
});
|
||||
*/
|
||||
|
||||
it('should decode properly', function () {
|
||||
// DOES NOT APPEAR TO correspond to my notes in 'whole-mcu-example.txt'.
|
||||
|
||||
dec._blitCallback = function (x, y, width, height, buf) {
|
||||
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
||||
console.log(fmt_rgb_buf(width, buf));
|
||||
};
|
||||
|
||||
dec.decode(data);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: On the 'full-frame decode' and 'frame udpate decode' tests, we are NOT actually asserting anything about the code's output yet.
|
||||
/*
|
||||
describe('Full frame decode test', function () {
|
||||
|
||||
it('should decode properly', function () {
|
||||
|
||||
throw "Won't work without jQuery (or some other way of loading data).";
|
||||
|
||||
dec._blitCallback = function (x, y, width, height, buf) {
|
||||
if (x == 0 && y == 0) {
|
||||
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
||||
console.log(fmt_rgb_buf(width, buf));
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: This path won't work for other people, and the test case needs to be made to understand that this
|
||||
// is async; plus, I had to manually add jQuery to the generated HTML.
|
||||
$.get("/novnc-tests/tests/frame4.hex", function (data) {
|
||||
data = parseHex(data);
|
||||
dec.decode(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
describe('Frame update decode test', function () {
|
||||
|
||||
it('should decode properly', function () {
|
||||
var data = parseHex('050501a69a3f6080008aa2a8000000900000000000000000');
|
||||
console.log(data);
|
||||
|
||||
// this data should NOT be solid-white!
|
||||
|
||||
dec._blitCallback = function (x, y, width, height, buf) {
|
||||
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
||||
console.log(fmt_rgb_buf(width, buf));
|
||||
};
|
||||
|
||||
dec.decode(data);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
39
vnc.html
39
vnc.html
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue