]> git.proxmox.com Git - mirror_novnc.git/blob - core/decoders/tight.js
Merge pull request #1179 from ssebs/super-key-feature
[mirror_novnc.git] / core / decoders / tight.js
1 /*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
5 * Copyright (C) 2018 Samuel Mannehed for Cendio AB
6 * Copyright (C) 2018 Pierre Ossman for Cendio AB
7 * Licensed under MPL 2.0 (see LICENSE.txt)
8 *
9 * See README.md for usage and integration instructions.
10 *
11 */
12
13 import * as Log from '../util/logging.js';
14 import Inflator from "../inflator.js";
15
16 export default class TightDecoder {
17 constructor() {
18 this._ctl = null;
19 this._filter = null;
20 this._numColors = 0;
21 this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
22 this._len = 0;
23
24 this._zlibs = [];
25 for (let i = 0; i < 4; i++) {
26 this._zlibs[i] = new Inflator();
27 }
28 }
29
30 decodeRect(x, y, width, height, sock, display, depth) {
31 if (this._ctl === null) {
32 if (sock.rQwait("TIGHT compression-control", 1)) {
33 return false;
34 }
35
36 this._ctl = sock.rQshift8();
37
38 // Reset streams if the server requests it
39 for (let i = 0; i < 4; i++) {
40 if ((this._ctl >> i) & 1) {
41 this._zlibs[i].reset();
42 Log.Info("Reset zlib stream " + i);
43 }
44 }
45
46 // Figure out filter
47 this._ctl = this._ctl >> 4;
48 }
49
50 let ret;
51
52 if (this._ctl === 0x08) {
53 ret = this._fillRect(x, y, width, height,
54 sock, display, depth);
55 } else if (this._ctl === 0x09) {
56 ret = this._jpegRect(x, y, width, height,
57 sock, display, depth);
58 } else if (this._ctl === 0x0A) {
59 ret = this._pngRect(x, y, width, height,
60 sock, display, depth);
61 } else if ((this._ctl & 0x80) == 0) {
62 ret = this._basicRect(this._ctl, x, y, width, height,
63 sock, display, depth);
64 } else {
65 throw new Error("Illegal tight compression received (ctl: " +
66 this._ctl + ")");
67 }
68
69 if (ret) {
70 this._ctl = null;
71 }
72
73 return ret;
74 }
75
76 _fillRect(x, y, width, height, sock, display, depth) {
77 if (sock.rQwait("TIGHT", 3)) {
78 return false;
79 }
80
81 const rQi = sock.rQi;
82 const rQ = sock.rQ;
83
84 display.fillRect(x, y, width, height,
85 [rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false);
86 sock.rQskipBytes(3);
87
88 return true;
89 }
90
91 _jpegRect(x, y, width, height, sock, display, depth) {
92 let data = this._readData(sock);
93 if (data === null) {
94 return false;
95 }
96
97 display.imageRect(x, y, "image/jpeg", data);
98
99 return true;
100 }
101
102 _pngRect(x, y, width, height, sock, display, depth) {
103 throw new Error("PNG received in standard Tight rect");
104 }
105
106 _basicRect(ctl, x, y, width, height, sock, display, depth) {
107 if (this._filter === null) {
108 if (ctl & 0x4) {
109 if (sock.rQwait("TIGHT", 1)) {
110 return false;
111 }
112
113 this._filter = sock.rQshift8();
114 } else {
115 // Implicit CopyFilter
116 this._filter = 0;
117 }
118 }
119
120 let streamId = ctl & 0x3;
121
122 let ret;
123
124 switch (this._filter) {
125 case 0: // CopyFilter
126 ret = this._copyFilter(streamId, x, y, width, height,
127 sock, display, depth);
128 break;
129 case 1: // PaletteFilter
130 ret = this._paletteFilter(streamId, x, y, width, height,
131 sock, display, depth);
132 break;
133 case 2: // GradientFilter
134 ret = this._gradientFilter(streamId, x, y, width, height,
135 sock, display, depth);
136 break;
137 default:
138 throw new Error("Illegal tight filter received (ctl: " +
139 this._filter + ")");
140 }
141
142 if (ret) {
143 this._filter = null;
144 }
145
146 return ret;
147 }
148
149 _copyFilter(streamId, x, y, width, height, sock, display, depth) {
150 const uncompressedSize = width * height * 3;
151 let data;
152
153 if (uncompressedSize < 12) {
154 if (sock.rQwait("TIGHT", uncompressedSize)) {
155 return false;
156 }
157
158 data = sock.rQshiftBytes(uncompressedSize);
159 } else {
160 data = this._readData(sock);
161 if (data === null) {
162 return false;
163 }
164
165 data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
166 if (data.length != uncompressedSize) {
167 throw new Error("Incomplete zlib block");
168 }
169 }
170
171 display.blitRgbImage(x, y, width, height, data, 0, false);
172
173 return true;
174 }
175
176 _paletteFilter(streamId, x, y, width, height, sock, display, depth) {
177 if (this._numColors === 0) {
178 if (sock.rQwait("TIGHT palette", 1)) {
179 return false;
180 }
181
182 const numColors = sock.rQpeek8() + 1;
183 const paletteSize = numColors * 3;
184
185 if (sock.rQwait("TIGHT palette", 1 + paletteSize)) {
186 return false;
187 }
188
189 this._numColors = numColors;
190 sock.rQskipBytes(1);
191
192 sock.rQshiftTo(this._palette, paletteSize);
193 }
194
195 const bpp = (this._numColors <= 2) ? 1 : 8;
196 const rowSize = Math.floor((width * bpp + 7) / 8);
197 const uncompressedSize = rowSize * height;
198
199 let data;
200
201 if (uncompressedSize < 12) {
202 if (sock.rQwait("TIGHT", uncompressedSize)) {
203 return false;
204 }
205
206 data = sock.rQshiftBytes(uncompressedSize);
207 } else {
208 data = this._readData(sock);
209 if (data === null) {
210 return false;
211 }
212
213 data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
214 if (data.length != uncompressedSize) {
215 throw new Error("Incomplete zlib block");
216 }
217 }
218
219 // Convert indexed (palette based) image data to RGB
220 if (this._numColors == 2) {
221 this._monoRect(x, y, width, height, data, this._palette, display);
222 } else {
223 this._paletteRect(x, y, width, height, data, this._palette, display);
224 }
225
226 this._numColors = 0;
227
228 return true;
229 }
230
231 _monoRect(x, y, width, height, data, palette, display) {
232 // Convert indexed (palette based) image data to RGB
233 // TODO: reduce number of calculations inside loop
234 const dest = this._getScratchBuffer(width * height * 4);
235 const w = Math.floor((width + 7) / 8);
236 const w1 = Math.floor(width / 8);
237
238 for (let y = 0; y < height; y++) {
239 let dp, sp, x;
240 for (x = 0; x < w1; x++) {
241 for (let b = 7; b >= 0; b--) {
242 dp = (y * width + x * 8 + 7 - b) * 4;
243 sp = (data[y * w + x] >> b & 1) * 3;
244 dest[dp] = palette[sp];
245 dest[dp + 1] = palette[sp + 1];
246 dest[dp + 2] = palette[sp + 2];
247 dest[dp + 3] = 255;
248 }
249 }
250
251 for (let b = 7; b >= 8 - width % 8; b--) {
252 dp = (y * width + x * 8 + 7 - b) * 4;
253 sp = (data[y * w + x] >> b & 1) * 3;
254 dest[dp] = palette[sp];
255 dest[dp + 1] = palette[sp + 1];
256 dest[dp + 2] = palette[sp + 2];
257 dest[dp + 3] = 255;
258 }
259 }
260
261 display.blitRgbxImage(x, y, width, height, dest, 0, false);
262 }
263
264 _paletteRect(x, y, width, height, data, palette, display) {
265 // Convert indexed (palette based) image data to RGB
266 const dest = this._getScratchBuffer(width * height * 4);
267 const total = width * height * 4;
268 for (let i = 0, j = 0; i < total; i += 4, j++) {
269 const sp = data[j] * 3;
270 dest[i] = palette[sp];
271 dest[i + 1] = palette[sp + 1];
272 dest[i + 2] = palette[sp + 2];
273 dest[i + 3] = 255;
274 }
275
276 display.blitRgbxImage(x, y, width, height, dest, 0, false);
277 }
278
279 _gradientFilter(streamId, x, y, width, height, sock, display, depth) {
280 throw new Error("Gradient filter not implemented");
281 }
282
283 _readData(sock) {
284 if (this._len === 0) {
285 if (sock.rQwait("TIGHT", 3)) {
286 return null;
287 }
288
289 let byte;
290
291 byte = sock.rQshift8();
292 this._len = byte & 0x7f;
293 if (byte & 0x80) {
294 byte = sock.rQshift8();
295 this._len |= (byte & 0x7f) << 7;
296 if (byte & 0x80) {
297 byte = sock.rQshift8();
298 this._len |= byte << 14;
299 }
300 }
301 }
302
303 if (sock.rQwait("TIGHT", this._len)) {
304 return null;
305 }
306
307 let data = sock.rQshiftBytes(this._len);
308 this._len = 0;
309
310 return data;
311 }
312
313 _getScratchBuffer(size) {
314 if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
315 this._scratchBuffer = new Uint8Array(size);
316 }
317 return this._scratchBuffer;
318 }
319 }