main
Raw Download raw file
  1/*
  2 * noVNC: HTML5 VNC client
  3 * Copyright (C) 2019 The noVNC authors
  4 * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
  5 * Licensed under MPL 2.0 (see LICENSE.txt)
  6 *
  7 * See README.md for usage and integration instructions.
  8 *
  9 */
 10
 11import * as Log from '../util/logging.js';
 12import Inflator from "../inflator.js";
 13
 14export default class TightDecoder {
 15    constructor() {
 16        this._ctl = null;
 17        this._filter = null;
 18        this._numColors = 0;
 19        this._palette = new Uint8Array(1024);  // 256 * 4 (max palette size * max bytes-per-pixel)
 20        this._len = 0;
 21
 22        this._zlibs = [];
 23        for (let i = 0; i < 4; i++) {
 24            this._zlibs[i] = new Inflator();
 25        }
 26    }
 27
 28    decodeRect(x, y, width, height, sock, display, depth) {
 29        if (this._ctl === null) {
 30            if (sock.rQwait("TIGHT compression-control", 1)) {
 31                return false;
 32            }
 33
 34            this._ctl = sock.rQshift8();
 35
 36            // Reset streams if the server requests it
 37            for (let i = 0; i < 4; i++) {
 38                if ((this._ctl >> i) & 1) {
 39                    this._zlibs[i].reset();
 40                    Log.Info("Reset zlib stream " + i);
 41                }
 42            }
 43
 44            // Figure out filter
 45            this._ctl = this._ctl >> 4;
 46        }
 47
 48        let ret;
 49
 50        if (this._ctl === 0x08) {
 51            ret = this._fillRect(x, y, width, height,
 52                                 sock, display, depth);
 53        } else if (this._ctl === 0x09) {
 54            ret = this._jpegRect(x, y, width, height,
 55                                 sock, display, depth);
 56        } else if (this._ctl === 0x0A) {
 57            ret = this._pngRect(x, y, width, height,
 58                                sock, display, depth);
 59        } else if ((this._ctl & 0x08) == 0) {
 60            ret = this._basicRect(this._ctl, x, y, width, height,
 61                                  sock, display, depth);
 62        } else {
 63            throw new Error("Illegal tight compression received (ctl: " +
 64                                   this._ctl + ")");
 65        }
 66
 67        if (ret) {
 68            this._ctl = null;
 69        }
 70
 71        return ret;
 72    }
 73
 74    _fillRect(x, y, width, height, sock, display, depth) {
 75        if (sock.rQwait("TIGHT", 3)) {
 76            return false;
 77        }
 78
 79        let pixel = sock.rQshiftBytes(3);
 80        display.fillRect(x, y, width, height, pixel, false);
 81
 82        return true;
 83    }
 84
 85    _jpegRect(x, y, width, height, sock, display, depth) {
 86        let data = this._readData(sock);
 87        if (data === null) {
 88            return false;
 89        }
 90
 91        display.imageRect(x, y, width, height, "image/jpeg", data);
 92
 93        return true;
 94    }
 95
 96    _pngRect(x, y, width, height, sock, display, depth) {
 97        throw new Error("PNG received in standard Tight rect");
 98    }
 99
100    _basicRect(ctl, x, y, width, height, sock, display, depth) {
101        if (this._filter === null) {
102            if (ctl & 0x4) {
103                if (sock.rQwait("TIGHT", 1)) {
104                    return false;
105                }
106
107                this._filter = sock.rQshift8();
108            } else {
109                // Implicit CopyFilter
110                this._filter = 0;
111            }
112        }
113
114        let streamId = ctl & 0x3;
115
116        let ret;
117
118        switch (this._filter) {
119            case 0: // CopyFilter
120                ret = this._copyFilter(streamId, x, y, width, height,
121                                       sock, display, depth);
122                break;
123            case 1: // PaletteFilter
124                ret = this._paletteFilter(streamId, x, y, width, height,
125                                          sock, display, depth);
126                break;
127            case 2: // GradientFilter
128                ret = this._gradientFilter(streamId, x, y, width, height,
129                                           sock, display, depth);
130                break;
131            default:
132                throw new Error("Illegal tight filter received (ctl: " +
133                                       this._filter + ")");
134        }
135
136        if (ret) {
137            this._filter = null;
138        }
139
140        return ret;
141    }
142
143    _copyFilter(streamId, x, y, width, height, sock, display, depth) {
144        const uncompressedSize = width * height * 3;
145        let data;
146
147        if (uncompressedSize === 0) {
148            return true;
149        }
150
151        if (uncompressedSize < 12) {
152            if (sock.rQwait("TIGHT", uncompressedSize)) {
153                return false;
154            }
155
156            data = sock.rQshiftBytes(uncompressedSize);
157        } else {
158            data = this._readData(sock);
159            if (data === null) {
160                return false;
161            }
162
163            this._zlibs[streamId].setInput(data);
164            data = this._zlibs[streamId].inflate(uncompressedSize);
165            this._zlibs[streamId].setInput(null);
166        }
167
168        let rgbx = new Uint8Array(width * height * 4);
169        for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {
170            rgbx[i]     = data[j];
171            rgbx[i + 1] = data[j + 1];
172            rgbx[i + 2] = data[j + 2];
173            rgbx[i + 3] = 255;  // Alpha
174        }
175
176        display.blitImage(x, y, width, height, rgbx, 0, false);
177
178        return true;
179    }
180
181    _paletteFilter(streamId, x, y, width, height, sock, display, depth) {
182        if (this._numColors === 0) {
183            if (sock.rQwait("TIGHT palette", 1)) {
184                return false;
185            }
186
187            const numColors = sock.rQpeek8() + 1;
188            const paletteSize = numColors * 3;
189
190            if (sock.rQwait("TIGHT palette", 1 + paletteSize)) {
191                return false;
192            }
193
194            this._numColors = numColors;
195            sock.rQskipBytes(1);
196
197            sock.rQshiftTo(this._palette, paletteSize);
198        }
199
200        const bpp = (this._numColors <= 2) ? 1 : 8;
201        const rowSize = Math.floor((width * bpp + 7) / 8);
202        const uncompressedSize = rowSize * height;
203
204        let data;
205
206        if (uncompressedSize === 0) {
207            return true;
208        }
209
210        if (uncompressedSize < 12) {
211            if (sock.rQwait("TIGHT", uncompressedSize)) {
212                return false;
213            }
214
215            data = sock.rQshiftBytes(uncompressedSize);
216        } else {
217            data = this._readData(sock);
218            if (data === null) {
219                return false;
220            }
221
222            this._zlibs[streamId].setInput(data);
223            data = this._zlibs[streamId].inflate(uncompressedSize);
224            this._zlibs[streamId].setInput(null);
225        }
226
227        // Convert indexed (palette based) image data to RGB
228        if (this._numColors == 2) {
229            this._monoRect(x, y, width, height, data, this._palette, display);
230        } else {
231            this._paletteRect(x, y, width, height, data, this._palette, display);
232        }
233
234        this._numColors = 0;
235
236        return true;
237    }
238
239    _monoRect(x, y, width, height, data, palette, display) {
240        // Convert indexed (palette based) image data to RGB
241        // TODO: reduce number of calculations inside loop
242        const dest = this._getScratchBuffer(width * height * 4);
243        const w = Math.floor((width + 7) / 8);
244        const w1 = Math.floor(width / 8);
245
246        for (let y = 0; y < height; y++) {
247            let dp, sp, x;
248            for (x = 0; x < w1; x++) {
249                for (let b = 7; b >= 0; b--) {
250                    dp = (y * width + x * 8 + 7 - b) * 4;
251                    sp = (data[y * w + x] >> b & 1) * 3;
252                    dest[dp]     = palette[sp];
253                    dest[dp + 1] = palette[sp + 1];
254                    dest[dp + 2] = palette[sp + 2];
255                    dest[dp + 3] = 255;
256                }
257            }
258
259            for (let b = 7; b >= 8 - width % 8; b--) {
260                dp = (y * width + x * 8 + 7 - b) * 4;
261                sp = (data[y * w + x] >> b & 1) * 3;
262                dest[dp]     = palette[sp];
263                dest[dp + 1] = palette[sp + 1];
264                dest[dp + 2] = palette[sp + 2];
265                dest[dp + 3] = 255;
266            }
267        }
268
269        display.blitImage(x, y, width, height, dest, 0, false);
270    }
271
272    _paletteRect(x, y, width, height, data, palette, display) {
273        // Convert indexed (palette based) image data to RGB
274        const dest = this._getScratchBuffer(width * height * 4);
275        const total = width * height * 4;
276        for (let i = 0, j = 0; i < total; i += 4, j++) {
277            const sp = data[j] * 3;
278            dest[i]     = palette[sp];
279            dest[i + 1] = palette[sp + 1];
280            dest[i + 2] = palette[sp + 2];
281            dest[i + 3] = 255;
282        }
283
284        display.blitImage(x, y, width, height, dest, 0, false);
285    }
286
287    _gradientFilter(streamId, x, y, width, height, sock, display, depth) {
288        // assume the TPIXEL is 3 bytes long
289        const uncompressedSize = width * height * 3;
290        let data;
291
292        if (uncompressedSize === 0) {
293            return true;
294        }
295
296        if (uncompressedSize < 12) {
297            if (sock.rQwait("TIGHT", uncompressedSize)) {
298                return false;
299            }
300
301            data = sock.rQshiftBytes(uncompressedSize);
302        } else {
303            data = this._readData(sock);
304            if (data === null) {
305                return false;
306            }
307
308            this._zlibs[streamId].setInput(data);
309            data = this._zlibs[streamId].inflate(uncompressedSize);
310            this._zlibs[streamId].setInput(null);
311        }
312
313        let rgbx = new Uint8Array(4 * width * height);
314
315        let rgbxIndex = 0, dataIndex = 0;
316        let left = new Uint8Array(3);
317        for (let x = 0; x < width; x++) {
318            for (let c = 0; c < 3; c++) {
319                const prediction = left[c];
320                const value = data[dataIndex++] + prediction;
321                rgbx[rgbxIndex++] = value;
322                left[c] = value;
323            }
324            rgbx[rgbxIndex++] = 255;
325        }
326
327        let upperIndex = 0;
328        let upper = new Uint8Array(3),
329            upperleft = new Uint8Array(3);
330        for (let y = 1; y < height; y++) {
331            left.fill(0);
332            upperleft.fill(0);
333            for (let x = 0; x < width; x++) {
334                for (let c = 0; c < 3; c++) {
335                    upper[c] = rgbx[upperIndex++];
336                    let prediction = left[c] + upper[c] - upperleft[c];
337                    if (prediction < 0) {
338                        prediction = 0;
339                    } else if (prediction > 255) {
340                        prediction = 255;
341                    }
342                    const value = data[dataIndex++] + prediction;
343                    rgbx[rgbxIndex++] = value;
344                    upperleft[c] = upper[c];
345                    left[c] = value;
346                }
347                rgbx[rgbxIndex++] = 255;
348                upperIndex++;
349            }
350        }
351
352        display.blitImage(x, y, width, height, rgbx, 0, false);
353
354        return true;
355    }
356
357    _readData(sock) {
358        if (this._len === 0) {
359            if (sock.rQwait("TIGHT", 3)) {
360                return null;
361            }
362
363            let byte;
364
365            byte = sock.rQshift8();
366            this._len = byte & 0x7f;
367            if (byte & 0x80) {
368                byte = sock.rQshift8();
369                this._len |= (byte & 0x7f) << 7;
370                if (byte & 0x80) {
371                    byte = sock.rQshift8();
372                    this._len |= byte << 14;
373                }
374            }
375        }
376
377        if (sock.rQwait("TIGHT", this._len)) {
378            return null;
379        }
380
381        let data = sock.rQshiftBytes(this._len, false);
382        this._len = 0;
383
384        return data;
385    }
386
387    _getScratchBuffer(size) {
388        if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
389            this._scratchBuffer = new Uint8Array(size);
390        }
391        return this._scratchBuffer;
392    }
393}