Feb 14, 2008

Jugando con pixels (III)

Lo prometido es deuda, y aunque he tardado bastante, aquí está la tercera entrega de esta serie. Está llena de ejemplos, todos con el código fuente para descargar y comentando.

Vamos a jugar!!

Pintando pixels

Existen varios métodos para trabajar con los pixels de un bitmapData, pero las operaciones básicas para manipular el valor de un pixel son: getPixel(), setPixel(), getPixel32() y setPixel32().

GetPixel y getPixel32 se encargan de leer el valor de un pixel, con la diferencia de que el primero se utiliza en las imágenes opacas mientras que el segundo permite leer el canal alpha del pixel. De la misma forma, con setPixel establecemos el color de un pixel sin transparencia y con setPixel32 el de uno que sí la soporte.

Si se utiliza getPixel o setPixel (versión de 24 bits) en un imagen que soporta transparencia, el canal alpha se establece en FF (totalmente opaco), y si se utiliza la versión de 32 bits en una imagen que no soporta canal alpha, el canal alpha se ignora.

La sintaxis de los métodos es muy simple: sólo hay que pasar las coordenadas del pixel y, en el caso que haya que pintarlo, un color:

[as3]public function getPixel(x:int, y:int):uint
public function setPixel(x:int, y:int, color:uint):void
public function getPixel32(x:int, y:int):uint
public function setPixel32(x:int, y:int, color:uint):void[/as3]

Vamos a ver algunos ejemplos.

Nota: no voy a poner el código entero para que el post no crezca demasiado, sólo las porciones más significativas. Los ejemplos se pueden descargar y el código fuente está comentado.

Ejemplo 1 (Paint)

Un Paint muy sencillo para mostrar los métodos getPixel y setPixel. Selecciona un color de la paleta con un click y pinta sobre el canvas con otro click. Para ilustrar mejor lo que es establecer un pixel no se puede pintar de forma continua.

This movie requires Flash Player 9

Descargar ejemplo

Para este ejemplo se crean dos bitmaps (con sus correspondientes bitmapData): uno para crear la paleta de colores y otro para poder pintar. Para crear cada uno de los colores de la paleta, se utiliza el método fillRect(), que sirve para rellenar de un color un rectángulo (como el bote de pintura). Ninguno de los bitmaps soporta el canal alpha, así que se utilizan colores de 24 bits. También recordar que la clase bitmap no puede recibir eventos, por lo que se crean 2 sprite como contenedores.
El método para coger el color es:

[as3]private function cogerColor(e:MouseEvent):void {
// Cogemos la posicion del raton sobre el bitmap de colores
var cx:int = _bmpColores.mouseX;
var cy:int = _bmpColores.mouseY;
// Obtenemos el color y lo almacenamos
_color_temp = _bmpDataColores.getPixel(cx, cy);
}[/as3]

y el método para pintar:

[as3]private function pintarColor(e:MouseEvent):void {
// Cogemos la posicion del raton sobre el bitmap del canvas
var cx:int = _bmpCanvas.mouseX;
var cy:int = _bmpCanvas.mouseY;
// Pintamos el pixel del color almacenado
_bmpDataCanvas.setPixel(cx, cy, _color_temp);
}[/as3]

Sencillo, ¿no?

Ejemplo 2 (Spray)

Otro ejemplo donde se puede pintar con una herramienta tipo spray:

This movie requires Flash Player 9

Descargar ejemplo

Aunque ya hemos visto lo fácil que es pintar un pixel, si se hace de uno en uno es una tarea muy lenta, por eso se suele utilizar otros métodos como el de este ejemplo.

[as3]private function pintar(e:Event):void
{
// Si se puede pintar…
if (_pintar_activo)
{
// Cogemos las coordenas del mouse
var p_mouse:Point = new Point(_bmp.mouseX, _bmp.mouseY);

for (var i:int = 0; i < 25; i++) { // Cogemos un punto aleatorio var p:Point = Random.obtenerPuntoCirculo(p_mouse,10); // Pintamos el pixel en la posicion obtenida _bmpData.setPixel(p.x, p.y, 0xFFCC99); } } }[/as3] La función pintar() se ejecuta en un enterFrame. Si se puede pintar, cogemos las coordenadas del mouse y pintamos 25 pixels por frame, generando el efecto de spray. La variable que controla si se puede pintar (_pintar_activo) se pone a true cuando dejamos presionado el botón izquierdo del mouse y se pone a false al soltarlo.

La clase Random es una clase propia que cuenta con varias utilidades para obtener números y puntos aleatorios. En este caso, el método obtenerPuntoCirculo() acepta un objeto de la clase Point y un radio, y devuelve otro Point que pertenece al área del círculo.

Para limpiar el canvas se utiliza esta función:

[as3]private function limpiarCanvas(e:MouseEvent):void {
_bmpData.fillRect(_bmpData.rect, 0x6699CC);
}[/as3]

Al pasar el parámetro rect del mismo bitmapData, se pinta toda la superficie del color pasado, borrando todos los pixels que se habían pintado antes.

Ejemplo 3 (Linea)

Y otro ejemplo muy simple para pintar una línea.

This movie requires Flash Player 9

Descargar ejemplo

En este caso se crea un bitmap transparente de color negro, pero con el canal alpha muy bajo (0x11000000). Hay dos variables que controlan las posiciones en x e y. En cada ciclo del enterFrame se pinta un pixel y se aumenta la variable de x.

[as3]private function dibujar(e:Event):void
{
// Pintamos un pixel utilizando un color de 32 bits, ya que el bmpData es transparente
_bmpDataCanvas.setPixel32(_posx, _posy, 0xFFFF0000);

// Aumentamos la posicion de x
_posx++;
}[/as3]

Al aumentar _posx en 1 pixel cada vez tenemos una línea continua, pero si lo hiciéramos en 2 o 3 píxeles tendríamos una linea punteada, por ejemplo.

Controlando los pixels

El problema de los ejemplos que hemos visto hasta ahora es que conseguimos pintar, pero no tenemos ningún control de nuestros pixels una vez que los pintamos. Al fin y al cabo, pintar es sólo coger un pixel de un bitmapData y cambiarlo de color, realmente no existe ningún objeto que podamos manipular. ¿Cómo hacer, por ejemplo, si queremos pintar de una forma irregular?

La clave para esto es crear un objeto realmente, así podemos manipularlo a nuestro antojo y cuando sea necesario, coger su posición (x,y) y pintar ahí un pixel.

Para ello nos creamos una nueva clase, Pixel. Nuestra clase Pixel no tiene porqué extender de nada, ya que sólo la vamos a utilizar como un objeto que movemos donde queremos, pero no necesitamos una representación visual de él. Una versión muy simple para pintar una linea, como en el ejemplo 3, podría ser así:

[as3]package {
public class Pixel {
public var x:Number;
public var y:Number;
public var name:String = “pixel_”

public function Pixel(num:int,posx:Number,posy:Number) {
x = posx;
y = posy;
name += String(num);
}

public function mover():void {
x++;
}
}
}[/as3]

Creamos un Pixel pasando una posición x, y (el num únicamente es para generar un nombre que podemos utilizar para identificarlos: pixel_0, pixel_1, etc). Cada vez que llamemos a su método mover() aumentamos la variable x en un pixel. Si luego accedemos a dicha variable (que es pública), podemos pintar un pixel en esa posición.

Hay que tener en cuenta que x e y son variable de la clase, no son las variables x e y que se heredan de la clase DisplayObject y que indican las coordenadas de un clip en el escenario. Se podía utilizar cualquier nombre, como posx y posy.

Vamos a ver un ejemplo donde se genera un movimiento algo más complicado. Se trata de crear un ángulo, darle una fuerza de salida al Pixel y aplicarle gravedad.

Ejemplo 4 (Explosión)

Con dicho movimiento, generamos una especie de explosión con cada click.

This movie requires Flash Player 9

Descargar ejemplo

En este caso creamos un bitmapData completamente transparente, new BitmapData(ANCHO, ALTO, true, 0). Al hacer click se crean 50 objetos Pixel, y se almacenan en un array.

[as3]// Crear 50 pixels y añadirlos al array
for (var i:int = 0; i < 50; i++) { _pixels.unshift(new Pixel(_cont, mouseX, mouseY)); _cont++; }[/as3] Hay que tener en cuenta que cuando se manipulan pixels se suele hacer con varios cientos o miles. La mejor forma es meterlos en un array, ya que es muy fácil recorrerlo después y aplicarles acciones, como en este caso: [as3]// Recorremos el array de pixels for (var a:String in _pixels) { // Cogemos cada pixel var p:Pixel = _pixels[a]; // Movemos el pixel a su siguiente posicion p.mover(); // Pintamos en el bitmap, en la posiciones // de nuestro pixel _bmpData.setPixel32(p.x, p.y, AMARILLO); }[/as3] Con este mecanismo, ahora sí tenemos control sobre todos nuestros pixels. Además de moverlos individualmente, podemos saber cuándo están fuera de pantalla para limpiar recursos, o guardar otras propiedades en la clase Pixel, como un color individual para cada uno.

Como no hemos creado una variable de clase para cada pixel, la única referencia que existe se encuentra en el array. Para poder borrar un pixel sólo hemos de quitar su posición en el array, de esta manera se queda sin referencia y el recolector de basura se encargará de eliminarlo de la memoria (mirar el código fuente y los comentarios).

La clase Pixel no está comentada, ya que sólo hay que entender que cuenta con unas variables que almacenan la posición. Explicar el movimiento angular no es el objetivo del ejemplo.

Fundiendo los pixels

Por ahora llevamos un buen camino: sabemos cómo manipular los pixels individualmente, como crearlos almacenándolos en un array, como gestionarlos a la vez y cómo pintar el recorrido en nuestro bitmapData. El problema que nos encontramos es que a medida que vamos pintando más y más, nuestro bitmapData queda lleno de pixels y se vuelve inservible para cualquier propósito que no sea dibujar.

Una técnica muy vistosa es crear un fade en cada pixel, así lo vamos degradando hasta que sea invisible. Pero claro, nuestro pixel sólo es un puntito de color que no cuenta con una propiedad alpha como si fuera un Sprite o un MovieClip. La solución pasa por aplicar el fade a todo el bitmapData, mediante un objeto ColorTransform y su propiedad alphaMultiplier.

Ejemplo 5 (Explosión Fade)

Click para generar la explosión con fade.

This movie requires Flash Player 9

Descargar ejemplo

Usando esta técnica el ejemplo cambia por completo y gana bastantes enteros. Veamos el código añadido.

Primero creamos una variable de clase para el objeto ColorTransform:

[as3]private var _ct:ColorTransform = new ColorTransform();[/as3]

En el constructor indicamos la cantidad de alfa (de 0 a 1):

[as3]_ct.alphaMultiplier = .9;[/as3]

Por último, en nuestro enterFrame lo aplicamos:

[as3]_bmpData.colorTransform(_bmpData.rect, _ct);[/as3]

Con esta última línea, lo que estamos haciendo es que en cada frame le damos a nuestro bitmapData una transparencia de 0.9, y al hacerlo de manera tan continuada acaba fundiendo lo que hemos dibujado. Por otro lado, como estamos continuamente pintando sobre nuestro bitmap siempre vemos algo en pantalla.

Si el valor de alphaMultiplier es bajo (por ejemplo, 0.7), apenas veremos nada, ya que el fundido se producirá demasiado rápido. Por el contrario, si nuestro valor se acerca a 1 (por ejemplo, 0.98), el objeto ColorTransform tardará demasiado en fundir la imagen.

Descárgate el ejemplo y prueba diferentes combinaciones entre el número de pixels que genera cada click, el valor de alphaMultiplier y el número de frames de la película para entender la relación entre los elementos.

Importante: para utilizar esta técnica el bitmapData debe tener la propiedad transparent a true, ya que si la imagen es opaca el fundido se verá reflejado sobre la misma.

Generando pixels indefinidamente

En más de una ocasión nos interesará generar partículas continuamente sin que el usuario tenga que interactuar. Un ejemplo podría ser crear la estela de un cometa o simular los propulsores de un cohete, o simplemente para obtener un efecto visual.

La idea es tan simple como garantizar que siempre haya un número mínimo de pixels en pantalla: para ello debemos eliminar los que se encuentren fuera de los límites y generar nuevos.

Para que resulte más claro, vamos a trabajar sobre un ejemplo

Nota: para no consumir recursos innecesarios, los ejemplos de este tipo sólo funcionarán cuando el cursor esté dentro del swf.

Ejemplo 6 (Cascadas)

Con las técnicas explicadas anteriormente, aquí tenemos una cascada de pixels. Al salir del área visible se generan nuevas partículas y el ciclo nunca se interrumpe.

This movie requires Flash Player 9

Descargar ejemplo

Para ello añadimos dos nuevas variables de clase:

[as3]private var _max_pixels:int = 400;
private var _pixels_por_frame:int = 20;[/as3]

La primera indica el número de pixels máximo que puede haber en pantalla, y la segunda el número de pixels por frame que se han de generar en caso que no se haya superado el número máximo.

Dichas variables se utilizan en el enterFrame:

[as3]if (_pixels.length <= _max_pixels) { for (var i:int = 0; i < _pixels_por_frame; i++) { // Código para crear los pixels } }[/as3] Miramos la longitud del array, y si es menor que el número de partículas, entramos en el bucle. En vez de crear una partícula por frame, este for nos permite generar un número elevado. Si no lo hiciéramos así, las partículas dependerían siempre del framerate de la película, y normalmente no podríamos conseguir muchas a la vez.

Por último, en el enterFrame, por cada pixel, se ejecuta este linea:

[as3]if (p.y > ALTO) eliminarPixel(p);[/as3]

Es imprescindible comprobar continuamente la posición del pixel para poder eliminarlo y generar nuevos. Evidentemente, en función del tipo de experimento, habrá que comprobar otros límites para eliminarlo.

Nótese que el número de pixels máximo nunca es exacto, ya que si hemos fijado un valor de 400 y tenemos en pantalla 392, al entrar en el bucle nos generará 20 más teniendo un total de 412. Esto no es significativo ya que se eliminan y crean con mucha rapidez. Lo importante es tener un valor que nos sirva visualmente sin influir excesivamente en el rendimiento.

Nótese también que si el número de pixels máximo es muy elevado y el de los pixels por frame no lo es, nunca se alcanzará el máximo. En el código, al final de la función del enterFrame, hay un trace que indica la longitud del array. Es bueno ir mirándolo hasta conseguir ajustar las cantidades.

Por último, comentar que cuando se juegan con partículas es muy importante tener en cuenta el rendimiento, ya que es muy fácil colapsar la CPU a partir de ciertos valores. Por eso en mis experimentos siempre utilizo un medidor de frames por segundo, que es el recuadro azul que se ve en el ejemplo. Utilizándolo se puede comprobar si nuestra película está rindiendo a un nivel aceptable. Todos estos swf tiene un framerate de 31, y en navegador es frecuente que bajen hasta los 24-26. Recomiendo usar siempre uno para tareas de depuración (en general con cualquier desarrollo que consuma muchos procesos). La clase es FPS y se encuentra en el paquete com.llops.utils.

Aplicando filtros: Glow

Como siempre, los filtros nos permiten crear efectos visuales muy potentes. En el tema que nos ocupa, el que mejor casa con la programación pixel es el filtro Glow. Este filtro nos permite crear una pequeña aureola de cualquier color alrededor del pixel, y a la vez le podemos aplicar blur para suavizar los puntos. Se utiliza normalmente para generar brillos, y la intensidad aumenta al concentrar partículas en un mismo punto.

Ejemplo 7 (Cascadas Glow)

Nuestra cascada de antes era algo sosa, vamos a ver como queda con un simple glow.

This movie requires Flash Player 9

Descargar ejemplo

La diferencia con el ejemplo anterior es notable. Los pixels ya no son planos y el resultado es mucho más vistoso.

Los filtros se aplican sobre el bitmap directamente, en el formato de array. Lo único que hay que hacer es importar la clase flash.filters.GlowFilter y crear uno:

[as3]var _glow:GlowFilter = new GlowFilter(_color_glow, 1, 4, 4);
_bmp.filters = [_glow];[/as3]

Aunque tiene más parámetros, los 4 primeros son los principales. Con ellos indicamos el color, la transparencia, y el blur en x y en y.

Nota: no poner un blur mayor que 12, ya que tiende a desvanecerse y no se aprecia.

Para ver los resultados con distintas combinaciones, en el código he puesto algunos colores y dos variables para el color interior y el del glow. Puedes hacer tus pruebas:

[as3]public const ROJO:uint = 0xFFFF0000;
public const NEGRO:uint = 0xFF000000;
public const BLANCO:uint = 0xFFFFFFFF;
public const AZULADO:uint = 0xFF0099FF;
public const AMARILLO:uint = 0xFFFFFF00;

private var _color_pixel:uint = BLANCO
private var _color_glow:uint = AZULADO;[/as3]

Y aquí va otro ejemplo, este con más intención:

Ejemplo 8 (Lava)

Simulación de un río de lava. Se rebaja la velocidad de los pixels para acumularlos y se aumenta el blur del glow.

This movie requires Flash Player 9

Descargar ejemplo

Podríamos decir que da bastante el pego :)

¿Todavía hay más?

Pues aún queda un truquillo más que puede dar mucho juego: el parámetro blendMode de DisplayObject, que consta de 14 modos. El resultado final de cada modo depende mucho del color del bitmap, las capas adyacentes, etc., pero en general, algunos modos como ADD, DIFFERENCE o INVERT suelen ofrecer posibilidades interesantes. Considéralo un efecto avanzado que puede requerir bastantes pruebas hasta dar con un resultado convincente.

Basta con una sola línea para utilizarlo:

[as3]_bmp.blendMode = BlendMode.ADD;[/as3]

Y la verdad es que siempre habrá mucho más: se le pueden añadir filtros de desplazamiento, ruido, interacción con el usuario, generación aleatoria de color… en definitiva, un sinfín de posibilidades. Sólo hace falta tener ganas de experimentar.

Y para acabar

Todos los ejemplos que he puesto hasta ahora han tenido una finalidad didáctica, así que no han sido muy espectaculares. De todas maneras, para los exigentes, y sin meterme en código complejo, dejo un último experimento que personalmente me gusta mucho ;)

Ejemplo 9 (Conexiones)

Combina los métodos descritos: transparencia, fade, glow, blendMode…

This movie requires Flash Player 9

Descargar ejemplo

Resumiendo

AS3 es uno de los lenguajes (sino el que más) que permite exprimir al máximo el concepto de “programación creativa”, y la programación orientada a pixel da buena prueba de ello.

En este tercer artículo he intentado poner algunas herramientas encima de la mesa, y ahora depende de cada uno utilizarlas a su manera. Soy un fan de este tipo de experimentos, así que si programas algo relacionado me encantará verlo :)

También me gustaría remarcar la idea que hasta ahora no hemos jugado con imagen, sino con bitmaps creados de cero, que son simples recuadros de color. Es fácil imaginar las posibilidades al añadir fotografía…

En cuanto a esto, decir que mi intención era escribir un par de artículos más sobre estos temas, pero he de reconocer que estoy un poco saturado de tanto pixel, y me apetece hacer cosas nuevas. Así que de momento, haré una pausa indefinida en esta serie. Espero que lo visto hasta ahora haya sido de interés.

Información del artículo

Post publicado el 14 de February de 2008 a las 3:16 por llops

Categorias: Artículos

Etiquetas: , , , , , , , ,

Comparte

11 comentarios

  • Muito bom amigo, parabéns. Venho acompanhando essa série e aprendendo muito. Gracias.

  • guirado

    Wow, no había visto ningún tutorial sobre el tema tan detallado y con tantos ejemplos, así que muchas gracias, de verdad.
    Ojalá haya más!!

  • Muy bueno…es un lujo contar con este material…y hace muy comporensible esta clase que tanto se nos resiste a mucho desarrolladores…

    Merci ! :)

  • Jose Manuel

    Que excelente tutorial, felicitaciones y gracias por compartir ese conocimiento.

  • Aldo David

    GRAAAAAANDE MAESTRO!!!! Sinceramente… ese ultimo ejemplo fue impactante!

  • camiand

    Que buenos tutoriales, te felicito y espero que sigas compartiendo tus conocimientos. Excelente!

  • DIos…buenisimo el tuto……nunka avia visto un tuto tan bien desarrollado….muchas gracias…

    PD:Impresionante los ultimos ejemplos.

  • gg

    exelente tutorial!…. pregunta: como capturar y guardar en un arreglo de pixeles de una imagen para despues modificarla?

  • princesitaaa

    que Dios lo bendiga y lo llene de sabiduria. me permito agradecerle el tutorial, es magnifico , le Doy gracias a Dios que todavia hay personas que se preocupan por colaborar, no sabe cuanto me ha ayudado su tutorial. gracias de verdad, que Dios lo bendiga.
    att: fernanda

  • tuna

    Muchas gracias por el tutorial.. !!…. genial contar con maestros como tú que se preocupan de entregar conocimiento a los aprendices.

    saludos desde Chile.. !

  • Rodolfo

    exelentes ejemplos . saves me urge un tutorial basico de como borrar pixeles de un mapa de bits . se puede? . se me hace casi inposible que as3 se me hacia casi inposibe que as pudiera trabajar con esto a tanta profundidad , pero con esto ya se me hace que si es posible. por fa te lo ruego que si saves lo expliques