]> git.proxmox.com Git - mirror_novnc.git/blob - core/decoders/tight.js
Handle empty rects from the server
[mirror_novnc.git] / core / decoders / tight.js
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
11 import * as Log from '../util/logging.js';
12 import Inflator from "../inflator.js";
13
14 export 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 const rQi = sock.rQi;
80 const rQ = sock.rQ;
81
82 display.fillRect(x, y, width, height,
83 [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
84 sock.rQskipBytes(3);
85
86 return true;
87 }
88
89 _jpegRect(x, y, width, height, sock, display, depth) {
90 let data = this._readData(sock);
91 if (data === null) {
92 return false;
93 }
94
95 display.imageRect(x, y, width, height, "image/jpeg", data);
96
97 return true;
98 }
99
100 _pngRect(x, y, width, height, sock, display, depth) {
101 throw new Error("PNG received in standard Tight rect");
102 }
103
104 _basicRect(ctl, x, y, width, height, sock, display, depth) {
105 if (this._filter === null) {
106 if (ctl & 0x4) {
107 if (sock.rQwait("TIGHT", 1)) {
108 return false;
109 }
110
111 this._filter = sock.rQshift8();
112 } else {
113 // Implicit CopyFilter
114 this._filter = 0;
115 }
116 }
117
118 let streamId = ctl & 0x3;
119
120 let ret;
121
122 switch (this._filter) {
123 case 0: // CopyFilter
124 ret = this._copyFilter(streamId, x, y, width, height,
125 sock, display, depth);
126 break;
127 case 1: // PaletteFilter
128 ret = this._paletteFilter(streamId, x, y, width, height,
129 sock, display, depth);
130 break;
131 case 2: // GradientFilter
132 ret = this._gradientFilter(streamId, x, y, width, height,
133 sock, display, depth);
134 break;
135 default:
136 throw new Error("Illegal tight filter received (ctl: " +
137 this._filter + ")");
138 }
139
140 if (ret) {
141 this._filter = null;
142 }
143
144 return ret;
145 }
146
147 _copyFilter(streamId, x, y, width, height, sock, display, depth) {
148 const uncompressedSize = width * height * 3;
149 let data;
150
151 if (uncompressedSize === 0) {
152 return true;
153 }
154
155 if (uncompressedSize < 12) {
156 if (sock.rQwait("TIGHT", uncompressedSize)) {
157 return false;
158 }
159
160 data = sock.rQshiftBytes(uncompressedSize);
161 } else {
162 data = this._readData(sock);
163 if (data === null) {
164 return false;
165 }
166
167 this._zlibs[streamId].setInput(data);
168 data = this._zlibs[streamId].inflate(uncompressedSize);
169 this._zlibs[streamId].setInput(null);
170 }
171
172 let rgbx = new Uint8Array(width * height * 4);
173 for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {
174 rgbx[i] = data[j];
175 rgbx[i + 1] = data[j + 1];
176 rgbx[i + 2] = data[j + 2];
177 rgbx[i + 3] = 255; // Alpha
178 }
179
180 display.blitImage(x, y, width, height, rgbx, 0, false);
181
182 return true;
183 }
184
185 _paletteFilter(streamId, x, y, width, height, sock, display, depth) {
186 if (this._numColors === 0) {
187 if (sock.rQwait("TIGHT palette", 1)) {
188 return false;
189 }
190
191 const numColors = sock.rQpeek8() + 1;
192 const paletteSize = numColors * 3;
193
194 if (sock.rQwait("TIGHT palette", 1 + paletteSize)) {
195 return false;
196 }
197
198 this._numColors = numColors;
199 sock.rQskipBytes(1);
200
201 sock.rQshiftTo(this._palette, paletteSize);
202 }
203
204 const bpp = (this._numColors <= 2) ? 1 : 8;
205 const rowSize = Math.floor((width * bpp + 7) / 8);
206 const uncompressedSize = rowSize * height;
207
208 let data;
209
210 if (uncompressedSize === 0) {
211 return true;
212 }
213
214 if (uncompressedSize < 12) {
215 if (sock.rQwait("TIGHT", uncompressedSize)) {
216 return false;
217 }
218
219 data = sock.rQshiftBytes(uncompressedSize);
220 } else {
221 data = this._readData(sock);
222 if (data === null) {
223 return false;
224 }
225
226 this._zlibs[streamId].setInput(data);
227 data = this._zlibs[streamId].inflate(uncompressedSize);
228 this._zlibs[streamId].setInput(null);
229 }
230
231 // Convert indexed (palette based) image data to RGB
232 if (this._numColors == 2) {
233 this._monoRect(x, y, width, height, data, this._palette, display);
234 } else {
235 this._paletteRect(x, y, width, height, data, this._palette, display);
236 }
237
238 this._numColors = 0;
239
240 return true;
241 }
242
243 _monoRect(x, y, width, height, data, palette, display) {
244 // Convert indexed (palette based) image data to RGB
245 // TODO: reduce number of calculations inside loop
246 const dest = this._getScratchBuffer(width * height * 4);
247 const w = Math.floor((width + 7) / 8);
248 const w1 = Math.floor(width / 8);
249
250 for (let y = 0; y < height; y++) {
251 let dp, sp, x;
252 for (x = 0; x < w1; x++) {
253 for (let b = 7; b >= 0; b--) {
254 dp = (y * width + x * 8 + 7 - b) * 4;
255 sp = (data[y * w + x] >> b & 1) * 3;
256 dest[dp] = palette[sp];
257 dest[dp + 1] = palette[sp + 1];
258 dest[dp + 2] = palette[sp + 2];
259 dest[dp + 3] = 255;
260 }
261 }
262
263 for (let b = 7; b >= 8 - width % 8; b--) {
264 dp = (y * width + x * 8 + 7 - b) * 4;
265 sp = (data[y * w + x] >> b & 1) * 3;
266 dest[dp] = palette[sp];
267 dest[dp + 1] = palette[sp + 1];
268 dest[dp + 2] = palette[sp + 2];
269 dest[dp + 3] = 255;
270 }
271 }
272
273 display.blitImage(x, y, width, height, dest, 0, false);
274 }
275
276 _paletteRect(x, y, width, height, data, palette, display) {
277 // Convert indexed (palette based) image data to RGB
278 const dest = this._getScratchBuffer(width * height * 4);
279 const total = width * height * 4;
280 for (let i = 0, j = 0; i < total; i += 4, j++) {
281 const sp = data[j] * 3;
282 dest[i] = palette[sp];
283 dest[i + 1] = palette[sp + 1];
284 dest[i + 2] = palette[sp + 2];
285 dest[i + 3] = 255;
286 }
287
288 display.blitImage(x, y, width, height, dest, 0, false);
289 }
290
291 _gradientFilter(streamId, x, y, width, height, sock, display, depth) {
292 throw new Error("Gradient filter not implemented");
293 }
294
295 _readData(sock) {
296 if (this._len === 0) {
297 if (sock.rQwait("TIGHT", 3)) {
298 return null;
299 }
300
301 let byte;
302
303 byte = sock.rQshift8();
304 this._len = byte & 0x7f;
305 if (byte & 0x80) {
306 byte = sock.rQshift8();
307 this._len |= (byte & 0x7f) << 7;
308 if (byte & 0x80) {
309 byte = sock.rQshift8();
310 this._len |= byte << 14;
311 }
312 }
313 }
314
315 if (sock.rQwait("TIGHT", this._len)) {
316 return null;
317 }
318
319 let data = sock.rQshiftBytes(this._len);
320 this._len = 0;
321
322 return data;
323 }
324
325 _getScratchBuffer(size) {
326 if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
327 this._scratchBuffer = new Uint8Array(size);
328 }
329 return this._scratchBuffer;
330 }
331 }