Home] [About] [Posts] [Resources] [Applets]
dir: Home /
Applets /
Fractal
published-date: 20 Jun 2025 14:14 +0700
warning: max iter value above 200 will likely hang your browser tab.
1window.onload = function()
2{
3
4const canvas = document.getElementById('viewport');
5const ctx = canvas.getContext('2d', {'alpha':false});
6const ftoi = (v)=>~~v
7var frame = null;
8var fractals = {};
9var inputProxy = null;
10
11
12class Complex {
13 constructor(re, im) {this.re=re||0;this.im=im||0};
14 addc(z){this.re+=z.re;this.im+=z.im;return this};
15 mulc(z){
16 let re = (this.re * z.re) - (this.im * z.im);
17 let im = (this.re * z.im) + (this.im * z.re);
18 this.re = re; this.im = im;
19 return this
20 };
21 divc(z){
22 let t_re = z.re;
23 let t_im = z.im;
24 z.inverse_i();
25 this.mulc(z);
26 z.re = t_re;
27 z.im = t_im;
28 return this;
29 };
30 square() {return this.re**2 + this.im**2};
31 conjugate_i(){this.im *=-1};
32 inverse_i(){
33 let l = this.square();
34 this.re = this.re/l;
35 this.im = -this.im/l;
36 return this
37 };
38 inverse(){return new Complex(this.re, this.im).inverse_i()};
39 magnitude(){return Math.sqrt(this.square())};
40};
41
42
43function cInputProxy () {
44 this.values = {};
45 this.target = {};
46 this.display = {};
47 this.defaults = {};
48 this.register = (name, _default=null, cast=(v)=>v) => {
49 this.target[name] = document.getElementById(name);
50 this.display[name] = this.target[name].parentElement.querySelector("#" + name + "_value");
51 this.values[name] = cast(this.target[name].value);
52 this.defaults[name] = _default;
53 this.target[name].addEventListener("input", (event) => {
54 this.values[name] = cast(event.target.value);
55 if (this.display[name] !== null) {
56 this.display[name].innerHTML = "" + event.target.value;
57 };
58 });
59 if (_default !== null) {
60 this.target[name].value = "" + _default;
61 this.values[name] = cast(_default);
62 if (this.display[name] !== null) {
63 this.display[name].innerHTML = "" + _default;
64 };
65 };
66 return this.target[name];
67 };
68};
69
70
71class Interp {
72
73 static _bi_loop(aw, ah, bw, bh, cb) {
74 let cx, cy, rx, ry, ax, ay, bx, by, axl, axh, ayl, ayh;
75 rx = (aw-2) / (bw-1);
76 ry = (ah-2) / (bh-1);
77 for (bx=0; bx<bw; bx++) {
78 for (by=0; by<bh; by++) {
79 axl = Math.floor(rx * bx);
80 axh = Math.ceil (rx * bx);
81 ayl = Math.floor(ry * by);
82 ayh = Math.ceil (ry * by);
83 cb(
84 bx, by,
85 axl, axh, ayl, ayh,
86 rx*bx-axl, ry*by-ayl
87 )
88 };
89 };
90 };
91
92 static _bi_inv(ms, md, bi_method) {
93 Interp._bi_loop(ms.w, ms.h, md.w, md.h,
94 (x, y, xl, xh, yl, yh, xt, yt)=>{
95 if (x<0 || x>=md.w || y<0 || y>=md.h) return;
96 if (xl<0 || xl>=ms.w || yl<0 || yl>=ms.h) return;
97 if (xh<0 || xh>=ms.w || yh<0 || yh>=ms.h) return;
98 md.set(x, y, bi_method(
99 ms.get(xl, yl), ms.get(xh, yl),
100 ms.get(xl, yh), ms.get(xh, yh),
101 xt, yt
102 ));
103 }
104 );
105 };
106
107 static nearest(a, b, t) {
108 return t >= .5 ? b : a;
109 };
110
111 static binearest(a1, b1, a2, b2, t1, t2) {
112 return Interp.nearest(
113 Interp.nearest(a1, b1, t1),
114 Interp.nearest(a2, b2, t1),
115 t2
116 )
117 };
118
119 static binearest_m(ms, md) {Interp._bi_inv(ms, md, Interp.binearest);};
120
121 static linear(a, b, t) {
122 return a + (b - a) * t
123 };
124
125 static bilinear(a1, b1, a2, b2, t1, t2) {
126 return Interp.linear(
127 Interp.linear(a1, b1, t1),
128 Interp.linear(a2, b2, t1),
129 t2
130 )
131 };
132
133 static bilinear_m(ms, md) {Interp._bi_inv(ms, md, Interp.bilinear)};
134};
135
136
137class ac2DMatrix {
138 itox(i) {return i % this.w};
139 itoy(i) {return ftoi(i / this.w)};
140 ctoi(x, y) {return x + (y * this.w)};
141 get(x, y) {return this.buf[this.ctoi(x, y)]};
142 set(x, y, v) {this.buf[this.ctoi(x, y)] = v;};
143 map(cb) {for (let i=0; i<this.length; i++){this.buf[i] = cb(this.buf[i])}};
144 zeros() {for (let i=0; i<this.length; i++) {this.buf[i] = 0}};
145 sum() {let s=0; for (let i=0; i<this.length; i++) {s+=this.buf[i]}; return s};
146 count(o) {let s=0; for (let i=0; i<this.length; i++) {if(this.buf[i]===o){s+=1}}; return s};
147 load2DArray(arr) {
148 for (let x=0; x<this.w; x++) {
149 for (let y=0; y<this.h; y++) {
150 this.set(x, y, arr[y][x]);
151 };
152 };
153 };
154 to2DArray() {
155 let r = []
156 for (let y=0; y<this.h; y++) {
157 r.push([]);
158 for (let x=0; x<this.w; x++) {
159 r[y].push(this.get(x, y));
160 };
161 };
162 return r;
163 };
164 queryKernel(mk, x, y, wrap) {
165 const lx = ftoi(mk.w/2);
166 const ly = ftoi(mk.h/2);
167 for (let kx=0; kx<mk.w; kx++) {
168 for (let ky=0; ky<mk.h; ky++) {
169 let dx = x+kx-lx;
170 let dy = y+ky-ly;
171 if (wrap||false) {
172 dx = iwrap(dx, this.w)
173 dy = iwrap(dy, this.h)
174 };
175 mk.set(kx, ky, this.get(dx, dy) || null)
176 };
177 };
178 };
179};
180
181
182class cFloat32Matrix extends ac2DMatrix {
183 constructor (w, h) {
184 super();
185 this.w = w; this.h = h; this.length = w*h;
186 this.buf = new Float32Array(this.length);
187 };
188 unloadImageData(data) {
189 for (let i=0; i<data.length; i+=4) {
190 let p = this.buf[ftoi(i/4)];
191 p = p > 1 ? 1 : p;
192 p = p < 0 ? 0 : p;
193 p = ftoi(p * 255);
194 data[i+0] = p;
195 data[i+1] = p;
196 data[i+2] = p;
197 };
198 };
199};
200
201class cComplexMatrix extends ac2DMatrix {
202 constructor (w, h) {
203 super();
204 this.w = w; this.h = h; this.length = w*h;
205 this.buf = [];
206 for (let y=0; y<h; y++) {
207 for (let x=0; x<w; x++) {
208 // this.buf.push(new Complex(x/(w-1)-.5, y/(h-1)-.5))
209 this.buf.push(new Complex(0,0))
210 };
211 };
212 };
213 set(x, y, im, re) {
214 let c = this.buf[this.ctoi(x, y)];
215 c.im = im||0;
216 c.re = re||0;
217 };
218 zeros() {for (let i=0; i<this.length; i++) {this.buf[i].im=0;this.buf[i].re=0;}};
219 sum() {
220 let s = new Complex(0, 0);
221 for (let i=0; i<this.length; i++) {s.addc(this.buf[i])};
222 return s
223 };
224 count(o) {throw "matrix count method is not defined"};
225 load2DArray(arr) {throw "matrix load2DArray method is not defined"};
226};
227
228
229class MandelbrotSet {
230 constructor(w, h) {
231 this.buffer = new cFloat32Matrix(w, h);
232 this.z = new Complex(0, 0);
233 this.c = new Complex(0, 0);
234 this.thresh_re = 1e9;
235 this.thresh_im = 1e9;
236 };
237 is_oob() {
238 return Math.abs(this.z.re) > this.thresh_re || Math.abs(this.z.im) > this.thresh_im
239 };
240 compute(iter, x, y, scale) {
241 scale = scale||1;
242 x = x||0;
243 y = y||0;
244 iter = Math.min(iter||10, 300);
245 let cv=0, pv=0, dv=0;
246 for (let i=0; i<this.buffer.length; i++){
247 let rx=this.buffer.itox(i)/this.buffer.w-.5;
248 let ry=this.buffer.itoy(i)/this.buffer.h-.5;
249 this.c.re = rx*scale+x;
250 this.c.im = ry*scale+y;
251 this.z.re = 0;
252 this.z.im = 0;
253 let j=0;
254 cv=0; pv=0; dv=0;
255 for (j=0; j<iter; j++){
256 this.z.mulc(this.z);
257 this.z.addc(this.c);
258 pv = cv;
259 cv = this.z.square();
260 dv = cv - pv;
261 if (this.is_oob()) break;
262 };
263 this.buffer.buf[i] = dv < 4 ? 0 : j/iter;
264 };
265 };
266};
267
268// ===========================================
269
270function renderCanvas() {
271 let imd = ctx.getImageData(0, 0, canvas.width, canvas.height);
272 Interp.bilinear_m(fractals['mandelbrot'].buffer, frame);
273 frame.unloadImageData(imd.data);
274 ctx.putImageData(imd, 0, 0);
275};
276
277
278function appInit() {
279 frame = new cFloat32Matrix(canvas.width, canvas.height);
280 fractals['mandelbrot'] = new MandelbrotSet(1000, 1000);
281 inputProxy = new cInputProxy();
282 inputProxy.register('OffsetX', -.743643887037151, parseFloat);
283 inputProxy.register('OffsetY', .131825904205330, parseFloat);
284 inputProxy.register('Zoom', 50, parseFloat);
285 inputProxy.register('Iter', 80, parseInt);
286 inputProxy.register('Compute').addEventListener('click', (e)=>{
287 appUpdate();
288 renderCanvas();
289 });
290 inputProxy.register('Reset').addEventListener('click', (e)=>{
291 inputProxy.values.Iter = 50;
292 inputProxy.values.OffsetX = -1;
293 inputProxy.values.OffsetY = 0;
294 inputProxy.values.Zoom = 0.33;
295 inputProxy.target.Iter.value = 50;
296 inputProxy.target.OffsetX.value = -1;
297 inputProxy.target.OffsetY.value = 0;
298 inputProxy.target.Zoom.value = 0.33;
299 appUpdate();
300 renderCanvas();
301 });
302 appUpdate();
303 renderCanvas();
304};
305
306function appUpdate() {
307 fractals['mandelbrot'].compute(
308 inputProxy.values.Iter,
309 inputProxy.values.OffsetX,
310 inputProxy.values.OffsetY,
311 1/inputProxy.values.Zoom
312 );
313};
314
315
316appInit()
317// fractals['mandelbrot'].compute(ii, -.743643887037151, .131825904205330, 1/50);
318
319};
Built with Hugo | previoip (c) 2025