| /* |
| * 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; |
| } |
| } |