]>
Commit | Line | Data |
---|---|---|
c4164bda JM |
1 | /* |
2 | * noVNC: HTML5 VNC client | |
af6b17ce | 3 | * Copyright (C) 2010 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 | |
15046f00 | 9 | "use strict"; |
8db09746 JM |
10 | /*jslint browser: true, white: false, bitwise: false */ |
11 | /*global window, Util, Base64 */ | |
c4164bda | 12 | |
8db09746 | 13 | function Canvas(conf) { |
d93d3e09 | 14 | |
8db09746 JM |
15 | conf = conf || {}; // Configuration |
16 | var that = {}, // Public API interface | |
c8460b03 | 17 | |
8db09746 JM |
18 | // Pre-declare functions used before definitions (jslint)jslint |
19 | setFillColor, fillRect, | |
97763d0e | 20 | |
8db09746 JM |
21 | // Private Canvas namespace variables |
22 | c_forceCanvas = false, | |
d41c33e4 | 23 | |
8db09746 JM |
24 | c_width = 0, |
25 | c_height = 0, | |
c8460b03 | 26 | |
8db09746 | 27 | c_prevStyle = "", |
48ebcdb1 | 28 | |
8db09746 JM |
29 | c_keyPress = null, |
30 | c_mouseButton = null, | |
31 | c_mouseMove = null; | |
e2e7c224 | 32 | |
f272267b | 33 | |
8db09746 JM |
34 | // Capability settings, default can be overridden |
35 | Util.conf_default(conf, that, 'prefer_js', null); | |
36 | Util.conf_default(conf, that, 'cursor_uri', null); | |
f272267b | 37 | |
8db09746 JM |
38 | // Configuration settings |
39 | Util.conf_default(conf, that, 'target', null); | |
b7155950 | 40 | // Area that traps keyboard input |
41 | Util.conf_default(conf, that, 'focusContainer', document); | |
8db09746 JM |
42 | Util.conf_default(conf, that, 'true_color', true); |
43 | Util.conf_default(conf, that, 'focused', true); | |
44 | Util.conf_default(conf, that, 'colourMap', []); | |
45 | Util.conf_default(conf, that, 'scale', 1); | |
8cf20615 | 46 | |
8db09746 JM |
47 | // Override some specific getters/setters |
48 | that.set_prefer_js = function(val) { | |
49 | if (val && c_forceCanvas) { | |
50 | Util.Warn("Preferring Javascript to Canvas ops is not supported"); | |
51 | return false; | |
e2e7c224 | 52 | } |
8db09746 JM |
53 | conf.prefer_js = val; |
54 | return true; | |
55 | }; | |
8cf20615 | 56 | |
8db09746 JM |
57 | that.get_colourMap = function(idx) { |
58 | if (typeof idx === 'undefined') { | |
59 | return conf.colourMap; | |
60 | } else { | |
61 | return conf.colourMap[idx]; | |
e2e7c224 | 62 | } |
8db09746 | 63 | }; |
e2e7c224 | 64 | |
8db09746 JM |
65 | that.set_colourMap = function(val, idx) { |
66 | if (typeof idx === 'undefined') { | |
67 | conf.colourMap = val; | |
68 | } else { | |
69 | conf.colourMap[idx] = val; | |
e2e7c224 | 70 | } |
8db09746 JM |
71 | }; |
72 | ||
73 | // Add some other getters/setters | |
74 | that.get_width = function() { | |
75 | return c_width; | |
76 | }; | |
77 | that.get_height = function() { | |
78 | return c_height; | |
79 | }; | |
f272267b | 80 | |
f272267b | 81 | |
f272267b | 82 | |
8db09746 JM |
83 | // |
84 | // Private functions | |
85 | // | |
f272267b | 86 | |
8db09746 JM |
87 | // Create the public API interface |
88 | function constructor() { | |
81e5adaf | 89 | Util.Debug(">> Canvas.init"); |
f272267b | 90 | |
8db09746 | 91 | var c, ctx, imgTest, tval, i, curDat, curSave, |
005d9ee9 | 92 | has_imageData = false, UE = Util.Engine; |
8db09746 JM |
93 | |
94 | if (! conf.target) { throw("target must be set"); } | |
95 | ||
96 | if (typeof conf.target === 'string') { | |
97 | conf.target = window.$(conf.target); | |
98 | } | |
99 | ||
100 | c = conf.target; | |
d93d3e09 | 101 | |
8db09746 | 102 | if (! c.getContext) { throw("no getContext method"); } |
d93d3e09 | 103 | |
8db09746 JM |
104 | if (! conf.ctx) { conf.ctx = c.getContext('2d'); } |
105 | ctx = conf.ctx; | |
106 | ||
005d9ee9 JM |
107 | if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); } |
108 | if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); } | |
109 | if (UE.trident) { Util.Debug("Browser: webkit " + UE.trident); } | |
110 | if (UE.presto) { Util.Debug("Browser: webkit " + UE.presto); } | |
111 | ||
8db09746 | 112 | that.clear(); |
d93d3e09 | 113 | |
d93d3e09 | 114 | /* |
48eed1ac JM |
115 | * Determine browser Canvas feature support |
116 | * and select fastest rendering methods | |
d93d3e09 JM |
117 | */ |
118 | tval = 0; | |
119 | try { | |
8db09746 | 120 | imgTest = ctx.getImageData(0, 0, 1,1); |
d93d3e09 JM |
121 | imgTest.data[0] = 123; |
122 | imgTest.data[3] = 255; | |
8db09746 JM |
123 | ctx.putImageData(imgTest, 0, 0); |
124 | tval = ctx.getImageData(0, 0, 1, 1).data[0]; | |
48eed1ac | 125 | if (tval === 123) { |
8db09746 | 126 | has_imageData = true; |
48eed1ac | 127 | } |
8db09746 | 128 | } catch (exc1) {} |
48eed1ac | 129 | |
8db09746 | 130 | if (has_imageData) { |
81e5adaf | 131 | Util.Info("Canvas supports imageData"); |
8db09746 JM |
132 | c_forceCanvas = false; |
133 | if (ctx.createImageData) { | |
d93d3e09 | 134 | // If it's there, it's faster |
81e5adaf | 135 | Util.Info("Using Canvas createImageData"); |
8db09746 JM |
136 | that.imageData = that.imageDataCreate; |
137 | } else if (ctx.getImageData) { | |
81e5adaf | 138 | Util.Info("Using Canvas getImageData"); |
8db09746 | 139 | that.imageData = that.imageDataGet; |
d93d3e09 | 140 | } |
81e5adaf | 141 | Util.Info("Prefering javascript operations"); |
8db09746 JM |
142 | if (conf.prefer_js === null) { |
143 | conf.prefer_js = true; | |
144 | } | |
145 | that.rgbxImage = that.rgbxImageData; | |
146 | that.cmapImage = that.cmapImageData; | |
d93d3e09 | 147 | } else { |
81e5adaf | 148 | Util.Warn("Canvas lacks imageData, using fillRect (slow)"); |
8db09746 JM |
149 | c_forceCanvas = true; |
150 | conf.prefer_js = false; | |
151 | that.rgbxImage = that.rgbxImageFill; | |
152 | that.cmapImage = that.cmapImageFill; | |
d93d3e09 | 153 | } |
532a9fd9 | 154 | |
2c2b492c JM |
155 | /* |
156 | * Determine browser support for setting the cursor via data URI | |
157 | * scheme | |
158 | */ | |
159 | curDat = []; | |
8db09746 | 160 | for (i=0; i < 8 * 8 * 4; i += 1) { |
2c2b492c JM |
161 | curDat.push(255); |
162 | } | |
8171f4d8 JM |
163 | try { |
164 | curSave = c.style.cursor; | |
8db09746 | 165 | that.changeCursor(curDat, curDat, 2, 2, 8, 8); |
8171f4d8 | 166 | if (c.style.cursor) { |
8db09746 JM |
167 | if (conf.cursor_uri === null) { |
168 | conf.cursor_uri = true; | |
169 | } | |
8171f4d8 JM |
170 | Util.Info("Data URI scheme cursor supported"); |
171 | } else { | |
8db09746 JM |
172 | if (conf.cursor_uri === null) { |
173 | conf.cursor_uri = false; | |
174 | } | |
8171f4d8 JM |
175 | Util.Warn("Data URI scheme cursor not supported"); |
176 | } | |
177 | c.style.cursor = curSave; | |
8db09746 | 178 | } catch (exc2) { |
8171f4d8 JM |
179 | Util.Error("Data URI scheme cursor test exception: " + exc2); |
180 | conf.cursor_uri = false; | |
2c2b492c | 181 | } |
2c2b492c | 182 | |
8db09746 | 183 | conf.focused = true; |
d93d3e09 | 184 | |
81e5adaf | 185 | Util.Debug("<< Canvas.init"); |
8db09746 JM |
186 | return that ; |
187 | } | |
188 | ||
189 | /* Translate DOM key event to keysym value */ | |
190 | function getKeysym(e) { | |
191 | var evt, keysym; | |
192 | evt = (e ? e : window.event); | |
193 | ||
194 | /* Remap modifier and special keys */ | |
195 | switch ( evt.keyCode ) { | |
196 | case 8 : keysym = 0xFF08; break; // BACKSPACE | |
197 | case 9 : keysym = 0xFF09; break; // TAB | |
198 | case 13 : keysym = 0xFF0D; break; // ENTER | |
199 | case 27 : keysym = 0xFF1B; break; // ESCAPE | |
200 | case 45 : keysym = 0xFF63; break; // INSERT | |
201 | case 46 : keysym = 0xFFFF; break; // DELETE | |
202 | case 36 : keysym = 0xFF50; break; // HOME | |
203 | case 35 : keysym = 0xFF57; break; // END | |
204 | case 33 : keysym = 0xFF55; break; // PAGE_UP | |
205 | case 34 : keysym = 0xFF56; break; // PAGE_DOWN | |
206 | case 37 : keysym = 0xFF51; break; // LEFT | |
207 | case 38 : keysym = 0xFF52; break; // UP | |
208 | case 39 : keysym = 0xFF53; break; // RIGHT | |
209 | case 40 : keysym = 0xFF54; break; // DOWN | |
210 | case 112 : keysym = 0xFFBE; break; // F1 | |
211 | case 113 : keysym = 0xFFBF; break; // F2 | |
212 | case 114 : keysym = 0xFFC0; break; // F3 | |
213 | case 115 : keysym = 0xFFC1; break; // F4 | |
214 | case 116 : keysym = 0xFFC2; break; // F5 | |
215 | case 117 : keysym = 0xFFC3; break; // F6 | |
216 | case 118 : keysym = 0xFFC4; break; // F7 | |
217 | case 119 : keysym = 0xFFC5; break; // F8 | |
218 | case 120 : keysym = 0xFFC6; break; // F9 | |
219 | case 121 : keysym = 0xFFC7; break; // F10 | |
220 | case 122 : keysym = 0xFFC8; break; // F11 | |
221 | case 123 : keysym = 0xFFC9; break; // F12 | |
222 | case 16 : keysym = 0xFFE1; break; // SHIFT | |
223 | case 17 : keysym = 0xFFE3; break; // CONTROL | |
224 | //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option) | |
225 | case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command) | |
226 | default : keysym = evt.keyCode; break; | |
227 | } | |
228 | ||
229 | /* Remap symbols */ | |
230 | switch (keysym) { | |
231 | case 186 : keysym = 59; break; // ; (IE) | |
232 | case 187 : keysym = 61; break; // = (IE) | |
233 | case 188 : keysym = 44; break; // , (Mozilla, IE) | |
234 | case 109 : // - (Mozilla) | |
235 | if (Util.Engine.gecko) { | |
236 | keysym = 45; } | |
237 | break; | |
238 | case 189 : keysym = 45; break; // - (IE) | |
239 | case 190 : keysym = 46; break; // . (Mozilla, IE) | |
240 | case 191 : keysym = 47; break; // / (Mozilla, IE) | |
241 | case 192 : keysym = 96; break; // ` (Mozilla, IE) | |
242 | case 219 : keysym = 91; break; // [ (Mozilla, IE) | |
243 | case 220 : keysym = 92; break; // \ (Mozilla, IE) | |
244 | case 221 : keysym = 93; break; // ] (Mozilla, IE) | |
245 | case 222 : keysym = 39; break; // ' (Mozilla, IE) | |
246 | } | |
d93d3e09 | 247 | |
8db09746 JM |
248 | /* Remap shifted and unshifted keys */ |
249 | if (!!evt.shiftKey) { | |
250 | switch (keysym) { | |
251 | case 48 : keysym = 41 ; break; // ) (shifted 0) | |
252 | case 49 : keysym = 33 ; break; // ! (shifted 1) | |
253 | case 50 : keysym = 64 ; break; // @ (shifted 2) | |
254 | case 51 : keysym = 35 ; break; // # (shifted 3) | |
255 | case 52 : keysym = 36 ; break; // $ (shifted 4) | |
256 | case 53 : keysym = 37 ; break; // % (shifted 5) | |
257 | case 54 : keysym = 94 ; break; // ^ (shifted 6) | |
258 | case 55 : keysym = 38 ; break; // & (shifted 7) | |
259 | case 56 : keysym = 42 ; break; // * (shifted 8) | |
260 | case 57 : keysym = 40 ; break; // ( (shifted 9) | |
d93d3e09 | 261 | |
8db09746 JM |
262 | case 59 : keysym = 58 ; break; // : (shifted `) |
263 | case 61 : keysym = 43 ; break; // + (shifted ;) | |
264 | case 44 : keysym = 60 ; break; // < (shifted ,) | |
265 | case 45 : keysym = 95 ; break; // _ (shifted -) | |
266 | case 46 : keysym = 62 ; break; // > (shifted .) | |
267 | case 47 : keysym = 63 ; break; // ? (shifted /) | |
268 | case 96 : keysym = 126; break; // ~ (shifted `) | |
269 | case 91 : keysym = 123; break; // { (shifted [) | |
270 | case 92 : keysym = 124; break; // | (shifted \) | |
271 | case 93 : keysym = 125; break; // } (shifted ]) | |
272 | case 39 : keysym = 34 ; break; // " (shifted ') | |
273 | } | |
274 | } else if ((keysym >= 65) && (keysym <=90)) { | |
275 | /* Remap unshifted A-Z */ | |
276 | keysym += 32; | |
277 | } | |
d93d3e09 | 278 | |
8db09746 JM |
279 | return keysym; |
280 | } | |
64ab5c4d | 281 | |
8db09746 JM |
282 | function onMouseButton(e, down) { |
283 | var evt, pos, bmask; | |
284 | if (! conf.focused) { | |
285 | return true; | |
286 | } | |
287 | evt = (e ? e : window.event); | |
288 | pos = Util.getEventPosition(e, conf.target, conf.scale); | |
289 | bmask = 1 << evt.button; | |
290 | //Util.Debug('mouse ' + pos.x + "," + pos.y + " down: " + down + " bmask: " + bmask); | |
291 | if (c_mouseButton) { | |
292 | c_mouseButton(pos.x, pos.y, down, bmask); | |
293 | } | |
294 | Util.stopEvent(e); | |
295 | return false; | |
296 | } | |
f272267b | 297 | |
8db09746 JM |
298 | function onMouseDown(e) { |
299 | onMouseButton(e, 1); | |
300 | } | |
f272267b | 301 | |
8db09746 JM |
302 | function onMouseUp(e) { |
303 | onMouseButton(e, 0); | |
304 | } | |
305 | ||
306 | function onMouseWheel(e) { | |
307 | var evt, pos, bmask, wheelData; | |
308 | evt = (e ? e : window.event); | |
309 | pos = Util.getEventPosition(e, conf.target, conf.scale); | |
310 | wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40; | |
311 | if (wheelData > 0) { | |
312 | bmask = 1 << 3; | |
313 | } else { | |
314 | bmask = 1 << 4; | |
315 | } | |
316 | //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y); | |
317 | if (c_mouseButton) { | |
318 | c_mouseButton(pos.x, pos.y, 1, bmask); | |
319 | c_mouseButton(pos.x, pos.y, 0, bmask); | |
320 | } | |
321 | Util.stopEvent(e); | |
322 | return false; | |
323 | } | |
324 | ||
325 | function onMouseMove(e) { | |
326 | var evt, pos; | |
327 | evt = (e ? e : window.event); | |
328 | pos = Util.getEventPosition(e, conf.target, conf.scale); | |
329 | //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y); | |
330 | if (c_mouseMove) { | |
331 | c_mouseMove(pos.x, pos.y); | |
332 | } | |
333 | } | |
532a9fd9 | 334 | |
8db09746 JM |
335 | function onKeyDown(e) { |
336 | //Util.Debug("keydown: " + getKeysym(e)); | |
337 | if (! conf.focused) { | |
338 | return true; | |
339 | } | |
340 | if (c_keyPress) { | |
341 | c_keyPress(getKeysym(e), 1); | |
342 | } | |
343 | Util.stopEvent(e); | |
344 | return false; | |
345 | } | |
4b4496ad | 346 | |
8db09746 JM |
347 | function onKeyUp(e) { |
348 | //Util.Debug("keyup: " + getKeysym(e)); | |
349 | if (! conf.focused) { | |
350 | return true; | |
351 | } | |
352 | if (c_keyPress) { | |
353 | c_keyPress(getKeysym(e), 0); | |
354 | } | |
355 | Util.stopEvent(e); | |
356 | return false; | |
357 | } | |
d93d3e09 | 358 | |
8db09746 JM |
359 | function onMouseDisable(e) { |
360 | var evt, pos; | |
361 | if (! conf.focused) { | |
362 | return true; | |
363 | } | |
364 | evt = (e ? e : window.event); | |
3cc74720 | 365 | pos = Util.getEventPosition(e, conf.target, conf.scale); |
8db09746 | 366 | /* Stop propagation if inside canvas area */ |
3cc74720 JM |
367 | if ((pos.x >= 0) && (pos.y >= 0) && |
368 | (pos.x < c_width) && (pos.y < c_height)) { | |
8db09746 JM |
369 | //Util.Debug("mouse event disabled"); |
370 | Util.stopEvent(e); | |
371 | return false; | |
d93d3e09 | 372 | } |
8db09746 JM |
373 | //Util.Debug("mouse event not disabled"); |
374 | return true; | |
375 | } | |
d93d3e09 | 376 | |
8db09746 JM |
377 | // |
378 | // Public API interface functions | |
379 | // | |
380 | ||
381 | that.getContext = function () { | |
382 | return conf.ctx; | |
383 | }; | |
384 | ||
385 | that.start = function(keyPressFunc, mouseButtonFunc, mouseMoveFunc) { | |
386 | var c; | |
387 | Util.Debug(">> Canvas.start"); | |
d93d3e09 | 388 | |
8db09746 JM |
389 | c = conf.target; |
390 | c_keyPress = keyPressFunc || null; | |
391 | c_mouseButton = mouseButtonFunc || null; | |
392 | c_mouseMove = mouseMoveFunc || null; | |
125d8bbb | 393 | |
b7155950 | 394 | Util.addEvent(conf.focusContainer, 'keydown', onKeyDown); |
395 | Util.addEvent(conf.focusContainer, 'keyup', onKeyUp); | |
8db09746 JM |
396 | Util.addEvent(c, 'mousedown', onMouseDown); |
397 | Util.addEvent(c, 'mouseup', onMouseUp); | |
398 | Util.addEvent(c, 'mousemove', onMouseMove); | |
399 | Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', | |
400 | onMouseWheel); | |
401 | ||
402 | /* Work around right and middle click browser behaviors */ | |
b7155950 | 403 | Util.addEvent(conf.focusContainer, 'click', onMouseDisable); |
404 | Util.addEvent(conf.focusContainer.body, 'contextmenu', onMouseDisable); | |
8db09746 JM |
405 | |
406 | Util.Debug("<< Canvas.start"); | |
407 | }; | |
125d8bbb | 408 | |
8db09746 | 409 | that.rescale = function(factor) { |
125d8bbb JM |
410 | var c, tp, x, y, |
411 | properties = ['transform', 'WebkitTransform', 'MozTransform', null]; | |
8db09746 JM |
412 | c = conf.target; |
413 | tp = properties.shift(); | |
414 | while (tp) { | |
415 | if (typeof c.style[tp] !== 'undefined') { | |
125d8bbb JM |
416 | break; |
417 | } | |
8db09746 | 418 | tp = properties.shift(); |
125d8bbb JM |
419 | } |
420 | ||
421 | if (tp === null) { | |
422 | Util.Debug("No scaling support"); | |
423 | return; | |
424 | } | |
425 | ||
8db09746 | 426 | if (conf.scale === factor) { |
125d8bbb JM |
427 | //Util.Debug("Canvas already scaled to '" + factor + "'"); |
428 | return; | |
429 | } | |
430 | ||
8db09746 | 431 | conf.scale = factor; |
125d8bbb JM |
432 | x = c.width - c.width * factor; |
433 | y = c.height - c.height * factor; | |
8db09746 JM |
434 | c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)"; |
435 | }; | |
436 | ||
437 | that.resize = function(width, height, true_color) { | |
438 | var c = conf.target; | |
439 | ||
440 | if (typeof true_color !== "undefined") { | |
441 | conf.true_color = true_color; | |
442 | } | |
443 | ||
444 | c.width = width; | |
445 | c.height = height; | |
446 | ||
447 | c_width = c.offsetWidth; | |
448 | c_height = c.offsetHeight; | |
449 | ||
450 | that.rescale(conf.scale); | |
451 | }; | |
452 | ||
453 | that.clear = function() { | |
454 | that.resize(640, 20); | |
455 | conf.ctx.clearRect(0, 0, c_width, c_height); | |
456 | }; | |
457 | ||
458 | that.stop = function() { | |
459 | var c = conf.target; | |
b7155950 | 460 | Util.removeEvent(conf.focusContainer, 'keydown', onKeyDown); |
461 | Util.removeEvent(conf.focusContainer, 'keyup', onKeyUp); | |
8db09746 JM |
462 | Util.removeEvent(c, 'mousedown', onMouseDown); |
463 | Util.removeEvent(c, 'mouseup', onMouseUp); | |
464 | Util.removeEvent(c, 'mousemove', onMouseMove); | |
15046f00 | 465 | Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', |
8db09746 | 466 | onMouseWheel); |
2bcb2d5b JM |
467 | |
468 | /* Work around right and middle click browser behaviors */ | |
b7155950 | 469 | Util.removeEvent(conf.focusContainer, 'click', onMouseDisable); |
470 | Util.removeEvent(conf.focusContainer.body, 'contextmenu', onMouseDisable); | |
2c2b492c JM |
471 | |
472 | // Turn off cursor rendering | |
8db09746 | 473 | if (conf.cursor_uri) { |
2c2b492c JM |
474 | c.style.cursor = "default"; |
475 | } | |
8db09746 JM |
476 | }; |
477 | ||
478 | setFillColor = function(color) { | |
479 | var rgb, newStyle; | |
480 | if (conf.true_color) { | |
481 | rgb = color; | |
482 | } else { | |
483 | rgb = conf.colourMap[color[0]]; | |
484 | } | |
485 | if (newStyle !== c_prevStyle) { | |
486 | newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; | |
487 | conf.ctx.fillStyle = newStyle; | |
488 | c_prevStyle = newStyle; | |
489 | } | |
490 | }; | |
491 | that.setFillColor = setFillColor; | |
492 | ||
493 | fillRect = function(x, y, width, height, color) { | |
494 | setFillColor(color); | |
495 | conf.ctx.fillRect(x, y, width, height); | |
496 | }; | |
497 | that.fillRect = fillRect; | |
498 | ||
499 | that.copyImage = function(old_x, old_y, new_x, new_y, width, height) { | |
500 | conf.ctx.drawImage(conf.target, old_x, old_y, width, height, | |
501 | new_x, new_y, width, height); | |
502 | }; | |
532a9fd9 | 503 | |
3875f847 JM |
504 | /* |
505 | * Tile rendering functions optimized for rendering engines. | |
506 | * | |
507 | * - In Chrome/webkit, Javascript image data array manipulations are | |
508 | * faster than direct Canvas fillStyle, fillRect rendering. In | |
509 | * gecko, Javascript array handling is much slower. | |
510 | */ | |
8db09746 | 511 | that.getTile = function(x, y, width, height, color) { |
d41c33e4 | 512 | var img, data, p, rgb, red, green, blue, j, i; |
c4164bda JM |
513 | img = {'x': x, 'y': y, 'width': width, 'height': height, |
514 | 'data': []}; | |
8db09746 | 515 | if (conf.prefer_js) { |
97763d0e | 516 | data = img.data; |
8db09746 | 517 | if (conf.true_color) { |
d41c33e4 JM |
518 | rgb = color; |
519 | } else { | |
8db09746 | 520 | rgb = conf.colourMap[color[0]]; |
d41c33e4 JM |
521 | } |
522 | red = rgb[0]; | |
523 | green = rgb[1]; | |
524 | blue = rgb[2]; | |
15046f00 JM |
525 | for (j = 0; j < height; j += 1) { |
526 | for (i = 0; i < width; i += 1) { | |
3875f847 | 527 | p = (i + (j * width) ) * 4; |
d41c33e4 JM |
528 | data[p + 0] = red; |
529 | data[p + 1] = green; | |
530 | data[p + 2] = blue; | |
531 | //data[p + 3] = 255; // Set Alpha | |
3875f847 JM |
532 | } |
533 | } | |
534 | } else { | |
8db09746 | 535 | fillRect(x, y, width, height, color); |
3875f847 JM |
536 | } |
537 | return img; | |
8db09746 | 538 | }; |
3875f847 | 539 | |
8db09746 | 540 | that.setSubTile = function(img, x, y, w, h, color) { |
d41c33e4 | 541 | var data, p, rgb, red, green, blue, width, j, i; |
8db09746 | 542 | if (conf.prefer_js) { |
97763d0e | 543 | data = img.data; |
c4164bda | 544 | width = img.width; |
8db09746 | 545 | if (conf.true_color) { |
d41c33e4 JM |
546 | rgb = color; |
547 | } else { | |
8db09746 | 548 | rgb = conf.colourMap[color[0]]; |
d41c33e4 JM |
549 | } |
550 | red = rgb[0]; | |
551 | green = rgb[1]; | |
552 | blue = rgb[2]; | |
15046f00 JM |
553 | for (j = 0; j < h; j += 1) { |
554 | for (i = 0; i < w; i += 1) { | |
3875f847 | 555 | p = (x + i + ((y + j) * width) ) * 4; |
97763d0e JM |
556 | data[p + 0] = red; |
557 | data[p + 1] = green; | |
558 | data[p + 2] = blue; | |
3875f847 JM |
559 | //img.data[p + 3] = 255; // Set Alpha |
560 | } | |
561 | } | |
562 | } else { | |
8db09746 | 563 | fillRect(img.x + x, img.y + y, w, h, color); |
3875f847 | 564 | } |
8db09746 | 565 | }; |
3875f847 | 566 | |
8db09746 JM |
567 | that.putTile = function(img) { |
568 | if (conf.prefer_js) { | |
569 | that.rgbxImage(img.x, img.y, img.width, img.height, img.data, 0); | |
3875f847 | 570 | } else { |
d93d3e09 | 571 | // No-op, under gecko already done by setSubTile |
3875f847 | 572 | } |
8db09746 | 573 | }; |
3875f847 | 574 | |
8db09746 JM |
575 | that.imageDataGet = function(width, height) { |
576 | return conf.ctx.getImageData(0, 0, width, height); | |
577 | }; | |
578 | that.imageDataCreate = function(width, height) { | |
579 | return conf.ctx.createImageData(width, height); | |
580 | }; | |
3875f847 | 581 | |
8db09746 | 582 | that.rgbxImageData = function(x, y, width, height, arr, offset) { |
7f4f41b0 | 583 | var img, i, j, data; |
8db09746 | 584 | img = that.imageData(width, height); |
97763d0e | 585 | data = img.data; |
d41c33e4 | 586 | for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { |
7f4f41b0 JM |
587 | data[i + 0] = arr[j + 0]; |
588 | data[i + 1] = arr[j + 1]; | |
589 | data[i + 2] = arr[j + 2]; | |
590 | data[i + 3] = 255; // Set Alpha | |
64ab5c4d | 591 | } |
8db09746 JM |
592 | conf.ctx.putImageData(img, x, y); |
593 | }; | |
64ab5c4d | 594 | |
d93d3e09 | 595 | // really slow fallback if we don't have imageData |
8db09746 | 596 | that.rgbxImageFill = function(x, y, width, height, arr, offset) { |
a7a89626 | 597 | var i, j, sx = 0, sy = 0; |
d93d3e09 | 598 | for (i=0, j=offset; i < (width * height); i+=1, j+=4) { |
8db09746 | 599 | fillRect(x+sx, y+sy, 1, 1, [arr[j+0], arr[j+1], arr[j+2]]); |
d93d3e09 JM |
600 | sx += 1; |
601 | if ((sx % width) === 0) { | |
602 | sx = 0; | |
603 | sy += 1; | |
604 | } | |
605 | } | |
8db09746 | 606 | }; |
d93d3e09 | 607 | |
8db09746 | 608 | that.cmapImageData = function(x, y, width, height, arr, offset) { |
15046f00 | 609 | var img, i, j, data, rgb, cmap; |
8db09746 | 610 | img = that.imageData(width, height); |
d41c33e4 | 611 | data = img.data; |
8db09746 | 612 | cmap = conf.colourMap; |
d93d3e09 | 613 | for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) { |
d41c33e4 JM |
614 | rgb = cmap[arr[j]]; |
615 | data[i + 0] = rgb[0]; | |
616 | data[i + 1] = rgb[1]; | |
617 | data[i + 2] = rgb[2]; | |
618 | data[i + 3] = 255; // Set Alpha | |
619 | } | |
8db09746 JM |
620 | conf.ctx.putImageData(img, x, y); |
621 | }; | |
d41c33e4 | 622 | |
8db09746 | 623 | that.cmapImageFill = function(x, y, width, height, arr, offset) { |
a7a89626 | 624 | var i, j, sx = 0, sy = 0, cmap; |
8db09746 | 625 | cmap = conf.colourMap; |
d93d3e09 | 626 | for (i=0, j=offset; i < (width * height); i+=1, j+=1) { |
8db09746 | 627 | fillRect(x+sx, y+sy, 1, 1, [arr[j]]); |
d93d3e09 JM |
628 | sx += 1; |
629 | if ((sx % width) === 0) { | |
630 | sx = 0; | |
631 | sy += 1; | |
632 | } | |
633 | } | |
8db09746 | 634 | }; |
d93d3e09 JM |
635 | |
636 | ||
8db09746 JM |
637 | that.blitImage = function(x, y, width, height, arr, offset) { |
638 | if (conf.true_color) { | |
639 | that.rgbxImage(x, y, width, height, arr, offset); | |
d41c33e4 | 640 | } else { |
8db09746 | 641 | that.cmapImage(x, y, width, height, arr, offset); |
d41c33e4 | 642 | } |
8db09746 | 643 | }; |
d9cbdc7d | 644 | |
8db09746 | 645 | that.blitStringImage = function(str, x, y) { |
d93d3e09 | 646 | var img = new Image(); |
8db09746 | 647 | img.onload = function () { conf.ctx.drawImage(img, x, y); }; |
d93d3e09 | 648 | img.src = str; |
8db09746 | 649 | }; |
f272267b | 650 | |
8db09746 | 651 | that.changeCursor = function(pixels, mask, hotx, hoty, w, h) { |
a7a89626 | 652 | var cur = [], cmap, rgb, IHDRsz, ANDsz, XORsz, url, idx, alpha, x, y; |
da6dd893 | 653 | //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h); |
2c2b492c | 654 | |
8db09746 | 655 | if (conf.cursor_uri === false) { |
da6dd893 | 656 | Util.Warn("changeCursor called but no cursor data URI support"); |
2c2b492c JM |
657 | return; |
658 | } | |
659 | ||
67b4e987 JM |
660 | // Push multi-byte little-endian values |
661 | cur.push16le = function (num) { | |
662 | this.push((num ) & 0xFF, | |
663 | (num >> 8) & 0xFF ); | |
664 | }; | |
665 | cur.push32le = function (num) { | |
666 | this.push((num ) & 0xFF, | |
667 | (num >> 8) & 0xFF, | |
668 | (num >> 16) & 0xFF, | |
669 | (num >> 24) & 0xFF ); | |
670 | }; | |
671 | ||
8db09746 | 672 | cmap = conf.colourMap; |
2c2b492c JM |
673 | IHDRsz = 40; |
674 | ANDsz = w * h * 4; | |
675 | XORsz = Math.ceil( (w * h) / 8.0 ); | |
676 | ||
677 | // Main header | |
678 | cur.push16le(0); // Reserved | |
679 | cur.push16le(2); // .CUR type | |
680 | cur.push16le(1); // Number of images, 1 for non-animated ico | |
681 | ||
682 | // Cursor #1 header | |
683 | cur.push(w); // width | |
684 | cur.push(h); // height | |
685 | cur.push(0); // colors, 0 -> true-color | |
686 | cur.push(0); // reserved | |
687 | cur.push16le(hotx); // hotspot x coordinate | |
688 | cur.push16le(hoty); // hotspot y coordinate | |
689 | cur.push32le(IHDRsz + XORsz + ANDsz); // cursor data byte size | |
690 | cur.push32le(22); // offset of cursor data in the file | |
691 | ||
692 | // Cursor #1 InfoHeader | |
693 | cur.push32le(IHDRsz); // Infoheader size | |
694 | cur.push32le(w); // Cursor width | |
695 | cur.push32le(h*2); // XOR+AND height | |
696 | cur.push16le(1); // number of planes | |
697 | cur.push16le(32); // bits per pixel | |
698 | cur.push32le(0); // Type of compression | |
699 | cur.push32le(XORsz + ANDsz); // Size of Image | |
700 | cur.push32le(0); | |
701 | cur.push32le(0); | |
702 | cur.push32le(0); | |
703 | cur.push32le(0); | |
704 | ||
705 | // XOR/color data | |
8db09746 JM |
706 | for (y = h-1; y >= 0; y -= 1) { |
707 | for (x = 0; x < w; x += 1) { | |
2c2b492c JM |
708 | idx = y * Math.ceil(w / 8) + Math.floor(x/8); |
709 | alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0; | |
710 | ||
8db09746 | 711 | if (conf.true_color) { |
2c2b492c JM |
712 | idx = ((w * y) + x) * 4; |
713 | cur.push(pixels[idx + 2]); // blue | |
714 | cur.push(pixels[idx + 1]); // green | |
715 | cur.push(pixels[idx + 0]); // red | |
716 | cur.push(alpha); // red | |
717 | } else { | |
718 | idx = (w * y) + x; | |
719 | rgb = cmap[pixels[idx]]; | |
720 | cur.push(rgb[2]); // blue | |
721 | cur.push(rgb[1]); // green | |
722 | cur.push(rgb[0]); // red | |
723 | cur.push(alpha); // alpha | |
724 | } | |
725 | } | |
726 | } | |
727 | ||
728 | // AND/bitmask data (ignored, just needs to be right size) | |
8db09746 JM |
729 | for (y = 0; y < h; y += 1) { |
730 | for (x = 0; x < Math.ceil(w / 8); x += 1) { | |
2c2b492c JM |
731 | cur.push(0x00); |
732 | } | |
733 | } | |
734 | ||
735 | url = "data:image/x-icon;base64," + Base64.encode(cur); | |
8db09746 | 736 | conf.target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default"; |
da6dd893 | 737 | //Util.Debug("<< changeCursor, cur.length: " + cur.length); |
c8460b03 JM |
738 | }; |
739 | ||
8db09746 JM |
740 | |
741 | ||
742 | return constructor(); // Return the public API interface | |
743 | ||
744 | } // End of Canvas() | |
745 |