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