/* * 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; } const rQi = sock.rQi; const rQ = sock.rQ; display.fillRect(x, y, width, height, [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], 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, 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); this._len = 0; return data; } _getScratchBuffer(size) { if (!this._scratchBuffer || (this._scratchBuffer.length < size)) { this._scratchBuffer = new Uint8Array(size); } return this._scratchBuffer; } }