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