]> git.proxmox.com Git - mirror_novnc.git/blame - include/display.js
Change noVNC license to from LGPLv3 to MPL 2.0
[mirror_novnc.git] / include / display.js
CommitLineData
c4164bda
JM
1/*
2 * noVNC: HTML5 VNC client
d58f8b51 3 * Copyright (C) 2012 Joel Martin
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
8db09746 9/*jslint browser: true, white: false, bitwise: false */
d3796c14 10/*global Util, Base64, changeCursor */
c4164bda 11
5210330a 12function Display(defaults) {
d890e864 13"use strict";
d93d3e09 14
5210330a
JM
15var that = {}, // Public API methods
16 conf = {}, // Configuration attributes
c8460b03 17
d890e864
JM
18 // Private Display namespace variables
19 c_ctx = null,
455e4657 20 c_forceCanvas = false,
d41c33e4 21
34d8b844
JM
22 // Queued drawing actions for in-order rendering
23 renderQ = [],
24
d890e864 25 // Predefine function variables (jslint)
a820f126 26 imageDataGet, rgbImageData, bgrxImageData, cmapImageData,
34d8b844 27 setFillColor, rescale, scan_renderQ,
d890e864 28
54e7cbdf
JM
29 // The full frame buffer (logical canvas) size
30 fb_width = 0,
31 fb_height = 0,
32 // The visible "physical canvas" viewport
33 viewport = {'x': 0, 'y': 0, 'w' : 0, 'h' : 0 },
34 cleanRect = {'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1},
c8460b03 35
8db09746 36 c_prevStyle = "",
490d471c
JM
37 tile = null,
38 tile16x16 = null,
39 tile_x = 0,
40 tile_y = 0;
48ebcdb1 41
f272267b 42
5210330a
JM
43// Configuration attributes
44Util.conf_defaults(conf, that, defaults, [
45 ['target', 'wo', 'dom', null, 'Canvas element for rendering'],
46 ['context', 'ro', 'raw', null, 'Canvas 2D context for rendering (read-only)'],
47 ['logo', 'rw', 'raw', null, 'Logo to display when cleared: {"width": width, "height": height, "data": data}'],
48 ['true_color', 'rw', 'bool', true, 'Use true-color pixel data'],
49 ['colourMap', 'rw', 'arr', [], 'Colour map array (when not true-color)'],
50 ['scale', 'rw', 'float', 1.0, 'Display area scale factor 0.0 - 1.0'],
a5df24b4 51 ['viewport', 'rw', 'bool', false, 'Use a viewport set with viewportChange()'],
5210330a
JM
52 ['width', 'rw', 'int', null, 'Display area width'],
53 ['height', 'rw', 'int', null, 'Display area height'],
ff36b127 54
5210330a 55 ['render_mode', 'ro', 'str', '', 'Canvas rendering mode (read-only)'],
d890e864 56
5210330a
JM
57 ['prefer_js', 'rw', 'str', null, 'Prefer Javascript over canvas methods'],
58 ['cursor_uri', 'rw', 'raw', null, 'Can we render cursor using data URI']
59 ]);
ff36b127 60
8db09746 61// Override some specific getters/setters
5210330a 62that.get_context = function () { return c_ctx; };
e2e7c224 63
d890e864 64that.set_scale = function(scale) { rescale(scale); };
8db09746 65
54e7cbdf
JM
66that.set_width = function (val) { that.resize(val, fb_height); };
67that.get_width = function() { return fb_width; };
3b20e7a9 68
54e7cbdf
JM
69that.set_height = function (val) { that.resize(fb_width, val); };
70that.get_height = function() { return fb_height; };
58b4c536
JM
71
72
f272267b 73
8db09746
JM
74//
75// Private functions
76//
f272267b 77
8db09746
JM
78// Create the public API interface
79function constructor() {
d890e864 80 Util.Debug(">> Display.constructor");
f272267b 81
490d471c 82 var c, func, i, curDat, curSave,
005d9ee9 83 has_imageData = false, UE = Util.Engine;
8db09746
JM
84
85 if (! conf.target) { throw("target must be set"); }
86
87 if (typeof conf.target === 'string') {
e4671910 88 throw("target must be a DOM element");
8db09746
JM
89 }
90
91 c = conf.target;
d93d3e09 92
8db09746 93 if (! c.getContext) { throw("no getContext method"); }
d93d3e09 94
d890e864 95 if (! c_ctx) { c_ctx = c.getContext('2d'); }
8db09746 96
9b940131 97 Util.Debug("User Agent: " + navigator.userAgent);
005d9ee9
JM
98 if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); }
99 if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); }
455e4657
JM
100 if (UE.trident) { Util.Debug("Browser: trident " + UE.trident); }
101 if (UE.presto) { Util.Debug("Browser: presto " + UE.presto); }
005d9ee9 102
8db09746 103 that.clear();
d93d3e09 104
490d471c
JM
105 // Check canvas features
106 if ('createImageData' in c_ctx) {
107 conf.render_mode = "canvas rendering";
d93d3e09 108 } else {
490d471c
JM
109 throw("Canvas does not support createImageData");
110 }
111 if (conf.prefer_js === null) {
112 Util.Info("Prefering javascript operations");
113 conf.prefer_js = true;
cdb55d26
JM
114 }
115
490d471c
JM
116 // Initialize cached tile imageData
117 tile16x16 = c_ctx.createImageData(16, 16);
118
2c2b492c
JM
119 /*
120 * Determine browser support for setting the cursor via data URI
121 * scheme
122 */
123 curDat = [];
8db09746 124 for (i=0; i < 8 * 8 * 4; i += 1) {
2c2b492c
JM
125 curDat.push(255);
126 }
8171f4d8
JM
127 try {
128 curSave = c.style.cursor;
9a23006e 129 changeCursor(conf.target, curDat, curDat, 2, 2, 8, 8);
8171f4d8 130 if (c.style.cursor) {
8db09746
JM
131 if (conf.cursor_uri === null) {
132 conf.cursor_uri = true;
133 }
8171f4d8
JM
134 Util.Info("Data URI scheme cursor supported");
135 } else {
8db09746
JM
136 if (conf.cursor_uri === null) {
137 conf.cursor_uri = false;
138 }
8171f4d8
JM
139 Util.Warn("Data URI scheme cursor not supported");
140 }
141 c.style.cursor = curSave;
8db09746 142 } catch (exc2) {
8171f4d8
JM
143 Util.Error("Data URI scheme cursor test exception: " + exc2);
144 conf.cursor_uri = false;
2c2b492c 145 }
2c2b492c 146
d890e864 147 Util.Debug("<< Display.constructor");
8db09746
JM
148 return that ;
149}
150
d890e864 151rescale = function(factor) {
125d8bbb
JM
152 var c, tp, x, y,
153 properties = ['transform', 'WebkitTransform', 'MozTransform', null];
8db09746
JM
154 c = conf.target;
155 tp = properties.shift();
156 while (tp) {
157 if (typeof c.style[tp] !== 'undefined') {
125d8bbb
JM
158 break;
159 }
8db09746 160 tp = properties.shift();
125d8bbb
JM
161 }
162
163 if (tp === null) {
164 Util.Debug("No scaling support");
165 return;
166 }
167
54e7cbdf
JM
168
169 if (typeof(factor) === "undefined") {
170 factor = conf.scale;
171 } else if (factor > 1.0) {
1a2371fc
JM
172 factor = 1.0;
173 } else if (factor < 0.1) {
174 factor = 0.1;
175 }
176
8db09746 177 if (conf.scale === factor) {
d890e864 178 //Util.Debug("Display already scaled to '" + factor + "'");
125d8bbb
JM
179 return;
180 }
181
8db09746 182 conf.scale = factor;
125d8bbb
JM
183 x = c.width - c.width * factor;
184 y = c.height - c.height * factor;
8db09746
JM
185 c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)";
186};
187
7cd6118c 188setFillColor = function(color) {
ac99a1f7 189 var bgr, newStyle;
7cd6118c 190 if (conf.true_color) {
ac99a1f7 191 bgr = color;
7cd6118c 192 } else {
ac99a1f7 193 bgr = conf.colourMap[color[0]];
7cd6118c 194 }
ac99a1f7 195 newStyle = "rgb(" + bgr[2] + "," + bgr[1] + "," + bgr[0] + ")";
7cd6118c
JM
196 if (newStyle !== c_prevStyle) {
197 c_ctx.fillStyle = newStyle;
198 c_prevStyle = newStyle;
199 }
200};
201
202
203//
204// Public API interface functions
205//
206
207// Shift and/or resize the visible viewport
54e7cbdf
JM
208that.viewportChange = function(deltaX, deltaY, width, height) {
209 var c = conf.target, v = viewport, cr = cleanRect,
210 saveImg = null, saveStyle, x1, y1, vx2, vy2, w, h;
211
a5df24b4
JM
212 if (!conf.viewport) {
213 Util.Debug("Setting viewport to full display region");
214 deltaX = -v.w; // Clamped later if out of bounds
215 deltaY = -v.h; // Clamped later if out of bounds
216 width = fb_width;
217 height = fb_height;
218 }
219
54e7cbdf
JM
220 if (typeof(deltaX) === "undefined") { deltaX = 0; }
221 if (typeof(deltaY) === "undefined") { deltaY = 0; }
222 if (typeof(width) === "undefined") { width = v.w; }
223 if (typeof(height) === "undefined") { height = v.h; }
224
225 // Size change
226
227 if (width > fb_width) { width = fb_width; }
228 if (height > fb_height) { height = fb_height; }
229
230 if ((v.w !== width) || (v.h !== height)) {
231 // Change width
232 if ((width < v.w) && (cr.x2 > v.x + width -1)) {
233 cr.x2 = v.x + width - 1;
234 }
235 v.w = width;
236
237 // Change height
238 if ((height < v.h) && (cr.y2 > v.y + height -1)) {
239 cr.y2 = v.y + height - 1;
240 }
241 v.h = height;
242
243
a5df24b4 244 if (v.w > 0 && v.h > 0 && c.width > 0 && c.height > 0) {
54e7cbdf
JM
245 saveImg = c_ctx.getImageData(0, 0,
246 (c.width < v.w) ? c.width : v.w,
247 (c.height < v.h) ? c.height : v.h);
248 }
249
250 c.width = v.w;
251 c.height = v.h;
252
253 if (saveImg) {
254 c_ctx.putImageData(saveImg, 0, 0);
255 }
256 }
257
258 vx2 = v.x + v.w - 1;
259 vy2 = v.y + v.h - 1;
260
261
262 // Position change
263
264 if ((deltaX < 0) && ((v.x + deltaX) < 0)) {
265 deltaX = - v.x;
266 }
267 if ((vx2 + deltaX) >= fb_width) {
268 deltaX -= ((vx2 + deltaX) - fb_width + 1);
269 }
270
271 if ((v.y + deltaY) < 0) {
272 deltaY = - v.y;
273 }
274 if ((vy2 + deltaY) >= fb_height) {
275 deltaY -= ((vy2 + deltaY) - fb_height + 1);
276 }
277
278 if ((deltaX === 0) && (deltaY === 0)) {
608e0f52 279 //Util.Debug("skipping viewport change");
54e7cbdf
JM
280 return;
281 }
608e0f52 282 Util.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
54e7cbdf
JM
283
284 v.x += deltaX;
285 vx2 += deltaX;
286 v.y += deltaY;
287 vy2 += deltaY;
288
289 // Update the clean rectangle
290 if (v.x > cr.x1) {
291 cr.x1 = v.x;
292 }
293 if (vx2 < cr.x2) {
294 cr.x2 = vx2;
295 }
296 if (v.y > cr.y1) {
297 cr.y1 = v.y;
298 }
299 if (vy2 < cr.y2) {
300 cr.y2 = vy2;
301 }
302
303 if (deltaX < 0) {
304 // Shift viewport left, redraw left section
305 x1 = 0;
306 w = - deltaX;
307 } else {
308 // Shift viewport right, redraw right section
309 x1 = v.w - deltaX;
310 w = deltaX;
311 }
312 if (deltaY < 0) {
313 // Shift viewport up, redraw top section
314 y1 = 0;
315 h = - deltaY;
316 } else {
317 // Shift viewport down, redraw bottom section
318 y1 = v.h - deltaY;
319 h = deltaY;
320 }
321
322 // Copy the valid part of the viewport to the shifted location
323 saveStyle = c_ctx.fillStyle;
324 c_ctx.fillStyle = "rgb(255,255,255)";
325 if (deltaX !== 0) {
326 //that.copyImage(0, 0, -deltaX, 0, v.w, v.h);
327 //that.fillRect(x1, 0, w, v.h, [255,255,255]);
328 c_ctx.drawImage(c, 0, 0, v.w, v.h, -deltaX, 0, v.w, v.h);
329 c_ctx.fillRect(x1, 0, w, v.h);
330 }
331 if (deltaY !== 0) {
332 //that.copyImage(0, 0, 0, -deltaY, v.w, v.h);
333 //that.fillRect(0, y1, v.w, h, [255,255,255]);
334 c_ctx.drawImage(c, 0, 0, v.w, v.h, 0, -deltaY, v.w, v.h);
335 c_ctx.fillRect(0, y1, v.w, h);
336 }
337 c_ctx.fillStyle = saveStyle;
608e0f52 338};
54e7cbdf 339
7cd6118c
JM
340
341// Return a map of clean and dirty areas of the viewport and reset the
342// tracking of clean and dirty areas.
343//
344// Returns: {'cleanBox': {'x': x, 'y': y, 'w': w, 'h': h},
345// 'dirtyBoxes': [{'x': x, 'y': y, 'w': w, 'h': h}, ...]}
54e7cbdf
JM
346that.getCleanDirtyReset = function() {
347 var v = viewport, c = cleanRect, cleanBox, dirtyBoxes = [],
348 vx2 = v.x + v.w - 1, vy2 = v.y + v.h - 1;
349
350
351 // Copy the cleanRect
352 cleanBox = {'x': c.x1, 'y': c.y1,
353 'w': c.x2 - c.x1 + 1, 'h': c.y2 - c.y1 + 1};
354
355 if ((c.x1 >= c.x2) || (c.y1 >= c.y2)) {
356 // Whole viewport is dirty
357 dirtyBoxes.push({'x': v.x, 'y': v.y, 'w': v.w, 'h': v.h});
358 } else {
359 // Redraw dirty regions
360 if (v.x < c.x1) {
361 // left side dirty region
362 dirtyBoxes.push({'x': v.x, 'y': v.y,
363 'w': c.x1 - v.x + 1, 'h': v.h});
364 }
365 if (vx2 > c.x2) {
366 // right side dirty region
367 dirtyBoxes.push({'x': c.x2 + 1, 'y': v.y,
368 'w': vx2 - c.x2, 'h': v.h});
369 }
370 if (v.y < c.y1) {
371 // top/middle dirty region
372 dirtyBoxes.push({'x': c.x1, 'y': v.y,
373 'w': c.x2 - c.x1 + 1, 'h': c.y1 - v.y});
374 }
375 if (vy2 > c.y2) {
376 // bottom/middle dirty region
377 dirtyBoxes.push({'x': c.x1, 'y': c.y2 + 1,
378 'w': c.x2 - c.x1 + 1, 'h': vy2 - c.y2});
379 }
380 }
381
382 // Reset the cleanRect to the whole viewport
383 cleanRect = {'x1': v.x, 'y1': v.y,
384 'x2': v.x + v.w - 1, 'y2': v.y + v.h - 1};
385
386 return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes};
a5df24b4
JM
387};
388
7cd6118c 389// Translate viewport coordinates to absolute coordinates
a5df24b4
JM
390that.absX = function(x) {
391 return x + viewport.x;
ff4bfcb7 392};
a5df24b4
JM
393that.absY = function(y) {
394 return y + viewport.y;
ff4bfcb7 395};
54e7cbdf
JM
396
397
d890e864 398that.resize = function(width, height) {
d890e864
JM
399 c_prevStyle = "";
400
54e7cbdf
JM
401 fb_width = width;
402 fb_height = height;
d890e864
JM
403
404 rescale(conf.scale);
54e7cbdf 405 that.viewportChange();
d890e864
JM
406};
407
408that.clear = function() {
409
410 if (conf.logo) {
411 that.resize(conf.logo.width, conf.logo.height);
412 that.blitStringImage(conf.logo.data, 0, 0);
413 } else {
414 that.resize(640, 20);
54e7cbdf 415 c_ctx.clearRect(0, 0, viewport.w, viewport.h);
d890e864
JM
416 }
417
34d8b844
JM
418 renderQ = [];
419
d890e864
JM
420 // No benefit over default ("source-over") in Chrome and firefox
421 //c_ctx.globalCompositeOperation = "copy";
422};
423
65bca0c9 424that.fillRect = function(x, y, width, height, color) {
d890e864 425 setFillColor(color);
54e7cbdf 426 c_ctx.fillRect(x - viewport.x, y - viewport.y, width, height);
8db09746 427};
8db09746 428
54e7cbdf
JM
429that.copyImage = function(old_x, old_y, new_x, new_y, w, h) {
430 var x1 = old_x - viewport.x, y1 = old_y - viewport.y,
431 x2 = new_x - viewport.x, y2 = new_y - viewport.y;
432 c_ctx.drawImage(conf.target, x1, y1, w, h, x2, y2, w, h);
8db09746 433};
532a9fd9 434
7cd6118c
JM
435
436// Start updating a tile
490d471c 437that.startTile = function(x, y, width, height, color) {
e79917c3 438 var data, bgr, red, green, blue, i;
490d471c
JM
439 tile_x = x;
440 tile_y = y;
441 if ((width === 16) && (height === 16)) {
442 tile = tile16x16;
443 } else {
444 tile = c_ctx.createImageData(width, height);
445 }
446 data = tile.data;
8db09746 447 if (conf.prefer_js) {
8db09746 448 if (conf.true_color) {
ac99a1f7 449 bgr = color;
d41c33e4 450 } else {
ac99a1f7 451 bgr = conf.colourMap[color[0]];
d41c33e4 452 }
ac99a1f7
JM
453 red = bgr[2];
454 green = bgr[1];
455 blue = bgr[0];
65bca0c9
JM
456 for (i = 0; i < (width * height * 4); i+=4) {
457 data[i ] = red;
458 data[i + 1] = green;
459 data[i + 2] = blue;
490d471c 460 data[i + 3] = 255;
65bca0c9 461 }
3875f847 462 } else {
65bca0c9 463 that.fillRect(x, y, width, height, color);
3875f847 464 }
8db09746 465};
3875f847 466
7cd6118c 467// Update sub-rectangle of the current tile
490d471c 468that.subTile = function(x, y, w, h, color) {
ac99a1f7 469 var data, p, bgr, red, green, blue, width, j, i, xend, yend;
8db09746 470 if (conf.prefer_js) {
490d471c
JM
471 data = tile.data;
472 width = tile.width;
8db09746 473 if (conf.true_color) {
ac99a1f7 474 bgr = color;
d41c33e4 475 } else {
ac99a1f7 476 bgr = conf.colourMap[color[0]];
d41c33e4 477 }
ac99a1f7
JM
478 red = bgr[2];
479 green = bgr[1];
480 blue = bgr[0];
65bca0c9
JM
481 xend = x + w;
482 yend = y + h;
483 for (j = y; j < yend; j += 1) {
484 for (i = x; i < xend; i += 1) {
485 p = (i + (j * width) ) * 4;
486 data[p ] = red;
97763d0e
JM
487 data[p + 1] = green;
488 data[p + 2] = blue;
490d471c 489 data[p + 3] = 255;
3875f847
JM
490 }
491 }
492 } else {
490d471c 493 that.fillRect(tile_x + x, tile_y + y, w, h, color);
3875f847 494 }
8db09746 495};
3875f847 496
7cd6118c 497// Draw the current tile to the screen
490d471c 498that.finishTile = function() {
8db09746 499 if (conf.prefer_js) {
ff4bfcb7 500 c_ctx.putImageData(tile, tile_x - viewport.x, tile_y - viewport.y);
3875f847 501 }
490d471c 502 // else: No-op, if not prefer_js then already done by setSubTile
8db09746 503};
3875f847 504
a820f126
MT
505rgbImageData = function(x, y, width, height, arr, offset) {
506 var img, i, j, data, v = viewport;
507 /*
508 if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
509 (x - v.x + width < 0) || (y - v.y + height < 0)) {
510 // Skipping because outside of viewport
511 return;
512 }
513 */
514 img = c_ctx.createImageData(width, height);
515 data = img.data;
516 for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+3) {
517 data[i ] = arr[j ];
518 data[i + 1] = arr[j + 1];
519 data[i + 2] = arr[j + 2];
520 data[i + 3] = 255; // Set Alpha
521 }
522 c_ctx.putImageData(img, x - v.x, y - v.y);
523};
524
ac99a1f7 525bgrxImageData = function(x, y, width, height, arr, offset) {
a5df24b4
JM
526 var img, i, j, data, v = viewport;
527 /*
528 if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
529 (x - v.x + width < 0) || (y - v.y + height < 0)) {
a5df24b4
JM
530 // Skipping because outside of viewport
531 return;
532 }
533 */
490d471c 534 img = c_ctx.createImageData(width, height);
97763d0e 535 data = img.data;
d41c33e4 536 for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
ac99a1f7 537 data[i ] = arr[j + 2];
7f4f41b0 538 data[i + 1] = arr[j + 1];
ac99a1f7 539 data[i + 2] = arr[j ];
7f4f41b0 540 data[i + 3] = 255; // Set Alpha
64ab5c4d 541 }
a5df24b4 542 c_ctx.putImageData(img, x - v.x, y - v.y);
8db09746 543};
64ab5c4d 544
d890e864 545cmapImageData = function(x, y, width, height, arr, offset) {
ac99a1f7 546 var img, i, j, data, bgr, cmap;
490d471c 547 img = c_ctx.createImageData(width, height);
d41c33e4 548 data = img.data;
8db09746 549 cmap = conf.colourMap;
d93d3e09 550 for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
ac99a1f7
JM
551 bgr = cmap[arr[j]];
552 data[i ] = bgr[2];
553 data[i + 1] = bgr[1];
554 data[i + 2] = bgr[0];
d41c33e4
JM
555 data[i + 3] = 255; // Set Alpha
556 }
54e7cbdf 557 c_ctx.putImageData(img, x - viewport.x, y - viewport.y);
8db09746 558};
d41c33e4 559
8db09746
JM
560that.blitImage = function(x, y, width, height, arr, offset) {
561 if (conf.true_color) {
e79917c3 562 bgrxImageData(x, y, width, height, arr, offset);
d41c33e4 563 } else {
490d471c 564 cmapImageData(x, y, width, height, arr, offset);
d41c33e4 565 }
8db09746 566};
d9cbdc7d 567
a820f126
MT
568that.blitRgbImage = function(x, y, width, height, arr, offset) {
569 if (conf.true_color) {
570 rgbImageData(x, y, width, height, arr, offset);
571 } else {
572 // prolly wrong...
573 cmapImageData(x, y, width, height, arr, offset);
574 }
575};
576
8db09746 577that.blitStringImage = function(str, x, y) {
d93d3e09 578 var img = new Image();
54e7cbdf
JM
579 img.onload = function () {
580 c_ctx.drawImage(img, x - viewport.x, y - viewport.y);
581 };
d93d3e09 582 img.src = str;
8db09746 583};
f272267b 584
bc28395a
JM
585// Wrap ctx.drawImage but relative to viewport
586that.drawImage = function(img, x, y) {
587 c_ctx.drawImage(img, x - viewport.x, y - viewport.y);
588};
589
34d8b844
JM
590that.renderQ_push = function(action) {
591 renderQ.push(action);
592 if (renderQ.length === 1) {
72a5596e
JM
593 // If this can be rendered immediately it will be, otherwise
594 // the scanner will start polling the queue (every
595 // requestAnimationFrame interval)
34d8b844
JM
596 scan_renderQ();
597 }
598};
599
600scan_renderQ = function() {
601 var a, ready = true;
602 while (ready && renderQ.length > 0) {
603 a = renderQ[0];
604 switch (a.type) {
605 case 'copy':
606 that.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height);
607 break;
608 case 'fill':
609 that.fillRect(a.x, a.y, a.width, a.height, a.color);
610 break;
611 case 'blit':
612 that.blitImage(a.x, a.y, a.width, a.height, a.data, 0);
613 break;
614 case 'blitRgb':
615 that.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0);
616 break;
617 case 'img':
618 if (a.img.complete) {
619 that.drawImage(a.img, a.x, a.y);
620 } else {
621 // We need to wait for this image to 'load'
622 // to keep things in-order
623 ready = false;
624 }
625 break;
626 }
627 if (ready) {
628 a = renderQ.shift();
629 }
630 }
631 if (renderQ.length > 0) {
632 requestAnimFrame(scan_renderQ);
633 }
634};
635
bc28395a 636
8db09746 637that.changeCursor = function(pixels, mask, hotx, hoty, w, h) {
8db09746 638 if (conf.cursor_uri === false) {
da6dd893 639 Util.Warn("changeCursor called but no cursor data URI support");
2c2b492c
JM
640 return;
641 }
642
9a23006e
JM
643 if (conf.true_color) {
644 changeCursor(conf.target, pixels, mask, hotx, hoty, w, h);
645 } else {
646 changeCursor(conf.target, pixels, mask, hotx, hoty, w, h, conf.colourMap);
647 }
43cf7bd8 648};
9a23006e 649
d3796c14 650that.defaultCursor = function() {
d890e864 651 conf.target.style.cursor = "default";
d3796c14
JM
652};
653
9a23006e
JM
654return constructor(); // Return the public API interface
655
d890e864 656} // End of Display()
9a23006e
JM
657
658
659/* Set CSS cursor property using data URI encoded cursor file */
660function changeCursor(target, pixels, mask, hotx, hoty, w, h, cmap) {
d890e864 661 "use strict";
9a23006e
JM
662 var cur = [], rgb, IHDRsz, RGBsz, ANDsz, XORsz, url, idx, alpha, x, y;
663 //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h);
664
67b4e987
JM
665 // Push multi-byte little-endian values
666 cur.push16le = function (num) {
667 this.push((num ) & 0xFF,
668 (num >> 8) & 0xFF );
669 };
670 cur.push32le = function (num) {
671 this.push((num ) & 0xFF,
672 (num >> 8) & 0xFF,
673 (num >> 16) & 0xFF,
674 (num >> 24) & 0xFF );
675 };
676
2c2b492c 677 IHDRsz = 40;
9a23006e 678 RGBsz = w * h * 4;
2c2b492c 679 XORsz = Math.ceil( (w * h) / 8.0 );
9a23006e 680 ANDsz = Math.ceil( (w * h) / 8.0 );
2c2b492c
JM
681
682 // Main header
9a23006e
JM
683 cur.push16le(0); // 0: Reserved
684 cur.push16le(2); // 2: .CUR type
685 cur.push16le(1); // 4: Number of images, 1 for non-animated ico
686
687 // Cursor #1 header (ICONDIRENTRY)
688 cur.push(w); // 6: width
689 cur.push(h); // 7: height
690 cur.push(0); // 8: colors, 0 -> true-color
691 cur.push(0); // 9: reserved
692 cur.push16le(hotx); // 10: hotspot x coordinate
693 cur.push16le(hoty); // 12: hotspot y coordinate
694 cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz);
695 // 14: cursor data byte size
696 cur.push32le(22); // 18: offset of cursor data in the file
697
698
699 // Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO)
700 cur.push32le(IHDRsz); // 22: Infoheader size
701 cur.push32le(w); // 26: Cursor width
702 cur.push32le(h*2); // 30: XOR+AND height
703 cur.push16le(1); // 34: number of planes
704 cur.push16le(32); // 36: bits per pixel
705 cur.push32le(0); // 38: Type of compression
706
707 cur.push32le(XORsz + ANDsz); // 43: Size of Image
708 // Gimp leaves this as 0
709
710 cur.push32le(0); // 46: reserved
711 cur.push32le(0); // 50: reserved
712 cur.push32le(0); // 54: reserved
713 cur.push32le(0); // 58: reserved
714
715 // 62: color data (RGBQUAD icColors[])
8db09746
JM
716 for (y = h-1; y >= 0; y -= 1) {
717 for (x = 0; x < w; x += 1) {
2c2b492c
JM
718 idx = y * Math.ceil(w / 8) + Math.floor(x/8);
719 alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
720
9a23006e 721 if (cmap) {
2c2b492c
JM
722 idx = (w * y) + x;
723 rgb = cmap[pixels[idx]];
724 cur.push(rgb[2]); // blue
725 cur.push(rgb[1]); // green
726 cur.push(rgb[0]); // red
727 cur.push(alpha); // alpha
9a23006e
JM
728 } else {
729 idx = ((w * y) + x) * 4;
730 cur.push(pixels[idx + 2]); // blue
731 cur.push(pixels[idx + 1]); // green
d890e864 732 cur.push(pixels[idx ]); // red
9a23006e 733 cur.push(alpha); // alpha
2c2b492c
JM
734 }
735 }
736 }
737
9a23006e
JM
738 // XOR/bitmask data (BYTE icXOR[])
739 // (ignored, just needs to be right size)
740 for (y = 0; y < h; y += 1) {
741 for (x = 0; x < Math.ceil(w / 8); x += 1) {
742 cur.push(0x00);
743 }
744 }
745
746 // AND/bitmask data (BYTE icAND[])
747 // (ignored, just needs to be right size)
8db09746
JM
748 for (y = 0; y < h; y += 1) {
749 for (x = 0; x < Math.ceil(w / 8); x += 1) {
2c2b492c
JM
750 cur.push(0x00);
751 }
752 }
753
754 url = "data:image/x-icon;base64," + Base64.encode(cur);
9a23006e 755 target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default";
da6dd893 756 //Util.Debug("<< changeCursor, cur.length: " + cur.length);
43cf7bd8 757}