| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- /*
- * noVNC: HTML5 VNC client
- * Copyright (C) 2012 Joel Martin
- * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
- * Copyright (C) 2018 Samuel Mannehed for Cendio AB
- * Copyright (C) 2018 Pierre Ossman for Cendio AB
- * 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 & 0x80) == 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;
- }
- const rQi = sock.rQi;
- const rQ = sock.rQ;
- display.fillRect(x, y, width, height,
- [rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false);
- sock.rQskipBytes(3);
- return true;
- }
- _jpegRect(x, y, width, height, sock, display, depth) {
- let data = this._readData(sock);
- if (data === null) {
- return false;
- }
- display.imageRect(x, y, "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 < 12) {
- if (sock.rQwait("TIGHT", uncompressedSize)) {
- return false;
- }
- data = sock.rQshiftBytes(uncompressedSize);
- } else {
- data = this._readData(sock);
- if (data === null) {
- return false;
- }
- data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
- if (data.length != uncompressedSize) {
- throw new Error("Incomplete zlib block");
- }
- }
- display.blitRgbImage(x, y, width, height, data, 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 < 12) {
- if (sock.rQwait("TIGHT", uncompressedSize)) {
- return false;
- }
- data = sock.rQshiftBytes(uncompressedSize);
- } else {
- data = this._readData(sock);
- if (data === null) {
- return false;
- }
- data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
- if (data.length != uncompressedSize) {
- throw new Error("Incomplete zlib block");
- }
- }
- // 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.blitRgbxImage(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.blitRgbxImage(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);
- this._len = 0;
- return data;
- }
- _getScratchBuffer(size) {
- if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
- this._scratchBuffer = new Uint8Array(size);
- }
- return this._scratchBuffer;
- }
- }
|