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