main
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}