f90abca0665ee1223d984f85d93ac7aaa99eebe6
[mandelbrot-web.git] / js / mandelbrot.js
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/. */
4
5 // Get the best-available indexedDB object.
6 var iDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
7 var mainDB;
8
9 var gMainCanvas, gMainContext;
10 var gColorPalette = [];
11 var gStartTime = 0;
12 var gCurrentImageData;
13 var gLastImageData;
14
15 function Startup() {
16   initDB();
17
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";
32   initTile.onload = function() { gMainContext.drawImage(initTile, 0, 0); };
33 }
34
35 function 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
63 function 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;
75         gPrefs.set(prefname, value);
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       }
91       gPrefs.set("Cr_min", Cr_min);
92       gPrefs.set("Cr_max", Cr_max);
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       }
108       gPrefs.set("Ci_min", Ci_min);
109       gPrefs.set("Ci_max", Ci_max);
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) {
150         gPrefs.set(prefname, value);
151         document.getElementById("proportional").value = value;
152       }
153       return value;
154     default:
155       return false;
156   }
157 }
158
159 function setVal(aName, aValue) {
160   switch (aName) {
161     case "image.width":
162     case "image.height":
163       gPrefs.set(aName, aValue);
164       document.getElementById(aName.replace(".", "_")).value = aValue;
165       break;
166     case "last_image.Cr_*":
167       gPrefs.set("Cr_min", aValue.Cr_min);
168       gPrefs.set("Cr_max", aValue.Cr_max);
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_*":
173       gPrefs.set("Ci_min", aValue.Ci_min);
174       gPrefs.set("Ci_max", aValue.Ci_max);
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":
185       setPalette(aValue);
186       break;
187    case "syncProportions":
188       gPrefs.set(aName, aValue);
189       document.getElementById("proportional").value = aValue;
190       break;
191   }
192 }
193
194 function 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
203 function 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
233 function checkProportions() {
234   if (!document.getElementById("proportional").checked) {
235     recalcCoord("Cr", "scale");
236   }
237 }
238
239 function roundCoord(floatval) {
240   // We should round to 10 decimals here or so
241   return parseFloat(floatval.toFixed(10));
242 }
243
244 function 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 }
277
278 function drawImage() {
279   var canvas = gMainCanvas;
280   var context = gMainContext;
281
282   document.getElementById("calcTime").textContent = "--";
283
284   if (gCurrentImageData) {
285     gLastImageData = gCurrentImageData;
286     document.getElementById("backButton").disabled = false;
287   }
288
289   gColorPalette = getColorPalette(document.getElementById("palette").value);
290
291   var Cr_vals = getAdjustVal("last_image.Cr_*");
292   var Cr_min = Cr_vals.Cr_min;
293   var Cr_max = Cr_vals.Cr_max;
294
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");
301
302   var iWidth = getAdjustVal("image.width");
303   var iHeight = getAdjustVal("image.height");
304
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
313   canvas.width = iWidth;
314   canvas.height = iHeight;
315
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
325 function 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
356 function 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
362 function complex(aReal, aImag) {
363   this.r = aReal;
364   this.i = aImag;
365 }
366 complex.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
379 function 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
389 function 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
404 function 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
416 function 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;
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;
522   }
523   return palette;
524 }
525
526 function 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
542 var zoomstart;
543 var imgBackup;
544 var zoomTouchID;
545
546 var 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
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           }
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         }
624         break;
625     }
626   }
627 };
628
629 function drawIfEmpty() {
630   if (!gCurrentImageData) {
631     drawImage();
632   }
633 }
634
635 function 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   }
643 }
644
645 function 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;
651     document.getElementById("backButton").disabled = true;
652   }
653 }
654
655 function setIter(aIter) {
656   gPrefs.set("iteration_max", aIter);
657   document.getElementById("iterMax").value = aIter;
658 }
659
660 function setPalette(aPaletteID) {
661   gPrefs.set("color_palette", aPaletteID);
662   document.getElementById("palette").value = aPaletteID;
663   gColorPalette = getColorPalette(aPaletteID);
664 }
665
666 function setAlgorithm(algoID) {
667   gPrefs.set("use_algorithm", algoID);
668   //document.getElementById("algorithm").value = algoID;
669 }
670
671 var 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
726 var 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 };