lunes, 30 de diciembre de 2013

¿Y qué es una espiral?

La espiral que más se asemeja al dibujo de Mellan es la espiral de Arquímedes, que consiste en el lugar geométrico de un punto moviéndose a velocidad constante sobre una recta que gira sobre un punto de origen fijo a velocidad angular constante.
A mí esta explicación me suena a chino.
 Además es imposible leerla sin parase a respirar. Pero para estos casos siempre contamos con la ayuda de recursos visuales, aquí tenemos una animación que he preparado para comprender mejor esta espiral:

espirala1_1

Podemos ver cómo el rombo que dibuja la espiral se encuentra en una recta que va girando a velocidad constante. 
Además el punto se aleja del origen también a velocidad constante, describiendo así la espiral de Arquímedes. 
El movimiento a velocidad constante del punto en la recta quizás sea difícil de ver en esta animación, pero vamos a darle la vuelta a la tortilla y a hacer que lo que gire en la animación sea el plano:
espirala2_1

Si han conseguido evitar el mareo, podemos ver cómo el punto va avanzando a velocidad constante en la recta. En este caso lo que está girando es el plano a una velocidad constante, mientras dibujamos una línea recta, también  a velocidad constante.
Una característica importante de la espiral de Arquímedes es que la distancia entre las distintas “vueltas” es constante.
 Este hecho hace que sea perfecta para hacer un grabado como el que hizo Claude Mellan. 
Si hubiera usado una espiral logarítimica, el aumento de distancia que se produce en cada vuelta habría dado al traste con el dibujo.
La fórmula en coordenadas polares mediante la que podemos representar este tipo de espirales es:
r = a + b \theta

Donde:
  • a y son dos valores que determinan dónde se inicial la espiral (a) y cómo de juntos están los brazos de la misma (b)
  • es la distancia al eje de cada punto
  • \theta es el ángulo respecto al origen y al eje x
Aquí podemos ver  3 ejemplos de la espiral para distintos valores de b:

bigualtodos
Vamos a dibujar espirales emulando a Mellan
Mi capacidad artística es nula, pero por suerte existen los ordenadores.
 Así que vamos a emular a Mellan y vamos a hacer nuestros dibujos en espiral usando nuestro pincel binario.
Para ello lo que necesitamos es una imagen inicial, a poder ser en blanco negro y con alto contraste, para que la imagen final sea más resultona.
 Por ejemplo, esta imagen:
Schrodinger
Así se quedó Schrödinger cuando se enteró de que Disney había comprado los derechos de Star Wars.
Y ahora tenemos que trazar la espiral respecto a la imagen. 
Para no hacer más cálculos de los necesarios obtenemos el radio máximo que podrá tener la espiral dentro de la imagen, usando un simple cálculo trigonométrico:
Como decía un profesor que tuve de química: "esto es de cajón de madera de pino". Claro que el lo decía después de poner una formulaca.
Como decía un profesor que tuve de química: “esto es de cajón de madera de pino”. Claro, que el lo decía después de poner una formulaca.
Ahora sabemos que debemos calcular una espiral cuya distancia al centro de la imagen vaya desde cero hasta el valor de r que acabamos de calcular.
 Para calcular los puntos de la espiral usaremos la fórmula que hemos visto antes, dándole valores al ángulo para obtener los valores de r.
Una vez obtenido un par de valores (r, \theta), su paso a coordenadas (x,y) se realiza de nuevo mediante una sencilla operación trigonométrica:
x = r\, cos \,\theta
y = r\, sen \,\theta
Los números obtenidos tendremos que redondearlos para que sean enteros, para hacerlos corresponder con pixeles de la imagen. Aquí tenemos un ejemplo en el que el ángulo va aumentando en un valor de 0.1:
tabla
Al hacer el cálculo de la espiral respecto a la imagen se pierde mucha información, estamos convirtiendo varios puntos de la espiral en uno solo para la imagen.
Una vez calculados los puntos de nuestra espiral, tendremos que rehacer el dibujo. Para ello recorreremos la imagen pixel a pixel, desde la esquina superior izquierda, hasta la esquina inferior derecha, y pondremos un punto blanco donde no se cruce con la espiral y un punto con el color original de la imagen donde coincida con la espiral.
De esta forma el resultado final sería este:
schrodiespiral
Y esta es la cara que se le quedó al pobre Schrödi cuando se enteró de que en las nuevas películas volverá a salir Jar Jar.

Ahora viene lo bueno, tú también puedes probarlo

Todo esto que les he explicado se ha convertido en una aplicación web que permite convertir en una espiral una imagen:
Captura de pantalla 2013-12-02 a las 23.38.09
¿Cómo funciona esto?
La aplicación es bastante sencilla, comienza con un script escrito en python que muestra la página inicial y contiene la lógica necesaria para poder subir un fichero al servidor. Aunque el procesamiento de la imagen ser hará mediante javascript en el navegador del usuario, la imagen debe estar en el mismo dominio que la aplicación para que pueda modificarse.
 En este caso el servicio se encuentra alojado en una cuenta gratuita de Google App Engine.
Una vez el fichero ha sido subido o se elige una imagen por defecto, se pasa a una páginahtml que realiza la transformación de la imagen mediante javascript
Para realizar esta transformación uso las ventajas que ofrece el objeto canvas en HTML5. 
Como su propio nombre indica, la clase canvas es un lienzo en el que podemos dibujar lo que nos apetezca dentro de una página HTML. 
En este caso empezamos dibujando la imagen original:
1
2
3
4
5
6
7
8
9
10
11
12
13
function drawImage(imageObj) {
    //obtener objeto canvas
    var canvas = document.getElementById('myCanvas');
    //obtener un contexto 2d del canvas
    var context = canvas.getContext('2d');
 
    //asociar al canvas la altura y anchura de la imagen
    canvas.width = imageObj.width;
    canvas.height = imageObj.height;
 
    //dibujar la imagen
    context.drawImage(imageObj, 0, 0);
}
imageObj es un objeto de tipo imagen, que se crea de la siguiente forma a partir de los parámetros de la página:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//objeto imagen
var imageObj = new Image();
//cuando la imagen se carga se dibuja
imageObj.onload = function() {
    drawImage(this);
}
 
// Obtener parámetros de la página
function getQueryParams(qs) {
    qs = qs.split("+").join(" ");
 
    var params = {}, tokens,
    re = /[?&]?([^=]+)=([^&]*)/g;
 
    while (tokens = re.exec(qs)) {
        params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
    }
 
    return params;
}
 
//Obtener los parametros de la pagina y utilizar el parametro img como ruta de la imagen
var param = getQueryParams(document.location.search);
imageObj.src = param.img;
Luego el usuario podrá pulsar la opción de espiral deseada, y ver cómo su imagen se transforma.
 La función que realiza el cambio es la siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
function espiralizaImagen(imageObj, b)
{
    //obtener objeto canvas
    var canvas = document.getElementById('myCanvas');
    //obtener un contexto 2d del canvas
    var context = canvas.getContext('2d');
 
    //Obtener los datos de la imagen que hay dentro del canvas
    var imageData = context.getImageData(0, 0, imageObj.width, imageObj.height);
 
    //Array con los valores de cada pixel de la imagen
    var data = imageData.data;
 
    // calcular centro de la imagen
    var xcenter = imageObj.width/2;
    var ycenter = imageObj.height/2;
 
    // calcular radio máximo de la espiral
    var rmax = Math.sqrt(xcenter*xcenter + ycenter*ycenter);
 
    //calculo de la espiral
    var r=0 , t=0, pold = '';
    var espiral = new Object();
 
    // mientras no alcancemos el radio maximo
    while(r
    {
        // calcular el radio.
        // t es el valor del angulo en radianes
        // b el valor que define la distancia entre lineas de la espiral
        r = b*t;
 
        // a partir del valor de r y t podemos calcular los valores x e y
        hx = Math.round(r*Math.cos(t));
        hy = Math.round(r*Math.sin(t));
 
        // definimos la variable para guardar el punto de la espiral
        var p = 'p'+hx+','+hy;
        // si no se ha guardado antes, lo hacemos e incrementamos t
        if (p!=pold){
 
            espiral[p]=1;
            pold=p;
            t+=_paso;
        }
        // si ya estaba guardado estamos repitiendo valores, aumentamos más el paso
        else
            t+=2*_paso;
 
    }
 
    // creación de la nueva imagen
    // el array con los datos de la imagen es de una sola dimensión
    // cada trio de valores corresponde con los valores RGB de un pixel
    for(var i = 0; i < data.length; i += 4) {
 
        //Calcular los valores x,y a los que corresponde el índice i actual
        var xi = i/4%imageObj.width;
        var yi = Math.floor(i/4/imageObj.width);
        xi-=Math.round(xcenter);
        yi=Math.round(ycenter)-yi;
 
        // valor que se pondrá si el pixel no esta en la espiral (#FFF)
        brightness=255;
 
        // clave con la que deberá estar guardado en la espiral
        var p = 'p'+xi+','+yi;
 
        // si no está en la espiral, pondremos los pixeles con color #FFF (blanco)
        if(!espiral.hasOwnProperty(p)){
            // red
            data[i] = brightness;
            // green
            data[i + 1] = brightness;
            // blue
            data[i + 2] = brightness;
        }
        // en caso contrario, esta en la espiral y lo dejamos tal como está
    }
 
    // sobreescribimos la imagen con los nuevos datos conseguidos
    context.putImageData(imageData, 0, 0);
 
    // creamos una URL de descarga para la imagen
    var dataUrl = canvas.toDataURL();
    // asociamos la imagen a nuestro objeto imagen
    var descarga= document.getElementById('canvasImg');
    descarga.src = dataUrl;
 
    // ocultamos el canvas y mostramos el objeto imagen para que pueda descargarse
    canvas.style.display = "none";
    descarga.style.display="inline";
}
Los puntos de la espiral se almacenan en una lista, que luego es usada para comprobar si el punto de la imagen está en la espiral o no.