]>
Commit | Line | Data |
---|---|---|
c4164bda JM |
1 | /* |
2 | * noVNC: HTML5 VNC client | |
d0c29bb6 | 3 | * Copyright (C) 2011 Joel Martin |
5f409eee | 4 | * Licensed under LGPL-3 (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 | 12 | function Display(defaults) { |
d890e864 | 13 | "use strict"; |
d93d3e09 | 14 | |
5210330a JM |
15 | var 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 | |
d890e864 JM |
22 | c_imageData, c_rgbxImage, c_cmapImage, |
23 | ||
24 | // Predefine function variables (jslint) | |
25 | imageDataCreate, imageDataGet, rgbxImageData, cmapImageData, | |
26 | rgbxImageFill, cmapImageFill, setFillColor, rescale, flush, | |
27 | ||
8db09746 JM |
28 | c_width = 0, |
29 | c_height = 0, | |
c8460b03 | 30 | |
8db09746 | 31 | c_prevStyle = "", |
48ebcdb1 | 32 | |
cdb55d26 JM |
33 | c_webkit_bug = false, |
34 | c_flush_timer = null; | |
f272267b | 35 | |
5210330a JM |
36 | // Configuration attributes |
37 | Util.conf_defaults(conf, that, defaults, [ | |
38 | ['target', 'wo', 'dom', null, 'Canvas element for rendering'], | |
39 | ['context', 'ro', 'raw', null, 'Canvas 2D context for rendering (read-only)'], | |
40 | ['logo', 'rw', 'raw', null, 'Logo to display when cleared: {"width": width, "height": height, "data": data}'], | |
41 | ['true_color', 'rw', 'bool', true, 'Use true-color pixel data'], | |
42 | ['colourMap', 'rw', 'arr', [], 'Colour map array (when not true-color)'], | |
43 | ['scale', 'rw', 'float', 1.0, 'Display area scale factor 0.0 - 1.0'], | |
44 | ['width', 'rw', 'int', null, 'Display area width'], | |
45 | ['height', 'rw', 'int', null, 'Display area height'], | |
ff36b127 | 46 | |
5210330a | 47 | ['render_mode', 'ro', 'str', '', 'Canvas rendering mode (read-only)'], |
d890e864 | 48 | |
5210330a JM |
49 | ['prefer_js', 'rw', 'str', null, 'Prefer Javascript over canvas methods'], |
50 | ['cursor_uri', 'rw', 'raw', null, 'Can we render cursor using data URI'] | |
51 | ]); | |
ff36b127 | 52 | |
8db09746 | 53 | // Override some specific getters/setters |
5210330a | 54 | that.get_context = function () { return c_ctx; }; |
e2e7c224 | 55 | |
d890e864 | 56 | that.set_scale = function(scale) { rescale(scale); }; |
8db09746 | 57 | |
d890e864 JM |
58 | that.set_width = function (val) { that.resize(val, c_height); }; |
59 | that.get_width = function() { return c_width; }; | |
3b20e7a9 | 60 | |
d890e864 JM |
61 | that.set_height = function (val) { that.resize(c_width, val); }; |
62 | that.get_height = function() { return c_height; }; | |
58b4c536 | 63 | |
5210330a JM |
64 | that.set_prefer_js = function(val) { |
65 | if (val && c_forceCanvas) { | |
66 | Util.Warn("Preferring Javascript to Canvas ops is not supported"); | |
67 | return false; | |
68 | } | |
69 | conf.prefer_js = val; | |
70 | return true; | |
71 | }; | |
72 | ||
58b4c536 | 73 | |
f272267b | 74 | |
8db09746 JM |
75 | // |
76 | // Private functions | |
77 | // | |
f272267b | 78 | |
8db09746 JM |
79 | // Create the public API interface |
80 | function constructor() { | |
d890e864 | 81 | Util.Debug(">> Display.constructor"); |
f272267b | 82 | |
d890e864 | 83 | var c, func, imgTest, tval, i, curDat, curSave, |
005d9ee9 | 84 | has_imageData = false, UE = Util.Engine; |
8db09746 JM |
85 | |
86 | if (! conf.target) { throw("target must be set"); } | |
87 | ||
88 | if (typeof conf.target === 'string') { | |
e4671910 | 89 | throw("target must be a DOM element"); |
8db09746 JM |
90 | } |
91 | ||
92 | c = conf.target; | |
d93d3e09 | 93 | |
8db09746 | 94 | if (! c.getContext) { throw("no getContext method"); } |
d93d3e09 | 95 | |
d890e864 | 96 | if (! c_ctx) { c_ctx = c.getContext('2d'); } |
8db09746 | 97 | |
9b940131 | 98 | Util.Debug("User Agent: " + navigator.userAgent); |
005d9ee9 JM |
99 | if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); } |
100 | if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); } | |
455e4657 JM |
101 | if (UE.trident) { Util.Debug("Browser: trident " + UE.trident); } |
102 | if (UE.presto) { Util.Debug("Browser: presto " + UE.presto); } | |
005d9ee9 | 103 | |
8db09746 | 104 | that.clear(); |
d93d3e09 | 105 | |
d93d3e09 | 106 | /* |
48eed1ac JM |
107 | * Determine browser Canvas feature support |
108 | * and select fastest rendering methods | |
d93d3e09 JM |
109 | */ |
110 | tval = 0; | |
111 | try { | |
d890e864 | 112 | imgTest = c_ctx.getImageData(0, 0, 1,1); |
d93d3e09 JM |
113 | imgTest.data[0] = 123; |
114 | imgTest.data[3] = 255; | |
d890e864 JM |
115 | c_ctx.putImageData(imgTest, 0, 0); |
116 | tval = c_ctx.getImageData(0, 0, 1, 1).data[0]; | |
48eed1ac | 117 | if (tval === 123) { |
8db09746 | 118 | has_imageData = true; |
48eed1ac | 119 | } |
8db09746 | 120 | } catch (exc1) {} |
48eed1ac | 121 | |
8db09746 | 122 | if (has_imageData) { |
81e5adaf | 123 | Util.Info("Canvas supports imageData"); |
8db09746 | 124 | c_forceCanvas = false; |
d890e864 | 125 | if (c_ctx.createImageData) { |
d93d3e09 | 126 | // If it's there, it's faster |
81e5adaf | 127 | Util.Info("Using Canvas createImageData"); |
3b20e7a9 | 128 | conf.render_mode = "createImageData rendering"; |
d890e864 JM |
129 | c_imageData = imageDataCreate; |
130 | } else if (c_ctx.getImageData) { | |
3b20e7a9 | 131 | // I think this is mostly just Opera |
81e5adaf | 132 | Util.Info("Using Canvas getImageData"); |
3b20e7a9 | 133 | conf.render_mode = "getImageData rendering"; |
d890e864 | 134 | c_imageData = imageDataGet; |
d93d3e09 | 135 | } |
81e5adaf | 136 | Util.Info("Prefering javascript operations"); |
8db09746 JM |
137 | if (conf.prefer_js === null) { |
138 | conf.prefer_js = true; | |
139 | } | |
d890e864 JM |
140 | c_rgbxImage = rgbxImageData; |
141 | c_cmapImage = cmapImageData; | |
d93d3e09 | 142 | } else { |
81e5adaf | 143 | Util.Warn("Canvas lacks imageData, using fillRect (slow)"); |
3b20e7a9 | 144 | conf.render_mode = "fillRect rendering (slow)"; |
8db09746 JM |
145 | c_forceCanvas = true; |
146 | conf.prefer_js = false; | |
d890e864 JM |
147 | c_rgbxImage = rgbxImageFill; |
148 | c_cmapImage = cmapImageFill; | |
d93d3e09 | 149 | } |
532a9fd9 | 150 | |
cdb55d26 JM |
151 | if (UE.webkit && UE.webkit >= 534.7 && UE.webkit <= 534.9) { |
152 | // Workaround WebKit canvas rendering bug #46319 | |
153 | conf.render_mode += ", webkit bug workaround"; | |
154 | Util.Debug("Working around WebKit bug #46319"); | |
155 | c_webkit_bug = true; | |
156 | for (func in {"fillRect":1, "copyImage":1, "rgbxImage":1, | |
157 | "cmapImage":1, "blitStringImage":1}) { | |
158 | that[func] = (function() { | |
159 | var myfunc = that[func]; // Save original function | |
160 | //Util.Debug("Wrapping " + func); | |
161 | return function() { | |
162 | myfunc.apply(this, arguments); | |
163 | if (!c_flush_timer) { | |
d890e864 | 164 | c_flush_timer = setTimeout(flush, 100); |
cdb55d26 JM |
165 | } |
166 | }; | |
43cf7bd8 | 167 | }()); |
cdb55d26 JM |
168 | } |
169 | } | |
170 | ||
2c2b492c JM |
171 | /* |
172 | * Determine browser support for setting the cursor via data URI | |
173 | * scheme | |
174 | */ | |
175 | curDat = []; | |
8db09746 | 176 | for (i=0; i < 8 * 8 * 4; i += 1) { |
2c2b492c JM |
177 | curDat.push(255); |
178 | } | |
8171f4d8 JM |
179 | try { |
180 | curSave = c.style.cursor; | |
9a23006e | 181 | changeCursor(conf.target, curDat, curDat, 2, 2, 8, 8); |
8171f4d8 | 182 | if (c.style.cursor) { |
8db09746 JM |
183 | if (conf.cursor_uri === null) { |
184 | conf.cursor_uri = true; | |
185 | } | |
8171f4d8 JM |
186 | Util.Info("Data URI scheme cursor supported"); |
187 | } else { | |
8db09746 JM |
188 | if (conf.cursor_uri === null) { |
189 | conf.cursor_uri = false; | |
190 | } | |
8171f4d8 JM |
191 | Util.Warn("Data URI scheme cursor not supported"); |
192 | } | |
193 | c.style.cursor = curSave; | |
8db09746 | 194 | } catch (exc2) { |
8171f4d8 JM |
195 | Util.Error("Data URI scheme cursor test exception: " + exc2); |
196 | conf.cursor_uri = false; | |
2c2b492c | 197 | } |
2c2b492c | 198 | |
d890e864 | 199 | Util.Debug("<< Display.constructor"); |
8db09746 JM |
200 | return that ; |
201 | } | |
202 | ||
d890e864 | 203 | rescale = function(factor) { |
125d8bbb JM |
204 | var c, tp, x, y, |
205 | properties = ['transform', 'WebkitTransform', 'MozTransform', null]; | |
8db09746 JM |
206 | c = conf.target; |
207 | tp = properties.shift(); | |
208 | while (tp) { | |
209 | if (typeof c.style[tp] !== 'undefined') { | |
125d8bbb JM |
210 | break; |
211 | } | |
8db09746 | 212 | tp = properties.shift(); |
125d8bbb JM |
213 | } |
214 | ||
215 | if (tp === null) { | |
216 | Util.Debug("No scaling support"); | |
217 | return; | |
218 | } | |
219 | ||
1a2371fc JM |
220 | if (factor > 1.0) { |
221 | factor = 1.0; | |
222 | } else if (factor < 0.1) { | |
223 | factor = 0.1; | |
224 | } | |
225 | ||
8db09746 | 226 | if (conf.scale === factor) { |
d890e864 | 227 | //Util.Debug("Display already scaled to '" + factor + "'"); |
125d8bbb JM |
228 | return; |
229 | } | |
230 | ||
8db09746 | 231 | conf.scale = factor; |
125d8bbb JM |
232 | x = c.width - c.width * factor; |
233 | y = c.height - c.height * factor; | |
8db09746 JM |
234 | c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)"; |
235 | }; | |
236 | ||
d890e864 JM |
237 | // Force canvas redraw (for webkit bug #46319 workaround) |
238 | flush = function() { | |
cdb55d26 JM |
239 | var old_val; |
240 | //Util.Debug(">> flush"); | |
cdb55d26 | 241 | old_val = conf.target.style.marginRight; |
c1d008f1 | 242 | conf.target.style.marginRight = "1px"; |
cdb55d26 JM |
243 | c_flush_timer = null; |
244 | setTimeout(function () { | |
245 | conf.target.style.marginRight = old_val; | |
246 | }, 1); | |
247 | }; | |
248 | ||
d890e864 | 249 | setFillColor = function(color) { |
8db09746 JM |
250 | var rgb, newStyle; |
251 | if (conf.true_color) { | |
252 | rgb = color; | |
253 | } else { | |
254 | rgb = conf.colourMap[color[0]]; | |
255 | } | |
65bca0c9 | 256 | newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; |
8db09746 | 257 | if (newStyle !== c_prevStyle) { |
d890e864 | 258 | c_ctx.fillStyle = newStyle; |
8db09746 JM |
259 | c_prevStyle = newStyle; |
260 | } | |
261 | }; | |
8db09746 | 262 | |
d890e864 JM |
263 | |
264 | // | |
265 | // Public API interface functions | |
266 | // | |
267 | ||
268 | that.resize = function(width, height) { | |
269 | var c = conf.target; | |
270 | ||
271 | c_prevStyle = ""; | |
272 | ||
273 | c.width = width; | |
274 | c.height = height; | |
275 | ||
276 | c_width = c.offsetWidth; | |
277 | c_height = c.offsetHeight; | |
278 | ||
279 | rescale(conf.scale); | |
280 | }; | |
281 | ||
282 | that.clear = function() { | |
283 | ||
284 | if (conf.logo) { | |
285 | that.resize(conf.logo.width, conf.logo.height); | |
286 | that.blitStringImage(conf.logo.data, 0, 0); | |
287 | } else { | |
288 | that.resize(640, 20); | |
289 | c_ctx.clearRect(0, 0, c_width, c_height); | |
290 | } | |
291 | ||
292 | // No benefit over default ("source-over") in Chrome and firefox | |
293 | //c_ctx.globalCompositeOperation = "copy"; | |
294 | }; | |
295 | ||
65bca0c9 | 296 | that.fillRect = function(x, y, width, height, color) { |
d890e864 JM |
297 | setFillColor(color); |
298 | c_ctx.fillRect(x, y, width, height); | |
8db09746 | 299 | }; |
8db09746 JM |
300 | |
301 | that.copyImage = function(old_x, old_y, new_x, new_y, width, height) { | |
d890e864 | 302 | c_ctx.drawImage(conf.target, old_x, old_y, width, height, |
8db09746 JM |
303 | new_x, new_y, width, height); |
304 | }; | |
532a9fd9 | 305 | |
3875f847 JM |
306 | /* |
307 | * Tile rendering functions optimized for rendering engines. | |
308 | * | |
309 | * - In Chrome/webkit, Javascript image data array manipulations are | |
310 | * faster than direct Canvas fillStyle, fillRect rendering. In | |
311 | * gecko, Javascript array handling is much slower. | |
312 | */ | |
8db09746 | 313 | that.getTile = function(x, y, width, height, color) { |
d3796c14 | 314 | var img, data = [], rgb, red, green, blue, i; |
c4164bda | 315 | img = {'x': x, 'y': y, 'width': width, 'height': height, |
65bca0c9 | 316 | 'data': data}; |
8db09746 | 317 | if (conf.prefer_js) { |
8db09746 | 318 | if (conf.true_color) { |
d41c33e4 JM |
319 | rgb = color; |
320 | } else { | |
8db09746 | 321 | rgb = conf.colourMap[color[0]]; |
d41c33e4 JM |
322 | } |
323 | red = rgb[0]; | |
324 | green = rgb[1]; | |
325 | blue = rgb[2]; | |
65bca0c9 JM |
326 | for (i = 0; i < (width * height * 4); i+=4) { |
327 | data[i ] = red; | |
328 | data[i + 1] = green; | |
329 | data[i + 2] = blue; | |
330 | } | |
3875f847 | 331 | } else { |
65bca0c9 | 332 | that.fillRect(x, y, width, height, color); |
3875f847 JM |
333 | } |
334 | return img; | |
8db09746 | 335 | }; |
3875f847 | 336 | |
8db09746 | 337 | that.setSubTile = function(img, x, y, w, h, color) { |
65bca0c9 | 338 | var data, p, rgb, red, green, blue, width, j, i, xend, yend; |
8db09746 | 339 | if (conf.prefer_js) { |
97763d0e | 340 | data = img.data; |
c4164bda | 341 | width = img.width; |
8db09746 | 342 | if (conf.true_color) { |
d41c33e4 JM |
343 | rgb = color; |
344 | } else { | |
8db09746 | 345 | rgb = conf.colourMap[color[0]]; |
d41c33e4 JM |
346 | } |
347 | red = rgb[0]; | |
348 | green = rgb[1]; | |
349 | blue = rgb[2]; | |
65bca0c9 JM |
350 | xend = x + w; |
351 | yend = y + h; | |
352 | for (j = y; j < yend; j += 1) { | |
353 | for (i = x; i < xend; i += 1) { | |
354 | p = (i + (j * width) ) * 4; | |
355 | data[p ] = red; | |
97763d0e JM |
356 | data[p + 1] = green; |
357 | data[p + 2] = blue; | |
3875f847 JM |
358 | } |
359 | } | |
360 | } else { | |
65bca0c9 | 361 | that.fillRect(img.x + x, img.y + y, w, h, color); |
3875f847 | 362 | } |
8db09746 | 363 | }; |
3875f847 | 364 | |
8db09746 JM |
365 | that.putTile = function(img) { |
366 | if (conf.prefer_js) { | |
d890e864 | 367 | c_rgbxImage(img.x, img.y, img.width, img.height, img.data, 0); |
3875f847 | 368 | } |
d3796c14 | 369 | // else: No-op, under gecko already done by setSubTile |
8db09746 | 370 | }; |
3875f847 | 371 | |
d890e864 JM |
372 | imageDataGet = function(width, height) { |
373 | return c_ctx.getImageData(0, 0, width, height); | |
8db09746 | 374 | }; |
d890e864 JM |
375 | imageDataCreate = function(width, height) { |
376 | return c_ctx.createImageData(width, height); | |
8db09746 | 377 | }; |
3875f847 | 378 | |
d890e864 | 379 | rgbxImageData = function(x, y, width, height, arr, offset) { |
7f4f41b0 | 380 | var img, i, j, data; |
d890e864 | 381 | img = c_imageData(width, height); |
97763d0e | 382 | data = img.data; |
d41c33e4 | 383 | for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { |
d890e864 | 384 | data[i ] = arr[j ]; |
7f4f41b0 JM |
385 | data[i + 1] = arr[j + 1]; |
386 | data[i + 2] = arr[j + 2]; | |
387 | data[i + 3] = 255; // Set Alpha | |
64ab5c4d | 388 | } |
d890e864 | 389 | c_ctx.putImageData(img, x, y); |
8db09746 | 390 | }; |
64ab5c4d | 391 | |
d93d3e09 | 392 | // really slow fallback if we don't have imageData |
d890e864 | 393 | rgbxImageFill = function(x, y, width, height, arr, offset) { |
a7a89626 | 394 | var i, j, sx = 0, sy = 0; |
d93d3e09 | 395 | for (i=0, j=offset; i < (width * height); i+=1, j+=4) { |
d890e864 | 396 | that.fillRect(x+sx, y+sy, 1, 1, [arr[j], arr[j+1], arr[j+2]]); |
d93d3e09 JM |
397 | sx += 1; |
398 | if ((sx % width) === 0) { | |
399 | sx = 0; | |
400 | sy += 1; | |
401 | } | |
402 | } | |
8db09746 | 403 | }; |
d93d3e09 | 404 | |
d890e864 | 405 | cmapImageData = function(x, y, width, height, arr, offset) { |
15046f00 | 406 | var img, i, j, data, rgb, cmap; |
d890e864 | 407 | img = c_imageData(width, height); |
d41c33e4 | 408 | data = img.data; |
8db09746 | 409 | cmap = conf.colourMap; |
d93d3e09 | 410 | for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) { |
d41c33e4 | 411 | rgb = cmap[arr[j]]; |
d890e864 | 412 | data[i ] = rgb[0]; |
d41c33e4 JM |
413 | data[i + 1] = rgb[1]; |
414 | data[i + 2] = rgb[2]; | |
415 | data[i + 3] = 255; // Set Alpha | |
416 | } | |
d890e864 | 417 | c_ctx.putImageData(img, x, y); |
8db09746 | 418 | }; |
d41c33e4 | 419 | |
d890e864 | 420 | cmapImageFill = function(x, y, width, height, arr, offset) { |
a7a89626 | 421 | var i, j, sx = 0, sy = 0, cmap; |
8db09746 | 422 | cmap = conf.colourMap; |
d93d3e09 | 423 | for (i=0, j=offset; i < (width * height); i+=1, j+=1) { |
65bca0c9 | 424 | that.fillRect(x+sx, y+sy, 1, 1, [arr[j]]); |
d93d3e09 JM |
425 | sx += 1; |
426 | if ((sx % width) === 0) { | |
427 | sx = 0; | |
428 | sy += 1; | |
429 | } | |
430 | } | |
8db09746 | 431 | }; |
d93d3e09 JM |
432 | |
433 | ||
8db09746 JM |
434 | that.blitImage = function(x, y, width, height, arr, offset) { |
435 | if (conf.true_color) { | |
d890e864 | 436 | c_rgbxImage(x, y, width, height, arr, offset); |
d41c33e4 | 437 | } else { |
d890e864 | 438 | c_cmapImage(x, y, width, height, arr, offset); |
d41c33e4 | 439 | } |
8db09746 | 440 | }; |
d9cbdc7d | 441 | |
8db09746 | 442 | that.blitStringImage = function(str, x, y) { |
d93d3e09 | 443 | var img = new Image(); |
d890e864 | 444 | img.onload = function () { c_ctx.drawImage(img, x, y); }; |
d93d3e09 | 445 | img.src = str; |
8db09746 | 446 | }; |
f272267b | 447 | |
8db09746 | 448 | that.changeCursor = function(pixels, mask, hotx, hoty, w, h) { |
8db09746 | 449 | if (conf.cursor_uri === false) { |
da6dd893 | 450 | Util.Warn("changeCursor called but no cursor data URI support"); |
2c2b492c JM |
451 | return; |
452 | } | |
453 | ||
9a23006e JM |
454 | if (conf.true_color) { |
455 | changeCursor(conf.target, pixels, mask, hotx, hoty, w, h); | |
456 | } else { | |
457 | changeCursor(conf.target, pixels, mask, hotx, hoty, w, h, conf.colourMap); | |
458 | } | |
43cf7bd8 | 459 | }; |
9a23006e | 460 | |
d3796c14 | 461 | that.defaultCursor = function() { |
d890e864 | 462 | conf.target.style.cursor = "default"; |
d3796c14 JM |
463 | }; |
464 | ||
9a23006e JM |
465 | return constructor(); // Return the public API interface |
466 | ||
d890e864 | 467 | } // End of Display() |
9a23006e JM |
468 | |
469 | ||
470 | /* Set CSS cursor property using data URI encoded cursor file */ | |
471 | function changeCursor(target, pixels, mask, hotx, hoty, w, h, cmap) { | |
d890e864 | 472 | "use strict"; |
9a23006e JM |
473 | var cur = [], rgb, IHDRsz, RGBsz, ANDsz, XORsz, url, idx, alpha, x, y; |
474 | //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h); | |
475 | ||
67b4e987 JM |
476 | // Push multi-byte little-endian values |
477 | cur.push16le = function (num) { | |
478 | this.push((num ) & 0xFF, | |
479 | (num >> 8) & 0xFF ); | |
480 | }; | |
481 | cur.push32le = function (num) { | |
482 | this.push((num ) & 0xFF, | |
483 | (num >> 8) & 0xFF, | |
484 | (num >> 16) & 0xFF, | |
485 | (num >> 24) & 0xFF ); | |
486 | }; | |
487 | ||
2c2b492c | 488 | IHDRsz = 40; |
9a23006e | 489 | RGBsz = w * h * 4; |
2c2b492c | 490 | XORsz = Math.ceil( (w * h) / 8.0 ); |
9a23006e | 491 | ANDsz = Math.ceil( (w * h) / 8.0 ); |
2c2b492c JM |
492 | |
493 | // Main header | |
9a23006e JM |
494 | cur.push16le(0); // 0: Reserved |
495 | cur.push16le(2); // 2: .CUR type | |
496 | cur.push16le(1); // 4: Number of images, 1 for non-animated ico | |
497 | ||
498 | // Cursor #1 header (ICONDIRENTRY) | |
499 | cur.push(w); // 6: width | |
500 | cur.push(h); // 7: height | |
501 | cur.push(0); // 8: colors, 0 -> true-color | |
502 | cur.push(0); // 9: reserved | |
503 | cur.push16le(hotx); // 10: hotspot x coordinate | |
504 | cur.push16le(hoty); // 12: hotspot y coordinate | |
505 | cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz); | |
506 | // 14: cursor data byte size | |
507 | cur.push32le(22); // 18: offset of cursor data in the file | |
508 | ||
509 | ||
510 | // Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO) | |
511 | cur.push32le(IHDRsz); // 22: Infoheader size | |
512 | cur.push32le(w); // 26: Cursor width | |
513 | cur.push32le(h*2); // 30: XOR+AND height | |
514 | cur.push16le(1); // 34: number of planes | |
515 | cur.push16le(32); // 36: bits per pixel | |
516 | cur.push32le(0); // 38: Type of compression | |
517 | ||
518 | cur.push32le(XORsz + ANDsz); // 43: Size of Image | |
519 | // Gimp leaves this as 0 | |
520 | ||
521 | cur.push32le(0); // 46: reserved | |
522 | cur.push32le(0); // 50: reserved | |
523 | cur.push32le(0); // 54: reserved | |
524 | cur.push32le(0); // 58: reserved | |
525 | ||
526 | // 62: color data (RGBQUAD icColors[]) | |
8db09746 JM |
527 | for (y = h-1; y >= 0; y -= 1) { |
528 | for (x = 0; x < w; x += 1) { | |
2c2b492c JM |
529 | idx = y * Math.ceil(w / 8) + Math.floor(x/8); |
530 | alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0; | |
531 | ||
9a23006e | 532 | if (cmap) { |
2c2b492c JM |
533 | idx = (w * y) + x; |
534 | rgb = cmap[pixels[idx]]; | |
535 | cur.push(rgb[2]); // blue | |
536 | cur.push(rgb[1]); // green | |
537 | cur.push(rgb[0]); // red | |
538 | cur.push(alpha); // alpha | |
9a23006e JM |
539 | } else { |
540 | idx = ((w * y) + x) * 4; | |
541 | cur.push(pixels[idx + 2]); // blue | |
542 | cur.push(pixels[idx + 1]); // green | |
d890e864 | 543 | cur.push(pixels[idx ]); // red |
9a23006e | 544 | cur.push(alpha); // alpha |
2c2b492c JM |
545 | } |
546 | } | |
547 | } | |
548 | ||
9a23006e JM |
549 | // XOR/bitmask data (BYTE icXOR[]) |
550 | // (ignored, just needs to be right size) | |
551 | for (y = 0; y < h; y += 1) { | |
552 | for (x = 0; x < Math.ceil(w / 8); x += 1) { | |
553 | cur.push(0x00); | |
554 | } | |
555 | } | |
556 | ||
557 | // AND/bitmask data (BYTE icAND[]) | |
558 | // (ignored, just needs to be right size) | |
8db09746 JM |
559 | for (y = 0; y < h; y += 1) { |
560 | for (x = 0; x < Math.ceil(w / 8); x += 1) { | |
2c2b492c JM |
561 | cur.push(0x00); |
562 | } | |
563 | } | |
564 | ||
565 | url = "data:image/x-icon;base64," + Base64.encode(cur); | |
9a23006e | 566 | target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default"; |
da6dd893 | 567 | //Util.Debug("<< changeCursor, cur.length: " + cur.length); |
43cf7bd8 | 568 | } |