Initial Commit
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
export default class CopyRectDecoder {
|
||||
|
||||
decodeRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
if (sock.rQwait("COPYRECT", 4)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let deltaX = sock.rQshift16();
|
||||
let deltaY = sock.rQshift16();
|
||||
|
||||
if ((width === 0) || (height === 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
display.copyImage(deltaX, deltaY, x, y, width, height, frame_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as Log from '../util/logging.js';
|
||||
|
||||
export default class HextileDecoder {
|
||||
constructor() {
|
||||
this._tiles = 0;
|
||||
this._lastsubencoding = 0;
|
||||
this._tileBuffer = new Uint8Array(16 * 16 * 4);
|
||||
}
|
||||
|
||||
decodeRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
if (this._tiles === 0) {
|
||||
this._tilesX = Math.ceil(width / 16);
|
||||
this._tilesY = Math.ceil(height / 16);
|
||||
this._totalTiles = this._tilesX * this._tilesY;
|
||||
this._tiles = this._totalTiles;
|
||||
}
|
||||
|
||||
while (this._tiles > 0) {
|
||||
let bytes = 1;
|
||||
|
||||
if (sock.rQwait("HEXTILE", bytes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let rQ = sock.rQ;
|
||||
let rQi = sock.rQi;
|
||||
|
||||
let subencoding = rQ[rQi]; // Peek
|
||||
if (subencoding > 30) { // Raw
|
||||
throw new Error("Illegal hextile subencoding (subencoding: " +
|
||||
subencoding + ")");
|
||||
}
|
||||
|
||||
const currTile = this._totalTiles - this._tiles;
|
||||
const tileX = currTile % this._tilesX;
|
||||
const tileY = Math.floor(currTile / this._tilesX);
|
||||
const tx = x + tileX * 16;
|
||||
const ty = y + tileY * 16;
|
||||
const tw = Math.min(16, (x + width) - tx);
|
||||
const th = Math.min(16, (y + height) - ty);
|
||||
|
||||
// Figure out how much we are expecting
|
||||
if (subencoding & 0x01) { // Raw
|
||||
bytes += tw * th * 4;
|
||||
} else {
|
||||
if (subencoding & 0x02) { // Background
|
||||
bytes += 4;
|
||||
}
|
||||
if (subencoding & 0x04) { // Foreground
|
||||
bytes += 4;
|
||||
}
|
||||
if (subencoding & 0x08) { // AnySubrects
|
||||
bytes++; // Since we aren't shifting it off
|
||||
|
||||
if (sock.rQwait("HEXTILE", bytes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let subrects = rQ[rQi + bytes - 1]; // Peek
|
||||
if (subencoding & 0x10) { // SubrectsColoured
|
||||
bytes += subrects * (4 + 2);
|
||||
} else {
|
||||
bytes += subrects * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sock.rQwait("HEXTILE", bytes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We know the encoding and have a whole tile
|
||||
rQi++;
|
||||
if (subencoding === 0) {
|
||||
if (this._lastsubencoding & 0x01) {
|
||||
// Weird: ignore blanks are RAW
|
||||
Log.Debug(" Ignoring blank after RAW");
|
||||
} else {
|
||||
display.fillRect(tx, ty, tw, th, this._background, frame_id);
|
||||
}
|
||||
} else if (subencoding & 0x01) { // Raw
|
||||
let pixels = tw * th;
|
||||
// Max sure the image is fully opaque
|
||||
for (let i = 0;i < pixels;i++) {
|
||||
rQ[rQi + i * 4 + 3] = 255;
|
||||
}
|
||||
display.blitImage(tx, ty, tw, th, rQ, rQi, frame_id);
|
||||
rQi += bytes - 1;
|
||||
} else {
|
||||
if (subencoding & 0x02) { // Background
|
||||
this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
||||
rQi += 4;
|
||||
}
|
||||
if (subencoding & 0x04) { // Foreground
|
||||
this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
||||
rQi += 4;
|
||||
}
|
||||
|
||||
this._startTile(tx, ty, tw, th, this._background);
|
||||
if (subencoding & 0x08) { // AnySubrects
|
||||
let subrects = rQ[rQi];
|
||||
rQi++;
|
||||
|
||||
for (let s = 0; s < subrects; s++) {
|
||||
let color;
|
||||
if (subencoding & 0x10) { // SubrectsColoured
|
||||
color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
||||
rQi += 4;
|
||||
} else {
|
||||
color = this._foreground;
|
||||
}
|
||||
const xy = rQ[rQi];
|
||||
rQi++;
|
||||
const sx = (xy >> 4);
|
||||
const sy = (xy & 0x0f);
|
||||
|
||||
const wh = rQ[rQi];
|
||||
rQi++;
|
||||
const sw = (wh >> 4) + 1;
|
||||
const sh = (wh & 0x0f) + 1;
|
||||
|
||||
this._subTile(sx, sy, sw, sh, color);
|
||||
}
|
||||
}
|
||||
this._finishTile(display, frame_id);
|
||||
}
|
||||
sock.rQi = rQi;
|
||||
this._lastsubencoding = subencoding;
|
||||
this._tiles--;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// start updating a tile
|
||||
_startTile(x, y, width, height, color) {
|
||||
this._tileX = x;
|
||||
this._tileY = y;
|
||||
this._tileW = width;
|
||||
this._tileH = height;
|
||||
|
||||
const red = color[0];
|
||||
const green = color[1];
|
||||
const blue = color[2];
|
||||
|
||||
const data = this._tileBuffer;
|
||||
for (let i = 0; i < width * height * 4; i += 4) {
|
||||
data[i] = red;
|
||||
data[i + 1] = green;
|
||||
data[i + 2] = blue;
|
||||
data[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
// update sub-rectangle of the current tile
|
||||
_subTile(x, y, w, h, color) {
|
||||
const red = color[0];
|
||||
const green = color[1];
|
||||
const blue = color[2];
|
||||
const xend = x + w;
|
||||
const yend = y + h;
|
||||
|
||||
const data = this._tileBuffer;
|
||||
const width = this._tileW;
|
||||
for (let j = y; j < yend; j++) {
|
||||
for (let i = x; i < xend; i++) {
|
||||
const p = (i + (j * width)) * 4;
|
||||
data[p] = red;
|
||||
data[p + 1] = green;
|
||||
data[p + 2] = blue;
|
||||
data[p + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw the current tile to the screen
|
||||
_finishTile(display, frame_id) {
|
||||
display.blitImage(this._tileX, this._tileY,
|
||||
this._tileW, this._tileH,
|
||||
this._tileBuffer, 0, frame_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
let wasm;
|
||||
const heap = new Array(32).fill(undefined);
|
||||
heap.push(undefined, null, true, false);
|
||||
|
||||
function getObject(idx) {
|
||||
return heap[idx];
|
||||
}
|
||||
let heap_next = heap.length;
|
||||
|
||||
function dropObject(idx) {
|
||||
if (idx < 36) return;
|
||||
heap[idx] = heap_next;
|
||||
heap_next = idx;
|
||||
}
|
||||
|
||||
function takeObject(idx) {
|
||||
const ret = getObject(idx);
|
||||
dropObject(idx);
|
||||
return ret;
|
||||
}
|
||||
const cachedTextDecoder = new TextDecoder('utf-8', {
|
||||
ignoreBOM: true,
|
||||
fatal: true
|
||||
});
|
||||
cachedTextDecoder.decode();
|
||||
let cachegetUint8Memory0 = null;
|
||||
|
||||
function getUint8Memory0() {
|
||||
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
|
||||
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachegetUint8Memory0;
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
let cachegetInt32Memory0 = null;
|
||||
|
||||
function getInt32Memory0() {
|
||||
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
|
||||
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachegetInt32Memory0;
|
||||
}
|
||||
|
||||
function getArrayU8FromWasm0(ptr, len) {
|
||||
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
function passArray8ToWasm0(arg, malloc) {
|
||||
const ptr = malloc(arg.length * 1);
|
||||
getUint8Memory0().set(arg, ptr / 1);
|
||||
WASM_VECTOR_LEN = arg.length;
|
||||
return ptr;
|
||||
}
|
||||
/**
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {ImageData}
|
||||
*/
|
||||
function decode_qoi(bytes) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
wasm.decode_qoi(retptr, ptr0, len0);
|
||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||
var r2 = getInt32Memory0()[retptr / 4 + 2];
|
||||
if (r2) {
|
||||
throw takeObject(r1);
|
||||
}
|
||||
return takeObject(r0);
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
}
|
||||
}
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
heap_next = heap[idx];
|
||||
|
||||
heap[idx] = obj;
|
||||
return idx;
|
||||
}
|
||||
|
||||
const cachedTextEncoder = new TextEncoder('utf-8');
|
||||
|
||||
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' ?
|
||||
function(arg, view) {
|
||||
return cachedTextEncoder.encodeInto(arg, view);
|
||||
} :
|
||||
function(arg, view) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
view.set(buf);
|
||||
return {
|
||||
read: arg.length,
|
||||
written: buf.length
|
||||
};
|
||||
});
|
||||
|
||||
function passStringToWasm0(arg, malloc, realloc) {
|
||||
|
||||
if (realloc === undefined) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
const ptr = malloc(buf.length);
|
||||
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
|
||||
WASM_VECTOR_LEN = buf.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let len = arg.length;
|
||||
let ptr = malloc(len);
|
||||
|
||||
const mem = getUint8Memory0();
|
||||
|
||||
let offset = 0;
|
||||
|
||||
for (; offset < len; offset++) {
|
||||
const code = arg.charCodeAt(offset);
|
||||
if (code > 0x7F) break;
|
||||
mem[ptr + offset] = code;
|
||||
}
|
||||
|
||||
if (offset !== len) {
|
||||
if (offset !== 0) {
|
||||
arg = arg.slice(offset);
|
||||
}
|
||||
ptr = realloc(ptr, len, len = offset + arg.length * 3);
|
||||
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
|
||||
const ret = encodeString(arg, view);
|
||||
|
||||
offset += ret.written;
|
||||
}
|
||||
|
||||
WASM_VECTOR_LEN = offset;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let cachegetUint8ClampedMemory0 = null;
|
||||
|
||||
function getUint8ClampedMemory0() {
|
||||
if (cachegetUint8ClampedMemory0 === null || cachegetUint8ClampedMemory0.buffer !== wasm.memory.buffer) {
|
||||
cachegetUint8ClampedMemory0 = new Uint8ClampedArray(wasm.memory.buffer);
|
||||
}
|
||||
return cachegetUint8ClampedMemory0;
|
||||
}
|
||||
|
||||
function getClampedArrayU8FromWasm0(ptr, len) {
|
||||
return getUint8ClampedMemory0().subarray(ptr / 1, ptr / 1 + len);
|
||||
}
|
||||
|
||||
function handleError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
wasm.__wbindgen_exn_store(addHeapObject(e));
|
||||
}
|
||||
}
|
||||
/**
|
||||
*/
|
||||
class QoiImage {
|
||||
|
||||
static __wrap(ptr) {
|
||||
const obj = Object.create(QoiImage.prototype);
|
||||
obj.ptr = ptr;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__destroy_into_raw() {
|
||||
const ptr = this.ptr;
|
||||
this.ptr = 0;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
free() {
|
||||
const ptr = this.__destroy_into_raw();
|
||||
wasm.__wbg_qoiimage_free(ptr);
|
||||
}
|
||||
/**
|
||||
*/
|
||||
constructor() {
|
||||
const ret = wasm.qoiimage_new();
|
||||
return QoiImage.__wrap(ret);
|
||||
}
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get_width() {
|
||||
const ret = wasm.qoiimage_get_width(this.ptr);
|
||||
return ret >>> 0;
|
||||
}
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get_height() {
|
||||
const ret = wasm.qoiimage_get_height(this.ptr);
|
||||
return ret >>> 0;
|
||||
}
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get_channels() {
|
||||
const ret = wasm.qoiimage_get_channels(this.ptr);
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
get_bytes() {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
wasm.qoiimage_get_bytes(retptr, this.ptr);
|
||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||
var v0 = getArrayU8FromWasm0(r0, r1).slice();
|
||||
wasm.__wbindgen_free(r0, r1 * 1);
|
||||
return v0;
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function load(module, imports) {
|
||||
if (typeof Response === 'function' && module instanceof Response) {
|
||||
const bytes = await module.arrayBuffer();
|
||||
return await WebAssembly.instantiate(bytes, imports);
|
||||
} else {
|
||||
const instance = await WebAssembly.instantiate(module, imports);
|
||||
|
||||
if (instance instanceof WebAssembly.Instance) {
|
||||
return {
|
||||
instance,
|
||||
module
|
||||
};
|
||||
|
||||
} else {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function init(input) {
|
||||
if (typeof input === 'undefined') {
|
||||
input = path + 'core/decoders/qoi/qoi_viewer_bg.wasm';
|
||||
}
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbg_new_693216e109162396 = function() {
|
||||
const ret = new Error();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_stack_0ddaca5d1abfb52f = function(arg0, arg1) {
|
||||
const ret = getObject(arg1).stack;
|
||||
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbg_error_09919627ac0992f5 = function(arg0, arg1) {
|
||||
try {
|
||||
console.error(getStringFromWasm0(arg0, arg1));
|
||||
} finally {
|
||||
wasm.__wbindgen_free(arg0, arg1);
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_newwithu8clampedarrayandsh_87d2f0a48030f922 = function() {
|
||||
return handleError(function(arg0, arg1, arg2, arg3) {
|
||||
const ret = new ImageData(getClampedArrayU8FromWasm0(arg0, arg1), arg2 >>> 0, arg3 >>> 0);
|
||||
return addHeapObject(ret);
|
||||
}, arguments)
|
||||
};
|
||||
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
|
||||
input = fetch(input);
|
||||
}
|
||||
const {
|
||||
instance,
|
||||
module
|
||||
} = await load(await input, imports);
|
||||
wasm = instance.exports;
|
||||
init.__wbindgen_wasm_module = module;
|
||||
return wasm;
|
||||
}
|
||||
|
||||
var arr;
|
||||
var path;
|
||||
|
||||
async function run() {
|
||||
self.addEventListener('message', async function(evt) {
|
||||
if (evt.data.path) {
|
||||
path = evt.data.path;
|
||||
await init();
|
||||
//Send message that worker is ready
|
||||
self.postMessage({
|
||||
result: 1
|
||||
})
|
||||
} else {
|
||||
try {
|
||||
let length = evt.data.length;
|
||||
let data = new Uint8Array(evt.data.sab.slice(0, length));
|
||||
let resultData = decode_qoi(data);
|
||||
if (!arr) {
|
||||
arr = new Uint8Array(evt.data.sabR);
|
||||
}
|
||||
let lengthR = resultData.data.length;
|
||||
arr.set(resultData.data);
|
||||
let img = {
|
||||
colorSpace: resultData.colorSpace,
|
||||
width: resultData.width,
|
||||
height: resultData.height
|
||||
};
|
||||
self.postMessage({
|
||||
result: 0,
|
||||
img: img,
|
||||
length: lengthR,
|
||||
width: evt.data.width,
|
||||
height: evt.data.height,
|
||||
x: evt.data.x,
|
||||
y: evt.data.y,
|
||||
frame_id: evt.data.frame_id
|
||||
});
|
||||
} catch (err) {
|
||||
self.postMessage({
|
||||
result: 2,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
run();
|
||||
Binary file not shown.
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
export default class RawDecoder {
|
||||
constructor() {
|
||||
this._lines = 0;
|
||||
}
|
||||
|
||||
decodeRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
if ((width === 0) || (height === 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._lines === 0) {
|
||||
this._lines = height;
|
||||
}
|
||||
|
||||
const pixelSize = depth == 8 ? 1 : 4;
|
||||
const bytesPerLine = width * pixelSize;
|
||||
|
||||
if (sock.rQwait("RAW", bytesPerLine)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const curY = y + (height - this._lines);
|
||||
const currHeight = Math.min(this._lines,
|
||||
Math.floor(sock.rQlen / bytesPerLine));
|
||||
const pixels = width * currHeight;
|
||||
|
||||
let data = sock.rQ;
|
||||
let index = sock.rQi;
|
||||
|
||||
// Convert data if needed
|
||||
if (depth == 8) {
|
||||
const newdata = new Uint8Array(pixels * 4);
|
||||
for (let i = 0; i < pixels; i++) {
|
||||
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 3] = 255;
|
||||
}
|
||||
data = newdata;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// Max sure the image is fully opaque
|
||||
for (let i = 0; i < pixels; i++) {
|
||||
data[i * 4 + 3] = 255;
|
||||
}
|
||||
|
||||
display.blitImage(x, curY, width, currHeight, data, index, frame_id);
|
||||
sock.rQskipBytes(currHeight * bytesPerLine);
|
||||
this._lines -= currHeight;
|
||||
if (this._lines > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
export default class RREDecoder {
|
||||
constructor() {
|
||||
this._subrects = 0;
|
||||
}
|
||||
|
||||
decodeRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
if (this._subrects === 0) {
|
||||
if (sock.rQwait("RRE", 4 + 4)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._subrects = sock.rQshift32();
|
||||
|
||||
let color = sock.rQshiftBytes(4); // Background
|
||||
display.fillRect(x, y, width, height, color);
|
||||
}
|
||||
|
||||
while (this._subrects > 0) {
|
||||
if (sock.rQwait("RRE", 4 + 8)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let color = sock.rQshiftBytes(4);
|
||||
let sx = sock.rQshift16();
|
||||
let sy = sock.rQshift16();
|
||||
let swidth = sock.rQshift16();
|
||||
let sheight = sock.rQshift16();
|
||||
display.fillRect(x + sx, y + sy, swidth, sheight, color, frame_id);
|
||||
|
||||
this._subrects--;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,540 @@
|
||||
/*
|
||||
* KasmVNC: HTML5 VNC client
|
||||
* Copyright (C) 2020 Kasm Technologies
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as Log from '../util/logging.js';
|
||||
import Inflator from "../inflator.js";
|
||||
|
||||
export default class TightDecoder {
|
||||
constructor(display) {
|
||||
this._ctl = null;
|
||||
this._filter = null;
|
||||
this._numColors = 0;
|
||||
this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
|
||||
this._len = 0;
|
||||
this._enableQOI = false;
|
||||
this._displayGlobal = display;
|
||||
|
||||
this._zlibs = [];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this._zlibs[i] = new Inflator();
|
||||
}
|
||||
this._itzlib = new Inflator();
|
||||
}
|
||||
|
||||
// ===== PROPERTIES =====
|
||||
|
||||
get enableQOI() { return this._enableQOI; }
|
||||
set enableQOI(enabled) {
|
||||
if(this._enableQOI === enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
this._enableQOI = this._enableQOIWorkers();
|
||||
} else {
|
||||
this._enableQOI = false;
|
||||
this._disableQOIWorkers();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Public Methods =====
|
||||
|
||||
decodeRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
if (this._ctl === null) {
|
||||
if (sock.rQwait("TIGHT compression-control", 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._ctl = sock.rQshift8();
|
||||
|
||||
// Reset streams if the server requests it
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if ((this._ctl >> i) & 1) {
|
||||
this._zlibs[i].reset();
|
||||
Log.Info("Reset zlib stream " + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out filter
|
||||
this._ctl = this._ctl >> 4;
|
||||
}
|
||||
|
||||
let ret;
|
||||
|
||||
if (this._ctl === 0x08) {
|
||||
ret = this._fillRect(x, y, width, height,
|
||||
sock, display, depth, frame_id);
|
||||
} else if (this._ctl === 0x09) {
|
||||
ret = this._jpegRect(x, y, width, height,
|
||||
sock, display, depth, frame_id);
|
||||
} else if (this._ctl === 0x0A) {
|
||||
ret = this._pngRect(x, y, width, height,
|
||||
sock, display, depth, frame_id);
|
||||
} else if ((this._ctl & 0x08) == 0) {
|
||||
ret = this._basicRect(this._ctl, x, y, width, height,
|
||||
sock, display, depth, frame_id);
|
||||
} else if (this._ctl === 0x0B) {
|
||||
ret = this._webpRect(x, y, width, height,
|
||||
sock, display, depth, frame_id);
|
||||
} else if (this._ctl === 0x0C) {
|
||||
ret = this._qoiRect(x, y, width, height,
|
||||
sock, display, depth, frame_id);
|
||||
} else if (this._ctl === 0x0D) {
|
||||
ret = this._itRect(x, y, width, height,
|
||||
sock, display, depth, frame_id);
|
||||
} else {
|
||||
throw new Error("Illegal tight compression received (ctl: " +
|
||||
this._ctl + ")");
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
this._ctl = null;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ===== Private Methods =====
|
||||
|
||||
_fillRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
if (sock.rQwait("TIGHT", 3)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rQi = sock.rQi;
|
||||
const rQ = sock.rQ;
|
||||
|
||||
display.fillRect(x, y, width, height,
|
||||
[rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], frame_id, false);
|
||||
sock.rQskipBytes(3);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_jpegRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
let data = this._readData(sock);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display.imageRect(x, y, width, height, "image/jpeg", data, frame_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_webpRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
let data = this._readData(sock);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display.imageRect(x, y, width, height, "image/webp", data, frame_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_processRectQ() {
|
||||
while (this._availableWorkers.length > 0 && this._qoiRects.length > 0) {
|
||||
let i = this._availableWorkers.pop();
|
||||
let worker = this._workers[i];
|
||||
let rect = this._qoiRects.shift();
|
||||
this._arrs[i].set(rect.data);
|
||||
worker.postMessage({
|
||||
length: rect.data.length,
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
depth: rect.depth,
|
||||
sab: this._sabs[i],
|
||||
sabR: this._sabsR[i],
|
||||
frame_id: rect.frame_id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_qoiRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
let data = this._readData(sock);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._enableQOI) {
|
||||
let dataClone = new Uint8Array(data);
|
||||
let item = {x: x,y: y,width: width,height: height,data: dataClone,depth: depth, frame_id: frame_id};
|
||||
if (this._qoiRects.length < 1000) {
|
||||
this._qoiRects.push(item);
|
||||
this._processRectQ();
|
||||
} else {
|
||||
Log.Warn("QOI queue exceeded limit.");
|
||||
this._qoiRects.splice(0, 500);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// intensity tinted
|
||||
_itRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
let data = this._readData(sock);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const r = data[0];
|
||||
const g = data[1];
|
||||
const b = data[2];
|
||||
const a = data[3];
|
||||
|
||||
const uncompressedSize = width * height / 2 + 1;
|
||||
|
||||
this._itzlib.reset();
|
||||
this._itzlib.setInput(data.slice(4));
|
||||
data = this._itzlib.inflate(uncompressedSize);
|
||||
this._itzlib.setInput(null);
|
||||
|
||||
// unpack
|
||||
let rgba = new Uint8Array(width * height * 4 + 4);
|
||||
for (let i = 0, d = 0; i < uncompressedSize; i++, d += 8) {
|
||||
let p = data[i];
|
||||
|
||||
rgba[d + 0] = r;
|
||||
rgba[d + 1] = g;
|
||||
rgba[d + 2] = b;
|
||||
rgba[d + 3] = a * ((p & 15) << 4) / 255;
|
||||
|
||||
rgba[d + 4] = r;
|
||||
rgba[d + 5] = g;
|
||||
rgba[d + 6] = b;
|
||||
rgba[d + 7] = a * (p & 240) / 255;
|
||||
}
|
||||
|
||||
let img = new ImageData(new Uint8ClampedArray(rgba.buffer, 0, width * height * 4), width, height);
|
||||
display.transparentRect(x, y, width, height, img, frame_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_pngRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
throw new Error("PNG received in standard Tight rect");
|
||||
}
|
||||
|
||||
_basicRect(ctl, x, y, width, height, sock, display, depth, frame_id) {
|
||||
if (this._filter === null) {
|
||||
if (ctl & 0x4) {
|
||||
if (sock.rQwait("TIGHT", 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._filter = sock.rQshift8();
|
||||
} else {
|
||||
// Implicit CopyFilter
|
||||
this._filter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let streamId = ctl & 0x3;
|
||||
|
||||
let ret;
|
||||
|
||||
switch (this._filter) {
|
||||
case 0: // CopyFilter
|
||||
ret = this._copyFilter(streamId, x, y, width, height,
|
||||
sock, display, depth, frame_id);
|
||||
break;
|
||||
case 1: // PaletteFilter
|
||||
ret = this._paletteFilter(streamId, x, y, width, height,
|
||||
sock, display, depth, frame_id);
|
||||
break;
|
||||
case 2: // GradientFilter
|
||||
ret = this._gradientFilter(streamId, x, y, width, height,
|
||||
sock, display, depth, frame_id);
|
||||
break;
|
||||
default:
|
||||
throw new Error("Illegal tight filter received (ctl: " +
|
||||
this._filter + ")");
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
this._filter = null;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
_copyFilter(streamId, x, y, width, height, sock, display, depth, frame_id) {
|
||||
const uncompressedSize = width * height * 3;
|
||||
let data;
|
||||
|
||||
if (uncompressedSize === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uncompressedSize < 12) {
|
||||
if (sock.rQwait("TIGHT", uncompressedSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data = sock.rQshiftBytes(uncompressedSize);
|
||||
} else {
|
||||
data = this._readData(sock);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._zlibs[streamId].setInput(data);
|
||||
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||
this._zlibs[streamId].setInput(null);
|
||||
}
|
||||
|
||||
let rgbx = new Uint8Array(width * height * 4);
|
||||
for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {
|
||||
rgbx[i] = data[j];
|
||||
rgbx[i + 1] = data[j + 1];
|
||||
rgbx[i + 2] = data[j + 2];
|
||||
rgbx[i + 3] = 255; // Alpha
|
||||
}
|
||||
|
||||
display.blitImage(x, y, width, height, rgbx, 0, frame_id, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_paletteFilter(streamId, x, y, width, height, sock, display, depth, frame_id) {
|
||||
if (this._numColors === 0) {
|
||||
if (sock.rQwait("TIGHT palette", 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const numColors = sock.rQpeek8() + 1;
|
||||
const paletteSize = numColors * 3;
|
||||
|
||||
if (sock.rQwait("TIGHT palette", 1 + paletteSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._numColors = numColors;
|
||||
sock.rQskipBytes(1);
|
||||
|
||||
sock.rQshiftTo(this._palette, paletteSize);
|
||||
}
|
||||
|
||||
const bpp = (this._numColors <= 2) ? 1 : 8;
|
||||
const rowSize = Math.floor((width * bpp + 7) / 8);
|
||||
const uncompressedSize = rowSize * height;
|
||||
|
||||
let data;
|
||||
|
||||
if (uncompressedSize === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uncompressedSize < 12) {
|
||||
if (sock.rQwait("TIGHT", uncompressedSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data = sock.rQshiftBytes(uncompressedSize);
|
||||
} else {
|
||||
data = this._readData(sock);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._zlibs[streamId].setInput(data);
|
||||
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||
this._zlibs[streamId].setInput(null);
|
||||
}
|
||||
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
if (this._numColors == 2) {
|
||||
this._monoRect(x, y, width, height, data, this._palette, display, frame_id);
|
||||
} else {
|
||||
this._paletteRect(x, y, width, height, data, this._palette, display, frame_id);
|
||||
}
|
||||
|
||||
this._numColors = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_monoRect(x, y, width, height, data, palette, display, frame_id) {
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
// TODO: reduce number of calculations inside loop
|
||||
const dest = this._getScratchBuffer(width * height * 4);
|
||||
const w = Math.floor((width + 7) / 8);
|
||||
const w1 = Math.floor(width / 8);
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
let dp, sp, x;
|
||||
for (x = 0; x < w1; x++) {
|
||||
for (let b = 7; b >= 0; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 4;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
dest[dp + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
for (let b = 7; b >= 8 - width % 8; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 4;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
dest[dp + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
display.blitImage(x, y, width, height, dest, 0, frame_id, false);
|
||||
}
|
||||
|
||||
_paletteRect(x, y, width, height, data, palette, display, frame_id) {
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
const dest = this._getScratchBuffer(width * height * 4);
|
||||
const total = width * height * 4;
|
||||
for (let i = 0, j = 0; i < total; i += 4, j++) {
|
||||
const sp = data[j] * 3;
|
||||
dest[i] = palette[sp];
|
||||
dest[i + 1] = palette[sp + 1];
|
||||
dest[i + 2] = palette[sp + 2];
|
||||
dest[i + 3] = 255;
|
||||
}
|
||||
|
||||
display.blitImage(x, y, width, height, dest, 0, frame_id, false);
|
||||
}
|
||||
|
||||
_gradientFilter(streamId, x, y, width, height, sock, display, depth, frame_id) {
|
||||
throw new Error("Gradient filter not implemented");
|
||||
}
|
||||
|
||||
_readData(sock) {
|
||||
if (this._len === 0) {
|
||||
if (sock.rQwait("TIGHT", 3)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let byte;
|
||||
|
||||
byte = sock.rQshift8();
|
||||
this._len = byte & 0x7f;
|
||||
if (byte & 0x80) {
|
||||
byte = sock.rQshift8();
|
||||
this._len |= (byte & 0x7f) << 7;
|
||||
if (byte & 0x80) {
|
||||
byte = sock.rQshift8();
|
||||
this._len |= byte << 14;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sock.rQwait("TIGHT", this._len)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let data = sock.rQshiftBytes(this._len);
|
||||
this._len = 0;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
_getScratchBuffer(size) {
|
||||
if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
|
||||
this._scratchBuffer = new Uint8Array(size);
|
||||
}
|
||||
return this._scratchBuffer;
|
||||
}
|
||||
|
||||
async _disableQOIWorkers() {
|
||||
if (this._workers) {
|
||||
this._enableQOI = false;
|
||||
this._availableWorkers = null;
|
||||
this._sabs = null;
|
||||
this._sabsR = null;
|
||||
this._arrs = null;
|
||||
this._arrsR = null;
|
||||
this._qoiRects = null;
|
||||
this._rectQlooping = null;
|
||||
for await (let i of Array.from(Array(this._threads).keys())) {
|
||||
this._workers[i].terminate();
|
||||
delete this._workers[i];
|
||||
}
|
||||
this._workers = null;
|
||||
}
|
||||
}
|
||||
|
||||
_enableQOIWorkers() {
|
||||
const supportsSharedArrayBuffers = typeof SharedArrayBuffer !== "undefined";
|
||||
if (!supportsSharedArrayBuffers) {
|
||||
Log.Warn("Enabling QOI Failed, client not compatible.");
|
||||
return false;
|
||||
}
|
||||
|
||||
let fullPath = window.location.pathname;
|
||||
let path = fullPath.substring(0, fullPath.lastIndexOf('/')+1);
|
||||
if ((window.navigator.hardwareConcurrency) && (window.navigator.hardwareConcurrency >= 4)) {
|
||||
this._threads = 16;
|
||||
} else {
|
||||
this._threads = 8;
|
||||
}
|
||||
this._workers = [];
|
||||
this._availableWorkers = [];
|
||||
this._sabs = [];
|
||||
this._sabsR = [];
|
||||
this._arrs = [];
|
||||
this._arrsR = [];
|
||||
this._qoiRects = [];
|
||||
this._rectQlooping = false;
|
||||
for (let i = 0; i < this._threads; i++) {
|
||||
this._workers.push(new Worker("core/decoders/qoi/decoder.js"));
|
||||
this._sabs.push(new SharedArrayBuffer(300000));
|
||||
this._sabsR.push(new SharedArrayBuffer(400000));
|
||||
this._arrs.push(new Uint8Array(this._sabs[i]));
|
||||
this._arrsR.push(new Uint8ClampedArray(this._sabsR[i]));
|
||||
this._workers[i].onmessage = (evt) => {
|
||||
this._availableWorkers.push(i);
|
||||
switch(evt.data.result) {
|
||||
case 0:
|
||||
let data = new Uint8ClampedArray(evt.data.length);
|
||||
data.set(this._arrsR[i].slice(0, evt.data.length));
|
||||
let img = new ImageData(data, evt.data.img.width, evt.data.img.height, {colorSpace: evt.data.img.colorSpace});
|
||||
|
||||
this._displayGlobal.blitQoi(
|
||||
evt.data.x,
|
||||
evt.data.y,
|
||||
evt.data.width,
|
||||
evt.data.height,
|
||||
img,
|
||||
0,
|
||||
evt.data.frame_id,
|
||||
false
|
||||
);
|
||||
this._processRectQ();
|
||||
break;
|
||||
case 1:
|
||||
Log.Info("QOI Worker is now available.");
|
||||
break;
|
||||
case 2:
|
||||
Log.Info("Error on worker: " + evt.error);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
for (let i = 0; i < this._threads; i++) {
|
||||
this._workers[i].postMessage({path:path});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
import TightDecoder from './tight.js';
|
||||
|
||||
export default class TightPNGDecoder extends TightDecoder {
|
||||
_pngRect(x, y, width, height, sock, display, depth, frame_id) {
|
||||
let data = this._readData(sock);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display.imageRect(x, y, width, height, "image/png", data, frame_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_basicRect(ctl, x, y, width, height, sock, display, depth) {
|
||||
throw new Error("BasicCompression received in TightPNG rect");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* KasmVNC: HTML5 VNC client
|
||||
* Copyright (C) 2020 Kasm Technologies
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as Log from '../util/logging.js';
|
||||
import Inflator from "../inflator.js";
|
||||
|
||||
export default class UDPDecoder {
|
||||
constructor() {
|
||||
this._filter = null;
|
||||
this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
|
||||
this._directDraw = false; //Draw directly to the canvas without ordering
|
||||
|
||||
this._zlibs = [];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this._zlibs[i] = new Inflator();
|
||||
}
|
||||
}
|
||||
|
||||
decodeRect(x, y, width, height, data, display, depth, frame_id) {
|
||||
let ctl = data[12];
|
||||
ctl = ctl >> 4;
|
||||
|
||||
let ret;
|
||||
|
||||
if (ctl === 0x08) {
|
||||
ret = this._fillRect(x, y, width, height, data, display, depth, frame_id);
|
||||
} else if (ctl === 0x09) {
|
||||
ret = this._jpegRect(x, y, width, height, data, display, depth, frame_id);
|
||||
} else if (ctl === 0x0A) {
|
||||
ret = this._pngRect(x, y, width, height, data, display, depth, frame_id);
|
||||
} else if ((ctl & 0x08) == 0) {
|
||||
ret = this._basicRect(ctl, x, y, width, height, data, display, depth, frame_id);
|
||||
} else if (ctl === 0x0B) {
|
||||
ret = this._webpRect(x, y, width, height, data, display, depth, frame_id);
|
||||
} else {
|
||||
throw new Error("Illegal udp compression received (ctl: " +
|
||||
ctl + ")");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
_fillRect(x, y, width, height, data, display, depth, frame_id) {
|
||||
|
||||
display.fillRect(x, y, width, height,
|
||||
[data[13], data[14], data[15]], frame_id, this._directDraw);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_jpegRect(x, y, width, height, data, display, depth, frame_id) {
|
||||
let img = this._readData(data);
|
||||
if (img === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display.imageRect(x, y, width, height, "image/jpeg", img, frame_id, this._directDraw);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_webpRect(x, y, width, height, data, display, depth, frame_id) {
|
||||
let img = this._readData(data);
|
||||
if (img === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display.imageRect(x, y, width, height, "image/webp", img, frame_id, this._directDraw);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_pngRect(x, y, width, height, data, display, depth, frame_id) {
|
||||
//throw new Error("PNG received in UDP rect");
|
||||
Log.Error("PNG received in UDP rect");
|
||||
}
|
||||
|
||||
_basicRect(ctl, x, y, width, height, data, display, depth, frame_id) {
|
||||
let zlibs_flags = data[12];
|
||||
// Reset streams if the server requests it
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if ((zlibs_flags >> i) & 1) {
|
||||
this._zlibs[i].reset();
|
||||
//Log.Debug("Reset zlib stream " + i);
|
||||
}
|
||||
}
|
||||
|
||||
let filter = data[13];
|
||||
let data_index = 14;
|
||||
let streamId = ctl & 0x3;
|
||||
if (!(ctl & 0x4)) {
|
||||
// Implicit CopyFilter
|
||||
filter = 0;
|
||||
data_index = 13;
|
||||
}
|
||||
|
||||
let ret;
|
||||
|
||||
switch (filter) {
|
||||
case 0: // CopyFilter
|
||||
ret = this._copyFilter(streamId, x, y, width, height,
|
||||
data, display, depth, frame_id, data_index);
|
||||
break;
|
||||
case 1: // PaletteFilter
|
||||
ret = this._paletteFilter(streamId, x, y, width, height,
|
||||
data, display, depth, frame_id);
|
||||
break;
|
||||
case 2: // GradientFilter
|
||||
ret = this._gradientFilter(streamId, x, y, width, height,
|
||||
data, display, depth, frame_id);
|
||||
break;
|
||||
default:
|
||||
throw new Error("Illegal tight filter received (ctl: " +
|
||||
this._filter + ")");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
_copyFilter(streamId, x, y, width, height, data, display, depth, frame_id, data_index=14) {
|
||||
const uncompressedSize = width * height * 3;
|
||||
|
||||
if (uncompressedSize === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uncompressedSize < 12) {
|
||||
data = data.slice(data_index, data_index + uncompressedSize);
|
||||
} else {
|
||||
data = this._readData(data, data_index);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._zlibs[streamId].setInput(data);
|
||||
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||
this._zlibs[streamId].setInput(null);
|
||||
}
|
||||
|
||||
let rgbx = new Uint8Array(width * height * 4);
|
||||
for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {
|
||||
rgbx[i] = data[j];
|
||||
rgbx[i + 1] = data[j + 1];
|
||||
rgbx[i + 2] = data[j + 2];
|
||||
rgbx[i + 3] = 255; // Alpha
|
||||
}
|
||||
|
||||
display.blitImage(x, y, width, height, rgbx, 0, frame_id, this._directDraw);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_paletteFilter(streamId, x, y, width, height, data, display, depth, frame_id) {
|
||||
const numColors = data[14] + 1;
|
||||
const paletteSize = numColors * 3;
|
||||
let palette = data.slice(15, 15 + paletteSize);
|
||||
|
||||
const bpp = (numColors <= 2) ? 1 : 8;
|
||||
const rowSize = Math.floor((width * bpp + 7) / 8);
|
||||
const uncompressedSize = rowSize * height;
|
||||
let data_i = 15 + paletteSize;
|
||||
|
||||
if (uncompressedSize === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uncompressedSize < 12) {
|
||||
data = data.slice(data_i, data_i + uncompressedSize);
|
||||
} else {
|
||||
data = this._readData(data, data_i);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._zlibs[streamId].setInput(data);
|
||||
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||
this._zlibs[streamId].setInput(null);
|
||||
}
|
||||
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
if (numColors == 2) {
|
||||
this._monoRect(x, y, width, height, data, palette, display, frame_id);
|
||||
} else {
|
||||
this._paletteRect(x, y, width, height, data, palette, display, frame_id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_monoRect(x, y, width, height, data, palette, display, frame_id) {
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
// TODO: reduce number of calculations inside loop
|
||||
const dest = this._getScratchBuffer(width * height * 4);
|
||||
const w = Math.floor((width + 7) / 8);
|
||||
const w1 = Math.floor(width / 8);
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
let dp, sp, x;
|
||||
for (x = 0; x < w1; x++) {
|
||||
for (let b = 7; b >= 0; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 4;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
dest[dp + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
for (let b = 7; b >= 8 - width % 8; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 4;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
dest[dp + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
display.blitImage(x, y, width, height, dest, 0, frame_id, this._directDraw);
|
||||
}
|
||||
|
||||
_paletteRect(x, y, width, height, data, palette, display, frame_id) {
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
const dest = this._getScratchBuffer(width * height * 4);
|
||||
const total = width * height * 4;
|
||||
for (let i = 0, j = 0; i < total; i += 4, j++) {
|
||||
const sp = data[j] * 3;
|
||||
dest[i] = palette[sp];
|
||||
dest[i + 1] = palette[sp + 1];
|
||||
dest[i + 2] = palette[sp + 2];
|
||||
dest[i + 3] = 255;
|
||||
}
|
||||
|
||||
display.blitImage(x, y, width, height, dest, 0, frame_id, this._directDraw);
|
||||
}
|
||||
|
||||
_gradientFilter(streamId, x, y, width, height, data, display, depth, frame_id) {
|
||||
throw new Error("Gradient filter not implemented");
|
||||
}
|
||||
|
||||
_readData(data, len_index = 13) {
|
||||
if (data.length < len_index + 2) {
|
||||
Log.Error("UDP Decoder, readData, invalid data len")
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
let i = len_index;
|
||||
let byte = data[i++];
|
||||
let len = byte & 0x7f;
|
||||
// lenth field is variably sized 1 to 3 bytes long
|
||||
if (byte & 0x80) {
|
||||
byte = data[i++]
|
||||
len |= (byte & 0x7f) << 7;
|
||||
if (byte & 0x80) {
|
||||
byte = data[i++];
|
||||
len |= byte << 14;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: get rid of me
|
||||
if (data.length !== len + i) {
|
||||
console.log('Rect of size ' + len + ' with data size ' + data.length + ' index of ' + i);
|
||||
}
|
||||
|
||||
|
||||
return data.slice(i);
|
||||
}
|
||||
|
||||
_getScratchBuffer(size) {
|
||||
if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
|
||||
this._scratchBuffer = new Uint8Array(size);
|
||||
}
|
||||
return this._scratchBuffer;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user