11.22.2007

Jugando con fractales

Hacía tiempo que tenía ganas de probar a renderizar un fractal y leyendo ayer en la wikipedia acerca de Mandelbrot vi que el código era muy simple. Después de todo es calcular si una función diverge tras una serie de iteraciones por cada pixel.

He hecho un pequeño código en python para hacer pruebecillas, la verdad que con muy poco código se obtienen imágenes bastante bonitas, de hecho estoy pensando en imprimir alguna y ponerla de poster. Se generan imágenes de este pelo:





El código, necesita PIL y python 2.4 y he dejado algunas funciones para generar animaciones (con un par de hilos para -intentar- aprovechar mi dual core) y 3 ó 4 test de resultados chulos. El código generado HTML a partir del .py usando, como no, vim, la putada es que blogger te mete saltos de linea y hay que quitar los tags br, con un simple replace basta.


import Image;
from threading import Thread;
SIZEX = 1024;
SIZEY = int(SIZEX*2.0/3.0);
NFRAMES = 50;
ZOOMSTEP = 1.0;

def get_color(xx,yy):
  x = x0 = xx;
  y = y0 = yy;
  colour = 0;
  iteration = 0
  maxiteration = 600

  while ( x*x + y*y < 4  and  iteration < maxiteration ):
    tx = x*x - y*y + x0;
    ty = 2*x*y + y0;
    x = tx;
    y = ty;
    iteration = iteration + 1;
  

  colour = float(iteration)/maxiteration;
 
  return colour;

class Palete:
    def __init__(self):
        self._colors = []
        pass;
    def addColor(self,color, t):
        self._colors.append((color,t));
    def getColor(self,t):
        #search for color
        i = 0;
        while(self._colors[i][1] < t):
            i+=1;
        if(i == 0):
            return self._colors[0][0];
        else:
            t0 = self._colors[i-1][1];
            t1 = self._colors[i][1]
            dist = t1-t0;
            d = (t-t0)/dist;
            c0 = self._colors[i-1][0];
            c1 = self._colors[i][0];
            #print d
            color = (c0[0] + d*(c1[0] - c0[0]),
                     c0[1] + d*(c1[1] - c0[1]),
                     c0[2] + d*(c1[2] - c0[2]));
            return color;

            

def get_img_fractal(zoom, px,py, palete):
    im = Image.new("RGB", (SIZEX,SIZEY))
    for x in range(SIZEX):
     for y in range(SIZEY):
        t = get_color(px + zoom*(-2.0 + 3.0*(float(x)/SIZEX)) ,py + zoom*(-1.0 + 2.0*(float(y)/SIZEY)));
        color = palete.getColor(t);        
        im.putpixel((x,y),(255*color[0],255*color[1],255*color[2]));
    return im;
    

class Fractal(Thread):
   def __init__ (self,fractals):
      Thread.__init__(self)
      self._fractals = fractals;
   def run(self):
       p = Palete();
       p.addColor((1.0,1.0,1.0),0.0);
       p.addColor((1.0,0.0,0.0),0.5);
       p.addColor((1.0,1.0,1.0),1.0);
       for x in self._fractals:
           print "generating %.3d.png" % x[3];
           get_img_fractal(x[0],x[1],x[2],p).save("%.3d.png" % x[3]);


    

def animation():

    zoom = 1.0;
    it = 0;
    fractals = [];
    for n in range(NFRAMES):  
        fractals.append((zoom,-0.77028065155993652446, -0.11144667326007166574,n));
        zoom = zoom/2.0;
        it+=1;

    #launch two threadç
    f1 = Fractal(fractals[:len(fractals)/2]);
    f2 = Fractal(fractals[len(fractals)/2:-1]);

    f1.start();    
    f2.start();

def testColoring():
    p = Palete();
    p.addColor((1.0,1.0,1.0),0.0);
    p.addColor((1.0,0.0,0.0),0.5);
    p.addColor((1.0,1.0,1.0),1.0);
    get_img_fractal(0.4,-0.77028065155993652446, -0.11144667326007166574,p).save("coloring.png");

def testColoring2():
    p = Palete();
    p.addColor((0.0,0.0,0.0),0.0);
    p.addColor((145.0/255.0,165.0/255.0,192.0/255.0),0.5);
    p.addColor((1.0,1.0,1.0),1.0);

    get_img_fractal(0.0001,-0.77028065155993652446, -0.11144667326007166574,p).save("coloring2.png");

def testColoring3():
    p = Palete();
    p.addColor((1.0,1.0,1.0),0.0);
    p.addColor((145.0/255.0,165.0/255.0,192.0/255.0),0.5);
    p.addColor((0.0,125.0/255.0,244.0/255.0),1.0);

    get_img_fractal(0.0001,-0.77028065155993652446, -0.11144667326007166574,p).save("coloring3.png");

def testColoring4():
    p = Palete();
    p.addColor((0.0,0.0,0.0),0.0);
    p.addColor((145.0/255.0,165.0/255.0,192.0/255.0),0.5);
    p.addColor((1.0,1.0,1.0),1.0);

    get_img_fractal(0.00001,-0.26605838294940658357,-0.65123057178473777796,p).save("coloring4.png");

#testColoring2();
#testColoring3();
testColoring4();
#animation();

2 comentarios:

winden dijo...

Pues si hay algo en lo que se puede aprovechar muchísimo las múltiples CPUs es al calcular fractales (véase la kindernoiser para el ejemplo extremo ;), igual que para calcular raytracing. Lo que si te conviene es partir la pantalla en cajas de 32x32 o algo así y asignar los calculos por cajas en lugar de por scanlines. El código que has pegado creo entender que calcula media pantalla en cada thread, imagina que la parte de arriba necesita mucha más CPU que la de abajo... en ese caso el thread de abajo va a estar un tiempo parado sin aprovecharse.

mrkoala dijo...

Y lo bien que quedan los fractales como wallpaper en los monitores de 24".... XD