add patch by prefiks pointed out in http://home.kairo.at/blog/2008-08/kairo_at_mandel...
[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() {
6e98af87 286 // XXX: should call filepicker!
37b05b56
RK
287 saveCanvas(document.getElementById("mbrotImage"), "/home/robert/temp/canvas-save.png")
288}
289
6e98af87
RK
290function updateIterMenu() {
291 try {
292 var currentIter = gPref.getIntPref("mandelbrot.iteration_max");
293 }
294 catch(e) {
295 var currentIter = 0;
296 }
297 if (currentIter < 10) {
298 currentIter = 500;
299 setIter(currentIter);
300 }
301
302 var popup = document.getElementById("menu_iterPopup");
303 var item = popup.firstChild;
304 while (item) {
305 if (item.getAttribute("name") == "iter") {
306 if (item.getAttribute("value") == currentIter)
307 item.setAttribute("checked","true");
308 else
309 item.removeAttribute("checked");
310 }
311 item = item.nextSibling;
312 }
313}
314
315function setIter(aIter) {
316 gPref.setIntPref("mandelbrot.iteration_max", aIter);
317}
318
319function updatePaletteMenu() {
320 try {
321 var currentPalette = gPref.getCharPref("mandelbrot.color_palette");
322 }
323 catch(e) {
324 var currentPalette = '';
325 }
326 if (!currentPalette.length) {
327 currentPalette = 'kairo';
328 setPalette(currentPalette);
329 }
330 if (!gColorPalette || !gColorPalette.length)
331 gColorPalette = getColorPalette(currentPalette);
332
333 var popup = document.getElementById("menu_palettePopup");
334 var item = popup.firstChild;
335 while (item) {
336 if (item.getAttribute("name") == "palette") {
337 if (item.getAttribute("value") == currentPalette)
338 item.setAttribute("checked", "true");
339 else
340 item.removeAttribute("checked");
341 }
342 item = item.nextSibling;
343 }
344}
345
346function setPalette(aPaletteID) {
347 gPref.setCharPref("mandelbrot.color_palette", aPaletteID);
348 gColorPalette = getColorPalette(aPaletteID);
349}
350
351function updateDebugMenu() {
352 var jitMenuItem = document.getElementById("jitEnabled");
353 jitMenuItem.setAttribute("checked", gPref.getBoolPref("javascript.options.jit.chrome"));
354}
355
356function toggleJITState(jitMenuItem) {
357 var jitEnabled = !gPref.getBoolPref("javascript.options.jit.chrome");
358 gPref.setBoolPref("javascript.options.jit.chrome", jitEnabled)
359 jitMenuItem.setAttribute("checked", jitEnabled? "true" : "false");
360}
361
362function updateAlgoMenu() {
363 try {
364 var currentAlgo = gPref.getCharPref("mandelbrot.use_algorithm");
365 }
366 catch(e) {
367 var currentAlgo = '';
368 }
369 if (!currentAlgo.length) {
370 currentAlgo = 'numeric';
371 setAlgorithm(currentAlgo);
372 }
373
374 var popup = document.getElementById("menu_algoPopup");
375 var item = popup.firstChild;
376 while (item) {
377 if (item.getAttribute("name") == "algorithm") {
378 if (item.getAttribute("value") == currentAlgo)
379 item.setAttribute("checked", "true");
380 else
381 item.removeAttribute("checked");
382 }
383 item = item.nextSibling;
384 }
385}
386
387function setAlgorithm(algoID) {
388 gPref.setCharPref("mandelbrot.use_algorithm", algoID);
389}
390
391
392/***** helper functions from external sources *****/
393
37b05b56
RK
394// function below is from from http://developer.mozilla.org/en/docs/Code_snippets:Canvas
395function saveCanvas(canvas, destFile) {
396 // convert string filepath to an nsIFile
397 var file = Components.classes["@mozilla.org/file/local;1"]
398 .createInstance(Components.interfaces.nsILocalFile);
399 file.initWithPath(destFile);
400
401 // create a data url from the canvas and then create URIs of the source and targets
402 var io = Components.classes["@mozilla.org/network/io-service;1"]
403 .getService(Components.interfaces.nsIIOService);
404 var source = io.newURI(canvas.toDataURL("image/png", ""), "UTF8", null);
405 var target = io.newFileURI(file);
406
407 // prepare to save the canvas data
408 var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
409 .createInstance(Components.interfaces.nsIWebBrowserPersist);
410
411 persist.persistFlags = Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
412 persist.persistFlags |= Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
413
414 // displays a download dialog (remove these 3 lines for silent download)
415 var xfer = Components.classes["@mozilla.org/transfer;1"]
416 .createInstance(Components.interfaces.nsITransfer);
417 xfer.init(source, target, "", null, null, null, persist);
418 persist.progressListener = xfer;
419
420 // save the canvas data to the file
421 persist.saveURI(source, null, null, null, null, file);
422}
423
424// function below is from http://developer.mozilla.org/en/docs/How_to_Quit_a_XUL_Application
425function quitApp(aForceQuit) {
426 var appStartup = Components.classes['@mozilla.org/toolkit/app-startup;1']
427 .getService(Components.interfaces.nsIAppStartup);
428
429 // eAttemptQuit will try to close each XUL window, but the XUL window can cancel the quit
430 // process if there is unsaved data. eForceQuit will quit no matter what.
431 var quitSeverity = aForceQuit ? Components.interfaces.nsIAppStartup.eForceQuit :
432 Components.interfaces.nsIAppStartup.eAttemptQuit;
433 appStartup.quit(quitSeverity);
434}