add a short_name so that FxOS versions support that will only display this one in...
[mandelbrot-web.git] / js / mandelbrot.js
CommitLineData
d0244cd3
RK
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6a7aa57d 4
7cde05f7 5// Get the best-available indexedDB object.
ee8b0401 6window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
7cde05f7
RK
7var mainDB;
8
ca6b14b6
RK
9var gDebug = false;
10var gAction, gActionLabel;
51442fd4 11var gMainCanvas, gMainContext;
6a7aa57d
RK
12var gColorPalette = [];
13var gStartTime = 0;
becdac35
RK
14var gCurrentImageData;
15var gLastImageData;
16
17function Startup() {
ca6b14b6
RK
18 gAction = document.getElementById("action");
19 gActionLabel = document.getElementById("actionlabel");
20 gAction.addEventListener("dbinit-done", initPrefs, false);
7cde05f7
RK
21 initDB();
22
51442fd4
RK
23 gMainCanvas = document.getElementById("mbrotImage");
24 gMainContext = gMainCanvas.getContext("2d");
25
26 gMainCanvas.addEventListener("mouseup", imgEvHandler, false);
27 gMainCanvas.addEventListener("mousedown", imgEvHandler, false);
28 gMainCanvas.addEventListener("mousemove", imgEvHandler, false);
29 gMainCanvas.addEventListener("touchstart", imgEvHandler, false);
30 gMainCanvas.addEventListener("touchend", imgEvHandler, false);
31 gMainCanvas.addEventListener("touchcancel", imgEvHandler, false);
32 gMainCanvas.addEventListener("touchleave", imgEvHandler, false);
33 gMainCanvas.addEventListener("touchmove", imgEvHandler, false);
34
35 var initTile = new Image();
36 initTile.src = "style/initial-overview.png";
b27a1f72 37 initTile.onload = function() { gMainContext.drawImage(initTile, 0, 0); };
becdac35
RK
38}
39
7cde05f7
RK
40function initDB() {
41 // Open DB.
ca6b14b6 42 var request = window.indexedDB.open("MainDB-mandelbrot", 1);
7cde05f7
RK
43 request.onerror = function(event) {
44 // Errors can be handled here. Error codes explain in:
45 // https://developer.mozilla.org/en/IndexedDB/IDBDatabaseException#Constants
ca6b14b6
RK
46 if (gDebug)
47 console.log("error opening mainDB: " + event.target.errorCode);
7cde05f7
RK
48 };
49 request.onsuccess = function(event) {
50 //document.getElementById("debug").textContent = "mainDB opened.";
51 mainDB = request.result;
ca6b14b6
RK
52 var throwEv = new CustomEvent("dbinit-done");
53 gAction.dispatchEvent(throwEv);
7cde05f7
RK
54 };
55 request.onupgradeneeded = function(event) {
56 mainDB = request.result;
ca6b14b6
RK
57 var ver = mainDB.version || 0; // version is empty string for a new DB
58 if (gDebug)
59 console.log("mainDB has version " + ver + ", upgrade needed.");
60 if (!mainDB.objectStoreNames.contains("prefs")) {
61 // Create a "prefs" objectStore.
62 var prefsStore = mainDB.createObjectStore("prefs");
63 }
64 if (!mainDB.objectStoreNames.contains("bookmarks")) {
65 // Create a "bookmarks" objectStore.
66 var prefsStore = mainDB.createObjectStore("bookmarks");
67 }
7cde05f7
RK
68 mainDB.onversionchange = function(event) {
69 mainDB.close();
70 mainDB = undefined;
71 initDB();
72 };
73 };
74}
75
ca6b14b6
RK
76function initPrefs() {
77 if (gDebug)
78 console.log("initializing prefs...");
79 gSyncPrefs.init(function () {
80 // Update the various settings fields to values from prefs.
81 if (gSyncPrefs.get("image.width"))
82 document.getElementById("image_width").value = gSyncPrefs.get("image.width");
83 if (gSyncPrefs.get("image.height"))
84 document.getElementById("image_height").value = gSyncPrefs.get("image.height");
85 if (gSyncPrefs.get("Cr_min"))
86 document.getElementById("Cr_min").value = gSyncPrefs.get("Cr_min");
87 if (gSyncPrefs.get("Cr_max"))
88 document.getElementById("Cr_max").value = gSyncPrefs.get("Cr_max");
89 if (gSyncPrefs.get("Ci_min"))
90 document.getElementById("Ci_min").value = gSyncPrefs.get("Ci_min");
91 if (gSyncPrefs.get("Ci_max"))
92 document.getElementById("Ci_max").value = gSyncPrefs.get("Ci_max");
93 if (gSyncPrefs.get("iteration_max"))
94 document.getElementById("iterMax").value = gSyncPrefs.get("iteration_max");
95 if (gSyncPrefs.get("color_palette"))
96 document.getElementById("palette").value = gSyncPrefs.get("color_palette");
97 if (gSyncPrefs.get("syncProportions") === true || gSyncPrefs.get("syncProportions") === false)
98 document.getElementById("proportional").checked = gSyncPrefs.get("syncProportions");
99 if (gSyncPrefs.get("use_algorithm"))
100 document.getElementById("algorithm").value = gSyncPrefs.get("use_algorithm");
101 if (gDebug)
102 console.log("prefs loaded.");
103 });
104}
105
becdac35
RK
106function getAdjustVal(aName) {
107 var value;
108 switch (aName) {
109 case "image.width":
110 case "image.height":
111 value = 0;
112 try {
113 value = document.getElementById(aName.replace(".", "_")).value;
114 }
115 catch (e) { }
ca6b14b6 116 if (!value || (value < 10) || (value > 5000)) {
becdac35 117 value = 300;
ca6b14b6 118 gSyncPrefs.set(aName, value);
728b58e4 119 document.getElementById(aName.replace(".", "_")).value = value;
becdac35
RK
120 }
121 return value;
122 case "last_image.Cr_*":
123 var Cr_min = -2.0;
124 var Cr_max = 1.0;
125 try {
126 Cr_min = parseFloat(document.getElementById("Cr_min").value);
127 Cr_max = parseFloat(document.getElementById("Cr_max").value);
128 }
129 catch (e) { }
130 if ((Cr_min < -3) || (Cr_min > 2) ||
131 (Cr_max < -3) || (Cr_max > 2) || (Cr_min >= Cr_max)) {
132 Cr_min = -2.0; Cr_max = 1.0;
133 }
ca6b14b6
RK
134 gSyncPrefs.set("Cr_min", Cr_min);
135 gSyncPrefs.set("Cr_max", Cr_max);
becdac35
RK
136 document.getElementById("Cr_min").value = Cr_min;
137 document.getElementById("Cr_max").value = Cr_max;
ca6b14b6 138 document.getElementById("Cr_scale").value = Cr_max - Cr_min;
becdac35
RK
139 return {Cr_min: Cr_min, Cr_max: Cr_max};
140 case "last_image.Ci_*":
141 var Ci_min = -1.5;
142 var Ci_max = 1.5;
143 try {
144 Ci_min = parseFloat(document.getElementById("Ci_min").value);
145 Ci_max = parseFloat(document.getElementById("Ci_max").value);
146 }
147 catch (e) { }
148 if ((Ci_min < -2.5) || (Ci_min > 2.5) ||
149 (Ci_max < -2.5) || (Ci_max > 2.5) || (Ci_min >= Ci_max)) {
150 Ci_min = -1.5; Ci_max = 1.5;
151 }
ca6b14b6
RK
152 gSyncPrefs.set("Ci_min", Ci_min);
153 gSyncPrefs.set("Ci_max", Ci_max);
becdac35
RK
154 document.getElementById("Ci_min").value = Ci_min;
155 document.getElementById("Ci_max").value = Ci_max;
ca6b14b6 156 document.getElementById("Ci_scale").value = Ci_max - Ci_min;
becdac35
RK
157 return {Ci_min: Ci_min, Ci_max: Ci_max};
158 case "iteration_max":
159 value = 500;
160 try {
161 value = document.getElementById("iterMax").value;
162 }
163 catch (e) {
164 setIter(value);
165 }
166 if (value < 10 || value > 10000) {
167 value = 500;
168 setIter(value);
169 }
170 return value;
171 case "use_algorithm":
172 value = "numeric";
173 try {
174 value = document.getElementById("algorithm").value;
175 }
176 catch (e) {
177 setAlgorithm(value);
178 }
179 return value;
180 case "color_palette":
181 value = "kairo";
182 try {
183 value = document.getElementById("palette").value;
184 }
185 catch(e) {
186 setPalette(value);
187 }
188 return value;
189 case "syncProportions":
190 value = true;
191 try {
192 value = document.getElementById("proportional").value;
193 }
194 catch(e) {
ca6b14b6 195 gSyncPrefs.set(prefname, value);
becdac35
RK
196 document.getElementById("proportional").value = value;
197 }
198 return value;
199 default:
200 return false;
201 }
202}
203
204function setVal(aName, aValue) {
205 switch (aName) {
206 case "image.width":
207 case "image.height":
ca6b14b6 208 gSyncPrefs.set(aName, aValue);
b27a1f72 209 document.getElementById(aName.replace(".", "_")).value = aValue;
becdac35
RK
210 break;
211 case "last_image.Cr_*":
ca6b14b6
RK
212 gSyncPrefs.set("Cr_min", aValue.Cr_min);
213 gSyncPrefs.set("Cr_max", aValue.Cr_max);
becdac35
RK
214 document.getElementById("Cr_min").value = aValue.Cr_min;
215 document.getElementById("Cr_max").value = aValue.Cr_max;
216 break;
217 case "last_image.Ci_*":
ca6b14b6
RK
218 gSyncPrefs.set("Ci_min", aValue.Ci_min);
219 gSyncPrefs.set("Ci_max", aValue.Ci_max);
becdac35
RK
220 document.getElementById("Ci_min").value = aValue.Ci_min;
221 document.getElementById("Ci_max").value = aValue.Ci_max;
222 break;
223 case "iteration_max":
224 setIter(aValue);
225 break;
226 case "use_algorithm":
227 setAlgorithm(aValue);
228 break;
229 case "color_palette":
b27a1f72 230 setPalette(aValue);
becdac35
RK
231 break;
232 case "syncProportions":
ca6b14b6 233 gSyncPrefs.set(aName, aValue);
becdac35
RK
234 document.getElementById("proportional").value = aValue;
235 break;
236 }
237}
238
5d56a513
RK
239function checkISValue(textbox, type) {
240 if (type == "coord") {
241 textbox.value = roundCoord(parseFloat(textbox.value));
242 }
243 else if (type == "dim") {
244 textbox.value = parseInt(textbox.value);
ca6b14b6
RK
245 if (textbox.id == "image_width")
246 gSyncPrefs.set("image.width", textbox.value);
247 if (textbox.id == "image_height")
248 gSyncPrefs.set("image.height", textbox.value);
5d56a513
RK
249 }
250}
251
252function recalcCoord(coord, target) {
253 var othercoord = (coord == "Ci") ? "Cr" : "Ci";
254 var owndim = (coord == "Ci") ? "height" : "width";
255 var otherdim = (coord == "Ci") ? "width" : "height";
256 var myscale;
257 if (target == "scale") {
258 myscale =
259 parseFloat(document.getElementById(coord + "_max").value) -
260 parseFloat(document.getElementById(coord + "_min").value);
261 document.getElementById(coord + "_scale").value = roundCoord(myscale);
262 }
263 else if (target == 'max') {
264 var mymax =
265 parseFloat(document.getElementById(coord + "_min").value) +
266 parseFloat(document.getElementById(coord + "_scale").value);
267 document.getElementById(coord + "_max").value = roundCoord(mymax);
268 myscale = document.getElementById(coord + "_scale").value;
269 }
270 if (document.getElementById("proportional").checked) {
271 var otherscale = myscale *
272 document.getElementById("image_" + otherdim).value /
273 document.getElementById("image_" + owndim).value;
274 document.getElementById(othercoord + "_scale").value = roundCoord(otherscale);
275 var othermax =
276 parseFloat(document.getElementById(othercoord + "_min").value) +
277 parseFloat(document.getElementById(othercoord + "_scale").value);
278 document.getElementById(othercoord + "_max").value = roundCoord(othermax);
279 }
280}
281
282function checkProportions() {
ca6b14b6
RK
283 var prop = document.getElementById("proportional").checked;
284 if (!prop) {
5d56a513
RK
285 recalcCoord("Cr", "scale");
286 }
ca6b14b6 287 gSyncPrefs.set("syncProportions", prop);
5d56a513
RK
288}
289
290function roundCoord(floatval) {
291 // We should round to 10 decimals here or so
292 return parseFloat(floatval.toFixed(10));
293}
294
becdac35
RK
295function adjustCoordsAndDraw(aC_min, aC_max) {
296 var iWidth = getAdjustVal("image.width");
297 var iHeight = getAdjustVal("image.height");
298
299 // correct coordinates
300 if (aC_min.r < -2)
301 aC_min.r = -2;
302 if (aC_max.r > 2)
303 aC_max.r = 2;
304 if ((aC_min.r > 2) || (aC_max.r < -2) || (aC_min.r >= aC_max.r)) {
305 aC_min.r = -2.0; aC_max.r = 1.0;
306 }
307 if (aC_min.i < -2)
308 aC_min.i = -2;
309 if (aC_max.i > 2)
310 aC_max.i = 2;
311 if ((aC_min.i > 2) || (aC_max.i < -2) || (aC_min.i >= aC_max.i)) {
312 aC_min.i = -1.3; aC_max.i = 1.3;
313 }
314
315 var CWidth = aC_max.r - aC_min.r;
316 var CHeight = aC_max.i - aC_min.i;
317 var C_mid = new complex(aC_min.r + CWidth / 2, aC_min.i + CHeight / 2);
318
319 var CRatio = Math.max(CWidth / iWidth, CHeight / iHeight);
320
321 setVal("last_image.Cr_*", {Cr_min: C_mid.r - iWidth * CRatio / 2,
322 Cr_max: C_mid.r + iWidth * CRatio / 2});
323 setVal("last_image.Ci_*", {Ci_min: C_mid.i - iHeight * CRatio / 2,
324 Ci_max: C_mid.i + iHeight * CRatio / 2});
325
326 drawImage();
327}
6a7aa57d
RK
328
329function drawImage() {
51442fd4
RK
330 var canvas = gMainCanvas;
331 var context = gMainContext;
6a7aa57d
RK
332
333 document.getElementById("calcTime").textContent = "--";
334
becdac35
RK
335 if (gCurrentImageData) {
336 gLastImageData = gCurrentImageData;
95d05599 337 document.getElementById("backButton").disabled = false;
becdac35
RK
338 }
339
6a7aa57d
RK
340 gColorPalette = getColorPalette(document.getElementById("palette").value);
341
becdac35
RK
342 var Cr_vals = getAdjustVal("last_image.Cr_*");
343 var Cr_min = Cr_vals.Cr_min;
344 var Cr_max = Cr_vals.Cr_max;
6a7aa57d 345
becdac35
RK
346 var Ci_vals = getAdjustVal("last_image.Ci_*");
347 var Ci_min = Ci_vals.Ci_min;
348 var Ci_max = Ci_vals.Ci_max;
349
350 var iterMax = getAdjustVal("iteration_max");
351 var algorithm = getAdjustVal("use_algorithm");
6a7aa57d 352
5d56a513
RK
353 var iWidth = getAdjustVal("image.width");
354 var iHeight = getAdjustVal("image.height");
6a7aa57d 355
becdac35
RK
356 gCurrentImageData = {
357 C_min: new complex(Cr_min, Ci_min),
358 C_max: new complex(Cr_max, Ci_max),
359 iWidth: iWidth,
360 iHeight: iHeight,
361 iterMax: iterMax
362 };
363
5d56a513
RK
364 canvas.width = iWidth;
365 canvas.height = iHeight;
366
6a7aa57d
RK
367 context.fillStyle = "rgba(255, 255, 255, 127)";
368 context.fillRect(0, 0, canvas.width, canvas.height);
369
370 gStartTime = new Date();
371
372 drawLine(0, [Cr_min, Cr_max, Ci_min, Ci_max],
373 canvas, context, iterMax, algorithm);
374}
375
376function drawLine(line, dimensions, canvas, context, iterMax, algorithm) {
377 var Cr_min = dimensions[0];
378 var Cr_max = dimensions[1];
379 var Cr_scale = Cr_max - Cr_min;
380
381 var Ci_min = dimensions[2];
382 var Ci_max = dimensions[3];
383 var Ci_scale = Ci_max - Ci_min;
384
385 var lines = Math.min(canvas.height - line, 8);
386 var imageData = context.createImageData(canvas.width, lines);
387 var pixels = imageData.data;
388 var idx = 0;
389 for (var img_y = line; img_y < canvas.height && img_y < line+8; img_y++)
390 for (var img_x = 0; img_x < canvas.width; img_x++) {
391 var C = new complex(Cr_min + (img_x / canvas.width) * Cr_scale,
392 Ci_min + (img_y / canvas.height) * Ci_scale);
393 var colors = drawPoint(context, img_x, img_y, C, iterMax, algorithm);
394 pixels[idx++] = colors[0];
395 pixels[idx++] = colors[1];
396 pixels[idx++] = colors[2];
397 pixels[idx++] = colors[3];
398 }
399 context.putImageData(imageData, 0, line);
400
401 if (img_y < canvas.height)
402 setTimeout(drawLine, 0, img_y, dimensions, canvas, context, iterMax, algorithm);
403 else if (gStartTime)
404 EndCalc();
405}
406
407function EndCalc() {
408 var endTime = new Date();
409 var timeUsed = (endTime.getTime() - gStartTime.getTime()) / 1000;
410 document.getElementById("calcTime").textContent = timeUsed.toFixed(3) + " seconds";
411}
412
413function complex(aReal, aImag) {
414 this.r = aReal;
415 this.i = aImag;
416}
417complex.prototype = {
418 square: function() {
419 return new complex(this.r * this.r - this.i * this.i,
420 2 * this.r * this.i);
421 },
422 dist: function() {
423 return Math.sqrt(this.r * this.r + this.i * this.i);
424 },
425 add: function(aComplex) {
426 return new complex(this.r + aComplex.r, this.i + aComplex.i);
427 }
428}
429
430function mandelbrotValueOO (aC, aIterMax) {
431 // this would be nice code in general but it looks like JS objects are too heavy for normal use.
432 var Z = new complex(0.0, 0.0);
433 for (var iter = 0; iter < aIterMax; iter++) {
434 Z = Z.square().add(aC);
435 if (Z.r * Z.r + Z.i * Z.i > 256) { break; }
436 }
437 return iter;
438}
439
440function mandelbrotValueNumeric (aC, aIterMax) {
441 // optimized numeric code for fast calculation
442 var Cr = aC.r, Ci = aC.i;
443 var Zr = 0.0, Zi = 0.0;
444 var Zr2 = Zr * Zr, Zi2 = Zi * Zi;
445 for (var iter = 0; iter < aIterMax; iter++) {
446 Zi = 2 * Zr * Zi + Ci;
447 Zr = Zr2 - Zi2 + Cr;
448
449 Zr2 = Zr * Zr; Zi2 = Zi * Zi;
450 if (Zr2 + Zi2 > 256) { break; }
451 }
452 return iter;
453}
454
455function getColor(aIterValue, aIterMax) {
456 var standardizedValue = Math.round(aIterValue * 1024 / aIterMax);
457 if (gColorPalette && gColorPalette.length)
458 return gColorPalette[standardizedValue];
459
460 // fallback to simple b/w if for some reason we don't have a palette
461 if (aIterValue == aIterMax)
462 return [0, 0, 0, 255];
463 else
464 return [255, 255, 255, 255];
465}
466
467function getColorPalette(palName) {
468 var palette = [];
469 switch (palName) {
470 case 'bw':
471 for (var i = 0; i < 1024; i++) {
472 palette[i] = [255, 255, 255, 255];
473 }
474 palette[1024] = [0, 0, 0, 255];
475 break;
476 case 'kairo':
477 // outer areas
478 for (var i = 0; i < 32; i++) {
479 var cc1 = Math.floor(i * 127 / 31);
480 var cc2 = 170 - Math.floor(i * 43 / 31);
481 palette[i] = [cc1, cc2, cc1, 255];
482 }
483 // inner areas
484 for (var i = 0; i < 51; i++) {
485 var cc = Math.floor(i * 170 / 50);
486 palette[32 + i] = [cc, 0, (170-cc), 255];
487 }
488 // corona
489 for (var i = 0; i < 101; i++) {
490 var cc = Math.floor(i * 200 / 100);
491 palette[83 + i] = [255, cc, 0, 255];
492 }
493 // inner corona
494 for (var i = 0; i < 201; i++) {
495 var cc1 = 255 - Math.floor(i * 85 / 200);
496 var cc2 = 200 - Math.floor(i * 30 / 200);
497 var cc3 = Math.floor(i * 170 / 200);
498 palette[184 + i] = [cc1, cc2, cc3, 255];
499 }
500 for (var i = 0; i < 301; i++) {
501 var cc1 = 170 - Math.floor(i * 43 / 300);
502 var cc2 = 170 + Math.floor(i * 85 / 300);
503 palette[385 + i] = [cc1, cc1, cc2, 255];
504 }
505 for (var i = 0; i < 338; i++) {
506 var cc = 127 + Math.floor(i * 128 / 337);
507 palette[686 + i] = [cc, cc, 255, 255];
508 }
509 palette[1024] = [0, 0, 0, 255];
510 break;
511 case 'rainbow-linear1':
512 for (var i = 0; i < 256; i++) {
513 palette[i] = [i, 0, 0, 255];
514 palette[256 + i] = [255, i, 0, 255];
515 palette[512 + i] = [255 - i, 255, i, 255];
516 palette[768 + i] = [i, 255-i, 255, 255];
517 }
518 palette[1024] = [0, 0, 0, 255];
519 break;
becdac35
RK
520 case 'rainbow-squared1':
521 for (var i = 0; i < 34; i++) {
522 var cc = Math.floor(i * 255 / 33);
523 palette[i] = [cc, 0, 0, 255];
524 }
525 for (var i = 0; i < 137; i++) {
526 var cc = Math.floor(i * 255 / 136);
527 palette[34 + i] = [255, cc, 0, 255];
528 }
529 for (var i = 0; i < 307; i++) {
530 var cc = Math.floor(i * 255 / 306);
531 palette[171 + i] = [255 - cc, 255, cc, 255];
532 }
533 for (var i = 0; i < 546; i++) {
534 var cc = Math.floor(i * 255 / 545);
535 palette[478 + i] = [cc, 255 - cc, 255, 255];
536 }
537 palette[1024] = [0, 0, 0, 255];
538 break;
539 case 'rainbow-linear2':
540 for (var i = 0; i < 205; i++) {
541 var cc = Math.floor(i * 255 / 204);
542 palette[i] = [255, cc, 0, 255];
543 palette[204 + i] = [255 - cc, 255, 0, 255];
544 palette[409 + i] = [0, 255, cc, 255];
545 palette[614 + i] = [0, 255 - cc, 255, 255];
546 palette[819 + i] = [cc, 0, 255, 255];
547 }
548 palette[1024] = [0, 0, 0, 255];
549 break;
550 case 'rainbow-squared2':
551 for (var i = 0; i < 19; i++) {
552 var cc = Math.floor(i * 255 / 18);
553 palette[i] = [255, cc, 0, 255];
554 }
555 for (var i = 0; i < 74; i++) {
556 var cc = Math.floor(i * 255 / 73);
557 palette[19 + i] = [255 - cc, 255, 0, 255];
558 }
559 for (var i = 0; i < 168; i++) {
560 var cc = Math.floor(i * 255 / 167);
561 palette[93 + i] = [0, 255, cc, 255];
562 }
563 for (var i = 0; i < 298; i++) {
564 var cc = Math.floor(i * 255 / 297);
565 palette[261 + i] = [0, 255 - cc, 255, 255];
566 }
567 for (var i = 0; i < 465; i++) {
568 var cc = Math.floor(i * 255 / 464);
569 palette[559 + i] = [cc, 0, 255, 255];
570 }
571 palette[1024] = [0, 0, 0, 255];
572 break;
6a7aa57d
RK
573 }
574 return palette;
575}
576
577function drawPoint(context, img_x, img_y, C, iterMax, algorithm) {
578 var itVal;
579 switch (algorithm) {
580 case 'oo':
581 itVal = mandelbrotValueOO(C, iterMax);
582 break;
583 case 'numeric':
584 default:
585 itVal = mandelbrotValueNumeric(C, iterMax);
586 break;
587 }
588 return getColor(itVal, iterMax);
589}
590
591// ########## UI functions ##########
592
becdac35
RK
593var zoomstart;
594var imgBackup;
595var zoomTouchID;
596
597var imgEvHandler = {
598 handleEvent: function(aEvent) {
599 var canvas = document.getElementById("mbrotImage");
600 var context = canvas.getContext("2d");
601 var touchEvent = aEvent.type.indexOf('touch') != -1;
602
603 // Bail out if this is neither a touch nor left-click.
604 if (!touchEvent && aEvent.button != 0)
605 return;
606
607 // Bail out if the started touch can't be found.
608 if (touchEvent && zoomstart &&
609 !aEvent.changedTouches.identifiedTouch(zoomTouchID))
610 return;
611
612 var coordObj = touchEvent ?
613 aEvent.changedTouches.identifiedTouch(zoomTouchID) :
614 aEvent;
615
616 switch (aEvent.type) {
617 case 'mousedown':
618 case 'touchstart':
619 if (touchEvent) {
620 zoomTouchID = aEvent.changedTouches.item(0).identifier;
621 coordObj = aEvent.changedTouches.identifiedTouch(zoomTouchID);
622 }
623 // left button - start dragzoom
624 zoomstart = {x: coordObj.clientX - canvas.offsetLeft,
625 y: coordObj.clientY - canvas.offsetTop};
626 imgBackup = context.getImageData(0, 0, canvas.width, canvas.height);
627 break;
628 case 'mouseup':
629 case 'touchend':
630 if (zoomstart) {
631 context.putImageData(imgBackup, 0, 0);
632 var zoomend = {x: coordObj.clientX - canvas.offsetLeft,
633 y: coordObj.clientY - canvas.offsetTop};
634
635 // make sure zoomend is bigger than zoomstart
636 if ((zoomend.x == zoomstart.x) || (zoomend.y == zoomstart.y)) {
637 // cannot zoom what has no area, discard it
638 zoomstart = undefined;
639 return;
640 }
641 if (zoomend.x < zoomstart.x)
642 [zoomend.x, zoomstart.x] = [zoomstart.x, zoomend.x];
643 if (zoomend.y < zoomstart.y)
644 [zoomend.y, zoomstart.y] = [zoomstart.y, zoomend.y];
645
95d05599
RK
646 if (gCurrentImageData) {
647 // determine new "coordinates"
648 var CWidth = gCurrentImageData.C_max.r - gCurrentImageData.C_min.r;
649 var CHeight = gCurrentImageData.C_max.i - gCurrentImageData.C_min.i;
650 var newC_min = new complex(
651 gCurrentImageData.C_min.r + zoomstart.x / gCurrentImageData.iWidth * CWidth,
652 gCurrentImageData.C_min.i + zoomstart.y / gCurrentImageData.iHeight * CHeight);
653 var newC_max = new complex(
654 gCurrentImageData.C_min.r + zoomend.x / gCurrentImageData.iWidth * CWidth,
655 gCurrentImageData.C_min.i + zoomend.y / gCurrentImageData.iHeight * CHeight);
656 }
657 else {
658 var newC_min = new complex(-2, -1.5);
659 var newC_max = new complex(1, 1.5);
660 }
becdac35
RK
661
662 adjustCoordsAndDraw(newC_min, newC_max);
663 }
664 zoomstart = undefined;
665 break;
666 case 'mousemove':
667 case 'touchmove':
668 if (zoomstart) {
669 context.putImageData(imgBackup, 0, 0);
670 context.strokeStyle = "rgb(255,255,31)";
671 context.strokeRect(zoomstart.x, zoomstart.y,
672 coordObj.clientX - canvas.offsetLeft - zoomstart.x,
673 coordObj.clientY - canvas.offsetTop - zoomstart.y);
674 }
95d05599 675 break;
becdac35
RK
676 }
677 }
678};
679
95d05599
RK
680function drawIfEmpty() {
681 if (!gCurrentImageData) {
682 drawImage();
683 }
684}
685
6a7aa57d
RK
686function toggleSettings() {
687 var fs = document.getElementById("settings");
688 if (fs.style.display != "block") {
689 fs.style.display = "block";
690 }
691 else {
692 fs.style.display = "none";
693 }
becdac35
RK
694}
695
696function goBack() {
697 if (gLastImageData) {
698 document.getElementById("iterMax").value = gLastImageData.iterMax;
699 // use gLastImageData.iWidth, gLastImageData.iHeight ???
700 adjustCoordsAndDraw(gLastImageData.C_min, gLastImageData.C_max);
701 gLastImageData = undefined;
95d05599 702 document.getElementById("backButton").disabled = true;
becdac35
RK
703 }
704}
705
706function setIter(aIter) {
ca6b14b6
RK
707 if (aIter)
708 document.getElementById("iterMax").value = aIter;
709 else
710 aIter = document.getElementById("iterMax").value;
711 gSyncPrefs.set("iteration_max", aIter);
becdac35
RK
712}
713
714function setPalette(aPaletteID) {
ca6b14b6
RK
715 if (aPaletteID)
716 document.getElementById("palette").value = aPaletteID;
717 else
718 aPaletteID = document.getElementById("palette").value;
719 gSyncPrefs.set("color_palette", aPaletteID);
becdac35
RK
720 gColorPalette = getColorPalette(aPaletteID);
721}
722
ca6b14b6
RK
723function setAlgorithm(aAlgoID) {
724 if (aAlgoID)
725 document.getElementById("algorithm").value = aAlgoID;
726 else
727 aAlgoID = document.getElementById("algorithm").value;
728 gSyncPrefs.set("use_algorithm", aAlgoID);
729}
730
731function callBookmark(evtarget) {
732 if (evtarget.id == "bookmarkSave" || evtarget.id == "bookmarkSeparator")
733 return;
734 if (evtarget.id == "bookmarkOverview") {
735 adjustCoordsAndDraw(new complex(0,0), new complex(0,0));
736 return;
737 }
738
739 if (evtarget.getAttribute('bmRowID')) {
740 var iterMax = 0;
741 var C_min = null;
742 var C_max = null;
743
744 // Get coordinates for this row ID.
745 /*
746 while (statement.executeStep()) {
747 iterMax = ;
748 C_min = new complex(, );
749 C_max = new complex(, );
750 }
751
752 if (iterMax && C_min && C_max) {
753 setIter(iterMax)
754 adjustCoordsAndDraw(C_min, C_max);
755 }
756 */
757 }
becdac35 758}
7cde05f7 759
ca6b14b6 760var gSyncPrefs = {
7cde05f7 761 objStore: "prefs",
ca6b14b6 762 shadow: {},
7cde05f7 763
ca6b14b6
RK
764 init: function(aCallback) {
765 // Fill the shadow from the DB.
7cde05f7
RK
766 if (!mainDB)
767 return;
768 var transaction = mainDB.transaction([this.objStore]);
ca6b14b6
RK
769 var objStore = transaction.objectStore(this.objStore);
770 if (objStore.getAll) { // currently Mozilla-specific
771 objStore.getAll().onsuccess = function(event) {
772 gSyncPrefs.shadow = event.target.result;
773 aCallback();
774 };
775 }
776 else { // Use cursor (standard method).
777 objStore.openCursor().onsuccess = function(event) {
778 var cursor = event.target.result;
779 if (cursor) {
780 gSyncPrefs.shadow[cursor.key] = cursor.value;
781 cursor.continue();
782 }
783 else {
784 aCallback();
785 }
786 };
787 }
788 },
789
790 get: function(aKey) {
791 // Only use the shadow.
792 return this.shadow[aKey];
7cde05f7
RK
793 },
794
ca6b14b6
RK
795 set: function(aKey, aValue) {
796 // First update the shadow.
797 this.shadow[aKey] = aValue;
798 // Now sync the DB with this.
7cde05f7
RK
799 if (!mainDB)
800 return;
801 var success = false;
802 var transaction = mainDB.transaction([this.objStore], "readwrite");
803 var objStore = transaction.objectStore(this.objStore);
804 var request = objStore.put(aValue, aKey);
805 request.onsuccess = function(event) {
806 success = true;
ca6b14b6 807 // Nothing else to be done!
7cde05f7
RK
808 };
809 request.onerror = function(event) {
ca6b14b6 810 // Errors could be handled here (but are ignored).
7cde05f7
RK
811 };
812 },
813
ca6b14b6
RK
814 unset: function(aKey) {
815 // First update the shadow.
816 delete this.shadow[aKey];
817 // Now sync the DB with this.
7cde05f7
RK
818 if (!mainDB)
819 return;
820 var success = false;
821 var transaction = mainDB.transaction([this.objStore], "readwrite");
822 var request = transaction.objectStore(this.objStore).delete(aKey);
823 request.onsuccess = function(event) {
824 success = true;
ca6b14b6 825 // Nothing else to be done!
7cde05f7
RK
826 };
827 request.onerror = function(event) {
ca6b14b6 828 // Errors could be handled here (but are ignored).
7cde05f7
RK
829 }
830 }
831};
832
833var gBMStore = {
834 objStore: "bookmarks",
835
836 getList: function(aCallback) {
837 if (!mainDB)
838 return;
839 var transaction = mainDB.transaction([this.objStore]);
840 var objStore = transaction.objectStore(this.objStore);
841 if (objStore.getAll) { // currently Mozilla-specific
842 objStore.getAll().onsuccess = function(event) {
843 aCallback(event.target.result);
844 };
845 }
846 else { // Use cursor (standard method).
847 var BMs = {};
848 objStore.openCursor().onsuccess = function(event) {
849 var cursor = event.target.result;
850 if (cursor) {
851 BMs[cursor.key] = cursor.value;
852 cursor.continue();
853 }
854 else {
855 aCallback(BMs);
856 }
857 };
858 }
859 },
860
861 get: function(aKey, aCallback) {
862 if (!mainDB)
863 return;
864 var transaction = mainDB.transaction([this.objStore]);
865 var request = transaction.objectStore(this.objStore).get(aKey);
866 request.onsuccess = function(event) {
867 aCallback(request.result, event);
868 };
869 request.onerror = function(event) {
870 // Errors can be handled here.
871 aCallback(undefined, event);
872 };
873 },
874
875 set: function(aKey, aValue, aCallback) {
876 if (!mainDB)
877 return;
878 var success = false;
879 var transaction = mainDB.transaction([this.objStore], "readwrite");
880 var objStore = transaction.objectStore(this.objStore);
881 var request = objStore.put(aValue, aKey);
882 request.onsuccess = function(event) {
883 success = true;
884 if (aCallback)
885 aCallback(success, event);
886 };
887 request.onerror = function(event) {
888 // Errors can be handled here.
889 if (aCallback)
890 aCallback(success, event);
891 };
892 },
893
894 unset: function(aKey, aCallback) {
895 if (!mainDB)
896 return;
897 var success = false;
898 var transaction = mainDB.transaction([this.objStore], "readwrite");
899 var request = transaction.objectStore(this.objStore).delete(aKey);
900 request.onsuccess = function(event) {
901 success = true;
902 if (aCallback)
903 aCallback(success, event);
904 };
905 request.onerror = function(event) {
906 // Errors can be handled here.
907 if (aCallback)
908 aCallback(success, event);
909 }
910 },
911
912 clear: function(aCallback) {
913 if (!mainDB)
914 return;
915 var success = false;
916 var transaction = mainDB.transaction([this.objStore], "readwrite");
917 var request = transaction.objectStore(this.objStore).clear();
918 request.onsuccess = function(event) {
919 success = true;
920 if (aCallback)
921 aCallback(success, event);
922 };
923 request.onerror = function(event) {
924 // Errors can be handled here.
925 if (aCallback)
926 aCallback(success, event);
927 }
928 }
929};