]>
Commit | Line | Data |
---|---|---|
c4164bda JM |
1 | /* |
2 | * noVNC: HTML5 VNC client | |
412d9306 | 3 | * Copyright (C) 2019 The noVNC Authors |
1d728ace | 4 | * Licensed under MPL 2.0 (see LICENSE.txt) |
c4164bda JM |
5 | * |
6 | * See README.md for usage and integration instructions. | |
7 | */ | |
c4164bda | 8 | |
6d6f0db0 | 9 | import * as Log from './util/logging.js'; |
3ae0bb09 | 10 | import Base64 from "./base64.js"; |
44f4c554 | 11 | import { supportsImageMetadata } from './util/browser.js'; |
ae510306 | 12 | |
0e4808bf JD |
13 | export default class Display { |
14 | constructor(target) { | |
15 | this._drawCtx = null; | |
ae510306 | 16 | |
0e4808bf JD |
17 | this._renderQ = []; // queue drawing actions for in-oder rendering |
18 | this._flushing = false; | |
ae510306 | 19 | |
0e4808bf JD |
20 | // the full frame buffer (logical canvas) size |
21 | this._fb_width = 0; | |
22 | this._fb_height = 0; | |
ae510306 | 23 | |
0e4808bf JD |
24 | this._prevDrawStyle = ""; |
25 | this._tile = null; | |
26 | this._tile16x16 = null; | |
27 | this._tile_x = 0; | |
28 | this._tile_y = 0; | |
ae510306 | 29 | |
0e4808bf | 30 | Log.Debug(">> Display.constructor"); |
3d7bb020 | 31 | |
0e4808bf JD |
32 | // The visible canvas |
33 | this._target = target; | |
ae510306 | 34 | |
0e4808bf JD |
35 | if (!this._target) { |
36 | throw new Error("Target must be set"); | |
37 | } | |
ae510306 | 38 | |
0e4808bf JD |
39 | if (typeof this._target === 'string') { |
40 | throw new Error('target must be a DOM element'); | |
41 | } | |
ae510306 | 42 | |
0e4808bf JD |
43 | if (!this._target.getContext) { |
44 | throw new Error("no getContext method"); | |
45 | } | |
2ba767a7 | 46 | |
0e4808bf | 47 | this._targetCtx = this._target.getContext('2d'); |
adf345fd | 48 | |
0e4808bf JD |
49 | // the visible canvas viewport (i.e. what actually gets seen) |
50 | this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height }; | |
ae510306 | 51 | |
0e4808bf JD |
52 | // The hidden canvas, where we do the actual rendering |
53 | this._backbuffer = document.createElement('canvas'); | |
54 | this._drawCtx = this._backbuffer.getContext('2d'); | |
84cd0e71 | 55 | |
942a3127 | 56 | this._damageBounds = { left: 0, top: 0, |
0e4808bf JD |
57 | right: this._backbuffer.width, |
58 | bottom: this._backbuffer.height }; | |
ae510306 | 59 | |
0e4808bf | 60 | Log.Debug("User Agent: " + navigator.userAgent); |
ae510306 | 61 | |
0e4808bf JD |
62 | // Check canvas features |
63 | if (!('createImageData' in this._drawCtx)) { | |
64 | throw new Error("Canvas does not support createImageData"); | |
65 | } | |
ff36b127 | 66 | |
0e4808bf JD |
67 | this._tile16x16 = this._drawCtx.createImageData(16, 16); |
68 | Log.Debug("<< Display.constructor"); | |
69 | ||
70 | // ===== PROPERTIES ===== | |
71 | ||
72 | this._scale = 1.0; | |
73 | this._clipViewport = false; | |
0e4808bf JD |
74 | |
75 | // ===== EVENT HANDLERS ===== | |
76 | ||
77 | this.onflush = () => {}; // A flush request has finished | |
78 | } | |
6d6f0db0 | 79 | |
747b4623 PO |
80 | // ===== PROPERTIES ===== |
81 | ||
0e4808bf | 82 | get scale() { return this._scale; } |
747b4623 PO |
83 | set scale(scale) { |
84 | this._rescale(scale); | |
0e4808bf | 85 | } |
747b4623 | 86 | |
0e4808bf | 87 | get clipViewport() { return this._clipViewport; } |
0460e5fd PO |
88 | set clipViewport(viewport) { |
89 | this._clipViewport = viewport; | |
747b4623 | 90 | // May need to readjust the viewport dimensions |
2b5f94fa | 91 | const vp = this._viewportLoc; |
747b4623 PO |
92 | this.viewportChangeSize(vp.w, vp.h); |
93 | this.viewportChangePos(0, 0); | |
0e4808bf | 94 | } |
747b4623 PO |
95 | |
96 | get width() { | |
97 | return this._fb_width; | |
0e4808bf JD |
98 | } |
99 | ||
747b4623 PO |
100 | get height() { |
101 | return this._fb_height; | |
0e4808bf | 102 | } |
747b4623 PO |
103 | |
104 | // ===== PUBLIC METHODS ===== | |
105 | ||
0e4808bf | 106 | viewportChangePos(deltaX, deltaY) { |
2b5f94fa | 107 | const vp = this._viewportLoc; |
6d6f0db0 SR |
108 | deltaX = Math.floor(deltaX); |
109 | deltaY = Math.floor(deltaY); | |
110 | ||
0460e5fd | 111 | if (!this._clipViewport) { |
6d6f0db0 SR |
112 | deltaX = -vp.w; // clamped later of out of bounds |
113 | deltaY = -vp.h; | |
114 | } | |
d1800d09 | 115 | |
2b5f94fa JD |
116 | const vx2 = vp.x + vp.w - 1; |
117 | const vy2 = vp.y + vp.h - 1; | |
490d471c | 118 | |
6d6f0db0 | 119 | // Position change |
1e13775b | 120 | |
6d6f0db0 SR |
121 | if (deltaX < 0 && vp.x + deltaX < 0) { |
122 | deltaX = -vp.x; | |
123 | } | |
124 | if (vx2 + deltaX >= this._fb_width) { | |
125 | deltaX -= vx2 + deltaX - this._fb_width + 1; | |
126 | } | |
54e7cbdf | 127 | |
6d6f0db0 SR |
128 | if (vp.y + deltaY < 0) { |
129 | deltaY = -vp.y; | |
130 | } | |
131 | if (vy2 + deltaY >= this._fb_height) { | |
132 | deltaY -= (vy2 + deltaY - this._fb_height + 1); | |
133 | } | |
54e7cbdf | 134 | |
6d6f0db0 SR |
135 | if (deltaX === 0 && deltaY === 0) { |
136 | return; | |
137 | } | |
138 | Log.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY); | |
54e7cbdf | 139 | |
6d6f0db0 SR |
140 | vp.x += deltaX; |
141 | vp.y += deltaY; | |
54e7cbdf | 142 | |
6d6f0db0 | 143 | this._damage(vp.x, vp.y, vp.w, vp.h); |
54e7cbdf | 144 | |
6d6f0db0 | 145 | this.flip(); |
0e4808bf | 146 | } |
1e13775b | 147 | |
0e4808bf | 148 | viewportChangeSize(width, height) { |
a8255821 | 149 | |
0460e5fd | 150 | if (!this._clipViewport || |
6d6f0db0 SR |
151 | typeof(width) === "undefined" || |
152 | typeof(height) === "undefined") { | |
84cd0e71 | 153 | |
6d6f0db0 SR |
154 | Log.Debug("Setting viewport to full display region"); |
155 | width = this._fb_width; | |
156 | height = this._fb_height; | |
157 | } | |
1e13775b | 158 | |
ab1ace38 PO |
159 | width = Math.floor(width); |
160 | height = Math.floor(height); | |
161 | ||
6d6f0db0 SR |
162 | if (width > this._fb_width) { |
163 | width = this._fb_width; | |
164 | } | |
165 | if (height > this._fb_height) { | |
166 | height = this._fb_height; | |
167 | } | |
636be753 | 168 | |
2b5f94fa | 169 | const vp = this._viewportLoc; |
6d6f0db0 SR |
170 | if (vp.w !== width || vp.h !== height) { |
171 | vp.w = width; | |
172 | vp.h = height; | |
636be753 | 173 | |
2b5f94fa | 174 | const canvas = this._target; |
6d6f0db0 SR |
175 | canvas.width = width; |
176 | canvas.height = height; | |
636be753 | 177 | |
6d6f0db0 SR |
178 | // The position might need to be updated if we've grown |
179 | this.viewportChangePos(0, 0); | |
adf345fd | 180 | |
6d6f0db0 SR |
181 | this._damage(vp.x, vp.y, vp.w, vp.h); |
182 | this.flip(); | |
636be753 | 183 | |
6d6f0db0 SR |
184 | // Update the visible size of the target canvas |
185 | this._rescale(this._scale); | |
186 | } | |
0e4808bf | 187 | } |
adf345fd | 188 | |
0e4808bf | 189 | absX(x) { |
a136b4b0 SM |
190 | if (this._scale === 0) { |
191 | return 0; | |
192 | } | |
6d6f0db0 | 193 | return x / this._scale + this._viewportLoc.x; |
0e4808bf | 194 | } |
adf345fd | 195 | |
0e4808bf | 196 | absY(y) { |
a136b4b0 SM |
197 | if (this._scale === 0) { |
198 | return 0; | |
199 | } | |
6d6f0db0 | 200 | return y / this._scale + this._viewportLoc.y; |
0e4808bf | 201 | } |
adf345fd | 202 | |
0e4808bf | 203 | resize(width, height) { |
6d6f0db0 | 204 | this._prevDrawStyle = ""; |
636be753 | 205 | |
6d6f0db0 SR |
206 | this._fb_width = width; |
207 | this._fb_height = height; | |
1e13775b | 208 | |
2b5f94fa | 209 | const canvas = this._backbuffer; |
6d6f0db0 | 210 | if (canvas.width !== width || canvas.height !== height) { |
1e13775b | 211 | |
6d6f0db0 | 212 | // We have to save the canvas data since changing the size will clear it |
2b5f94fa | 213 | let saveImg = null; |
6d6f0db0 SR |
214 | if (canvas.width > 0 && canvas.height > 0) { |
215 | saveImg = this._drawCtx.getImageData(0, 0, canvas.width, canvas.height); | |
216 | } | |
1e13775b | 217 | |
6d6f0db0 SR |
218 | if (canvas.width !== width) { |
219 | canvas.width = width; | |
220 | } | |
221 | if (canvas.height !== height) { | |
222 | canvas.height = height; | |
223 | } | |
1e13775b | 224 | |
6d6f0db0 SR |
225 | if (saveImg) { |
226 | this._drawCtx.putImageData(saveImg, 0, 0); | |
227 | } | |
228 | } | |
2ba767a7 | 229 | |
6d6f0db0 SR |
230 | // Readjust the viewport as it may be incorrectly sized |
231 | // and positioned | |
2b5f94fa | 232 | const vp = this._viewportLoc; |
6d6f0db0 SR |
233 | this.viewportChangeSize(vp.w, vp.h); |
234 | this.viewportChangePos(0, 0); | |
0e4808bf | 235 | } |
6d6f0db0 SR |
236 | |
237 | // Track what parts of the visible canvas that need updating | |
0e4808bf | 238 | _damage(x, y, w, h) { |
6d6f0db0 SR |
239 | if (x < this._damageBounds.left) { |
240 | this._damageBounds.left = x; | |
241 | } | |
242 | if (y < this._damageBounds.top) { | |
243 | this._damageBounds.top = y; | |
244 | } | |
245 | if ((x + w) > this._damageBounds.right) { | |
246 | this._damageBounds.right = x + w; | |
247 | } | |
248 | if ((y + h) > this._damageBounds.bottom) { | |
249 | this._damageBounds.bottom = y + h; | |
250 | } | |
0e4808bf | 251 | } |
2ba767a7 | 252 | |
6d6f0db0 SR |
253 | // Update the visible canvas with the contents of the |
254 | // rendering canvas | |
0e4808bf | 255 | flip(from_queue) { |
6d6f0db0 SR |
256 | if (this._renderQ.length !== 0 && !from_queue) { |
257 | this._renderQ_push({ | |
258 | 'type': 'flip' | |
259 | }); | |
260 | } else { | |
2b5f94fa JD |
261 | let x = this._damageBounds.left; |
262 | let y = this._damageBounds.top; | |
263 | let w = this._damageBounds.right - x; | |
264 | let h = this._damageBounds.bottom - y; | |
2ba767a7 | 265 | |
2b5f94fa JD |
266 | let vx = x - this._viewportLoc.x; |
267 | let vy = y - this._viewportLoc.y; | |
1e13775b | 268 | |
6d6f0db0 SR |
269 | if (vx < 0) { |
270 | w += vx; | |
271 | x -= vx; | |
272 | vx = 0; | |
84cd0e71 | 273 | } |
6d6f0db0 SR |
274 | if (vy < 0) { |
275 | h += vy; | |
276 | y -= vy; | |
277 | vy = 0; | |
84cd0e71 | 278 | } |
6d6f0db0 SR |
279 | |
280 | if ((vx + w) > this._viewportLoc.w) { | |
281 | w = this._viewportLoc.w - vx; | |
84cd0e71 | 282 | } |
6d6f0db0 SR |
283 | if ((vy + h) > this._viewportLoc.h) { |
284 | h = this._viewportLoc.h - vy; | |
84cd0e71 | 285 | } |
84cd0e71 | 286 | |
6d6f0db0 SR |
287 | if ((w > 0) && (h > 0)) { |
288 | // FIXME: We may need to disable image smoothing here | |
289 | // as well (see copyImage()), but we haven't | |
290 | // noticed any problem yet. | |
291 | this._targetCtx.drawImage(this._backbuffer, | |
292 | x, y, w, h, | |
293 | vx, vy, w, h); | |
294 | } | |
84cd0e71 | 295 | |
6d6f0db0 SR |
296 | this._damageBounds.left = this._damageBounds.top = 65535; |
297 | this._damageBounds.right = this._damageBounds.bottom = 0; | |
298 | } | |
0e4808bf | 299 | } |
84cd0e71 | 300 | |
0e4808bf | 301 | pending() { |
6d6f0db0 | 302 | return this._renderQ.length > 0; |
0e4808bf | 303 | } |
84cd0e71 | 304 | |
0e4808bf | 305 | flush() { |
6d6f0db0 | 306 | if (this._renderQ.length === 0) { |
747b4623 | 307 | this.onflush(); |
6d6f0db0 SR |
308 | } else { |
309 | this._flushing = true; | |
310 | } | |
0e4808bf | 311 | } |
2ba767a7 | 312 | |
0e4808bf | 313 | fillRect(x, y, width, height, color, from_queue) { |
6d6f0db0 SR |
314 | if (this._renderQ.length !== 0 && !from_queue) { |
315 | this._renderQ_push({ | |
316 | 'type': 'fill', | |
317 | 'x': x, | |
318 | 'y': y, | |
319 | 'width': width, | |
320 | 'height': height, | |
321 | 'color': color | |
322 | }); | |
323 | } else { | |
324 | this._setFillColor(color); | |
325 | this._drawCtx.fillRect(x, y, width, height); | |
326 | this._damage(x, y, width, height); | |
327 | } | |
0e4808bf | 328 | } |
1e13775b | 329 | |
0e4808bf | 330 | copyImage(old_x, old_y, new_x, new_y, w, h, from_queue) { |
6d6f0db0 SR |
331 | if (this._renderQ.length !== 0 && !from_queue) { |
332 | this._renderQ_push({ | |
333 | 'type': 'copy', | |
334 | 'old_x': old_x, | |
335 | 'old_y': old_y, | |
336 | 'x': new_x, | |
337 | 'y': new_y, | |
338 | 'width': w, | |
339 | 'height': h, | |
340 | }); | |
341 | } else { | |
342 | // Due to this bug among others [1] we need to disable the image-smoothing to | |
343 | // avoid getting a blur effect when copying data. | |
344 | // | |
345 | // 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719 | |
346 | // | |
347 | // We need to set these every time since all properties are reset | |
348 | // when the the size is changed | |
349 | this._drawCtx.mozImageSmoothingEnabled = false; | |
350 | this._drawCtx.webkitImageSmoothingEnabled = false; | |
351 | this._drawCtx.msImageSmoothingEnabled = false; | |
352 | this._drawCtx.imageSmoothingEnabled = false; | |
353 | ||
354 | this._drawCtx.drawImage(this._backbuffer, | |
355 | old_x, old_y, w, h, | |
356 | new_x, new_y, w, h); | |
357 | this._damage(new_x, new_y, w, h); | |
358 | } | |
0e4808bf | 359 | } |
6d6f0db0 | 360 | |
4babdf33 | 361 | imageRect(x, y, width, height, mime, arr) { |
c4eb4ddc PO |
362 | /* The internal logic cannot handle empty images, so bail early */ |
363 | if ((width === 0) || (height === 0)) { | |
364 | return; | |
365 | } | |
366 | ||
2b5f94fa | 367 | const img = new Image(); |
6d6f0db0 | 368 | img.src = "data: " + mime + ";base64," + Base64.encode(arr); |
c4eb4ddc | 369 | |
6d6f0db0 SR |
370 | this._renderQ_push({ |
371 | 'type': 'img', | |
372 | 'img': img, | |
373 | 'x': x, | |
4babdf33 PO |
374 | 'y': y, |
375 | 'width': width, | |
376 | 'height': height | |
6d6f0db0 | 377 | }); |
0e4808bf | 378 | } |
6d6f0db0 SR |
379 | |
380 | // start updating a tile | |
0e4808bf | 381 | startTile(x, y, width, height, color) { |
6d6f0db0 SR |
382 | this._tile_x = x; |
383 | this._tile_y = y; | |
384 | if (width === 16 && height === 16) { | |
385 | this._tile = this._tile16x16; | |
386 | } else { | |
387 | this._tile = this._drawCtx.createImageData(width, height); | |
388 | } | |
d9ca5e5b | 389 | |
2b5f94fa JD |
390 | const red = color[2]; |
391 | const green = color[1]; | |
392 | const blue = color[0]; | |
6d6f0db0 | 393 | |
2b5f94fa JD |
394 | const data = this._tile.data; |
395 | for (let i = 0; i < width * height * 4; i += 4) { | |
134ec26e PO |
396 | data[i] = red; |
397 | data[i + 1] = green; | |
398 | data[i + 2] = blue; | |
399 | data[i + 3] = 255; | |
6d6f0db0 | 400 | } |
0e4808bf | 401 | } |
6d6f0db0 SR |
402 | |
403 | // update sub-rectangle of the current tile | |
0e4808bf | 404 | subTile(x, y, w, h, color) { |
2b5f94fa JD |
405 | const red = color[2]; |
406 | const green = color[1]; | |
407 | const blue = color[0]; | |
408 | const xend = x + w; | |
409 | const yend = y + h; | |
410 | ||
411 | const data = this._tile.data; | |
412 | const width = this._tile.width; | |
413 | for (let j = y; j < yend; j++) { | |
414 | for (let i = x; i < xend; i++) { | |
415 | const p = (i + (j * width)) * 4; | |
134ec26e PO |
416 | data[p] = red; |
417 | data[p + 1] = green; | |
418 | data[p + 2] = blue; | |
419 | data[p + 3] = 255; | |
f00193e0 | 420 | } |
6d6f0db0 | 421 | } |
0e4808bf | 422 | } |
6d6f0db0 SR |
423 | |
424 | // draw the current tile to the screen | |
0e4808bf | 425 | finishTile() { |
134ec26e PO |
426 | this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y); |
427 | this._damage(this._tile_x, this._tile_y, | |
428 | this._tile.width, this._tile.height); | |
0e4808bf | 429 | } |
6d6f0db0 | 430 | |
0e4808bf | 431 | blitImage(x, y, width, height, arr, offset, from_queue) { |
6d6f0db0 SR |
432 | if (this._renderQ.length !== 0 && !from_queue) { |
433 | // NB(directxman12): it's technically more performant here to use preallocated arrays, | |
434 | // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, | |
435 | // this probably isn't getting called *nearly* as much | |
2b5f94fa | 436 | const new_arr = new Uint8Array(width * height * 4); |
6d6f0db0 | 437 | new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); |
1578fa68 | 438 | this._renderQ_push({ |
6d6f0db0 SR |
439 | 'type': 'blit', |
440 | 'data': new_arr, | |
1578fa68 | 441 | 'x': x, |
6d6f0db0 SR |
442 | 'y': y, |
443 | 'width': width, | |
444 | 'height': height, | |
1578fa68 | 445 | }); |
6d6f0db0 | 446 | } else { |
26586b9d | 447 | this._bgrxImageData(x, y, width, height, arr, offset); |
6d6f0db0 | 448 | } |
0e4808bf | 449 | } |
6d6f0db0 | 450 | |
6786fd87 | 451 | blitRgbImage(x, y, width, height, arr, offset, from_queue) { |
6d6f0db0 SR |
452 | if (this._renderQ.length !== 0 && !from_queue) { |
453 | // NB(directxman12): it's technically more performant here to use preallocated arrays, | |
454 | // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, | |
455 | // this probably isn't getting called *nearly* as much | |
2b5f94fa | 456 | const new_arr = new Uint8Array(width * height * 3); |
6d6f0db0 SR |
457 | new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); |
458 | this._renderQ_push({ | |
459 | 'type': 'blitRgb', | |
460 | 'data': new_arr, | |
461 | 'x': x, | |
462 | 'y': y, | |
463 | 'width': width, | |
464 | 'height': height, | |
465 | }); | |
6d6f0db0 | 466 | } else { |
26586b9d | 467 | this._rgbImageData(x, y, width, height, arr, offset); |
6d6f0db0 | 468 | } |
0e4808bf | 469 | } |
6d6f0db0 | 470 | |
0e4808bf | 471 | blitRgbxImage(x, y, width, height, arr, offset, from_queue) { |
6d6f0db0 SR |
472 | if (this._renderQ.length !== 0 && !from_queue) { |
473 | // NB(directxman12): it's technically more performant here to use preallocated arrays, | |
474 | // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, | |
475 | // this probably isn't getting called *nearly* as much | |
2b5f94fa | 476 | const new_arr = new Uint8Array(width * height * 4); |
6d6f0db0 SR |
477 | new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); |
478 | this._renderQ_push({ | |
479 | 'type': 'blitRgbx', | |
480 | 'data': new_arr, | |
481 | 'x': x, | |
482 | 'y': y, | |
483 | 'width': width, | |
484 | 'height': height, | |
485 | }); | |
486 | } else { | |
487 | this._rgbxImageData(x, y, width, height, arr, offset); | |
488 | } | |
0e4808bf | 489 | } |
1e13775b | 490 | |
0e4808bf | 491 | drawImage(img, x, y) { |
6d6f0db0 SR |
492 | this._drawCtx.drawImage(img, x, y); |
493 | this._damage(x, y, img.width, img.height); | |
0e4808bf | 494 | } |
d1800d09 | 495 | |
0e4808bf | 496 | autoscale(containerWidth, containerHeight) { |
a136b4b0 | 497 | let scaleRatio; |
6e7e6f9c | 498 | |
a136b4b0 SM |
499 | if (containerWidth === 0 || containerHeight === 0) { |
500 | scaleRatio = 0; | |
6d6f0db0 | 501 | |
6d6f0db0 | 502 | } else { |
a136b4b0 SM |
503 | |
504 | const vp = this._viewportLoc; | |
505 | const targetAspectRatio = containerWidth / containerHeight; | |
506 | const fbAspectRatio = vp.w / vp.h; | |
507 | ||
508 | if (fbAspectRatio >= targetAspectRatio) { | |
509 | scaleRatio = containerWidth / vp.w; | |
510 | } else { | |
511 | scaleRatio = containerHeight / vp.h; | |
512 | } | |
6d6f0db0 | 513 | } |
d3796c14 | 514 | |
6d6f0db0 | 515 | this._rescale(scaleRatio); |
0e4808bf | 516 | } |
6d6f0db0 | 517 | |
747b4623 PO |
518 | // ===== PRIVATE METHODS ===== |
519 | ||
0e4808bf | 520 | _rescale(factor) { |
6d6f0db0 | 521 | this._scale = factor; |
2b5f94fa | 522 | const vp = this._viewportLoc; |
6d6f0db0 SR |
523 | |
524 | // NB(directxman12): If you set the width directly, or set the | |
525 | // style width to a number, the canvas is cleared. | |
526 | // However, if you set the style width to a string | |
527 | // ('NNNpx'), the canvas is scaled without clearing. | |
ab1ace38 PO |
528 | const width = factor * vp.w + 'px'; |
529 | const height = factor * vp.h + 'px'; | |
6d6f0db0 SR |
530 | |
531 | if ((this._target.style.width !== width) || | |
532 | (this._target.style.height !== height)) { | |
533 | this._target.style.width = width; | |
534 | this._target.style.height = height; | |
535 | } | |
0e4808bf | 536 | } |
9a23006e | 537 | |
0e4808bf | 538 | _setFillColor(color) { |
2b5f94fa | 539 | const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')'; |
6d6f0db0 SR |
540 | if (newStyle !== this._prevDrawStyle) { |
541 | this._drawCtx.fillStyle = newStyle; | |
542 | this._prevDrawStyle = newStyle; | |
543 | } | |
0e4808bf | 544 | } |
6d6f0db0 | 545 | |
0e4808bf | 546 | _rgbImageData(x, y, width, height, arr, offset) { |
2b5f94fa JD |
547 | const img = this._drawCtx.createImageData(width, height); |
548 | const data = img.data; | |
549 | for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 3) { | |
6d6f0db0 SR |
550 | data[i] = arr[j]; |
551 | data[i + 1] = arr[j + 1]; | |
552 | data[i + 2] = arr[j + 2]; | |
553 | data[i + 3] = 255; // Alpha | |
554 | } | |
555 | this._drawCtx.putImageData(img, x, y); | |
556 | this._damage(x, y, img.width, img.height); | |
0e4808bf | 557 | } |
6d6f0db0 | 558 | |
0e4808bf | 559 | _bgrxImageData(x, y, width, height, arr, offset) { |
2b5f94fa JD |
560 | const img = this._drawCtx.createImageData(width, height); |
561 | const data = img.data; | |
562 | for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 4) { | |
6d6f0db0 SR |
563 | data[i] = arr[j + 2]; |
564 | data[i + 1] = arr[j + 1]; | |
565 | data[i + 2] = arr[j]; | |
566 | data[i + 3] = 255; // Alpha | |
567 | } | |
568 | this._drawCtx.putImageData(img, x, y); | |
569 | this._damage(x, y, img.width, img.height); | |
0e4808bf | 570 | } |
6d6f0db0 | 571 | |
0e4808bf | 572 | _rgbxImageData(x, y, width, height, arr, offset) { |
6d6f0db0 | 573 | // NB(directxman12): arr must be an Type Array view |
2b5f94fa | 574 | let img; |
44f4c554 | 575 | if (supportsImageMetadata) { |
6d6f0db0 SR |
576 | img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height); |
577 | } else { | |
578 | img = this._drawCtx.createImageData(width, height); | |
579 | img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4)); | |
580 | } | |
581 | this._drawCtx.putImageData(img, x, y); | |
582 | this._damage(x, y, img.width, img.height); | |
0e4808bf | 583 | } |
6d6f0db0 | 584 | |
0e4808bf | 585 | _renderQ_push(action) { |
6d6f0db0 SR |
586 | this._renderQ.push(action); |
587 | if (this._renderQ.length === 1) { | |
588 | // If this can be rendered immediately it will be, otherwise | |
589 | // the scanner will wait for the relevant event | |
590 | this._scan_renderQ(); | |
591 | } | |
0e4808bf | 592 | } |
6d6f0db0 | 593 | |
0e4808bf | 594 | _resume_renderQ() { |
6d6f0db0 SR |
595 | // "this" is the object that is ready, not the |
596 | // display object | |
597 | this.removeEventListener('load', this._noVNC_display._resume_renderQ); | |
598 | this._noVNC_display._scan_renderQ(); | |
0e4808bf | 599 | } |
6d6f0db0 | 600 | |
0e4808bf | 601 | _scan_renderQ() { |
2b5f94fa | 602 | let ready = true; |
6d6f0db0 | 603 | while (ready && this._renderQ.length > 0) { |
2b5f94fa | 604 | const a = this._renderQ[0]; |
6d6f0db0 SR |
605 | switch (a.type) { |
606 | case 'flip': | |
607 | this.flip(true); | |
608 | break; | |
609 | case 'copy': | |
610 | this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true); | |
611 | break; | |
612 | case 'fill': | |
613 | this.fillRect(a.x, a.y, a.width, a.height, a.color, true); | |
614 | break; | |
615 | case 'blit': | |
616 | this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true); | |
617 | break; | |
618 | case 'blitRgb': | |
619 | this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true); | |
620 | break; | |
621 | case 'blitRgbx': | |
622 | this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true); | |
623 | break; | |
624 | case 'img': | |
c4eb4ddc PO |
625 | /* IE tends to set "complete" prematurely, so check dimensions */ |
626 | if (a.img.complete && (a.img.width !== 0) && (a.img.height !== 0)) { | |
4babdf33 PO |
627 | if (a.img.width !== a.width || a.img.height !== a.height) { |
628 | Log.Error("Decoded image has incorrect dimensions. Got " + | |
629 | a.img.width + "x" + a.img.height + ". Expected " + | |
630 | a.width + "x" + a.height + "."); | |
631 | return; | |
632 | } | |
6d6f0db0 SR |
633 | this.drawImage(a.img, a.x, a.y); |
634 | } else { | |
635 | a.img._noVNC_display = this; | |
636 | a.img.addEventListener('load', this._resume_renderQ); | |
637 | // We need to wait for this image to 'load' | |
638 | // to keep things in-order | |
639 | ready = false; | |
640 | } | |
641 | break; | |
1e13775b SR |
642 | } |
643 | ||
6d6f0db0 SR |
644 | if (ready) { |
645 | this._renderQ.shift(); | |
1578fa68 | 646 | } |
6d6f0db0 | 647 | } |
1e13775b | 648 | |
6d6f0db0 SR |
649 | if (this._renderQ.length === 0 && this._flushing) { |
650 | this._flushing = false; | |
747b4623 | 651 | this.onflush(); |
6d6f0db0 | 652 | } |
0e4808bf JD |
653 | } |
654 | } |