mirror of
https://github.com/novnc/noVNC.git
synced 2026-06-05 11:59:39 +00:00
We don't know how long the caller will hang on to this data, so we need to be safe by default and assume it will kept indefinitely. That means we can't return a reference to the internal buffer, as that will get overwritten with future messages. We want to avoid unnecessary copying in performance critical code, though. So allow code to explicitly ask for a shared buffer, assuming they know the data needs to be consumed immediately.
328 lines
9.6 KiB
JavaScript
328 lines
9.6 KiB
JavaScript
/*
|
|
* noVNC: HTML5 VNC client
|
|
* 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() {
|
|
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._zlibs = [];
|
|
for (let i = 0; i < 4; i++) {
|
|
this._zlibs[i] = new Inflator();
|
|
}
|
|
}
|
|
|
|
decodeRect(x, y, width, height, sock, display, depth) {
|
|
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);
|
|
} else if (this._ctl === 0x09) {
|
|
ret = this._jpegRect(x, y, width, height,
|
|
sock, display, depth);
|
|
} else if (this._ctl === 0x0A) {
|
|
ret = this._pngRect(x, y, width, height,
|
|
sock, display, depth);
|
|
} else if ((this._ctl & 0x08) == 0) {
|
|
ret = this._basicRect(this._ctl, x, y, width, height,
|
|
sock, display, depth);
|
|
} else {
|
|
throw new Error("Illegal tight compression received (ctl: " +
|
|
this._ctl + ")");
|
|
}
|
|
|
|
if (ret) {
|
|
this._ctl = null;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
_fillRect(x, y, width, height, sock, display, depth) {
|
|
if (sock.rQwait("TIGHT", 3)) {
|
|
return false;
|
|
}
|
|
|
|
let pixel = sock.rQshiftBytes(3);
|
|
display.fillRect(x, y, width, height, pixel, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
_jpegRect(x, y, width, height, sock, display, depth) {
|
|
let data = this._readData(sock);
|
|
if (data === null) {
|
|
return false;
|
|
}
|
|
|
|
display.imageRect(x, y, width, height, "image/jpeg", data);
|
|
|
|
return true;
|
|
}
|
|
|
|
_pngRect(x, y, width, height, sock, display, depth) {
|
|
throw new Error("PNG received in standard Tight rect");
|
|
}
|
|
|
|
_basicRect(ctl, x, y, width, height, sock, display, depth) {
|
|
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);
|
|
break;
|
|
case 1: // PaletteFilter
|
|
ret = this._paletteFilter(streamId, x, y, width, height,
|
|
sock, display, depth);
|
|
break;
|
|
case 2: // GradientFilter
|
|
ret = this._gradientFilter(streamId, x, y, width, height,
|
|
sock, display, depth);
|
|
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) {
|
|
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, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
_paletteFilter(streamId, x, y, width, height, sock, display, depth) {
|
|
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);
|
|
} else {
|
|
this._paletteRect(x, y, width, height, data, this._palette, display);
|
|
}
|
|
|
|
this._numColors = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
_monoRect(x, y, width, height, data, palette, display) {
|
|
// 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, false);
|
|
}
|
|
|
|
_paletteRect(x, y, width, height, data, palette, display) {
|
|
// 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, false);
|
|
}
|
|
|
|
_gradientFilter(streamId, x, y, width, height, sock, display, depth) {
|
|
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, false);
|
|
this._len = 0;
|
|
|
|
return data;
|
|
}
|
|
|
|
_getScratchBuffer(size) {
|
|
if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
|
|
this._scratchBuffer = new Uint8Array(size);
|
|
}
|
|
return this._scratchBuffer;
|
|
}
|
|
}
|