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