make UI prefs actually work, add menus for debug options, i.e. turning TraceMonkey...
[mandelbrot.git] / xulapp / chrome / mandelbrot / content / mandelbrot.js
1 var gColorPalette = [];
2 var gPref = Components.classes["@mozilla.org/preferences-service;1"]
3                       .getService(Components.interfaces.nsIPrefService)
4                       .getBranch(null);
5 var gStartTime = 0;
6
7 function Startup() {
8   updateIterMenu();
9   updatePaletteMenu();
10   document.getElementById("statusLabel").value =
11       document.getElementById("mbrotBundle").getString("statusEmpty");
12 }
13
14 function drawImage() {
15   let canvas = document.getElementById("mbrotImage");
16   if (canvas.getContext) {
17     let context = canvas.getContext("2d");
18
19     document.getElementById("statusLabel").value =
20         document.getElementById("mbrotBundle").getString("statusDrawing");
21
22     // example:
23     // context.fillStyle = "rgb(200,0,0)";
24     // context.fillRect (10, 10, 55, 50); // x, y, width, height
25     //
26     // context.fillStyle = "rgba(0, 0, 200, 0.5)";
27     // context.fillRect (30, 30, 55, 50);
28
29     let Cr_min = -2.0;
30     let Cr_max = 1.0;
31     let Cr_scale = Cr_max - Cr_min;
32
33     let Ci_min = -1.5;
34     let Ci_max = 1.5;
35     let Ci_scale = Ci_max - Ci_min;
36
37     let iterMax = gPref.getIntPref("mandelbrot.iteration_max");
38     let algorithm = gPref.getCharPref("mandelbrot.use_algorithm");
39
40     gStartTime = new Date();
41
42     for (let img_x = 0; img_x < canvas.width; img_x++) {
43       for (let img_y = 0; img_y < canvas.height; img_y++) {
44         let C = new complex(Cr_min + (img_x / canvas.width) * Cr_scale,
45                             Ci_min + (img_y / canvas.height) * Ci_scale);
46         window.setTimeout(drawPoint, 0, context, img_x, img_y, C, iterMax, algorithm);
47       }
48     }
49     window.setTimeout(EndCalc, 0);
50   }
51 }
52
53 function EndCalc() {
54   let endTime = new Date();
55   let timeUsed = (endTime.getTime() - gStartTime.getTime()) / 1000;
56   document.getElementById("statusLabel").value =
57       document.getElementById("mbrotBundle").getFormattedString("statusTime", [timeUsed.toFixed(3)]);
58 }
59
60 function complex(aReal, aImag) {
61   this.r = aReal;
62   this.i = aImag;
63   this.square = function() {
64     return new complex(this.r * this.r - this.i * this.i,
65                        2 * this.r * this.i);
66   }
67   this.dist = function() {
68     return Math.sqrt(this.r * this.r + this.i * this.i);
69   }
70   this.add = function(aComplex) {
71     return new complex(this.r + aComplex.r, this.i + aComplex.i);
72   }
73 }
74
75 function mandelbrotValueOO (aC, aIterMax) {
76   // this would be nice code in general but it looks like JS objects are too heavy for normal use.
77   let Z = new complex(0.0, 0.0);
78   for (var iter = 0; iter < aIterMax; iter++) {
79     Z = Z.square().add(aC);
80     if (Z.r * Z.r + Z.i * Z.i > 256) { break; }
81   }
82   return iter;
83 }
84
85 function mandelbrotValueNumeric (aC, aIterMax) {
86   // optimized numeric code for fast calculation
87   let Cr = aC.r, Ci = aC.i;
88   let Zr = 0.0, Zi = 0.0;
89   let Zr2 = Zr * Zr, Zi2 = Zi * Zi;
90   for (var iter = 0; iter < aIterMax; iter++) {
91     Zi = 2 * Zr * Zi + Ci;
92     Zr = Zr2 - Zi2 + Cr;
93
94     Zr2 = Zr * Zr; Zi2 = Zi * Zi;
95     if (Zr2 + Zi2 > 256) { break; }
96   }
97   return iter;
98 }
99
100 function getColor(aIterValue, aIterMax) {
101   let standardizedValue = Math.round(aIterValue * 1024 / aIterMax);
102   if (gColorPalette && gColorPalette.length)
103     return gColorPalette[standardizedValue];
104
105   // fallback to simple b/w if for some reason we don't have a palette
106   if (aIterValue == aIterMax)
107     return "rgb(0,0,0)";
108   else
109     return "rgb(255,255,255)";
110 }
111
112 function getColorPalette(palName) {
113   var palette = [];
114   switch (palName) {
115     case 'bw':
116       for (let i = 0; i < 1024; i++) {
117         palette[i] = 'rgb(255,255,255)';
118       }
119       palette[1024] = 'rgb(0,0,0)';
120       break;
121     case 'kairo':
122       // outer areas
123       for (let i = 0; i < 32; i++) {
124         let cc1 = Math.floor(i * 127 / 31);
125         let cc2 = 170 - Math.floor(i * 43 / 31);
126         palette[i] = 'rgb(' + cc1 + ',' + cc2 + ',' + cc1 + ')';
127       }
128       // inner areas
129       for (let i = 0; i < 51; i++) {
130         let cc = Math.floor(i * 170 / 50);
131         palette[32 + i] = 'rgb(' + cc + ',0,' + (170 + cc) + ')';
132       }
133       // corona
134       for (let i = 0; i < 101; i++) {
135         let cc = Math.floor(i * 200 / 100);
136         palette[83 + i] = 'rgb(255,' + cc + ',0)';
137       }
138       // inner corona
139       for (let i = 0; i < 201; i++) {
140         let cc1 = 255 - Math.floor(i * 85 / 200);
141         let cc2 = 200 - Math.floor(i * 30 / 200);
142         let cc3 = Math.floor(i * 170 / 200);
143         palette[184 + i] = 'rgb(' + cc1 + ',' + cc2 + ',' + cc3 + ')';
144       }
145       for (let i = 0; i < 301; i++) {
146         let cc1 = 170 - Math.floor(i * 43 / 300);
147         let cc2 = 170 + Math.floor(i * 85 / 300);
148         palette[385 + i] = 'rgb(' + cc1 + ',' + cc1 + ',' + cc2 + ')';
149       }
150       for (let i = 0; i < 338; i++) {
151         let cc = 127 + Math.floor(i * 128 / 337);
152         palette[686 + i] = 'rgb(' + cc + ',' + cc + ',255)';
153       }
154       palette[1024] = 'rgb(0,0,0)';
155       break;
156     case 'rainbow-linear1':
157       for (let i = 0; i < 256; i++) {
158         palette[i] = 'rgb(' + i + ',0,0)';
159         palette[256 + i] = 'rgb(255,' + i + ',0)';
160         palette[512 + i] = 'rgb(' + (255 - i) + ',255,' + i + ')';
161         palette[768 + i] = 'rgb(' + i + ',' + (255 - i) + ',255)';
162       }
163       palette[1024] = 'rgb(0,0,0)';
164       break;
165   }
166 /*
167 Select Case palnr
168 Case 1  'Standard-Palette (QB-Colors)
169      For i = 0 To 1024
170          xx = CInt(i * 500 / 1024 + 2)
171          If xx <= 15 Then clr = xx
172          If xx > 15 Then clr = CInt(Sqr((xx - 15 + 1) * 15 ^ 2 / 485))
173          If xx >= 500 Then clr = 0
174          palette(i) = QBColor(clr)
175      Next
176 Case 3  'Regenbogen-Palette 1 (qu.)
177      For i = 0 To 33
178          clr = CInt(i * 255 / 33)
179          palette(i) = RGB(clr, 0, 0)
180      Next
181      For i = 0 To 136
182          clr = CInt(i * 255 / 136)
183          palette(34 + i) = RGB(255, clr, 0)
184      Next
185      For i = 0 To 306
186          clr = CInt(i * 255 / 306)
187          palette(171 + i) = RGB(255 - clr, 255, clr)
188      Next
189      For i = 0 To 545
190          clr = CInt(i * 255 / 545)
191          palette(478 + i) = RGB(clr, 255 - clr, 255)
192      Next
193 Case 4  'Regenbogen-Palette 2 (linear)
194      For i = 0 To 204
195          clr = CInt(i * 255 / 204)
196          palette(i) = RGB(255, clr, 0)
197          palette(204 + i) = RGB(255 - clr, 255, 0)
198          palette(409 + i) = RGB(0, 255, clr)
199          palette(614 + i) = RGB(0, 255 - clr, 255)
200          palette(819 + i) = RGB(clr, 0, 255)
201      Next
202 Case 5  'Regenbogen-Palette 2 (qu.)
203      For i = 0 To 18
204          clr = CInt(i * 255 / 18)
205          palette(i) = RGB(255, clr, 0)
206      Next
207      For i = 0 To 73
208          clr = CInt(i * 255 / 73)
209          palette(20 + i) = RGB(255 - clr, 255, 0)
210      Next
211      For i = 0 To 167
212          clr = CInt(i * 255 / 167)
213          palette(93 + i) = RGB(0, 255, clr)
214      Next
215      For i = 0 To 297
216          clr = CInt(i * 255 / 297)
217          palette(261 + i) = RGB(0, 255 - clr, 255)
218      Next
219      For i = 0 To 464
220          clr = CInt(i * 255 / 464)
221          palette(559 + i) = RGB(clr, 0, 255)
222      Next
223 */
224   return palette;
225 }
226
227 function drawPoint(context, img_x, img_y, C, iterMax, algorithm) {
228   var itVal;
229   switch (algorithm) {
230     case 'oo':
231       itVal = mandelbrotValueOO(C, iterMax);
232       break;
233     case 'numeric':
234     default:
235       itVal = mandelbrotValueNumeric(C, iterMax);
236       break;
237   }
238   context.fillStyle = getColor(itVal, iterMax);
239   context.fillRect (img_x, img_y, 1, 1); // x, y, width, height
240 }
241
242 /***** pure UI functions *****/
243
244 function saveImage() {
245   // XXX: should call filepicker!
246   saveCanvas(document.getElementById("mbrotImage"), "/home/robert/temp/canvas-save.png")
247 }
248
249 function updateIterMenu() {
250   try {
251     var currentIter = gPref.getIntPref("mandelbrot.iteration_max");
252   }
253   catch(e) {
254     var currentIter = 0;
255   }
256   if (currentIter < 10) {
257     currentIter = 500;
258     setIter(currentIter);
259   }
260
261   var popup = document.getElementById("menu_iterPopup");
262   var item = popup.firstChild;
263   while (item) {
264     if (item.getAttribute("name") == "iter") {
265       if (item.getAttribute("value") == currentIter)
266         item.setAttribute("checked","true");
267       else
268         item.removeAttribute("checked");
269     }
270     item = item.nextSibling;
271   }
272 }
273
274 function setIter(aIter) {
275   gPref.setIntPref("mandelbrot.iteration_max", aIter);
276 }
277
278 function updatePaletteMenu() {
279   try {
280     var currentPalette = gPref.getCharPref("mandelbrot.color_palette");
281   }
282   catch(e) {
283     var currentPalette = '';
284   }
285   if (!currentPalette.length) {
286     currentPalette = 'kairo';
287     setPalette(currentPalette);
288   }
289   if (!gColorPalette || !gColorPalette.length)
290     gColorPalette = getColorPalette(currentPalette);
291
292   var popup = document.getElementById("menu_palettePopup");
293   var item = popup.firstChild;
294   while (item) {
295     if (item.getAttribute("name") == "palette") {
296       if (item.getAttribute("value") == currentPalette)
297         item.setAttribute("checked", "true");
298       else
299         item.removeAttribute("checked");
300     }
301     item = item.nextSibling;
302   }
303 }
304
305 function setPalette(aPaletteID) {
306   gPref.setCharPref("mandelbrot.color_palette", aPaletteID);
307   gColorPalette = getColorPalette(aPaletteID);
308 }
309
310 function updateDebugMenu() {
311   var jitMenuItem = document.getElementById("jitEnabled");
312   jitMenuItem.setAttribute("checked", gPref.getBoolPref("javascript.options.jit.chrome"));
313 }
314
315 function toggleJITState(jitMenuItem) {
316   var jitEnabled = !gPref.getBoolPref("javascript.options.jit.chrome");
317   gPref.setBoolPref("javascript.options.jit.chrome", jitEnabled)
318   jitMenuItem.setAttribute("checked", jitEnabled? "true" : "false");
319 }
320
321 function updateAlgoMenu() {
322   try {
323     var currentAlgo = gPref.getCharPref("mandelbrot.use_algorithm");
324   }
325   catch(e) {
326     var currentAlgo = '';
327   }
328   if (!currentAlgo.length) {
329     currentAlgo = 'numeric';
330     setAlgorithm(currentAlgo);
331   }
332
333   var popup = document.getElementById("menu_algoPopup");
334   var item = popup.firstChild;
335   while (item) {
336     if (item.getAttribute("name") == "algorithm") {
337       if (item.getAttribute("value") == currentAlgo)
338         item.setAttribute("checked", "true");
339       else
340         item.removeAttribute("checked");
341     }
342     item = item.nextSibling;
343   }
344 }
345
346 function setAlgorithm(algoID) {
347   gPref.setCharPref("mandelbrot.use_algorithm", algoID);
348 }
349
350
351 /***** helper functions from external sources *****/
352
353 // function below is from from http://developer.mozilla.org/en/docs/Code_snippets:Canvas
354 function saveCanvas(canvas, destFile) {
355   // convert string filepath to an nsIFile
356   var file = Components.classes["@mozilla.org/file/local;1"]
357                        .createInstance(Components.interfaces.nsILocalFile);
358   file.initWithPath(destFile);
359
360   // create a data url from the canvas and then create URIs of the source and targets  
361   var io = Components.classes["@mozilla.org/network/io-service;1"]
362                      .getService(Components.interfaces.nsIIOService);
363   var source = io.newURI(canvas.toDataURL("image/png", ""), "UTF8", null);
364   var target = io.newFileURI(file);
365
366   // prepare to save the canvas data
367   var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
368                           .createInstance(Components.interfaces.nsIWebBrowserPersist);
369
370   persist.persistFlags = Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
371   persist.persistFlags |= Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
372
373   // displays a download dialog (remove these 3 lines for silent download)
374   var xfer = Components.classes["@mozilla.org/transfer;1"]
375                        .createInstance(Components.interfaces.nsITransfer);
376   xfer.init(source, target, "", null, null, null, persist);
377   persist.progressListener = xfer;
378
379   // save the canvas data to the file
380   persist.saveURI(source, null, null, null, null, file);
381 }
382
383 // function below is from http://developer.mozilla.org/en/docs/How_to_Quit_a_XUL_Application
384 function quitApp(aForceQuit) {
385   var appStartup = Components.classes['@mozilla.org/toolkit/app-startup;1']
386                              .getService(Components.interfaces.nsIAppStartup);
387
388   // eAttemptQuit will try to close each XUL window, but the XUL window can cancel the quit
389   // process if there is unsaved data. eForceQuit will quit no matter what.
390   var quitSeverity = aForceQuit ? Components.interfaces.nsIAppStartup.eForceQuit :
391                                   Components.interfaces.nsIAppStartup.eAttemptQuit;
392   appStartup.quit(quitSeverity);
393 }