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