Initial Git commit
authorJerome St-Louis <jerome@ecere.com>
Tue, 17 May 2011 04:36:08 +0000 (00:36 -0400)
committerJerome St-Louis <jerome@ecere.com>
Tue, 17 May 2011 04:36:08 +0000 (00:36 -0400)
16 files changed:
GradientDesigner.ec [new file with mode: 0644]
LICENSE [new file with mode: 0644]
fractals.ec [new file with mode: 0644]
fractals.epj [new file with mode: 0644]
samples/MOI.frc [new file with mode: 0644]
samples/colorful.frc [new file with mode: 0644]
samples/coolSpiral.frc [new file with mode: 0644]
samples/deep.frc [new file with mode: 0644]
samples/hot.frc [new file with mode: 0644]
samples/hot2.frc [new file with mode: 0644]
samples/oldColors.frc [new file with mode: 0644]
samples/purplishmandelbrot.frc [new file with mode: 0644]
samples/seaHorseValley.frc [new file with mode: 0644]
samples/star.frc [new file with mode: 0644]
samples/sunnySpiral.frc [new file with mode: 0644]
samples/woah.frc [new file with mode: 0644]

diff --git a/GradientDesigner.ec b/GradientDesigner.ec
new file mode 100644 (file)
index 0000000..baec4d0
--- /dev/null
@@ -0,0 +1,337 @@
+#ifdef ECERE_STATIC
+import static "ecere"
+#else
+import "ecere"
+#endif
+
+/*
+static ColorKey defaultKeys[] =
+{ 
+   { navy, 0.0f },
+   { green, 0.1f },
+   { yellow, 0.2f },
+   { orange, 0.3f },
+   { red, 0.4f },
+   { purple, 0.5f },
+   { ivory, 0.6f },
+   { tomato, 0.7f },
+   { white, 0.8f },
+   { powderBlue, 0.9f },
+   { black, 1 }
+};
+*/
+/*
+static ColorKey defaultKeys[] =
+{ 
+   { navy, 0.0f },
+   { Color { 146, 237, 237 }, 0.2f },
+   { white, 0.3f },
+   { lightYellow, 0.5f },
+   { Color { 255, 100, 0 }, 0.8f },
+   { navy, 0.99f },
+   { black, 1 }
+};
+*/
+static ColorKey defaultKeys[] =
+{
+   { navy, 0.0f },
+   { Color { 146, 213, 237 }, 0.198606268f },
+   { white, 0.3f },
+   { Color { 255, 255, 124 }, 0.444250882f },
+   { Color { 255, 100, 0 }, 0.634146333f },
+   { navy, 1 }
+};
+
+class ArrayColorKeys : OldArray
+{
+   type = class(ColorKey);
+   ColorKey * _;
+};
+
+class ColorGradient
+{
+   ArrayColorKeys keys { };
+   float smoothness;
+   smoothness = 1;
+
+   ColorGradient()
+   {
+      keys.size = sizeof(defaultKeys) / sizeof(ColorKey);
+      memcpy(keys._, defaultKeys, sizeof(ColorKey) * keys.size);
+   }
+}
+
+define handleWidth = 15;
+
+static uint CompareKeys(ColorKey a, ColorKey b)
+{
+   if(a.percent > b.percent) return 1;
+   else if(a.percent < b.percent) return -1;
+   else return 0;
+}
+
+class KeyHandle : Window
+{
+   size = { handleWidth, 20 };
+   borderStyle = deep;
+
+   bool sliding;
+   float percent;
+   int startPos, startX;
+
+   bool OnLeftButtonDown(int x, int y, Modifiers mods)
+   {
+      GradientDesigner designer = (GradientDesigner)master;
+      sliding = true;
+      startPos = position.x;
+      startX = x;
+
+      Capture();
+      
+      return true;
+   }
+
+   bool OnMouseMove(int x, int y, Modifiers mods)
+   {
+      if(sliding)
+      {
+         GradientDesigner designer = (GradientDesigner)master;
+         int c;
+         ColorKey * key;
+
+         for(c = 0; c < designer.gradient.keys.size; c++)
+         {
+            key = &designer.gradient.keys._[c];
+            if(key->percent == percent && key->color == background)
+               break;
+         }
+
+         x -= startX;
+         x += startPos;
+         x -= designer.preview.position.x;
+         x += handleWidth / 2;
+         key->percent = (float) x / (float) designer.preview.size.w;
+
+         x = Max(x, 0);
+         x = Min(x, designer.preview.size.w);
+         percent = (float) x / (float) designer.preview.size.w;
+
+         position.x = (percent * designer.preview.size.w) + designer.preview.position.x - handleWidth/2;
+         startPos = position.x;
+
+         qsort(designer.gradient.keys._, designer.gradient.keys.size, sizeof(ColorKey), CompareKeys);
+
+         for(c = 0; c < designer.gradient.keys.size; c++)
+         {
+            key = &designer.gradient.keys._[c];
+            if(key->percent < 0) key->percent = 0;
+            else if(key->percent > 1) key->percent = 1;
+         }
+         
+         designer.Update(null);
+         designer.NotifyUpdate(designer.master);
+      }
+      return true;
+   }
+
+   bool OnLeftDoubleClick(int x, int y, Modifiers mods)
+   {
+      ColorPicker colorPicker { master = this };
+      colorPicker.color = background;
+      incref colorPicker;
+      if(colorPicker.Modal() == ok)
+      {
+         GradientDesigner designer = (GradientDesigner)master;
+         int c;
+         ColorKey * key;
+
+         for(c = 0; c < designer.gradient.keys.size; c++)
+         {
+            key = &designer.gradient.keys._[c];
+            if(key->percent == percent && key->color == background)
+               break;
+         }
+         background = colorPicker.color;
+         key->color = background;
+         designer.Update(null);
+         designer.NotifyUpdate(designer.master);
+      }
+      delete colorPicker;
+      return false;
+   }
+
+   bool OnKeyDown(Key key, unichar ch)
+   {
+      if(key == del)
+      {
+         GradientDesigner designer = (GradientDesigner)master;
+         ColorKey * key;
+         int c;
+
+         for(c = 0; c < designer.gradient.keys.size; c++)
+         {
+            key = &designer.gradient.keys._[c];
+            if(key->percent == percent && key->color == background)
+               break;
+         }
+         *key = designer.gradient.keys._[designer.gradient.keys.size-1];
+         designer.gradient.keys.size--;
+         qsort(designer.gradient.keys._, designer.gradient.keys.size, sizeof(ColorKey), CompareKeys);
+         Destroy(0);                  
+
+         designer.Update(null);
+         designer.NotifyUpdate(designer.master);
+         return false;
+      }
+      return true;
+   }
+
+   bool OnLeftButtonUp(int x, int y, Modifiers mods)
+   {
+      if(sliding)
+      {
+         ReleaseCapture();
+         sliding = false;
+      }
+      return true;
+   }
+
+   void OnRedraw(Surface surface)
+   {
+      if(active)
+      {
+         if(background.r > 128 || background.g > 128)
+            surface.SetForeground(darkGray);
+         else
+            surface.SetForeground(white);
+
+         surface.Rectangle(0,0,clientSize.w-1, clientSize.h-1);
+         surface.Rectangle(1,1,clientSize.w-2, clientSize.h-2);
+      }
+   }
+
+   bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
+   {
+      Update(null);
+      return true;
+   }
+}
+
+class GradientDesigner : Window
+{
+   text = "Gradient Designer";
+   borderStyle = fixed;
+   size = { 600, 300 };
+   tabCycle = true;
+
+   ColorGradient gradient { };
+   Window preview
+   {
+      this, anchor = { left = 10, right = 10, top = 75, bottom = 75 };
+      borderStyle = deep;
+
+      void OnRedraw(Surface surface)
+      {
+         ColorGradient gradient = ((GradientDesigner)master).gradient;
+         surface.Gradient(gradient.keys._, gradient.keys.size, gradient.smoothness, horizontal, 0,0, clientSize.w-1, clientSize.h-1);
+      }
+   };
+
+   void OnRedraw(Surface surface)
+   {
+      int c;
+      
+      for(c = 0; c < gradient.keys.size; c++)
+      {
+         ColorKey * key = gradient.keys._ + c;
+         int x = key->percent * preview.size.w + preview.position.x;
+         surface.SetForeground(black);
+         surface.DrawLine(x - 6, preview.position.y - 10, x, preview.position.y);
+         surface.DrawLine(x + 6, preview.position.y - 10, x, preview.position.y);
+         surface.SetForeground(gray);
+         surface.DrawLine(x - 5, preview.position.y - 10, x, preview.position.y - 1);
+         surface.DrawLine(x + 5, preview.position.y - 10, x, preview.position.y - 1);
+      }
+   }
+   ScrollBar smoothness
+   {
+      this, borderStyle = deep, clientSize = { 124, 18 }, position = { 16, 240 }, range = 100;
+      text = "Smoothness"; 
+
+      void NotifyScrolling(ScrollBar scrollBar, ScrollBarAction action, int position, Key key)
+      {
+         gradient.smoothness = position / 100.0f;
+         Update(null);
+         NotifyUpdate(master);
+      }
+   };
+   Label lblSmoothness { this, labeledWindow = smoothness, position = { 16, 220 } };
+
+   void UpdateHandles()
+   {
+      Window window, next;
+      int c;
+
+      smoothness.thumbPosition = (int)gradient.smoothness * 100;
+      for(window = firstChild; window; window = next)
+      {
+         next = window.next;
+         if(window._class == class(KeyHandle))
+         {
+            window.Destroy(0);
+         }
+      }
+      for(c = 0; c < gradient.keys.size; c++)
+      {
+         ColorKey * key = gradient.keys._ + c;
+         KeyHandle handle { this, percent = key->percent, position = { key->percent * preview.size.w + preview.position.x - handleWidth/2, preview.position.y - 30 }, background = key->color };
+         handle.Create();
+      }
+   }
+
+   bool OnCreate()
+   {
+      UpdateHandles();
+      return true;
+   }
+   ColorPicker picker { master = this, autoCreate = false, color = white };
+
+   bool OnLeftButtonDown(int x, int y, Modifiers mods)
+   {
+      if(x >= preview.position.x && x < preview.position.x + preview.size.w && y < preview.position.y && y > preview.position.y - 30)
+      {
+         if(picker.Modal() == ok)
+         {
+            ColorKey * key;
+            int c;
+            KeyHandle handle;
+            
+            gradient.keys.size++;
+            key = gradient.keys._ + gradient.keys.size - 1;
+            x -= preview.position.x;
+            x = Max(Min(x, preview.size.w), 0);
+            key->percent = (float) x / (float) preview.size.w;
+            key->color = picker.color;
+
+            handle = KeyHandle { this, percent = key->percent, position = { key->percent * preview.size.w + preview.position.x - handleWidth/2, preview.position.y - 30 }, background = key->color };
+            handle.Create();
+            
+            qsort(gradient.keys._, gradient.keys.size, sizeof(ColorKey), CompareKeys);
+
+            for(c = 0; c < gradient.keys.size; c++)
+            {
+               key = &gradient.keys._[c];
+               if(key->percent == handle.percent && key->color == handle.background)
+                  break;
+            }
+            key->color = picker.color;
+            handle.background = picker.color;
+            Update(null);
+            NotifyUpdate(master);
+         }
+      }
+      return true;
+   }
+
+   virtual void Window::NotifyUpdate();
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..61b4682
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+   Copyright (c) 1996-2007, Jerome Jacovella-St-Louis
+   Copyright (c) 2005-2007, Ecere Corporation
+
+   All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of Ecere Corporation nor the names of its contributors
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/fractals.ec b/fractals.ec
new file mode 100644 (file)
index 0000000..6bb9477
--- /dev/null
@@ -0,0 +1,1482 @@
+#ifdef ECERE_STATIC
+import static "ecere"
+#else
+import "ecere"
+#endif
+
+import "GradientDesigner"
+
+define app = (GuiApplication)__thisModule;
+define gradient = fractalsDesigner.gradientDesigner.gradient;
+
+struct Complex
+{
+   double a, b;
+};
+
+class Buffer
+{
+   float * pixels;
+   Complex * z;
+   int width, height;   
+
+   void Allocate(int w, int h)
+   {
+      width = w;
+      height = h;
+      pixels = renew pixels float[w * h];
+      z = renew z Complex[w * h];
+      FillBytes(pixels, 0, w * h * sizeof(float));
+      FillBytes(z, 0, w * h * sizeof(Complex));    // renew0 crashes
+   }
+   void Free()
+   {
+      delete pixels;
+      delete z;
+   }
+}
+
+class ParallelFractalRenderThread : Thread
+{
+   FractalRenderThread thread;
+   int yStart, yEnd;
+   int depth;
+   Semaphore go { };
+   Semaphore done { };
+   bool terminate;
+   int minY, maxY;
+
+   void Render(int ns, int ne, int start, int end)
+   {
+      int x, y;
+      int w = thread.buffer.width;
+      int h = thread.buffer.height;
+      Complex C, C0, d;
+      double delta;
+      float * buffer = thread.buffer.pixels;
+      Complex * bufferZ = thread.buffer.z;
+      int bufferPos;
+      int power = thread.exponent;
+      bool julia = thread.isJulia;
+      double logOf2 = log(2);
+      double log2logOf2 = log(2*logOf2);
+      double logPower = log(power);
+      int n = ns;
+
+      if(w > h)
+      {
+         d.a = thread.range;
+         d.b = thread.range * h / w;
+      }
+      else
+      {
+         d.b = thread.range;
+         d.a = thread.range * w / h;
+      }
+
+      C0.a = thread.center.a - d.a/2;
+      C0.b = thread.center.b - d.b/2;
+      delta = d.a / w;
+
+      if(julia)
+         C = thread.juliaPoint;
+      else
+      {
+         C = C0;
+         C.b += start * d.b / h;
+      }
+
+      minY = MAXINT;
+      maxY = MAXINT;
+      for( y = start; y <=end && !terminate; y++)
+      {
+         float * buf;
+         bool got = false;
+         bufferPos = y * w;
+         buf = buffer + bufferPos;         
+         for( x = 0; x < w/* && !terminate*/; x++)
+         {
+            /*int n;
+            for(n = ns; n<=ne; n++)
+            {*/
+               if(*buf >= n)
+               {
+                  int c;
+                  double zm;
+                  Complex Z, oldZ;
+
+                  if(n == 0 && julia)
+                     Z = Complex { x * delta + C0.a, y * delta + C0.b };
+                  else
+                     Z = bufferZ[bufferPos];
+
+                  oldZ = Z;
+                  for(c = 1; c<power; c++)
+                  {
+                     Z = { Z.a*oldZ.a - Z.b*oldZ.b, Z.a*oldZ.b + Z.b*oldZ.a };
+                  }
+                  Z.a += C.a;
+                  Z.b += C.b;
+
+                  zm = sqrt(Z.a * Z.a + Z.b * Z.b);
+                  if(zm < 2)
+                     *buf = n + 1;
+                  else if(power == 2)
+                     *buf = (float)(n + 1 - log(log(zm)) / logOf2);
+                  else
+                     *buf = (float)(n + (log2logOf2 - log(log(zm)) / logPower));
+                  bufferZ[bufferPos] = Z;
+                  got = true;
+               }
+            //}
+            bufferPos++;
+            buf++;
+            if(!julia) C.a += delta;
+         }
+         if(got)
+         {
+            if(minY == MAXINT) minY = y;
+            maxY = y;
+         }
+         if(!julia) 
+         {
+            C.a = C0.a;
+            C.b += delta;
+         }
+      }
+   }
+
+   unsigned int Main()
+   {
+      while(true)
+      {
+         go.Wait();
+         if(terminate)
+            break;
+         Render(depth, depth, yStart, yEnd);
+         done.Release();
+      }
+      return 0;
+   }
+}
+
+define numThreads = 16;
+
+class FractalRenderThread : Thread
+{
+   Complex center;
+   double range;
+   int iterations;
+   int exponent;
+   // Complex juliaPoint { -0.726895347709114071439, 0.188887129043845954792 };
+   Complex juliaPoint { 0.37799999117851257000, -0.30700001120567322000 };
+   bool isJulia;
+   int loop;
+   float maxLoops;
+   bool useBlack;
+   bool doLoop;
+
+   doLoop = true;
+   loop = 100;
+   useBlack = true;
+   maxLoops = 5000;
+   float numScales;
+
+   range = 2.5;
+   exponent = 2;
+   iterations = 100;
+
+   Buffer buffer {};
+   Mutex mutex {};
+   Fractal fractal;
+   bool terminate;
+   
+   int depth, maxDepth;
+
+   unsigned int Main()
+   {
+      int t;
+      int c;
+      ParallelFractalRenderThread * threads = new ParallelFractalRenderThread[numThreads];
+      int yStart = 0;
+      for(t = 0; t < numThreads; t++)
+      {
+         int yEnd;
+         if(t == numThreads - 1)
+            yEnd = buffer.height - 1;
+         else
+            yEnd = yStart + buffer.height / numThreads - 1;
+         threads[t] = ParallelFractalRenderThread { thread = this, yStart = yStart, yEnd = yEnd, minY = MAXINT, maxY = MAXINT };
+         threads[t].Create();
+         yStart = yEnd + 1;
+      }
+      for(c = maxDepth; c<iterations && !terminate; c++)
+      {
+         int yStart = MAXINT;
+         int max = MAXINT;
+         mutex.Wait();
+
+         for(t = 0; t < numThreads; t++)
+         {
+            threads[t].depth = c;
+            threads[t].go.Release();
+         }
+         for(t = 0; t < numThreads; t++)
+            threads[t].done.Wait();
+         mutex.Release();         
+
+         app.Lock();
+
+         for(t = 0; t < numThreads; t++)
+         {
+            if(threads[t].minY != MAXINT)
+            {
+               threads[t].yStart = threads[t].minY;
+               threads[t].yEnd = threads[t].maxY;
+            }
+            else
+            {
+               threads[t].yStart = threads[t].yEnd + 1;
+            }
+         }
+         /*
+         for(t = 0; t < numThreads; t++)
+         {
+            if(threads[t].minY != MAXINT) { yStart = threads[t].minY; break; }
+         }
+         for(t = numThreads-1; t >=0 numThreads; t--)
+         {
+            if(threads[t].maxY != MAXINT) { max = threads[t].maxY; break; }
+         }
+         for(t = 0; t < numThreads; t++)
+         {
+            int yEnd;
+            if(t == numThreads - 1)
+               yEnd = max;
+            else
+               yEnd = yStart + (max + 1 - yStart) / numThreads - 1;
+            threads[t].yStart = yStart;
+            threads[t].yEnd = yEnd;
+            yStart = yEnd + 1;
+         }*/
+
+         if(!terminate)
+         {
+            if(!(c%25) && c == maxDepth)
+               fractal.UpdateDepth((depth < c) ? depth : (c+1), c+1);
+            else
+            {
+               depth = (depth < c) ? depth : (c+1);
+               //fractal.params.depthScroll.range = maxDepth;
+               //fractal.params.depthLabel.SetText("%d / %d", depth, fractal.params.depthScroll.range-1);
+            }
+         }
+         maxDepth = c + 1;
+         app.Unlock();
+      }
+      for(t = 0; t < numThreads; t++)
+      {
+         threads[t].terminate = true;
+         threads[t].go.Release();
+         threads[t].Wait();
+      }
+      app.Lock();
+      if(!terminate)
+         fractal.UpdateDepth(Min(depth, iterations), maxDepth);
+      app.Unlock();
+      return 0;
+   }
+}
+
+static define numColors = 30000;
+static ColorAlpha palette[numColors];
+
+class Fractal : Window
+{
+   borderStyle = sizable;
+   size = Size { 408, 428 };
+   hasHorzScroll = true;
+   hasVertScroll = true;
+   isActiveClient = true;
+   hasMaximize = true;
+   hasMinimize = true;
+
+   FractalParams params;
+   FractalRenderThread thread;
+
+   Point mouse0, mouse1;
+
+   void UpdateDepth(int d, int max)
+   {
+      params.depthScroll.range = max+1;
+      params.depthScroll.thumbPosition = d;
+   }
+
+   void Reset()
+   {
+      if(thread.isJulia)
+      {
+         thread.range = 3;
+         thread.center = Complex { 0, 0 };
+      }
+      else
+      {
+         thread.range = 3;
+         if(thread.exponent == 2)
+            thread.center = { -0.75, 0 }; 
+         else
+            thread.center = { 0, 0 }; 
+      }
+      params.UpdateControls();
+      Render(true);
+   }
+
+   Size imageSize { 400, 400 };
+   scrollArea = imageSize;
+   Bitmap image {};
+   
+   bool selecting;
+
+   bool OnLeftButtonDown(int x, int y, Modifiers mods)
+   {
+      if(x >= 0 && y >= 0 && x < clientSize.w && y < clientSize.h)
+      {
+         x -= Max((clientSize.w - imageSize.w) / 2, 0);
+         y -= Max((clientSize.h - imageSize.h) / 2, 0);
+         x += scroll.x;
+         y += scroll.y;
+         if(x >= 0 && y >= 0 && x < imageSize.w && y < imageSize.h)
+         {
+            mouse0 = mouse1 = Point { x, y };
+            Update(null);
+            Capture();
+            selecting = true;
+         }
+      }
+      return true;
+   }
+
+   bool OnLeftButtonUp(int x, int y, Modifiers mods)
+   {
+      if(selecting)
+      {
+         Complex C0, d, point;
+         int w = image.width;
+         int h = image.height;
+         int dx = Abs(mouse1.x - mouse0.x);
+         int dy = Abs(mouse1.y - mouse0.y);
+
+         if(w > h)
+         {
+            d.a = thread.range;
+            d.b = thread.range * h / w;
+         }
+         else
+         {
+            d.b = thread.range;
+            d.a = thread.range * w / h;
+         }
+
+         C0.a = thread.center.a - d.a/2;
+         C0.b = thread.center.b - d.b/2;
+         point = Complex { (mouse0.x + mouse1.x) / 2.0 * d.a / w + C0.a, (mouse0.y + mouse1.y) / 2.0 * d.b / h + C0.b };
+
+         if(Abs(x - mouse0.x) > 5 && Abs(y - mouse0.y) > 5)
+         {
+            thread.center = point;
+
+            if(dx > dy)
+               thread.range = d.a * dx / w;
+            else
+               thread.range = d.b * dy / h;
+
+            Render(true);
+            params.UpdateControls();
+         }
+         else if(!thread.isJulia)
+         {
+            FractalsDesigner designer = (FractalsDesigner) master;
+
+            // Click for Julia
+            designer.julia.thread.juliaPoint = point;
+            designer.paramsJulia.UpdateControls();
+            designer.julia.Render(true);
+         }
+         ReleaseCapture();
+         selecting = false;
+         Update(null);
+      }
+      return true;
+   }
+
+   bool OnRightButtonUp(int x, int y, Modifiers mods)
+   {
+      Complex C0, d, point;
+      int w = image.width;
+      int h = image.height;
+
+      x -= Max((clientSize.w - imageSize.w) / 2, 0);
+      y -= Max((clientSize.h - imageSize.h) / 2, 0);
+      x += scroll.x;
+      y += scroll.y;
+
+      if(w > h)
+      {
+         d.a = thread.range;
+         d.b = thread.range * h / w;
+      }
+      else
+      {
+         d.b = thread.range;
+         d.a = thread.range * w / h;
+      }
+
+      C0.a = thread.center.a - d.a/2;
+      C0.b = thread.center.b - d.b/2;
+
+      point = Complex { x * d.a / w + C0.a, y * d.b / h + C0.b };
+
+      thread.range *= 1.5;
+      thread.center = point;
+
+      Render(true);
+      params.UpdateControls();
+
+      ReleaseCapture();
+      selecting = false;
+      Update(null);
+      return true;
+   }
+
+   bool OnMouseMove(int x, int y, Modifiers mods)
+   {
+      if(selecting)
+      {
+         x -= Max((clientSize.w - imageSize.w) / 2, 0);
+         y -= Max((clientSize.h - imageSize.h) / 2, 0);
+         x += scroll.x;
+         y += scroll.y;
+
+         mouse1 = Point { x, y };
+         Update(null);
+      }
+      else
+      {
+         if(mods.ctrl)
+            params.depthScroll.thumbPosition = 
+                  (int)((double)x / (double)clientSize.w * (double)(params.depthScroll.range - 1));
+         if(mods.shift)
+            params.loop.thumbPosition = (int)((double)y / (double)clientSize.h * 512);
+      }
+      return true;
+   }
+
+   void ComputeImage()
+   {
+      if(this && image.picture)
+      {
+         int w = image.width, h = image.height;
+         int x, y;
+         float * cbuffer;
+         int max;
+         int depth;
+         ColorAlpha * picture = (ColorAlpha *)image.picture;
+
+         thread.mutex.Wait();
+
+         cbuffer = thread.buffer.pixels;
+         depth = thread.depth + 1;
+
+         //max = depth-1;
+         max = numColors-1;
+
+         for(y = 0; y<h; y++)
+         {
+            int bufferPos = y * w;
+            for(x = 0; x<w; x++)
+            {
+               double i = cbuffer[bufferPos+x];
+               if(i >= depth-1)
+               {
+                  *(picture++) = thread.useBlack ? black : palette[numColors - 1];
+               }
+               else
+               {
+                  int index;
+                  if(thread.doLoop)
+                     index = Min((int)(i * thread.numScales), ((int)(thread.maxLoops * numColors)) - 1) % numColors;
+                  else
+                     index = Min((int)(i * thread.numScales), numColors - 1);
+                  if(index < 0) index = 0;
+                  *(picture++) = palette[index];
+               }
+            }
+         }
+         thread.mutex.Release();
+      }
+   }
+
+   void OnRedraw(Surface surface)
+   {
+      JuliaFractal julia = ((FractalsDesigner)master).julia;
+      int x = Max((clientSize.w - imageSize.w) / 2, 0);
+      int y = Max((clientSize.h - imageSize.h) / 2, 0);
+      int w = imageSize.w;
+      int h = imageSize.h;
+      surface.Blit(image, x, y, scroll.x, scroll.y, image.width, image.height);
+      surface.SetForeground(lime);
+      if(selecting)
+         surface.Rectangle(x+mouse0.x - scroll.x, y+mouse0.y - scroll.y, x+mouse1.x - scroll.x, y+mouse1.y - scroll.y);
+      if(!thread.isJulia && julia)
+      {
+         Complex juliaPoint = julia.thread.juliaPoint;
+         Complex C0, d;
+         int w = image.width;
+         int h = image.height;
+         if(w > h)
+         {
+            d.a = thread.range;
+            d.b = thread.range * h / w;
+         }
+         else
+         {
+            d.b = thread.range;
+            d.a = thread.range * w / h;
+         }
+         C0.a = thread.center.a - d.a/2;
+         C0.b = thread.center.b - d.b/2;
+
+         x += (int)((juliaPoint.a - C0.a) * w / d.a ) - scroll.x;
+         y += (int)((juliaPoint.b - C0.b) * h / d.b ) - scroll.y;
+
+         surface.DrawLine(x - 5, y, x + 5, y);
+         surface.DrawLine(x, y - 5, x, y + 5);
+      }
+   }
+
+   void ResetDepth()
+   {
+      thread.terminate = true;
+      app.Unlock();
+      thread.Wait();
+      app.Lock();
+      image.Free();
+      thread.depth = 0;
+      thread.maxDepth = 0;
+   }
+
+   void Render(bool resetDepth)
+   {
+      thread.terminate = true;
+      app.Unlock();
+      thread.Wait();
+      app.Lock();
+
+      if(resetDepth)
+      {
+         int width = imageSize.w;
+         int height = imageSize.h;
+         thread.buffer.Allocate(width, height);
+         image.Free();
+         image.Allocate(null, width, height, 0, pixelFormat888, false);
+         thread.depth = 0;
+         thread.maxDepth = 0;
+      }
+      thread.terminate = false;
+      thread.Create();
+   }
+
+   bool OnCreate()
+   {
+      Render(true);
+      params.UpdateControls();
+      return true;
+   }
+
+   void OnDestroy()
+   {
+      thread.terminate = true;
+      app.Unlock();
+      thread.Wait();
+      app.Lock();
+   }
+
+   void OnScroll(ScrollBarAction action, int position, Key key)
+   {
+      Update(null);
+   }
+
+   OnHScroll = OnScroll;
+   OnVScroll = OnScroll;
+
+   menu = Menu { };
+   Menu fileMenu { parent = menu, "File", f };
+
+   MenuItem exportItem
+   {
+      fileMenu, "Export Image...", e, ctrlE;
+
+      bool NotifySelect(MenuItem selection, Modifiers mods)
+      {
+         bool result = false;
+         FileDialog exportDialog = ((FractalsDesigner)master).exportDialog;
+         exportDialog.SetText("Export %s image", text);
+         if(exportDialog.Modal())
+         {
+            char * ext = exportDialog.types[exportDialog.fileType].typeExtension;
+            if(!ext)
+            {
+               char extension[MAX_EXTENSION];
+               GetExtension(exportDialog.filePath, extension);
+               if(!extension[0])
+               {
+                  ext = "jpg";
+                  ChangeExtension(exportDialog.filePath, ext, exportDialog.filePath);
+               }
+            }
+            image.Save(exportDialog.filePath, ext, (void *) bool::true);
+         }
+         return true;
+      }
+   };
+
+   MenuItem exportItemFiltered
+   {
+      fileMenu, "Export Image (filtered half)...", f;
+
+      bool NotifySelect(MenuItem selection, Modifiers mods)
+      {
+         bool result = false;
+         FileDialog exportDialog = ((FractalsDesigner)master).exportDialog;
+         exportDialog.SetText("Export %s image", text);
+         if(exportDialog.Modal())
+         {
+            char * ext = exportDialog.types[exportDialog.fileType].typeExtension;
+            Bitmap filtered { };
+            if(!ext)
+            {
+               char extension[MAX_EXTENSION];
+               GetExtension(exportDialog.filePath, extension);
+               if(!extension[0])
+               {
+                  ext = "jpg";
+                  ChangeExtension(exportDialog.filePath, ext, exportDialog.filePath);
+               }
+            }
+            if(filtered.Allocate(null, image.width/2, image.height/2, 0, pixelFormat888, false))
+            {
+               Surface surface = filtered.GetSurface(0,0,null);
+               surface.Filter(image, 0,0,0,0, filtered.width, filtered.height, image.width, image.height);
+               filtered.Save(exportDialog.filePath, ext, (void *) bool::true);
+               delete surface;
+            }
+            delete filtered;
+         }
+         return true;
+      }
+   };
+}
+
+class MandelbrotFractal : Fractal
+{
+   text = "Mandelbrot";
+   FractalRenderThread mandelbrotThread { fractal = this, center = { -0.75, 0 }, range = 3 /*, range = 0.049179316033124996, center = { -0.7623287774189061, -0.1281808432395703 }*/ };
+
+   thread = mandelbrotThread;
+}
+
+class JuliaFractal : Fractal
+{
+   text = "Julia";
+   FractalRenderThread juliaThread { fractal = this, isJulia = true, range = 3 };
+   thread = juliaThread;
+}
+
+class FractalParams : Window
+{
+   text = "Mandelbrot Parameters";
+   background = activeBorder;
+   borderStyle = fixed;
+   tabCycle = true;
+   stayOnTop = true;
+   size = Size { 206, 506 };
+
+   Fractal fractal;
+
+   Label { labeledWindow = width, parent = this, position = Point { 16, 8 } };
+   Label { labeledWindow = height, parent = this, position = Point { 104, 8 } };
+   EditBox width
+   {
+      parent = this, text = "Width", position = Point { 16, 32 };
+
+      bool NotifyModified(EditBox editBox)
+      {
+         fractal.imageSize.w = atoi(editBox.contents);
+         fractal.scrollArea = fractal.imageSize;
+         fractal.clientSize = fractal.imageSize;
+         fractal.Render(true);
+         return true;
+      }
+   };
+   EditBox height
+   {
+      parent = this, text = "Height", position = Point { 104, 32 };
+
+      bool NotifyModified(EditBox editBox)
+      {
+         fractal.imageSize.h = atoi(editBox.contents);
+         fractal.scrollArea = fractal.imageSize;
+         fractal.clientSize = fractal.imageSize;
+         fractal.Render(true);
+         return true;
+      }
+   };
+   EditBox exponent 
+   {
+      parent = this, text = "Exponent", position = Point { 16, 88 };
+
+      bool NotifyModified(EditBox editBox)
+      {
+         FractalsDesigner master = (FractalsDesigner)this.master;
+
+         master.mandelbrot.thread.exponent = master.julia.thread.exponent = atoi(editBox.contents);
+
+         master.paramsJulia.UpdateControls();
+         master.julia.Render(true);
+
+         master.paramsMandelbrot.UpdateControls();
+         master.mandelbrot.Render(true);
+         return true;
+      }
+   };
+   EditBox iterations 
+   {
+      parent = this, text = "Iterations", position = Point { 104, 88 };
+
+      bool NotifyModified(EditBox editBox)
+      {
+         fractal.thread.mutex.Wait();
+         fractal.thread.iterations = atoi(editBox.contents);
+         fractal.thread.depth = fractal.thread.iterations;
+         fractal.thread.mutex.Release();
+         fractal.Render(false);
+         return true;
+      }
+   };
+   ScrollBar depthScroll 
+   {
+      parent = this, text = "scrollBar1", size = Size { 172, 16 }, position = Point { 16, 120 };
+
+      void NotifyScrolling(ScrollBar scrollBar, ScrollBarAction action, int position, Key key)
+      {
+         if(fractal)
+         {
+            fractal.thread.depth = position;
+            if(action != setRange)
+            {
+               fractal.ComputeImage();
+               fractal.Update(null);
+               UpdateDisplay();
+            }
+            depthLabel.SetText("%d / %d", fractal.thread.depth, scrollBar.range-1);
+         }
+      }
+   };
+   EditBox centerX 
+   {
+      parent = this, text = "X", size = Size { 166, 19 }, position = Point { 16, 192 };
+
+      bool NotifyModified(EditBox editBox)
+      {
+         fractal.thread.center.a = strtod(editBox.contents, null);
+         fractal.Render(true);
+         return true;
+      }
+   };
+   EditBox centerY 
+   {
+      parent = this, text = "Y", size = Size { 166, 19 }, position = Point { 16, 240 };
+
+      bool NotifyModified(EditBox editBox)
+      {
+         fractal.thread.center.b = strtod(editBox.contents, null);
+         fractal.Render(true);
+         return true;
+      }
+   };
+   EditBox rangeEdit 
+   {
+      parent = this, text = "Range", size = Size { 166, 19 }, position = Point { 16, 296 };
+
+      bool NotifyModified(EditBox editBox)
+      {
+         fractal.thread.range = strtod(editBox.contents, null);
+         fractal.Render(true);
+         return true;
+      }
+   };
+   Button reset 
+   {
+      parent = this, text = "Reset", size = Size { 170, 21 }, position = Point { 16, 328 };
+
+      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
+      {
+         fractal.Reset();
+         return true;
+      }
+   };
+   Label depthLabel { parent = this, anchor = Anchor { top = 152, right = 16 } };
+   Label label1 { labeledWindow = exponent, parent = this, position = Point { 16, 64 } };
+   Label label2 { labeledWindow = iterations, parent = this, position = Point { 104, 64 } };
+   Label label4 { labeledWindow = rangeEdit, parent = this, position = Point { 16, 272 } };
+   Label label5 { labeledWindow = centerY, parent = this, position = Point { 16, 224 } };
+   Label label3 { labeledWindow = centerX, parent = this, position = Point { 16, 176 } };
+
+   ScrollBar loop
+   {
+      this, borderStyle = deep, clientSize = { 124, 18 }, position = { 10, 376 }, range = 5000;
+      text = "Period";
+
+      void NotifyScrolling(ScrollBar scrollBar, ScrollBarAction action, int position, Key key)
+      {
+         if(fractal)
+         {
+            position = Max(position, 1);
+            fractal.thread.loop = position;         
+            fractal.thread.numScales = numColors / position;
+
+            loopEdit.Clear();
+            loopEdit.SetContents("%d", fractal.thread.loop);
+
+            fractal.ComputeImage();
+            fractal.Update(null);
+            UpdateDisplay();
+         }
+      }
+   };
+   Label lblLoop { this, labeledWindow = loop, position = { 10, 356 } };
+   EditBox loopEdit
+   {
+      this, position = { 60, 356 }, size = { 80, 20 };
+
+      bool OnKeyHit(Key key, unichar ch)
+      {
+         if((SmartKey)key == enter) { Deactivate(); Activate(); }
+         return EditBox::OnKeyHit(key, ch);
+      }
+
+      bool NotifyModified(EditBox editBox)
+      {
+         int value = atoi(editBox.contents);
+         if(value != loop.thumbPosition)
+            loop.thumbPosition = value;
+         return true;
+      }
+   };
+   Button loopCheck
+   {
+      this, checked = true, isCheckbox = true, position = { 110, 406 }, text = "Loop";
+
+      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
+      {
+         fractal.thread.doLoop = button.checked;
+         fractal.ComputeImage();
+         fractal.Update(null);
+         UpdateDisplay();
+         return true;
+      }
+   };
+   Button useBlack
+   {
+      this, checked = true, isCheckbox = true, position = { 10, 406 }, text = "Black";
+
+      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
+      {
+         fractal.thread.useBlack = button.checked;
+         fractal.ComputeImage();
+         fractal.Update(null);
+         UpdateDisplay();
+         return true;
+      }
+   };
+
+   ScrollBar maxLoop
+   {
+      this, borderStyle = deep, clientSize = { 124, 18 }, position = { 10, 450 }, range = 5000;
+      text = "Max Loops";
+
+      void NotifyScrolling(ScrollBar scrollBar, ScrollBarAction action, int position, Key key)
+      {
+         if(fractal)
+         {
+            fractal.thread.maxLoops = position;
+            maxLoopEdit.Clear();
+            maxLoopEdit.SetContents("%.0f", fractal.thread.maxLoops);
+            fractal.ComputeImage();
+            fractal.Update(null);
+            UpdateDisplay();
+         }
+      }
+   };
+   Label lblMaxLoop { this, labeledWindow = maxLoop, position = { 10, 430 } };
+   EditBox maxLoopEdit
+   {
+      this, position = { 70, 430 }, size = { 70, 20 };
+
+      bool OnKeyHit(Key key, unichar ch)
+      {
+         if((SmartKey)key == enter) { Deactivate(); Activate(); }
+         return EditBox::OnKeyHit(key, ch);
+      }
+
+      bool NotifyModified(EditBox editBox)
+      {
+         float value = atof(editBox.contents);
+         //if(value != maxLoop.thumbPosition)
+         {
+            //thumbPosition = value;
+            fractal.thread.maxLoops = value;
+            fractal.ComputeImage();
+            fractal.Update(null);
+            UpdateDisplay();
+         }
+         return true;
+      }
+   };
+
+   virtual void UpdateControls()
+   {
+      float maxLoops = fractal.thread.maxLoops;
+
+      exponent.SetContents("%d", fractal.thread.exponent);
+      iterations.SetContents("%d", fractal.thread.iterations);
+      centerX.SetContents("%.20f", fractal.thread.center.a);
+      centerY.SetContents("%.20f", fractal.thread.center.b);
+      rangeEdit.SetContents("%.20f", fractal.thread.range);
+      width.SetContents("%d", fractal.imageSize.w);
+      height.SetContents("%d", fractal.imageSize.h);
+
+      loop.thumbPosition = (int)fractal.thread.loop;
+      loopEdit.Clear(); loopEdit.SetContents("%d", fractal.thread.loop);
+      maxLoop.thumbPosition = (int)fractal.thread.maxLoops;
+      fractal.thread.maxLoops = maxLoops;
+      maxLoopEdit.Clear(); maxLoopEdit.SetContents("%f", fractal.thread.maxLoops);
+      useBlack.checked = fractal.thread.useBlack;
+      loopCheck.checked = fractal.thread.doLoop;
+   }
+
+   bool OnCreate()
+   {
+      width.Activate();
+      return true;
+   }
+}
+
+class FractalParamsJulia : FractalParams
+{
+   text = "Julia Parameters";
+   size = Size { 206, 610 };
+
+   EditBox juliaX 
+   {
+      parent = this, text = "Xj", size = Size { 166, 19 }, position = Point { 16, 504 };
+
+      bool NotifyModified(EditBox editBox)
+      {
+         fractal.Render(true);
+         fractal.thread.juliaPoint.a = strtod(editBox.contents, null);
+         return true;
+      }
+   };
+   EditBox juliaY 
+   {
+      parent = this, text = "Yj", size = Size { 166, 19 }, position = Point { 16, 552 };
+
+      bool NotifyModified(EditBox editBox)
+      {
+         fractal.Render(true);
+         fractal.thread.juliaPoint.b = strtod(editBox.contents, null);
+         return true;
+      }
+   };
+   Label label7 { labeledWindow = juliaY, parent = this, position = Point { 16, 528 } };
+   Label label6 { labeledWindow = juliaX, parent = this, position = Point { 16, 480 } };
+
+   void UpdateControls()
+   {
+      FractalParams::UpdateControls();
+      juliaX.SetContents("%.20f", fractal.thread.juliaPoint.a);
+      juliaY.SetContents("%.20f", fractal.thread.juliaPoint.b);
+   }
+}
+
+static FileFilter fractalFilters[] =
+{
+   { "ECERE Fractal Files (*.frc)", "frc" },
+   { "All files", null }
+};
+static FileType fractalTypes[] =
+{
+   { "ECERE Fractal", "frc", always },
+};
+
+static FileFilter imageFilters[] =
+{
+   {
+      "Image Files (*.jpg, *.jpeg, *.bmp, *.pcx, *.png, *.gif)",
+      "jpg, jpeg, bmp, pcx, png, gif"
+   },
+   { "All files", null }
+};
+static FileType imageTypes[] =
+{
+   { "Based on extension", null,  always },
+   { "JPG Image",          "jpg", always },
+   { "BMP Image",          "bmp", always },
+   { "PCX Image",          "pcx", always },
+   { "PNG Image",          "png", always },
+   { "GIF Image",          "gif", always }
+};
+
+#define FRC_RECOGNITION { 'e', 'F', 'R', 'C', 11, 12, 3, 0 }
+static byte frcRecognition[] = FRC_RECOGNITION;
+
+class FractalsDesigner : Window
+{
+   text = "Ecere Fractals Explorer";
+   background = dimGray;
+   borderStyle = sizable;
+   hasMaximize = true;
+   hasMinimize = true;
+   hasClose = true;
+   hasMenuBar = true;
+   menu = Menu {  };
+   size = Size { 1000, 600 };
+   state = maximized;
+   isDocument = true;
+   hasHorzScroll = true;
+   hasVertScroll = true;
+
+   Menu fileMenu { menu, "File", f };
+   MandelbrotFractal mandelbrot { this, position = Point { 10, 10 }, params = paramsMandelbrot };
+   FractalParams paramsMandelbrot { this, anchor = Anchor { right = 260, top = 10 }, fractal = mandelbrot };
+   JuliaFractal julia { this, position = Point { 420, 10 }, params = paramsJulia };
+   FractalParamsJulia paramsJulia { this, anchor = Anchor { right = 10, top = 10 }, fractal = julia };
+   GradientDesigner gradientDesigner
+   {
+      this, anchor = { left = 10, bottom = 10 }, stayOnTop = true;
+      void NotifyUpdate()
+      {
+         PaletteGradient(palette, numColors, gradient.keys._, gradient.keys.size, gradient.smoothness);
+
+         mandelbrot.ComputeImage();
+         julia.ComputeImage();
+         mandelbrot.Update(null);
+         julia.Update(null);
+         UpdateDisplay();
+      }
+   };
+   bool LoadFractals(char * fileName)
+   {
+      File f = FileOpen(fileName, read);
+      if(f)
+      {
+         static byte frcRead[8];
+         mandelbrot.ResetDepth();
+         julia.ResetDepth();
+
+         // First attempt to treat this as an archive file
+         if(f.Read(frcRead, sizeof(frcRead), 1) == 1 &&
+            !strncmp(frcRead, frcRecognition, sizeof(frcRecognition)))
+         {
+            TempFile bufferZ;
+
+            mandelbrot.thread.terminate = true;
+            julia.thread.terminate = true;
+            app.Unlock();
+            mandelbrot.thread.Wait();
+            julia.thread.Wait();
+            app.Lock();
+
+            // New binary format
+            f.Get(mandelbrot.imageSize);
+            f.Get(mandelbrot.thread.exponent);
+            f.Get(mandelbrot.thread.iterations);
+            f.Get(mandelbrot.thread.center);
+            f.Get(mandelbrot.thread.range);
+            f.Get(mandelbrot.thread.depth);
+            f.Get(mandelbrot.thread.maxDepth);
+            f.Get(bufferZ);
+            mandelbrot.thread.buffer.Allocate(mandelbrot.imageSize.w, mandelbrot.imageSize.h);
+            if(bufferZ)
+            {
+               bufferZ.Read(mandelbrot.thread.buffer.z, sizeof(Complex), mandelbrot.thread.buffer.width * mandelbrot.thread.buffer.height);
+               bufferZ.Read(mandelbrot.thread.buffer.pixels, sizeof(float), mandelbrot.thread.buffer.width * mandelbrot.thread.buffer.height);
+            }
+            delete bufferZ;
+
+            f.Get(julia.imageSize);
+            f.Get(julia.thread.exponent);
+            f.Get(julia.thread.iterations);
+            f.Get(julia.thread.center);
+            f.Get(julia.thread.range);
+            f.Get(julia.thread.depth);
+            f.Get(julia.thread.maxDepth);
+            //julia.thread.iterations = julia.thread.maxDepth;
+            f.Get(bufferZ);
+            julia.thread.buffer.Allocate(julia.imageSize.w, julia.imageSize.h);
+            if(bufferZ)
+            {
+               bufferZ.Read(julia.thread.buffer.z, sizeof(Complex), julia.thread.buffer.width * julia.thread.buffer.height);
+               bufferZ.Read(julia.thread.buffer.pixels, sizeof(float), julia.thread.buffer.width * julia.thread.buffer.height);
+            }
+            delete bufferZ;
+
+            f.Get(julia.thread.juliaPoint);
+
+            f.Get(gradient.smoothness);
+            {
+               int size;
+               f.Get(size);
+               if(size)
+               {
+                  gradient.keys.size = size;
+                  f.Read(gradient.keys._, sizeof(ColorKey), gradient.keys.size);
+               }
+            }
+            {
+               int loop = 0;
+               f.Get(loop);
+               if(loop)
+               {
+                  mandelbrot.thread.loop = loop;
+                  f.Get(mandelbrot.thread.maxLoops);
+                  if(mandelbrot.thread.maxLoops < 0.00001) mandelbrot.thread.maxLoops = 4999;
+                  f.Get(mandelbrot.thread.useBlack);
+                  f.Get(mandelbrot.thread.doLoop);
+               }
+               loop = 0;
+               f.Get(loop);
+               if(loop)
+               {
+                  julia.thread.loop = loop;
+                  f.Get(julia.thread.maxLoops);
+                  if(julia.thread.maxLoops < 0.00001) julia.thread.maxLoops = 4999;
+                  f.Get(julia.thread.useBlack);
+                  f.Get(julia.thread.doLoop);
+               }
+               else
+               {
+                  julia.thread.loop = mandelbrot.thread.loop;
+                  julia.thread.maxLoops = mandelbrot.thread.maxLoops;
+                  julia.thread.useBlack = mandelbrot.thread.useBlack;
+                  julia.thread.doLoop = mandelbrot.thread.doLoop;
+               }
+            }
+
+            gradientDesigner.UpdateHandles();
+
+            mandelbrot.clientSize = mandelbrot.imageSize;
+            julia.clientSize = julia.imageSize;
+
+            mandelbrot.params.UpdateControls();
+            julia.params.UpdateControls();
+
+            mandelbrot.image.Free();
+            mandelbrot.image.Allocate(null, mandelbrot.imageSize.w, mandelbrot.imageSize.h, 0, pixelFormat888, false);
+            mandelbrot.ComputeImage();
+
+            julia.image.Free();
+            julia.image.Allocate(null, julia.imageSize.w, julia.imageSize.h, 0, pixelFormat888, false);
+            julia.ComputeImage();
+
+            julia.thread.terminate = false;
+            julia.thread.Create();
+
+            mandelbrot.thread.terminate = false;
+            mandelbrot.thread.Create();
+         }
+         else
+         {
+            // Old ASCII Format
+            f.Seek(0, start);
+
+            mandelbrot.imageSize.w = f.GetValue();
+            mandelbrot.imageSize.h = f.GetValue();
+            mandelbrot.thread.exponent = f.GetValue();
+            mandelbrot.thread.iterations = f.GetValue();
+            mandelbrot.thread.center.a = f.GetDouble();
+            mandelbrot.thread.center.b = f.GetDouble();
+            mandelbrot.thread.range = f.GetDouble();
+
+            julia.imageSize.w = f.GetValue();
+            julia.imageSize.h = f.GetValue();
+            julia.thread.exponent = f.GetValue();
+            julia.thread.iterations = f.GetValue();
+            julia.thread.center.a = f.GetDouble();
+            julia.thread.center.b = f.GetDouble();
+            julia.thread.range = f.GetDouble();
+
+            julia.thread.juliaPoint.a = f.GetDouble();
+            julia.thread.juliaPoint.b = f.GetDouble();
+
+            {
+               int size = f.GetValue();
+               if(size)
+               {
+                  int c;
+                  gradient.keys.size = size;
+                  for(c = 0; c < gradient.keys.size; c++)
+                  {
+                     gradient.keys._[c].percent = f.GetFloat();
+                     gradient.keys._[c].color.a = 255;
+                     gradient.keys._[c].color.color.r = (byte)f.GetValue();
+                     gradient.keys._[c].color.color.g = (byte)f.GetValue();
+                     gradient.keys._[c].color.color.b = (byte)f.GetValue();
+                  }
+                  gradient.smoothness = f.GetFloat();
+                  {
+                     int loop = 0;
+                     loop = f.GetValue();
+                     if(loop)
+                     {
+                        mandelbrot.thread.loop = loop;
+                        mandelbrot.thread.maxLoops = f.GetFloat();
+                        mandelbrot.thread.useBlack = f.GetValue();
+                        mandelbrot.thread.doLoop = f.GetValue();
+                     }
+                     loop = f.GetValue();
+                     if(loop)
+                     {
+                        julia.thread.loop = loop;
+                        julia.thread.maxLoops = f.GetFloat();
+                        julia.thread.useBlack = f.GetValue();
+                        julia.thread.doLoop = f.GetValue();
+                     }
+                     else
+                     {
+                        julia.thread.loop = mandelbrot.thread.loop;
+                        julia.thread.maxLoops = mandelbrot.thread.maxLoops;
+                        julia.thread.useBlack = mandelbrot.thread.useBlack;
+                        julia.thread.doLoop = mandelbrot.thread.doLoop;
+                     }
+                  }
+               }
+            }
+
+            gradientDesigner.UpdateHandles();
+
+            paramsJulia.UpdateControls();
+            paramsMandelbrot.UpdateControls();
+
+            julia.scrollArea = julia.imageSize;
+            julia.clientSize = julia.imageSize;
+
+            mandelbrot.scrollArea = mandelbrot.imageSize;
+            mandelbrot.clientSize = mandelbrot.imageSize;
+
+            julia.Render(true);
+            mandelbrot.Render(true);
+         }
+         delete f;
+
+         this.fileName = fileName;
+         return true;
+      }
+      return false;
+   }
+   MenuItem openItem
+   {
+      fileMenu, "Open", o, ctrlO;
+
+      bool NotifySelect(MenuItem selection, Modifiers mods)
+      {
+         if(openDialog.Modal() == ok)
+         {
+            if(LoadFractals(openDialog.filePath))
+            {
+            }
+         }
+         return true;
+      }
+   };
+   MenuDivider { fileMenu };
+   MenuPlacement { fileMenu, "Export Image...", e };
+   MenuPlacement { fileMenu, "Export Image (filtered half)...", f };
+   MenuDivider { fileMenu };
+   MenuItem saveItemData
+   {
+      fileMenu, "Save (with data)";
+      bool NotifySelect(MenuItem selection, Modifiers mods)
+      {
+         return MenuFileSave(selection, mods);
+      }
+   };
+   MenuItem saveItemAsData
+   {
+      fileMenu, "Save As (with data)...";
+      
+      bool NotifySelect(MenuItem selection, Modifiers mods)
+      {
+         return MenuFileSaveAs(selection, mods);
+      }
+   };
+   MenuDivider { fileMenu };
+   MenuItem saveItem
+   {
+      fileMenu, "Save", s, ctrlS;
+
+      bool NotifySelect(MenuItem selection, Modifiers mods)
+      {
+         saveASCII = true;
+         MenuFileSave(selection, mods);
+         saveASCII = false;
+         return true;
+      }
+   };
+   MenuItem saveItemAs
+   {
+      fileMenu, "Save As...", a;
+
+      bool NotifySelect(MenuItem selection, Modifiers mods)
+      {
+         saveASCII = true;
+         MenuFileSaveAs(selection, mods);
+         saveASCII = false;
+         return true;
+      }
+   };
+   MenuDivider { fileMenu };
+   MenuItem exitItem { fileMenu, "Exit", x, altF4, NotifySelect = MenuFileExit };
+
+   FileDialog exportDialog
+   {
+      master = this, type = save,
+      filters = imageFilters, sizeFilters = sizeof(imageFilters),
+      types = imageTypes, sizeTypes = sizeof(imageTypes)
+   };
+   FileDialog mySaveDialog
+   {
+      master = this, type = save, text = "Save Fractals Settings...",
+      types = fractalTypes, sizeTypes = sizeof(fractalTypes), filters = fractalFilters, sizeFilters = sizeof(fractalFilters)
+   };
+   
+   FileDialog openDialog
+   {
+      master = this, type = open, text = "Load Fractals Settings...",
+      types = fractalTypes, sizeTypes = sizeof(fractalTypes), filters = fractalFilters, sizeFilters = sizeof(fractalFilters)
+   };
+   bool saveASCII;
+
+   saveDialog = mySaveDialog;
+   
+   bool OnSaveFile(char * fileName)
+   {
+      File f = FileOpen(fileName, write);
+      if(f)
+      {
+         if(!saveASCII)
+         {
+            TempFile bufferZ { };
+            f.Write(frcRecognition, sizeof(frcRecognition), 1);
+            f.Put(mandelbrot.imageSize);
+            f.Put(mandelbrot.thread.exponent);
+            f.Put(mandelbrot.thread.iterations);
+            f.Put(mandelbrot.thread.center);
+            f.Put(mandelbrot.thread.range);
+            f.Put(mandelbrot.thread.depth);
+            f.Put(mandelbrot.thread.maxDepth);
+
+            bufferZ.Write(mandelbrot.thread.buffer.z, sizeof(Complex), mandelbrot.thread.buffer.width * mandelbrot.thread.buffer.height);
+            bufferZ.Write(mandelbrot.thread.buffer.pixels, sizeof(float), mandelbrot.thread.buffer.width * mandelbrot.thread.buffer.height);
+            f.Put(bufferZ);
+            bufferZ.Truncate(0);
+            bufferZ.Seek(0, start);
+
+            f.Put(julia.imageSize);
+            f.Put(julia.thread.exponent);
+            f.Put(julia.thread.iterations);
+            f.Put(julia.thread.center);
+            f.Put(julia.thread.range);
+            f.Put(julia.thread.depth);
+            f.Put(julia.thread.maxDepth);
+
+            bufferZ.Write(julia.thread.buffer.z, sizeof(Complex), julia.thread.buffer.width * julia.thread.buffer.height);
+            bufferZ.Write(julia.thread.buffer.pixels, sizeof(float), julia.thread.buffer.width * julia.thread.buffer.height);
+            f.Put(bufferZ);
+
+            f.Put(julia.thread.juliaPoint);
+
+            f.Put(gradient.smoothness);
+            { int size = gradient.keys.size; f.Put(size); }
+            f.Write(gradient.keys._, sizeof(ColorKey), gradient.keys.size);
+
+            f.Put(mandelbrot.thread.loop);
+            f.Put(mandelbrot.thread.maxLoops);
+            f.Put(mandelbrot.thread.useBlack);
+            f.Put(mandelbrot.thread.doLoop);
+
+            f.Put(julia.thread.loop);
+            f.Put(julia.thread.maxLoops);
+            f.Put(julia.thread.useBlack);
+            f.Put(julia.thread.doLoop);
+
+            delete bufferZ;
+         }
+         else
+         {
+            int c;
+            f.Printf("%d\n",  mandelbrot.imageSize.w);
+            f.Printf("%d\n",  mandelbrot.imageSize.h);
+            f.Printf("%d\n",  mandelbrot.thread.exponent);
+            f.Printf("%d\n",  mandelbrot.thread.iterations);
+            f.Printf("%.20f\n",  mandelbrot.thread.center.a);
+            f.Printf("%.20f\n",  mandelbrot.thread.center.b);
+            f.Printf("%.20f\n",  mandelbrot.thread.range);
+
+            f.Printf("%d\n",  julia.imageSize.w);
+            f.Printf("%d\n",  julia.imageSize.h);
+            f.Printf("%d\n",  julia.thread.exponent);
+            f.Printf("%d\n",  julia.thread.iterations);
+            f.Printf("%.20f\n",  julia.thread.center.a);
+            f.Printf("%.20f\n",  julia.thread.center.b);
+            f.Printf("%.20f\n",  julia.thread.range);
+
+            f.Printf("%.20f\n",  julia.thread.juliaPoint.a);
+            f.Printf("%.20f\n",  julia.thread.juliaPoint.b);
+
+            f.Printf("%d\n", gradient.keys.size);
+            for(c = 0; c < gradient.keys.size; c++)
+            {
+               f.Printf("%.20f\n", gradient.keys._[c].percent);
+               f.Printf("%d\n", gradient.keys._[c].color.color.r);
+               f.Printf("%d\n", gradient.keys._[c].color.color.g);
+               f.Printf("%d\n", gradient.keys._[c].color.color.b);
+            }
+            f.Printf("%.20f\n", gradient.smoothness);
+
+            f.Printf("%d\n", mandelbrot.thread.loop);
+            f.Printf("%.20f\n", mandelbrot.thread.maxLoops);
+            f.Printf("%d\n", mandelbrot.thread.useBlack);
+            f.Printf("%d\n", mandelbrot.thread.doLoop);
+
+            f.Printf("%d\n", julia.thread.loop);
+            f.Printf("%.20f\n", julia.thread.maxLoops);
+            f.Printf("%d\n", julia.thread.useBlack);
+            f.Printf("%d\n", julia.thread.doLoop);
+         }
+         delete f;
+      }           
+      return true;
+   }
+
+   bool OnPostCreate()
+   {
+      if(app.argc > 1)
+         LoadFractals(app.argv[1]);
+      mandelbrot.Activate();
+      return true;
+   }
+}
+
+FractalsDesigner fractalsDesigner { };
diff --git a/fractals.epj b/fractals.epj
new file mode 100644 (file)
index 0000000..2a56ec3
--- /dev/null
@@ -0,0 +1,38 @@
+{
+   "Version" : 0.2,
+   "ModuleName" : "fractals",
+   "Options" : {
+      "MemoryGuard" : false,
+      "Profile" : false,
+      "StrictNameSpaces" : false,
+      "TargetType" : "Executable",
+      "TargetFileName" : "fractals",
+      "Libraries" : [
+         "ecere"
+      ],
+      "Console" : false,
+      "Compress" : false
+   },
+   "Configurations" : [
+      {
+         "Name" : "Debug",
+         "Options" : {
+            "Debug" : true
+         }
+      },
+      {
+         "Name" : "Release",
+         "Options" : {
+            "Optimization" : "Speed"
+         }
+      }
+   ],
+   "Files" : [
+      "fractals.ec",
+      "GradientDesigner.ec"
+   ],
+   "ResourcesPath" : "",
+   "Resources" : [
+
+   ]
+}
\ No newline at end of file
diff --git a/samples/MOI.frc b/samples/MOI.frc
new file mode 100644 (file)
index 0000000..17efb63
--- /dev/null
@@ -0,0 +1,42 @@
+400
+400
+2
+1000
+0.26809175612074615000
+0.00290833390405489810
+0.04324445480531056300
+800
+800
+2
+5000
+0.17555712890625000000
+-0.33306621093750000000
+0.01337402343750000000
+0.28528142690585712000
+0.01469244783850202700
+4
+0.00000000000000000000
+255
+0
+0
+0.31010451912879944000
+255
+255
+255
+0.63588851690292358000
+242
+104
+226
+1.00000000000000000000
+255
+0
+0
+0.98999994993209839000
+76
+4999.00000000000000000000
+1
+1
+9
+4999.00000000000000000000
+1
+1
diff --git a/samples/colorful.frc b/samples/colorful.frc
new file mode 100644 (file)
index 0000000..b5a3e93
--- /dev/null
@@ -0,0 +1,70 @@
+400
+400
+4
+100
+0.00000000000000000000
+0.00000000000000000000
+3.00000000000000000000
+400
+400
+4
+1000
+0.00000000000000000000
+0.00000000000000000000
+3.00000000000000000000
+0.63000000000000000000
+-0.36749999999999999000
+11
+0.00000000000000000000
+0
+0
+128
+0.10000000149011612000
+0
+128
+0
+0.20000000298023224000
+255
+255
+0
+0.30000001192092896000
+255
+165
+0
+0.40000000596046448000
+255
+0
+0
+0.50000000000000000000
+128
+0
+128
+0.60000002384185791000
+255
+255
+240
+0.69999992847442627000
+255
+99
+71
+0.80000001192092896000
+255
+255
+255
+0.89999991655349731000
+176
+224
+230
+1.00000000000000000000
+0
+0
+0
+0.00000000000000000000
+100
+4999.00000000000000000000
+1
+1
+100
+4999.00000000000000000000
+1
+1
diff --git a/samples/coolSpiral.frc b/samples/coolSpiral.frc
new file mode 100644 (file)
index 0000000..fef7c89
--- /dev/null
@@ -0,0 +1,42 @@
+400
+400
+2
+100
+0.00000000000000000000
+0.00000000000000000000
+2.50000000000000000000
+400
+400
+2
+150
+-0.19867734611034393000
+-0.00888702273368835450
+0.48599210381507874000
+0.37799999117851257000
+-0.30700001120567322000
+5
+0.00000000000000000000
+0
+0
+128
+0.18641115725040436000
+0
+0
+0
+0.37804871797561646000
+1
+97
+104
+0.52787452936172485000
+255
+255
+240
+1.00000000000000000000
+0
+0
+0
+0.00000000000000000000
+150
+4999.00000000000000000000
+0
+0
diff --git a/samples/deep.frc b/samples/deep.frc
new file mode 100644 (file)
index 0000000..92dfb43
--- /dev/null
@@ -0,0 +1,46 @@
+800
+800
+2
+1500
+0.36926081705566405000
+-0.09344808499511718400
+0.00145261168945312520
+400
+400
+2
+2000
+0.00000000000000000000
+0.00000000000000000000
+3.00000000000000000000
+-0.69374499999999995000
+-0.29999999999999999000
+6
+0.00000000000000000000
+0
+0
+128
+0.19860625267028809000
+146
+213
+237
+0.30000001192092896000
+255
+255
+255
+0.44425091147422791000
+255
+255
+124
+0.63414639234542847000
+255
+100
+0
+1.00000000000000000000
+0
+0
+128
+0.00000000000000000000
+147
+4999.00000000000000000000
+1
+1
diff --git a/samples/hot.frc b/samples/hot.frc
new file mode 100644 (file)
index 0000000..d7787eb
--- /dev/null
@@ -0,0 +1,50 @@
+400
+400
+2
+1000
+-0.89643740653991699000
+0.25743749737739563000
+0.13822497427463531000
+400
+400
+2
+1000
+0.00309374998323619370
+-0.00024999998277053237
+0.16912500560283661000
+-0.74538516998291016000
+0.11303885281085968000
+6
+0.00000000000000000000
+255
+255
+255
+0.16027875244617462000
+0
+24
+25
+0.50871080160140991000
+77
+0
+0
+0.76306629180908203000
+217
+162
+0
+0.82926815748214722000
+222
+212
+136
+1.00000000000000000000
+0
+0
+0
+0.00000000000000000000
+60
+4999.00000000000000000000
+1
+0
+180
+4999.00000000000000000000
+1
+0
diff --git a/samples/hot2.frc b/samples/hot2.frc
new file mode 100644 (file)
index 0000000..6fb4303
--- /dev/null
@@ -0,0 +1,50 @@
+400
+400
+2
+1000
+-0.89643728733062744000
+0.25743749737739563000
+0.13822494447231293000
+800
+400
+2
+1000
+0.00309374998323619370
+-0.00024999998277053237
+0.40000000596046448000
+-0.74538516998291016000
+0.11303885281085968000
+6
+0.00000000000000000000
+255
+255
+255
+0.16027875244617462000
+0
+24
+25
+0.50871080160140991000
+77
+0
+0
+0.76306641101837158000
+217
+162
+0
+0.82926815748214722000
+222
+212
+136
+1.00000000000000000000
+0
+0
+0
+0.00000000000000000000
+60
+4999.00000000000000000000
+1
+0
+180
+4999.00000000000000000000
+1
+1
diff --git a/samples/oldColors.frc b/samples/oldColors.frc
new file mode 100644 (file)
index 0000000..d1644d8
--- /dev/null
@@ -0,0 +1,66 @@
+400
+400
+2
+100
+-0.75000000000000000000
+0.00000000000000000000
+3.00000000000000000000
+400
+400
+2
+100
+0.00000000000000000000
+0.00000000000000000000
+3.00000000000000000000
+0.37799999117851257000
+-0.30700001120567322000
+11
+0.00000000000000000000
+0
+0
+128
+0.10000000149011612000
+0
+128
+0
+0.20000000298023224000
+255
+255
+0
+0.30000001192092896000
+255
+165
+0
+0.40000000596046448000
+255
+0
+0
+0.50000000000000000000
+128
+0
+128
+0.60000002384185791000
+255
+255
+240
+0.69999998807907104000
+255
+99
+71
+0.80000001192092896000
+255
+255
+255
+0.89999997615814209000
+176
+224
+230
+1.00000000000000000000
+0
+0
+0
+0.98999994993209839000
+100
+4999.00000000000000000000
+1
+1
diff --git a/samples/purplishmandelbrot.frc b/samples/purplishmandelbrot.frc
new file mode 100644 (file)
index 0000000..dbaf868
--- /dev/null
@@ -0,0 +1,38 @@
+2800
+2200
+2
+600
+-0.75000000000000000000
+0.00000000000000000000
+3.00000000000000000000
+400
+400
+2
+100
+0.00000000000000000000
+0.00000000000000000000
+3.00000000000000000000
+0.37799999117851257000
+-0.30700001120567322000
+3
+0.00000000000000000000
+0
+0
+0
+0.23344947397708893000
+43
+43
+122
+1.00000000000000000000
+255
+255
+255
+0.98999994993209839000
+100
+4999.00000000000000000000
+1
+0
+100
+4999.00000000000000000000
+1
+1
diff --git a/samples/seaHorseValley.frc b/samples/seaHorseValley.frc
new file mode 100644 (file)
index 0000000..4a14824
--- /dev/null
@@ -0,0 +1,50 @@
+400
+400
+2
+1000
+-0.76232877741890614000
+-0.12818084323957030000
+0.04917931603312499600
+400
+400
+2
+1000
+0.00000000000000000000
+0.00000000000000000000
+3.00000000000000000000
+-0.72689534770911413000
+0.18888712904384594000
+6
+0.00000000000000000000
+0
+0
+128
+0.19860626757144928000
+146
+213
+237
+0.30000001192092896000
+255
+255
+255
+0.44425088167190552000
+255
+255
+124
+0.63414633274078369000
+255
+100
+0
+1.00000000000000000000
+0
+0
+128
+0.00000000000000000000
+100
+4999.00000000000000000000
+1
+1
+100
+4999.00000000000000000000
+1
+1
diff --git a/samples/star.frc b/samples/star.frc
new file mode 100644 (file)
index 0000000..4c6c0c5
--- /dev/null
@@ -0,0 +1,16 @@
+400
+400
+5
+500
+-0.69424901779316717000
+-0.29999612278567511000
+0.00168594977911376950
+800
+800
+5
+2000
+0.00000000000000000000
+0.02812500000000000100
+0.99375000000000002000
+-0.69374499999999995000
+-0.29999999999999999000
diff --git a/samples/sunnySpiral.frc b/samples/sunnySpiral.frc
new file mode 100644 (file)
index 0000000..132601f
--- /dev/null
@@ -0,0 +1,46 @@
+400
+400
+2
+1000
+-0.75000000000000000000
+0.00000000000000000000
+3.00000000000000000000
+400
+400
+2
+1000
+-0.19685624539852142000
+0.50236874818801880000
+0.58953744173049927000
+0.37799999117851257000
+-0.30700001120567322000
+6
+0.00000000000000000000
+0
+0
+128
+0.19860625267028809000
+146
+213
+237
+0.30000001192092896000
+255
+255
+255
+0.44425091147422791000
+255
+255
+124
+0.63414639234542847000
+255
+100
+0
+1.00000000000000000000
+0
+0
+128
+0.00000000000000000000
+100
+1.50000000000000000000
+1
+1
diff --git a/samples/woah.frc b/samples/woah.frc
new file mode 100644 (file)
index 0000000..ee81b02
--- /dev/null
@@ -0,0 +1,50 @@
+400
+400
+2
+100
+-0.04506194218993187000
+-0.98681175708770752000
+0.00000000143657574725
+3840
+2400
+2
+26993
+0.00953226536512374880
+-0.02591441385447979000
+0.04704407602548599200
+-0.04506194218993187000
+-0.98681175708770752000
+6
+0.00000000000000000000
+0
+0
+128
+0.19860625267028809000
+146
+213
+237
+0.30000001192092896000
+255
+255
+255
+0.44425091147422791000
+255
+255
+124
+0.63414639234542847000
+255
+100
+0
+1.00000000000000000000
+0
+0
+128
+0.00000000000000000000
+263
+4999.00000000000000000000
+1
+1
+127
+4999.00000000000000000000
+1
+1