actually make filepicker work for saving the image
[mandelbrot.git] / xulapp / chrome / mandelbrot / content / mandelbrot.js
CommitLineData
5b823560
RK
1/* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is KaiRo.at Mandelbrot, XULRunner version.
15 *
16 * The Initial Developer of the Original Code is
17 * Robert Kaiser <kairo@kairo.at>.
18 * Portions created by the Initial Developer are Copyright (C) 2008
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Contributor(s):
22 * Robert Kaiser <kairo@kairo.at>
23 *
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
35 *
36 * ***** END LICENSE BLOCK ***** */
37
6e98af87
RK
38var gColorPalette = [];
39var gPref = Components.classes["@mozilla.org/preferences-service;1"]
40 .getService(Components.interfaces.nsIPrefService)
41 .getBranch(null);
42var gStartTime = 0;
43
44function Startup() {
45 updateIterMenu();
46 updatePaletteMenu();
47 document.getElementById("statusLabel").value =
48 document.getElementById("mbrotBundle").getString("statusEmpty");
49}
37b05b56
RK
50
51function drawImage() {
8a9c8e3f 52 let canvas = document.getElementById("mbrotImage");
2cb9a6b5 53 let context = canvas.getContext("2d");
37b05b56 54
2cb9a6b5
RK
55 document.getElementById("statusLabel").value =
56 document.getElementById("mbrotBundle").getString("statusDrawing");
57
58 let iterMax = gPref.getIntPref("mandelbrot.iteration_max");
59 let algorithm = gPref.getCharPref("mandelbrot.use_algorithm");
6e98af87 60
2cb9a6b5
RK
61 context.fillStyle = "rgb(255, 255, 255)";
62 context.fillRect(0, 0, canvas.width, canvas.height);
37b05b56 63
2cb9a6b5
RK
64 gStartTime = new Date();
65
66 drawLine(0, canvas, context, iterMax, algorithm);
67}
68
69function drawLine(line, canvas, context, iterMax, algorithm) {
8a9c8e3f
RK
70 let Cr_min = -2.0;
71 let Cr_max = 1.0;
72 let Cr_scale = Cr_max - Cr_min;
37b05b56 73
8a9c8e3f
RK
74 let Ci_min = -1.5;
75 let Ci_max = 1.5;
76 let Ci_scale = Ci_max - Ci_min;
37b05b56 77
2cb9a6b5
RK
78 let pixels = [];
79 for (var img_y = line; img_y < canvas.height && img_y < line+8; img_y++)
80 for (let img_x = 0; img_x < canvas.width; img_x++) {
8a9c8e3f 81 let C = new complex(Cr_min + (img_x / canvas.width) * Cr_scale,
37b05b56 82 Ci_min + (img_y / canvas.height) * Ci_scale);
2cb9a6b5 83 pixels.push.apply(pixels, drawPoint(context, img_x, img_y, C, iterMax, algorithm));
37b05b56 84 }
2cb9a6b5
RK
85 context.putImageData({width: canvas.width, height: pixels.length/4/canvas.width, data: pixels}, 0, line);
86
87 if (img_y < canvas.height)
88 setTimeout(drawLine, 0, img_y, canvas, context, iterMax, algorithm);
89 else
90 EndCalc();
37b05b56
RK
91}
92
6e98af87
RK
93function EndCalc() {
94 let endTime = new Date();
95 let timeUsed = (endTime.getTime() - gStartTime.getTime()) / 1000;
96 document.getElementById("statusLabel").value =
97 document.getElementById("mbrotBundle").getFormattedString("statusTime", [timeUsed.toFixed(3)]);
98}
99
37b05b56
RK
100function complex(aReal, aImag) {
101 this.r = aReal;
102 this.i = aImag;
2cb9a6b5
RK
103}
104complex.prototype = {
105 square: function() {
37b05b56
RK
106 return new complex(this.r * this.r - this.i * this.i,
107 2 * this.r * this.i);
2cb9a6b5
RK
108 },
109 dist: function() {
37b05b56 110 return Math.sqrt(this.r * this.r + this.i * this.i);
2cb9a6b5
RK
111 },
112 add: function(aComplex) {
37b05b56
RK
113 return new complex(this.r + aComplex.r, this.i + aComplex.i);
114 }
115}
116
6e98af87
RK
117function mandelbrotValueOO (aC, aIterMax) {
118 // this would be nice code in general but it looks like JS objects are too heavy for normal use.
8444612a 119 let Z = new complex(0.0, 0.0);
37b05b56
RK
120 for (var iter = 0; iter < aIterMax; iter++) {
121 Z = Z.square().add(aC);
122 if (Z.r * Z.r + Z.i * Z.i > 256) { break; }
123 }
6e98af87
RK
124 return iter;
125}
8444612a 126
6e98af87
RK
127function mandelbrotValueNumeric (aC, aIterMax) {
128 // optimized numeric code for fast calculation
8444612a
RK
129 let Cr = aC.r, Ci = aC.i;
130 let Zr = 0.0, Zi = 0.0;
131 let Zr2 = Zr * Zr, Zi2 = Zi * Zi;
132 for (var iter = 0; iter < aIterMax; iter++) {
133 Zi = 2 * Zr * Zi + Ci;
134 Zr = Zr2 - Zi2 + Cr;
135
136 Zr2 = Zr * Zr; Zi2 = Zi * Zi;
137 if (Zr2 + Zi2 > 256) { break; }
138 }
37b05b56
RK
139 return iter;
140}
141
142function getColor(aIterValue, aIterMax) {
8a9c8e3f 143 let standardizedValue = Math.round(aIterValue * 1024 / aIterMax);
6e98af87
RK
144 if (gColorPalette && gColorPalette.length)
145 return gColorPalette[standardizedValue];
146
147 // fallback to simple b/w if for some reason we don't have a palette
148 if (aIterValue == aIterMax)
2cb9a6b5 149 return [0, 0, 0, 255];
6e98af87 150 else
2cb9a6b5 151 return [255, 255, 255, 255];
37b05b56
RK
152}
153
154function getColorPalette(palName) {
155 var palette = [];
156 switch (palName) {
157 case 'bw':
8a9c8e3f 158 for (let i = 0; i < 1024; i++) {
2cb9a6b5 159 palette[i] = [255, 255, 255, 255];
37b05b56 160 }
2cb9a6b5 161 palette[1024] = [0, 0, 0, 255];
37b05b56
RK
162 break;
163 case 'kairo':
164 // outer areas
8a9c8e3f
RK
165 for (let i = 0; i < 32; i++) {
166 let cc1 = Math.floor(i * 127 / 31);
167 let cc2 = 170 - Math.floor(i * 43 / 31);
2cb9a6b5 168 palette[i] = [cc1, cc2, cc1, 255];
37b05b56
RK
169 }
170 // inner areas
8a9c8e3f
RK
171 for (let i = 0; i < 51; i++) {
172 let cc = Math.floor(i * 170 / 50);
2cb9a6b5 173 palette[32 + i] = [cc, 0, (170-cc), 255];
37b05b56
RK
174 }
175 // corona
8a9c8e3f
RK
176 for (let i = 0; i < 101; i++) {
177 let cc = Math.floor(i * 200 / 100);
2cb9a6b5 178 palette[83 + i] = [255, cc, 0, 255];
37b05b56
RK
179 }
180 // inner corona
8a9c8e3f
RK
181 for (let i = 0; i < 201; i++) {
182 let cc1 = 255 - Math.floor(i * 85 / 200);
183 let cc2 = 200 - Math.floor(i * 30 / 200);
184 let cc3 = Math.floor(i * 170 / 200);
2cb9a6b5 185 palette[184 + i] = [cc1, cc2, cc3, 255];
37b05b56 186 }
8a9c8e3f
RK
187 for (let i = 0; i < 301; i++) {
188 let cc1 = 170 - Math.floor(i * 43 / 300);
189 let cc2 = 170 + Math.floor(i * 85 / 300);
2cb9a6b5 190 palette[385 + i] = [cc1, cc1, cc2, 255];
37b05b56 191 }
8a9c8e3f
RK
192 for (let i = 0; i < 338; i++) {
193 let cc = 127 + Math.floor(i * 128 / 337);
2cb9a6b5 194 palette[686 + i] = [cc, cc, 255, 255];
37b05b56 195 }
2cb9a6b5 196 palette[1024] = [0, 0, 0, 255];
37b05b56
RK
197 break;
198 case 'rainbow-linear1':
8a9c8e3f 199 for (let i = 0; i < 256; i++) {
2cb9a6b5
RK
200 palette[i] = [i, 0, 0, 255];
201 palette[256 + i] = [255, i, 0, 255];
202 palette[512 + i] = [255 - i, 255, i, 255];
203 palette[768 + i] = [i, 255-i, 255, 255];
37b05b56 204 }
2cb9a6b5 205 palette[1024] = [0, 0, 0, 255];
37b05b56
RK
206 break;
207 }
208/*
209Select Case palnr
210Case 1 'Standard-Palette (QB-Colors)
211 For i = 0 To 1024
212 xx = CInt(i * 500 / 1024 + 2)
213 If xx <= 15 Then clr = xx
214 If xx > 15 Then clr = CInt(Sqr((xx - 15 + 1) * 15 ^ 2 / 485))
215 If xx >= 500 Then clr = 0
216 palette(i) = QBColor(clr)
217 Next
218Case 3 'Regenbogen-Palette 1 (qu.)
219 For i = 0 To 33
220 clr = CInt(i * 255 / 33)
221 palette(i) = RGB(clr, 0, 0)
222 Next
223 For i = 0 To 136
224 clr = CInt(i * 255 / 136)
225 palette(34 + i) = RGB(255, clr, 0)
226 Next
227 For i = 0 To 306
228 clr = CInt(i * 255 / 306)
229 palette(171 + i) = RGB(255 - clr, 255, clr)
230 Next
231 For i = 0 To 545
232 clr = CInt(i * 255 / 545)
233 palette(478 + i) = RGB(clr, 255 - clr, 255)
234 Next
235Case 4 'Regenbogen-Palette 2 (linear)
236 For i = 0 To 204
237 clr = CInt(i * 255 / 204)
238 palette(i) = RGB(255, clr, 0)
239 palette(204 + i) = RGB(255 - clr, 255, 0)
240 palette(409 + i) = RGB(0, 255, clr)
241 palette(614 + i) = RGB(0, 255 - clr, 255)
242 palette(819 + i) = RGB(clr, 0, 255)
243 Next
244Case 5 'Regenbogen-Palette 2 (qu.)
245 For i = 0 To 18
246 clr = CInt(i * 255 / 18)
247 palette(i) = RGB(255, clr, 0)
248 Next
249 For i = 0 To 73
250 clr = CInt(i * 255 / 73)
251 palette(20 + i) = RGB(255 - clr, 255, 0)
252 Next
253 For i = 0 To 167
254 clr = CInt(i * 255 / 167)
255 palette(93 + i) = RGB(0, 255, clr)
256 Next
257 For i = 0 To 297
258 clr = CInt(i * 255 / 297)
259 palette(261 + i) = RGB(0, 255 - clr, 255)
260 Next
261 For i = 0 To 464
262 clr = CInt(i * 255 / 464)
263 palette(559 + i) = RGB(clr, 0, 255)
264 Next
265*/
266 return palette;
267}
268
6e98af87
RK
269function drawPoint(context, img_x, img_y, C, iterMax, algorithm) {
270 var itVal;
271 switch (algorithm) {
272 case 'oo':
273 itVal = mandelbrotValueOO(C, iterMax);
274 break;
275 case 'numeric':
276 default:
277 itVal = mandelbrotValueNumeric(C, iterMax);
278 break;
279 }
2cb9a6b5 280 return getColor(itVal, iterMax);
37b05b56
RK
281}
282
6e98af87
RK
283/***** pure UI functions *****/
284
37b05b56 285function saveImage() {
740b86d1
RK
286 const bundle = document.getElementById("mbrotBundle");
287 const nsIFilePicker = Components.interfaces.nsIFilePicker;
288 var fp = null;
289 try {
290 fp = Components.classes["@mozilla.org/filepicker;1"]
291 .createInstance(nsIFilePicker);
292 } catch (e) {}
293 if (!fp) return;
294 var promptString = bundle.getString("savePrompt");
295 fp.init(window, promptString, nsIFilePicker.modeSave);
296 fp.appendFilter(bundle.getString("pngFilterName"), "*.png");
297 fp.defaultString = "mandelbrot.png";
298
299 var fpResult = fp.show();
300 if (fpResult != nsIFilePicker.returnCancel) {
301 saveCanvas(document.getElementById("mbrotImage"), fp.file);
302 }
37b05b56
RK
303}
304
6e98af87
RK
305function updateIterMenu() {
306 try {
307 var currentIter = gPref.getIntPref("mandelbrot.iteration_max");
308 }
309 catch(e) {
310 var currentIter = 0;
311 }
312 if (currentIter < 10) {
313 currentIter = 500;
314 setIter(currentIter);
315 }
316
317 var popup = document.getElementById("menu_iterPopup");
318 var item = popup.firstChild;
319 while (item) {
320 if (item.getAttribute("name") == "iter") {
321 if (item.getAttribute("value") == currentIter)
322 item.setAttribute("checked","true");
323 else
324 item.removeAttribute("checked");
325 }
326 item = item.nextSibling;
327 }
328}
329
330function setIter(aIter) {
331 gPref.setIntPref("mandelbrot.iteration_max", aIter);
332}
333
334function updatePaletteMenu() {
335 try {
336 var currentPalette = gPref.getCharPref("mandelbrot.color_palette");
337 }
338 catch(e) {
339 var currentPalette = '';
340 }
341 if (!currentPalette.length) {
342 currentPalette = 'kairo';
343 setPalette(currentPalette);
344 }
345 if (!gColorPalette || !gColorPalette.length)
346 gColorPalette = getColorPalette(currentPalette);
347
348 var popup = document.getElementById("menu_palettePopup");
349 var item = popup.firstChild;
350 while (item) {
351 if (item.getAttribute("name") == "palette") {
352 if (item.getAttribute("value") == currentPalette)
353 item.setAttribute("checked", "true");
354 else
355 item.removeAttribute("checked");
356 }
357 item = item.nextSibling;
358 }
359}
360
361function setPalette(aPaletteID) {
362 gPref.setCharPref("mandelbrot.color_palette", aPaletteID);
363 gColorPalette = getColorPalette(aPaletteID);
364}
365
366function updateDebugMenu() {
367 var jitMenuItem = document.getElementById("jitEnabled");
368 jitMenuItem.setAttribute("checked", gPref.getBoolPref("javascript.options.jit.chrome"));
369}
370
371function toggleJITState(jitMenuItem) {
372 var jitEnabled = !gPref.getBoolPref("javascript.options.jit.chrome");
373 gPref.setBoolPref("javascript.options.jit.chrome", jitEnabled)
374 jitMenuItem.setAttribute("checked", jitEnabled? "true" : "false");
375}
376
377function updateAlgoMenu() {
378 try {
379 var currentAlgo = gPref.getCharPref("mandelbrot.use_algorithm");
380 }
381 catch(e) {
382 var currentAlgo = '';
383 }
384 if (!currentAlgo.length) {
385 currentAlgo = 'numeric';
386 setAlgorithm(currentAlgo);
387 }
388
389 var popup = document.getElementById("menu_algoPopup");
390 var item = popup.firstChild;
391 while (item) {
392 if (item.getAttribute("name") == "algorithm") {
393 if (item.getAttribute("value") == currentAlgo)
394 item.setAttribute("checked", "true");
395 else
396 item.removeAttribute("checked");
397 }
398 item = item.nextSibling;
399 }
400}
401
402function setAlgorithm(algoID) {
403 gPref.setCharPref("mandelbrot.use_algorithm", algoID);
404}
405
406
407/***** helper functions from external sources *****/
408
740b86d1
RK
409// function below is based on http://developer.mozilla.org/en/docs/Code_snippets:Canvas
410// custom modifications:
411// - use "a"-prefix on function arguments
412// - take an nsILocalFile as aDestFile argument
413// - always do silent download
414function saveCanvas(aCanvas, aDestFile) {
37b05b56
RK
415 // create a data url from the canvas and then create URIs of the source and targets
416 var io = Components.classes["@mozilla.org/network/io-service;1"]
417 .getService(Components.interfaces.nsIIOService);
740b86d1 418 var source = io.newURI(aCanvas.toDataURL("image/png", ""), "UTF8", null);
37b05b56
RK
419
420 // prepare to save the canvas data
421 var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
422 .createInstance(Components.interfaces.nsIWebBrowserPersist);
423
424 persist.persistFlags = Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
425 persist.persistFlags |= Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
426
37b05b56 427 // save the canvas data to the file
740b86d1 428 persist.saveURI(source, null, null, null, null, aDestFile);
37b05b56
RK
429}
430
431// function below is from http://developer.mozilla.org/en/docs/How_to_Quit_a_XUL_Application
432function quitApp(aForceQuit) {
433 var appStartup = Components.classes['@mozilla.org/toolkit/app-startup;1']
434 .getService(Components.interfaces.nsIAppStartup);
435
436 // eAttemptQuit will try to close each XUL window, but the XUL window can cancel the quit
437 // process if there is unsaved data. eForceQuit will quit no matter what.
438 var quitSeverity = aForceQuit ? Components.interfaces.nsIAppStartup.eForceQuit :
439 Components.interfaces.nsIAppStartup.eAttemptQuit;
440 appStartup.quit(quitSeverity);
441}