main
Raw Download raw file
  1/*
  2 * noVNC: HTML5 VNC client
  3 * Copyright (C) 2019 The noVNC authors
  4 * Licensed under MPL 2.0 (see LICENSE.txt)
  5 *
  6 * See README.md for usage and integration instructions.
  7 *
  8 */
  9
 10import * as Log from '../util/logging.js';
 11
 12export default class HextileDecoder {
 13    constructor() {
 14        this._tiles = 0;
 15        this._lastsubencoding = 0;
 16        this._tileBuffer = new Uint8Array(16 * 16 * 4);
 17    }
 18
 19    decodeRect(x, y, width, height, sock, display, depth) {
 20        if (this._tiles === 0) {
 21            this._tilesX = Math.ceil(width / 16);
 22            this._tilesY = Math.ceil(height / 16);
 23            this._totalTiles = this._tilesX * this._tilesY;
 24            this._tiles = this._totalTiles;
 25        }
 26
 27        while (this._tiles > 0) {
 28            let bytes = 1;
 29
 30            if (sock.rQwait("HEXTILE", bytes)) {
 31                return false;
 32            }
 33
 34            let subencoding = sock.rQpeek8();
 35            if (subencoding > 30) {  // Raw
 36                throw new Error("Illegal hextile subencoding (subencoding: " +
 37                            subencoding + ")");
 38            }
 39
 40            const currTile = this._totalTiles - this._tiles;
 41            const tileX = currTile % this._tilesX;
 42            const tileY = Math.floor(currTile / this._tilesX);
 43            const tx = x + tileX * 16;
 44            const ty = y + tileY * 16;
 45            const tw = Math.min(16, (x + width) - tx);
 46            const th = Math.min(16, (y + height) - ty);
 47
 48            // Figure out how much we are expecting
 49            if (subencoding & 0x01) {  // Raw
 50                bytes += tw * th * 4;
 51            } else {
 52                if (subencoding & 0x02) {  // Background
 53                    bytes += 4;
 54                }
 55                if (subencoding & 0x04) {  // Foreground
 56                    bytes += 4;
 57                }
 58                if (subencoding & 0x08) {  // AnySubrects
 59                    bytes++;  // Since we aren't shifting it off
 60
 61                    if (sock.rQwait("HEXTILE", bytes)) {
 62                        return false;
 63                    }
 64
 65                    let subrects = sock.rQpeekBytes(bytes).at(-1);
 66                    if (subencoding & 0x10) {  // SubrectsColoured
 67                        bytes += subrects * (4 + 2);
 68                    } else {
 69                        bytes += subrects * 2;
 70                    }
 71                }
 72            }
 73
 74            if (sock.rQwait("HEXTILE", bytes)) {
 75                return false;
 76            }
 77
 78            // We know the encoding and have a whole tile
 79            sock.rQshift8();
 80            if (subencoding === 0) {
 81                if (this._lastsubencoding & 0x01) {
 82                    // Weird: ignore blanks are RAW
 83                    Log.Debug("     Ignoring blank after RAW");
 84                } else {
 85                    display.fillRect(tx, ty, tw, th, this._background);
 86                }
 87            } else if (subencoding & 0x01) {  // Raw
 88                let pixels = tw * th;
 89                let data = sock.rQshiftBytes(pixels * 4, false);
 90                // Max sure the image is fully opaque
 91                for (let i = 0;i <  pixels;i++) {
 92                    data[i * 4 + 3] = 255;
 93                }
 94                display.blitImage(tx, ty, tw, th, data, 0);
 95            } else {
 96                if (subencoding & 0x02) {  // Background
 97                    this._background = new Uint8Array(sock.rQshiftBytes(4));
 98                }
 99                if (subencoding & 0x04) {  // Foreground
100                    this._foreground = new Uint8Array(sock.rQshiftBytes(4));
101                }
102
103                this._startTile(tx, ty, tw, th, this._background);
104                if (subencoding & 0x08) {  // AnySubrects
105                    let subrects = sock.rQshift8();
106
107                    for (let s = 0; s < subrects; s++) {
108                        let color;
109                        if (subencoding & 0x10) {  // SubrectsColoured
110                            color = sock.rQshiftBytes(4);
111                        } else {
112                            color = this._foreground;
113                        }
114                        const xy = sock.rQshift8();
115                        const sx = (xy >> 4);
116                        const sy = (xy & 0x0f);
117
118                        const wh = sock.rQshift8();
119                        const sw = (wh >> 4) + 1;
120                        const sh = (wh & 0x0f) + 1;
121
122                        this._subTile(sx, sy, sw, sh, color);
123                    }
124                }
125                this._finishTile(display);
126            }
127            this._lastsubencoding = subencoding;
128            this._tiles--;
129        }
130
131        return true;
132    }
133
134    // start updating a tile
135    _startTile(x, y, width, height, color) {
136        this._tileX = x;
137        this._tileY = y;
138        this._tileW = width;
139        this._tileH = height;
140
141        const red = color[0];
142        const green = color[1];
143        const blue = color[2];
144
145        const data = this._tileBuffer;
146        for (let i = 0; i < width * height * 4; i += 4) {
147            data[i]     = red;
148            data[i + 1] = green;
149            data[i + 2] = blue;
150            data[i + 3] = 255;
151        }
152    }
153
154    // update sub-rectangle of the current tile
155    _subTile(x, y, w, h, color) {
156        const red = color[0];
157        const green = color[1];
158        const blue = color[2];
159        const xend = x + w;
160        const yend = y + h;
161
162        const data = this._tileBuffer;
163        const width = this._tileW;
164        for (let j = y; j < yend; j++) {
165            for (let i = x; i < xend; i++) {
166                const p = (i + (j * width)) * 4;
167                data[p]     = red;
168                data[p + 1] = green;
169                data[p + 2] = blue;
170                data[p + 3] = 255;
171            }
172        }
173    }
174
175    // draw the current tile to the screen
176    _finishTile(display) {
177        display.blitImage(this._tileX, this._tileY,
178                          this._tileW, this._tileH,
179                          this._tileBuffer, 0);
180    }
181}