added .gitignore for all to enjoy
[fractals] / fractals.ec
1 #ifdef ECERE_STATIC
2 import static "ecere"
3 #else
4 import "ecere"
5 #endif
6
7 import "GradientDesigner"
8
9 define app = (GuiApplication)__thisModule;
10 define gradient = fractalsDesigner.gradientDesigner.gradient;
11
12 struct Complex
13 {
14    double a, b;
15 };
16
17 class Buffer
18 {
19    float * pixels;
20    Complex * z;
21    int width, height;   
22
23    void Allocate(int w, int h)
24    {
25       width = w;
26       height = h;
27       pixels = renew pixels float[w * h];
28       z = renew z Complex[w * h];
29       FillBytes(pixels, 0, w * h * sizeof(float));
30       FillBytes(z, 0, w * h * sizeof(Complex));    // renew0 crashes
31    }
32    void Free()
33    {
34       delete pixels;
35       delete z;
36    }
37 }
38
39 class ParallelFractalRenderThread : Thread
40 {
41    FractalRenderThread thread;
42    int yStart, yEnd;
43    int depth;
44    Semaphore go { };
45    Semaphore done { };
46    bool terminate;
47    int minY, maxY;
48
49    void Render(int ns, int ne, int start, int end)
50    {
51       int x, y;
52       int w = thread.buffer.width;
53       int h = thread.buffer.height;
54       Complex C, C0, d;
55       double delta;
56       float * buffer = thread.buffer.pixels;
57       Complex * bufferZ = thread.buffer.z;
58       int bufferPos;
59       int power = thread.exponent;
60       bool julia = thread.isJulia;
61       double logOf2 = log(2);
62       double log2logOf2 = log(2*logOf2);
63       double logPower = log(power);
64       int n = ns;
65
66       if(w > h)
67       {
68          d.a = thread.range;
69          d.b = thread.range * h / w;
70       }
71       else
72       {
73          d.b = thread.range;
74          d.a = thread.range * w / h;
75       }
76
77       C0.a = thread.center.a - d.a/2;
78       C0.b = thread.center.b - d.b/2;
79       delta = d.a / w;
80
81       if(julia)
82          C = thread.juliaPoint;
83       else
84       {
85          C = C0;
86          C.b += start * d.b / h;
87       }
88
89       minY = MAXINT;
90       maxY = MAXINT;
91       for( y = start; y <=end && !terminate; y++)
92       {
93          float * buf;
94          bool got = false;
95          bufferPos = y * w;
96          buf = buffer + bufferPos;         
97          for( x = 0; x < w/* && !terminate*/; x++)
98          {
99             /*int n;
100             for(n = ns; n<=ne; n++)
101             {*/
102                if(*buf >= n)
103                {
104                   int c;
105                   double zm;
106                   Complex Z, oldZ;
107
108                   if(n == 0 && julia)
109                      Z = Complex { x * delta + C0.a, y * delta + C0.b };
110                   else
111                      Z = bufferZ[bufferPos];
112
113                   oldZ = Z;
114                   for(c = 1; c<power; c++)
115                   {
116                      Z = { Z.a*oldZ.a - Z.b*oldZ.b, Z.a*oldZ.b + Z.b*oldZ.a };
117                   }
118                   Z.a += C.a;
119                   Z.b += C.b;
120
121                   zm = sqrt(Z.a * Z.a + Z.b * Z.b);
122                   if(zm < 2)
123                      *buf = n + 1;
124                   else if(power == 2)
125                      *buf = (float)(n + 1 - log(log(zm)) / logOf2);
126                   else
127                      *buf = (float)(n + (log2logOf2 - log(log(zm)) / logPower));
128                   bufferZ[bufferPos] = Z;
129                   got = true;
130                }
131             //}
132             bufferPos++;
133             buf++;
134             if(!julia) C.a += delta;
135          }
136          if(got)
137          {
138             if(minY == MAXINT) minY = y;
139             maxY = y;
140          }
141          if(!julia) 
142          {
143             C.a = C0.a;
144             C.b += delta;
145          }
146       }
147    }
148
149    unsigned int Main()
150    {
151       while(true)
152       {
153          go.Wait();
154          if(terminate)
155             break;
156          Render(depth, depth, yStart, yEnd);
157          done.Release();
158       }
159       return 0;
160    }
161 }
162
163 define numThreads = 16;
164
165 class FractalRenderThread : Thread
166 {
167    Complex center;
168    double range;
169    int iterations;
170    int exponent;
171    // Complex juliaPoint { -0.726895347709114071439, 0.188887129043845954792 };
172    Complex juliaPoint { 0.37799999117851257000, -0.30700001120567322000 };
173    bool isJulia;
174    int loop;
175    float maxLoops;
176    bool useBlack;
177    bool doLoop;
178
179    doLoop = true;
180    loop = 100;
181    useBlack = true;
182    maxLoops = 5000;
183    float numScales;
184
185    range = 2.5;
186    exponent = 2;
187    iterations = 100;
188
189    Buffer buffer {};
190    Mutex mutex {};
191    Fractal fractal;
192    bool terminate;
193    
194    int depth, maxDepth;
195
196    unsigned int Main()
197    {
198       int t;
199       int c;
200       ParallelFractalRenderThread * threads = new ParallelFractalRenderThread[numThreads];
201       int yStart = 0;
202       for(t = 0; t < numThreads; t++)
203       {
204          int yEnd;
205          if(t == numThreads - 1)
206             yEnd = buffer.height - 1;
207          else
208             yEnd = yStart + buffer.height / numThreads - 1;
209          threads[t] = ParallelFractalRenderThread { thread = this, yStart = yStart, yEnd = yEnd, minY = MAXINT, maxY = MAXINT };
210          threads[t].Create();
211          yStart = yEnd + 1;
212       }
213       for(c = maxDepth; c<iterations && !terminate; c++)
214       {
215          int yStart = MAXINT;
216          int max = MAXINT;
217          mutex.Wait();
218
219          for(t = 0; t < numThreads; t++)
220          {
221             threads[t].depth = c;
222             threads[t].go.Release();
223          }
224          for(t = 0; t < numThreads; t++)
225             threads[t].done.Wait();
226          mutex.Release();         
227
228          app.Lock();
229
230          for(t = 0; t < numThreads; t++)
231          {
232             if(threads[t].minY != MAXINT)
233             {
234                threads[t].yStart = threads[t].minY;
235                threads[t].yEnd = threads[t].maxY;
236             }
237             else
238             {
239                threads[t].yStart = threads[t].yEnd + 1;
240             }
241          }
242          /*
243          for(t = 0; t < numThreads; t++)
244          {
245             if(threads[t].minY != MAXINT) { yStart = threads[t].minY; break; }
246          }
247          for(t = numThreads-1; t >=0 numThreads; t--)
248          {
249             if(threads[t].maxY != MAXINT) { max = threads[t].maxY; break; }
250          }
251          for(t = 0; t < numThreads; t++)
252          {
253             int yEnd;
254             if(t == numThreads - 1)
255                yEnd = max;
256             else
257                yEnd = yStart + (max + 1 - yStart) / numThreads - 1;
258             threads[t].yStart = yStart;
259             threads[t].yEnd = yEnd;
260             yStart = yEnd + 1;
261          }*/
262
263          if(!terminate)
264          {
265             if(!(c%25) && c == maxDepth)
266                fractal.UpdateDepth((depth < c) ? depth : (c+1), c+1);
267             else
268             {
269                depth = (depth < c) ? depth : (c+1);
270                //fractal.params.depthScroll.range = maxDepth;
271                //fractal.params.depthLabel.SetText("%d / %d", depth, fractal.params.depthScroll.range-1);
272             }
273          }
274          maxDepth = c + 1;
275          app.Unlock();
276       }
277       for(t = 0; t < numThreads; t++)
278       {
279          threads[t].terminate = true;
280          threads[t].go.Release();
281          threads[t].Wait();
282       }
283       app.Lock();
284       if(!terminate)
285          fractal.UpdateDepth(Min(depth, iterations), maxDepth);
286       app.Unlock();
287       return 0;
288    }
289 }
290
291 static define numColors = 30000;
292 static ColorAlpha palette[numColors];
293
294 class Fractal : Window
295 {
296    borderStyle = sizable;
297    size = Size { 408, 428 };
298    hasHorzScroll = true;
299    hasVertScroll = true;
300    isActiveClient = true;
301    hasMaximize = true;
302    hasMinimize = true;
303
304    FractalParams params;
305    FractalRenderThread thread;
306
307    Point mouse0, mouse1;
308
309    void UpdateDepth(int d, int max)
310    {
311       params.depthScroll.range = max+1;
312       params.depthScroll.thumbPosition = d;
313    }
314
315    void Reset()
316    {
317       if(thread.isJulia)
318       {
319          thread.range = 3;
320          thread.center = Complex { 0, 0 };
321       }
322       else
323       {
324          thread.range = 3;
325          if(thread.exponent == 2)
326             thread.center = { -0.75, 0 }; 
327          else
328             thread.center = { 0, 0 }; 
329       }
330       params.UpdateControls();
331       Render(true);
332    }
333
334    Size imageSize { 400, 400 };
335    scrollArea = imageSize;
336    Bitmap image {};
337    
338    bool selecting;
339
340    bool OnLeftButtonDown(int x, int y, Modifiers mods)
341    {
342       if(x >= 0 && y >= 0 && x < clientSize.w && y < clientSize.h)
343       {
344          x -= Max((clientSize.w - imageSize.w) / 2, 0);
345          y -= Max((clientSize.h - imageSize.h) / 2, 0);
346          x += scroll.x;
347          y += scroll.y;
348          if(x >= 0 && y >= 0 && x < imageSize.w && y < imageSize.h)
349          {
350             mouse0 = mouse1 = Point { x, y };
351             Update(null);
352             Capture();
353             selecting = true;
354          }
355       }
356       return true;
357    }
358
359    bool OnLeftButtonUp(int x, int y, Modifiers mods)
360    {
361       if(selecting)
362       {
363          Complex C0, d, point;
364          int w = image.width;
365          int h = image.height;
366          int dx = Abs(mouse1.x - mouse0.x);
367          int dy = Abs(mouse1.y - mouse0.y);
368
369          if(w > h)
370          {
371             d.a = thread.range;
372             d.b = thread.range * h / w;
373          }
374          else
375          {
376             d.b = thread.range;
377             d.a = thread.range * w / h;
378          }
379
380          C0.a = thread.center.a - d.a/2;
381          C0.b = thread.center.b - d.b/2;
382          point = Complex { (mouse0.x + mouse1.x) / 2.0 * d.a / w + C0.a, (mouse0.y + mouse1.y) / 2.0 * d.b / h + C0.b };
383
384          if(Abs(x - mouse0.x) > 5 && Abs(y - mouse0.y) > 5)
385          {
386             thread.center = point;
387
388             if(dx > dy)
389                thread.range = d.a * dx / w;
390             else
391                thread.range = d.b * dy / h;
392
393             Render(true);
394             params.UpdateControls();
395          }
396          else if(!thread.isJulia)
397          {
398             FractalsDesigner designer = (FractalsDesigner) master;
399
400             // Click for Julia
401             designer.julia.thread.juliaPoint = point;
402             designer.paramsJulia.UpdateControls();
403             designer.julia.Render(true);
404          }
405          ReleaseCapture();
406          selecting = false;
407          Update(null);
408       }
409       return true;
410    }
411
412    bool OnRightButtonUp(int x, int y, Modifiers mods)
413    {
414       Complex C0, d, point;
415       int w = image.width;
416       int h = image.height;
417
418       x -= Max((clientSize.w - imageSize.w) / 2, 0);
419       y -= Max((clientSize.h - imageSize.h) / 2, 0);
420       x += scroll.x;
421       y += scroll.y;
422
423       if(w > h)
424       {
425          d.a = thread.range;
426          d.b = thread.range * h / w;
427       }
428       else
429       {
430          d.b = thread.range;
431          d.a = thread.range * w / h;
432       }
433
434       C0.a = thread.center.a - d.a/2;
435       C0.b = thread.center.b - d.b/2;
436
437       point = Complex { x * d.a / w + C0.a, y * d.b / h + C0.b };
438
439       thread.range *= 1.5;
440       thread.center = point;
441
442       Render(true);
443       params.UpdateControls();
444
445       ReleaseCapture();
446       selecting = false;
447       Update(null);
448       return true;
449    }
450
451    bool OnMouseMove(int x, int y, Modifiers mods)
452    {
453       if(selecting)
454       {
455          x -= Max((clientSize.w - imageSize.w) / 2, 0);
456          y -= Max((clientSize.h - imageSize.h) / 2, 0);
457          x += scroll.x;
458          y += scroll.y;
459
460          mouse1 = Point { x, y };
461          Update(null);
462       }
463       else
464       {
465          if(mods.ctrl)
466             params.depthScroll.thumbPosition = 
467                   (int)((double)x / (double)clientSize.w * (double)(params.depthScroll.range - 1));
468          if(mods.shift)
469             params.loop.thumbPosition = (int)((double)y / (double)clientSize.h * 512);
470       }
471       return true;
472    }
473
474    void ComputeImage()
475    {
476       if(this && image.picture)
477       {
478          int w = image.width, h = image.height;
479          int x, y;
480          float * cbuffer;
481          int max;
482          int depth;
483          ColorAlpha * picture = (ColorAlpha *)image.picture;
484
485          thread.mutex.Wait();
486
487          cbuffer = thread.buffer.pixels;
488          depth = thread.depth + 1;
489
490          //max = depth-1;
491          max = numColors-1;
492
493          for(y = 0; y<h; y++)
494          {
495             int bufferPos = y * w;
496             for(x = 0; x<w; x++)
497             {
498                double i = cbuffer[bufferPos+x];
499                if(i >= depth-1)
500                {
501                   *(picture++) = thread.useBlack ? black : palette[numColors - 1];
502                }
503                else
504                {
505                   int index;
506                   if(thread.doLoop)
507                      index = Min((int)(i * thread.numScales), ((int)(thread.maxLoops * numColors)) - 1) % numColors;
508                   else
509                      index = Min((int)(i * thread.numScales), numColors - 1);
510                   if(index < 0) index = 0;
511                   *(picture++) = palette[index];
512                }
513             }
514          }
515          thread.mutex.Release();
516       }
517    }
518
519    void OnRedraw(Surface surface)
520    {
521       JuliaFractal julia = ((FractalsDesigner)master).julia;
522       int x = Max((clientSize.w - imageSize.w) / 2, 0);
523       int y = Max((clientSize.h - imageSize.h) / 2, 0);
524       int w = imageSize.w;
525       int h = imageSize.h;
526       surface.Blit(image, x, y, scroll.x, scroll.y, image.width, image.height);
527       surface.SetForeground(lime);
528       if(selecting)
529          surface.Rectangle(x+mouse0.x - scroll.x, y+mouse0.y - scroll.y, x+mouse1.x - scroll.x, y+mouse1.y - scroll.y);
530       if(!thread.isJulia && julia)
531       {
532          Complex juliaPoint = julia.thread.juliaPoint;
533          Complex C0, d;
534          int w = image.width;
535          int h = image.height;
536          if(w > h)
537          {
538             d.a = thread.range;
539             d.b = thread.range * h / w;
540          }
541          else
542          {
543             d.b = thread.range;
544             d.a = thread.range * w / h;
545          }
546          C0.a = thread.center.a - d.a/2;
547          C0.b = thread.center.b - d.b/2;
548
549          x += (int)((juliaPoint.a - C0.a) * w / d.a ) - scroll.x;
550          y += (int)((juliaPoint.b - C0.b) * h / d.b ) - scroll.y;
551
552          surface.DrawLine(x - 5, y, x + 5, y);
553          surface.DrawLine(x, y - 5, x, y + 5);
554       }
555    }
556
557    void ResetDepth()
558    {
559       thread.terminate = true;
560       app.Unlock();
561       thread.Wait();
562       app.Lock();
563       image.Free();
564       thread.depth = 0;
565       thread.maxDepth = 0;
566    }
567
568    void Render(bool resetDepth)
569    {
570       thread.terminate = true;
571       app.Unlock();
572       thread.Wait();
573       app.Lock();
574
575       if(resetDepth)
576       {
577          int width = imageSize.w;
578          int height = imageSize.h;
579          thread.buffer.Allocate(width, height);
580          image.Free();
581          image.Allocate(null, width, height, 0, pixelFormat888, false);
582          thread.depth = 0;
583          thread.maxDepth = 0;
584       }
585       thread.terminate = false;
586       thread.Create();
587    }
588
589    bool OnCreate()
590    {
591       Render(true);
592       params.UpdateControls();
593       return true;
594    }
595
596    void OnDestroy()
597    {
598       thread.terminate = true;
599       app.Unlock();
600       thread.Wait();
601       app.Lock();
602    }
603
604    void OnScroll(ScrollBarAction action, int position, Key key)
605    {
606       Update(null);
607    }
608
609    OnHScroll = OnScroll;
610    OnVScroll = OnScroll;
611
612    menu = Menu { };
613    Menu fileMenu { parent = menu, "File", f };
614
615    MenuItem exportItem
616    {
617       fileMenu, "Export Image...", e, ctrlE;
618
619       bool NotifySelect(MenuItem selection, Modifiers mods)
620       {
621          bool result = false;
622          FileDialog exportDialog = ((FractalsDesigner)master).exportDialog;
623          exportDialog.SetText("Export %s image", text);
624          if(exportDialog.Modal())
625          {
626             char * ext = exportDialog.types[exportDialog.fileType].typeExtension;
627             if(!ext)
628             {
629                char extension[MAX_EXTENSION];
630                GetExtension(exportDialog.filePath, extension);
631                if(!extension[0])
632                {
633                   ext = "jpg";
634                   ChangeExtension(exportDialog.filePath, ext, exportDialog.filePath);
635                }
636             }
637             image.Save(exportDialog.filePath, ext, (void *) bool::true);
638          }
639          return true;
640       }
641    };
642
643    MenuItem exportItemFiltered
644    {
645       fileMenu, "Export Image (filtered half)...", f;
646
647       bool NotifySelect(MenuItem selection, Modifiers mods)
648       {
649          bool result = false;
650          FileDialog exportDialog = ((FractalsDesigner)master).exportDialog;
651          exportDialog.SetText("Export %s image", text);
652          if(exportDialog.Modal())
653          {
654             char * ext = exportDialog.types[exportDialog.fileType].typeExtension;
655             Bitmap filtered { };
656             if(!ext)
657             {
658                char extension[MAX_EXTENSION];
659                GetExtension(exportDialog.filePath, extension);
660                if(!extension[0])
661                {
662                   ext = "jpg";
663                   ChangeExtension(exportDialog.filePath, ext, exportDialog.filePath);
664                }
665             }
666             if(filtered.Allocate(null, image.width/2, image.height/2, 0, pixelFormat888, false))
667             {
668                Surface surface = filtered.GetSurface(0,0,null);
669                surface.Filter(image, 0,0,0,0, filtered.width, filtered.height, image.width, image.height);
670                filtered.Save(exportDialog.filePath, ext, (void *) bool::true);
671                delete surface;
672             }
673             delete filtered;
674          }
675          return true;
676       }
677    };
678 }
679
680 class MandelbrotFractal : Fractal
681 {
682    text = "Mandelbrot";
683    FractalRenderThread mandelbrotThread { fractal = this, center = { -0.75, 0 }, range = 3 /*, range = 0.049179316033124996, center = { -0.7623287774189061, -0.1281808432395703 }*/ };
684
685    thread = mandelbrotThread;
686 }
687
688 class JuliaFractal : Fractal
689 {
690    text = "Julia";
691    FractalRenderThread juliaThread { fractal = this, isJulia = true, range = 3 };
692    thread = juliaThread;
693 }
694
695 class FractalParams : Window
696 {
697    text = "Mandelbrot Parameters";
698    background = activeBorder;
699    borderStyle = fixed;
700    tabCycle = true;
701    stayOnTop = true;
702    size = Size { 206, 506 };
703
704    Fractal fractal;
705
706    Label { labeledWindow = width, parent = this, position = Point { 16, 8 } };
707    Label { labeledWindow = height, parent = this, position = Point { 104, 8 } };
708    EditBox width
709    {
710       parent = this, text = "Width", position = Point { 16, 32 };
711
712       bool NotifyModified(EditBox editBox)
713       {
714          fractal.imageSize.w = atoi(editBox.contents);
715          fractal.scrollArea = fractal.imageSize;
716          fractal.clientSize = fractal.imageSize;
717          fractal.Render(true);
718          return true;
719       }
720    };
721    EditBox height
722    {
723       parent = this, text = "Height", position = Point { 104, 32 };
724
725       bool NotifyModified(EditBox editBox)
726       {
727          fractal.imageSize.h = atoi(editBox.contents);
728          fractal.scrollArea = fractal.imageSize;
729          fractal.clientSize = fractal.imageSize;
730          fractal.Render(true);
731          return true;
732       }
733    };
734    EditBox exponent 
735    {
736       parent = this, text = "Exponent", position = Point { 16, 88 };
737
738       bool NotifyModified(EditBox editBox)
739       {
740          FractalsDesigner master = (FractalsDesigner)this.master;
741
742          master.mandelbrot.thread.exponent = master.julia.thread.exponent = atoi(editBox.contents);
743
744          master.paramsJulia.UpdateControls();
745          master.julia.Render(true);
746
747          master.paramsMandelbrot.UpdateControls();
748          master.mandelbrot.Render(true);
749          return true;
750       }
751    };
752    EditBox iterations 
753    {
754       parent = this, text = "Iterations", position = Point { 104, 88 };
755
756       bool NotifyModified(EditBox editBox)
757       {
758          fractal.thread.mutex.Wait();
759          fractal.thread.iterations = atoi(editBox.contents);
760          fractal.thread.depth = fractal.thread.iterations;
761          fractal.thread.mutex.Release();
762          fractal.Render(false);
763          return true;
764       }
765    };
766    ScrollBar depthScroll 
767    {
768       parent = this, text = "scrollBar1", size = Size { 172, 16 }, position = Point { 16, 120 };
769
770       void NotifyScrolling(ScrollBar scrollBar, ScrollBarAction action, int position, Key key)
771       {
772          if(fractal)
773          {
774             fractal.thread.depth = position;
775             if(action != setRange)
776             {
777                fractal.ComputeImage();
778                fractal.Update(null);
779                UpdateDisplay();
780             }
781             depthLabel.SetText("%d / %d", fractal.thread.depth, scrollBar.range-1);
782          }
783       }
784    };
785    EditBox centerX 
786    {
787       parent = this, text = "X", size = Size { 166, 19 }, position = Point { 16, 192 };
788
789       bool NotifyModified(EditBox editBox)
790       {
791          fractal.thread.center.a = strtod(editBox.contents, null);
792          fractal.Render(true);
793          return true;
794       }
795    };
796    EditBox centerY 
797    {
798       parent = this, text = "Y", size = Size { 166, 19 }, position = Point { 16, 240 };
799
800       bool NotifyModified(EditBox editBox)
801       {
802          fractal.thread.center.b = strtod(editBox.contents, null);
803          fractal.Render(true);
804          return true;
805       }
806    };
807    EditBox rangeEdit 
808    {
809       parent = this, text = "Range", size = Size { 166, 19 }, position = Point { 16, 296 };
810
811       bool NotifyModified(EditBox editBox)
812       {
813          fractal.thread.range = strtod(editBox.contents, null);
814          fractal.Render(true);
815          return true;
816       }
817    };
818    Button reset 
819    {
820       parent = this, text = "Reset", size = Size { 170, 21 }, position = Point { 16, 328 };
821
822       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
823       {
824          fractal.Reset();
825          return true;
826       }
827    };
828    Label depthLabel { parent = this, anchor = Anchor { top = 152, right = 16 } };
829    Label label1 { labeledWindow = exponent, parent = this, position = Point { 16, 64 } };
830    Label label2 { labeledWindow = iterations, parent = this, position = Point { 104, 64 } };
831    Label label4 { labeledWindow = rangeEdit, parent = this, position = Point { 16, 272 } };
832    Label label5 { labeledWindow = centerY, parent = this, position = Point { 16, 224 } };
833    Label label3 { labeledWindow = centerX, parent = this, position = Point { 16, 176 } };
834
835    ScrollBar loop
836    {
837       this, borderStyle = deep, clientSize = { 124, 18 }, position = { 10, 376 }, range = 5000;
838       text = "Period";
839
840       void NotifyScrolling(ScrollBar scrollBar, ScrollBarAction action, int position, Key key)
841       {
842          if(fractal)
843          {
844             position = Max(position, 1);
845             fractal.thread.loop = position;         
846             fractal.thread.numScales = numColors / position;
847
848             loopEdit.Clear();
849             loopEdit.SetContents("%d", fractal.thread.loop);
850
851             fractal.ComputeImage();
852             fractal.Update(null);
853             UpdateDisplay();
854          }
855       }
856    };
857    Label lblLoop { this, labeledWindow = loop, position = { 10, 356 } };
858    EditBox loopEdit
859    {
860       this, position = { 60, 356 }, size = { 80, 20 };
861
862       bool OnKeyHit(Key key, unichar ch)
863       {
864          if((SmartKey)key == enter) { Deactivate(); Activate(); }
865          return EditBox::OnKeyHit(key, ch);
866       }
867
868       bool NotifyModified(EditBox editBox)
869       {
870          int value = atoi(editBox.contents);
871          if(value != loop.thumbPosition)
872             loop.thumbPosition = value;
873          return true;
874       }
875    };
876    Button loopCheck
877    {
878       this, checked = true, isCheckbox = true, position = { 110, 406 }, text = "Loop";
879
880       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
881       {
882          fractal.thread.doLoop = button.checked;
883          fractal.ComputeImage();
884          fractal.Update(null);
885          UpdateDisplay();
886          return true;
887       }
888    };
889    Button useBlack
890    {
891       this, checked = true, isCheckbox = true, position = { 10, 406 }, text = "Black";
892
893       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
894       {
895          fractal.thread.useBlack = button.checked;
896          fractal.ComputeImage();
897          fractal.Update(null);
898          UpdateDisplay();
899          return true;
900       }
901    };
902
903    ScrollBar maxLoop
904    {
905       this, borderStyle = deep, clientSize = { 124, 18 }, position = { 10, 450 }, range = 5000;
906       text = "Max Loops";
907
908       void NotifyScrolling(ScrollBar scrollBar, ScrollBarAction action, int position, Key key)
909       {
910          if(fractal)
911          {
912             fractal.thread.maxLoops = position;
913             maxLoopEdit.Clear();
914             maxLoopEdit.SetContents("%.0f", fractal.thread.maxLoops);
915             fractal.ComputeImage();
916             fractal.Update(null);
917             UpdateDisplay();
918          }
919       }
920    };
921    Label lblMaxLoop { this, labeledWindow = maxLoop, position = { 10, 430 } };
922    EditBox maxLoopEdit
923    {
924       this, position = { 70, 430 }, size = { 70, 20 };
925
926       bool OnKeyHit(Key key, unichar ch)
927       {
928          if((SmartKey)key == enter) { Deactivate(); Activate(); }
929          return EditBox::OnKeyHit(key, ch);
930       }
931
932       bool NotifyModified(EditBox editBox)
933       {
934          float value = atof(editBox.contents);
935          //if(value != maxLoop.thumbPosition)
936          {
937             //thumbPosition = value;
938             fractal.thread.maxLoops = value;
939             fractal.ComputeImage();
940             fractal.Update(null);
941             UpdateDisplay();
942          }
943          return true;
944       }
945    };
946
947    virtual void UpdateControls()
948    {
949       float maxLoops = fractal.thread.maxLoops;
950
951       exponent.SetContents("%d", fractal.thread.exponent);
952       iterations.SetContents("%d", fractal.thread.iterations);
953       centerX.SetContents("%.20f", fractal.thread.center.a);
954       centerY.SetContents("%.20f", fractal.thread.center.b);
955       rangeEdit.SetContents("%.20f", fractal.thread.range);
956       width.SetContents("%d", fractal.imageSize.w);
957       height.SetContents("%d", fractal.imageSize.h);
958
959       loop.thumbPosition = (int)fractal.thread.loop;
960       loopEdit.Clear(); loopEdit.SetContents("%d", fractal.thread.loop);
961       maxLoop.thumbPosition = (int)fractal.thread.maxLoops;
962       fractal.thread.maxLoops = maxLoops;
963       maxLoopEdit.Clear(); maxLoopEdit.SetContents("%f", fractal.thread.maxLoops);
964       useBlack.checked = fractal.thread.useBlack;
965       loopCheck.checked = fractal.thread.doLoop;
966    }
967
968    bool OnCreate()
969    {
970       width.Activate();
971       return true;
972    }
973 }
974
975 class FractalParamsJulia : FractalParams
976 {
977    text = "Julia Parameters";
978    size = Size { 206, 610 };
979
980    EditBox juliaX 
981    {
982       parent = this, text = "Xj", size = Size { 166, 19 }, position = Point { 16, 504 };
983
984       bool NotifyModified(EditBox editBox)
985       {
986          fractal.Render(true);
987          fractal.thread.juliaPoint.a = strtod(editBox.contents, null);
988          return true;
989       }
990    };
991    EditBox juliaY 
992    {
993       parent = this, text = "Yj", size = Size { 166, 19 }, position = Point { 16, 552 };
994
995       bool NotifyModified(EditBox editBox)
996       {
997          fractal.Render(true);
998          fractal.thread.juliaPoint.b = strtod(editBox.contents, null);
999          return true;
1000       }
1001    };
1002    Label label7 { labeledWindow = juliaY, parent = this, position = Point { 16, 528 } };
1003    Label label6 { labeledWindow = juliaX, parent = this, position = Point { 16, 480 } };
1004
1005    void UpdateControls()
1006    {
1007       FractalParams::UpdateControls();
1008       juliaX.SetContents("%.20f", fractal.thread.juliaPoint.a);
1009       juliaY.SetContents("%.20f", fractal.thread.juliaPoint.b);
1010    }
1011 }
1012
1013 static FileFilter fractalFilters[] =
1014 {
1015    { "ECERE Fractal Files (*.frc)", "frc" },
1016    { "All files", null }
1017 };
1018 static FileType fractalTypes[] =
1019 {
1020    { "ECERE Fractal", "frc", always },
1021 };
1022
1023 static FileFilter imageFilters[] =
1024 {
1025    {
1026       "Image Files (*.jpg, *.jpeg, *.bmp, *.pcx, *.png, *.gif)",
1027       "jpg, jpeg, bmp, pcx, png, gif"
1028    },
1029    { "All files", null }
1030 };
1031 static FileType imageTypes[] =
1032 {
1033    { "Based on extension", null,  always },
1034    { "JPG Image",          "jpg", always },
1035    { "BMP Image",          "bmp", always },
1036    { "PCX Image",          "pcx", always },
1037    { "PNG Image",          "png", always },
1038    { "GIF Image",          "gif", always }
1039 };
1040
1041 #define FRC_RECOGNITION { 'e', 'F', 'R', 'C', 11, 12, 3, 0 }
1042 static byte frcRecognition[] = FRC_RECOGNITION;
1043
1044 class FractalsDesigner : Window
1045 {
1046    text = "Ecere Fractals Explorer";
1047    background = dimGray;
1048    borderStyle = sizable;
1049    hasMaximize = true;
1050    hasMinimize = true;
1051    hasClose = true;
1052    hasMenuBar = true;
1053    menu = Menu {  };
1054    size = Size { 1000, 600 };
1055    state = maximized;
1056    isDocument = true;
1057    hasHorzScroll = true;
1058    hasVertScroll = true;
1059
1060    Menu fileMenu { menu, "File", f };
1061    MandelbrotFractal mandelbrot { this, position = Point { 10, 10 }, params = paramsMandelbrot };
1062    FractalParams paramsMandelbrot { this, anchor = Anchor { right = 260, top = 10 }, fractal = mandelbrot };
1063    JuliaFractal julia { this, position = Point { 420, 10 }, params = paramsJulia };
1064    FractalParamsJulia paramsJulia { this, anchor = Anchor { right = 10, top = 10 }, fractal = julia };
1065    GradientDesigner gradientDesigner
1066    {
1067       this, anchor = { left = 10, bottom = 10 }, stayOnTop = true;
1068       void NotifyUpdate()
1069       {
1070          PaletteGradient(palette, numColors, gradient.keys._, gradient.keys.size, gradient.smoothness);
1071
1072          mandelbrot.ComputeImage();
1073          julia.ComputeImage();
1074          mandelbrot.Update(null);
1075          julia.Update(null);
1076          UpdateDisplay();
1077       }
1078    };
1079    bool LoadFractals(char * fileName)
1080    {
1081       File f = FileOpen(fileName, read);
1082       if(f)
1083       {
1084          static byte frcRead[8];
1085          mandelbrot.ResetDepth();
1086          julia.ResetDepth();
1087
1088          // First attempt to treat this as an archive file
1089          if(f.Read(frcRead, sizeof(frcRead), 1) == 1 &&
1090             !strncmp(frcRead, frcRecognition, sizeof(frcRecognition)))
1091          {
1092             TempFile bufferZ;
1093
1094             mandelbrot.thread.terminate = true;
1095             julia.thread.terminate = true;
1096             app.Unlock();
1097             mandelbrot.thread.Wait();
1098             julia.thread.Wait();
1099             app.Lock();
1100
1101             // New binary format
1102             f.Get(mandelbrot.imageSize);
1103             f.Get(mandelbrot.thread.exponent);
1104             f.Get(mandelbrot.thread.iterations);
1105             f.Get(mandelbrot.thread.center);
1106             f.Get(mandelbrot.thread.range);
1107             f.Get(mandelbrot.thread.depth);
1108             f.Get(mandelbrot.thread.maxDepth);
1109             f.Get(bufferZ);
1110             mandelbrot.thread.buffer.Allocate(mandelbrot.imageSize.w, mandelbrot.imageSize.h);
1111             if(bufferZ)
1112             {
1113                bufferZ.Read(mandelbrot.thread.buffer.z, sizeof(Complex), mandelbrot.thread.buffer.width * mandelbrot.thread.buffer.height);
1114                bufferZ.Read(mandelbrot.thread.buffer.pixels, sizeof(float), mandelbrot.thread.buffer.width * mandelbrot.thread.buffer.height);
1115             }
1116             delete bufferZ;
1117
1118             f.Get(julia.imageSize);
1119             f.Get(julia.thread.exponent);
1120             f.Get(julia.thread.iterations);
1121             f.Get(julia.thread.center);
1122             f.Get(julia.thread.range);
1123             f.Get(julia.thread.depth);
1124             f.Get(julia.thread.maxDepth);
1125             //julia.thread.iterations = julia.thread.maxDepth;
1126             f.Get(bufferZ);
1127             julia.thread.buffer.Allocate(julia.imageSize.w, julia.imageSize.h);
1128             if(bufferZ)
1129             {
1130                bufferZ.Read(julia.thread.buffer.z, sizeof(Complex), julia.thread.buffer.width * julia.thread.buffer.height);
1131                bufferZ.Read(julia.thread.buffer.pixels, sizeof(float), julia.thread.buffer.width * julia.thread.buffer.height);
1132             }
1133             delete bufferZ;
1134
1135             f.Get(julia.thread.juliaPoint);
1136
1137             f.Get(gradient.smoothness);
1138             {
1139                int size;
1140                f.Get(size);
1141                if(size)
1142                {
1143                   gradient.keys.size = size;
1144                   f.Read(gradient.keys._, sizeof(ColorKey), gradient.keys.size);
1145                }
1146             }
1147             {
1148                int loop = 0;
1149                f.Get(loop);
1150                if(loop)
1151                {
1152                   mandelbrot.thread.loop = loop;
1153                   f.Get(mandelbrot.thread.maxLoops);
1154                   if(mandelbrot.thread.maxLoops < 0.00001) mandelbrot.thread.maxLoops = 4999;
1155                   f.Get(mandelbrot.thread.useBlack);
1156                   f.Get(mandelbrot.thread.doLoop);
1157                }
1158                loop = 0;
1159                f.Get(loop);
1160                if(loop)
1161                {
1162                   julia.thread.loop = loop;
1163                   f.Get(julia.thread.maxLoops);
1164                   if(julia.thread.maxLoops < 0.00001) julia.thread.maxLoops = 4999;
1165                   f.Get(julia.thread.useBlack);
1166                   f.Get(julia.thread.doLoop);
1167                }
1168                else
1169                {
1170                   julia.thread.loop = mandelbrot.thread.loop;
1171                   julia.thread.maxLoops = mandelbrot.thread.maxLoops;
1172                   julia.thread.useBlack = mandelbrot.thread.useBlack;
1173                   julia.thread.doLoop = mandelbrot.thread.doLoop;
1174                }
1175             }
1176
1177             gradientDesigner.UpdateHandles();
1178
1179             mandelbrot.clientSize = mandelbrot.imageSize;
1180             julia.clientSize = julia.imageSize;
1181
1182             mandelbrot.params.UpdateControls();
1183             julia.params.UpdateControls();
1184
1185             mandelbrot.image.Free();
1186             mandelbrot.image.Allocate(null, mandelbrot.imageSize.w, mandelbrot.imageSize.h, 0, pixelFormat888, false);
1187             mandelbrot.ComputeImage();
1188
1189             julia.image.Free();
1190             julia.image.Allocate(null, julia.imageSize.w, julia.imageSize.h, 0, pixelFormat888, false);
1191             julia.ComputeImage();
1192
1193             julia.thread.terminate = false;
1194             julia.thread.Create();
1195
1196             mandelbrot.thread.terminate = false;
1197             mandelbrot.thread.Create();
1198          }
1199          else
1200          {
1201             // Old ASCII Format
1202             f.Seek(0, start);
1203
1204             mandelbrot.imageSize.w = f.GetValue();
1205             mandelbrot.imageSize.h = f.GetValue();
1206             mandelbrot.thread.exponent = f.GetValue();
1207             mandelbrot.thread.iterations = f.GetValue();
1208             mandelbrot.thread.center.a = f.GetDouble();
1209             mandelbrot.thread.center.b = f.GetDouble();
1210             mandelbrot.thread.range = f.GetDouble();
1211
1212             julia.imageSize.w = f.GetValue();
1213             julia.imageSize.h = f.GetValue();
1214             julia.thread.exponent = f.GetValue();
1215             julia.thread.iterations = f.GetValue();
1216             julia.thread.center.a = f.GetDouble();
1217             julia.thread.center.b = f.GetDouble();
1218             julia.thread.range = f.GetDouble();
1219
1220             julia.thread.juliaPoint.a = f.GetDouble();
1221             julia.thread.juliaPoint.b = f.GetDouble();
1222
1223             {
1224                int size = f.GetValue();
1225                if(size)
1226                {
1227                   int c;
1228                   gradient.keys.size = size;
1229                   for(c = 0; c < gradient.keys.size; c++)
1230                   {
1231                      gradient.keys._[c].percent = f.GetFloat();
1232                      gradient.keys._[c].color.a = 255;
1233                      gradient.keys._[c].color.color.r = (byte)f.GetValue();
1234                      gradient.keys._[c].color.color.g = (byte)f.GetValue();
1235                      gradient.keys._[c].color.color.b = (byte)f.GetValue();
1236                   }
1237                   gradient.smoothness = f.GetFloat();
1238                   {
1239                      int loop = 0;
1240                      loop = f.GetValue();
1241                      if(loop)
1242                      {
1243                         mandelbrot.thread.loop = loop;
1244                         mandelbrot.thread.maxLoops = f.GetFloat();
1245                         mandelbrot.thread.useBlack = f.GetValue();
1246                         mandelbrot.thread.doLoop = f.GetValue();
1247                      }
1248                      loop = f.GetValue();
1249                      if(loop)
1250                      {
1251                         julia.thread.loop = loop;
1252                         julia.thread.maxLoops = f.GetFloat();
1253                         julia.thread.useBlack = f.GetValue();
1254                         julia.thread.doLoop = f.GetValue();
1255                      }
1256                      else
1257                      {
1258                         julia.thread.loop = mandelbrot.thread.loop;
1259                         julia.thread.maxLoops = mandelbrot.thread.maxLoops;
1260                         julia.thread.useBlack = mandelbrot.thread.useBlack;
1261                         julia.thread.doLoop = mandelbrot.thread.doLoop;
1262                      }
1263                   }
1264                }
1265             }
1266
1267             gradientDesigner.UpdateHandles();
1268
1269             paramsJulia.UpdateControls();
1270             paramsMandelbrot.UpdateControls();
1271
1272             julia.scrollArea = julia.imageSize;
1273             julia.clientSize = julia.imageSize;
1274
1275             mandelbrot.scrollArea = mandelbrot.imageSize;
1276             mandelbrot.clientSize = mandelbrot.imageSize;
1277
1278             julia.Render(true);
1279             mandelbrot.Render(true);
1280          }
1281          delete f;
1282
1283          this.fileName = fileName;
1284          return true;
1285       }
1286       return false;
1287    }
1288    MenuItem openItem
1289    {
1290       fileMenu, "Open", o, ctrlO;
1291
1292       bool NotifySelect(MenuItem selection, Modifiers mods)
1293       {
1294          if(openDialog.Modal() == ok)
1295          {
1296             if(LoadFractals(openDialog.filePath))
1297             {
1298             }
1299          }
1300          return true;
1301       }
1302    };
1303    MenuDivider { fileMenu };
1304    MenuPlacement { fileMenu, "Export Image...", e };
1305    MenuPlacement { fileMenu, "Export Image (filtered half)...", f };
1306    MenuDivider { fileMenu };
1307    MenuItem saveItemData
1308    {
1309       fileMenu, "Save (with data)";
1310       bool NotifySelect(MenuItem selection, Modifiers mods)
1311       {
1312          return MenuFileSave(selection, mods);
1313       }
1314    };
1315    MenuItem saveItemAsData
1316    {
1317       fileMenu, "Save As (with data)...";
1318       
1319       bool NotifySelect(MenuItem selection, Modifiers mods)
1320       {
1321          return MenuFileSaveAs(selection, mods);
1322       }
1323    };
1324    MenuDivider { fileMenu };
1325    MenuItem saveItem
1326    {
1327       fileMenu, "Save", s, ctrlS;
1328
1329       bool NotifySelect(MenuItem selection, Modifiers mods)
1330       {
1331          saveASCII = true;
1332          MenuFileSave(selection, mods);
1333          saveASCII = false;
1334          return true;
1335       }
1336    };
1337    MenuItem saveItemAs
1338    {
1339       fileMenu, "Save As...", a;
1340
1341       bool NotifySelect(MenuItem selection, Modifiers mods)
1342       {
1343          saveASCII = true;
1344          MenuFileSaveAs(selection, mods);
1345          saveASCII = false;
1346          return true;
1347       }
1348    };
1349    MenuDivider { fileMenu };
1350    MenuItem exitItem { fileMenu, "Exit", x, altF4, NotifySelect = MenuFileExit };
1351
1352    FileDialog exportDialog
1353    {
1354       master = this, type = save,
1355       filters = imageFilters, sizeFilters = sizeof(imageFilters),
1356       types = imageTypes, sizeTypes = sizeof(imageTypes)
1357    };
1358    FileDialog mySaveDialog
1359    {
1360       master = this, type = save, text = "Save Fractals Settings...",
1361       types = fractalTypes, sizeTypes = sizeof(fractalTypes), filters = fractalFilters, sizeFilters = sizeof(fractalFilters)
1362    };
1363    
1364    FileDialog openDialog
1365    {
1366       master = this, type = open, text = "Load Fractals Settings...",
1367       types = fractalTypes, sizeTypes = sizeof(fractalTypes), filters = fractalFilters, sizeFilters = sizeof(fractalFilters)
1368    };
1369    bool saveASCII;
1370
1371    saveDialog = mySaveDialog;
1372    
1373    bool OnSaveFile(char * fileName)
1374    {
1375       File f = FileOpen(fileName, write);
1376       if(f)
1377       {
1378          if(!saveASCII)
1379          {
1380             TempFile bufferZ { };
1381             f.Write(frcRecognition, sizeof(frcRecognition), 1);
1382             f.Put(mandelbrot.imageSize);
1383             f.Put(mandelbrot.thread.exponent);
1384             f.Put(mandelbrot.thread.iterations);
1385             f.Put(mandelbrot.thread.center);
1386             f.Put(mandelbrot.thread.range);
1387             f.Put(mandelbrot.thread.depth);
1388             f.Put(mandelbrot.thread.maxDepth);
1389
1390             bufferZ.Write(mandelbrot.thread.buffer.z, sizeof(Complex), mandelbrot.thread.buffer.width * mandelbrot.thread.buffer.height);
1391             bufferZ.Write(mandelbrot.thread.buffer.pixels, sizeof(float), mandelbrot.thread.buffer.width * mandelbrot.thread.buffer.height);
1392             f.Put(bufferZ);
1393             bufferZ.Truncate(0);
1394             bufferZ.Seek(0, start);
1395
1396             f.Put(julia.imageSize);
1397             f.Put(julia.thread.exponent);
1398             f.Put(julia.thread.iterations);
1399             f.Put(julia.thread.center);
1400             f.Put(julia.thread.range);
1401             f.Put(julia.thread.depth);
1402             f.Put(julia.thread.maxDepth);
1403
1404             bufferZ.Write(julia.thread.buffer.z, sizeof(Complex), julia.thread.buffer.width * julia.thread.buffer.height);
1405             bufferZ.Write(julia.thread.buffer.pixels, sizeof(float), julia.thread.buffer.width * julia.thread.buffer.height);
1406             f.Put(bufferZ);
1407
1408             f.Put(julia.thread.juliaPoint);
1409
1410             f.Put(gradient.smoothness);
1411             { int size = gradient.keys.size; f.Put(size); }
1412             f.Write(gradient.keys._, sizeof(ColorKey), gradient.keys.size);
1413
1414             f.Put(mandelbrot.thread.loop);
1415             f.Put(mandelbrot.thread.maxLoops);
1416             f.Put(mandelbrot.thread.useBlack);
1417             f.Put(mandelbrot.thread.doLoop);
1418
1419             f.Put(julia.thread.loop);
1420             f.Put(julia.thread.maxLoops);
1421             f.Put(julia.thread.useBlack);
1422             f.Put(julia.thread.doLoop);
1423
1424             delete bufferZ;
1425          }
1426          else
1427          {
1428             int c;
1429             f.Printf("%d\n",  mandelbrot.imageSize.w);
1430             f.Printf("%d\n",  mandelbrot.imageSize.h);
1431             f.Printf("%d\n",  mandelbrot.thread.exponent);
1432             f.Printf("%d\n",  mandelbrot.thread.iterations);
1433             f.Printf("%.20f\n",  mandelbrot.thread.center.a);
1434             f.Printf("%.20f\n",  mandelbrot.thread.center.b);
1435             f.Printf("%.20f\n",  mandelbrot.thread.range);
1436
1437             f.Printf("%d\n",  julia.imageSize.w);
1438             f.Printf("%d\n",  julia.imageSize.h);
1439             f.Printf("%d\n",  julia.thread.exponent);
1440             f.Printf("%d\n",  julia.thread.iterations);
1441             f.Printf("%.20f\n",  julia.thread.center.a);
1442             f.Printf("%.20f\n",  julia.thread.center.b);
1443             f.Printf("%.20f\n",  julia.thread.range);
1444
1445             f.Printf("%.20f\n",  julia.thread.juliaPoint.a);
1446             f.Printf("%.20f\n",  julia.thread.juliaPoint.b);
1447
1448             f.Printf("%d\n", gradient.keys.size);
1449             for(c = 0; c < gradient.keys.size; c++)
1450             {
1451                f.Printf("%.20f\n", gradient.keys._[c].percent);
1452                f.Printf("%d\n", gradient.keys._[c].color.color.r);
1453                f.Printf("%d\n", gradient.keys._[c].color.color.g);
1454                f.Printf("%d\n", gradient.keys._[c].color.color.b);
1455             }
1456             f.Printf("%.20f\n", gradient.smoothness);
1457
1458             f.Printf("%d\n", mandelbrot.thread.loop);
1459             f.Printf("%.20f\n", mandelbrot.thread.maxLoops);
1460             f.Printf("%d\n", mandelbrot.thread.useBlack);
1461             f.Printf("%d\n", mandelbrot.thread.doLoop);
1462
1463             f.Printf("%d\n", julia.thread.loop);
1464             f.Printf("%.20f\n", julia.thread.maxLoops);
1465             f.Printf("%d\n", julia.thread.useBlack);
1466             f.Printf("%d\n", julia.thread.doLoop);
1467          }
1468          delete f;
1469       }           
1470       return true;
1471    }
1472
1473    bool OnPostCreate()
1474    {
1475       if(app.argc > 1)
1476          LoadFractals(app.argv[1]);
1477       mandelbrot.Activate();
1478       return true;
1479    }
1480 }
1481
1482 FractalsDesigner fractalsDesigner { };