Ahora que ya sabemos cómo funciona el color en un pixel, vamos a ver la clase clave para manipularlos: BitmapData, y por extensión, la clase Bitmap, ya que funcionan en conjunto.
Nota: Sino leíste la primera parte de la serie, te recomiendo que le eches un vistazo antes de seguir.
La clase BitmapData, como su nombre indica, representa los datos de un bitmap, o lo que es lo mismo, almacena todos los pixels de una imagen. Esta representación solamente existe en memoria. Por otro lado, la clase Bitmap es un envoltorio para BitmapData, y permite manipular la imagen como objeto visual.
Pero… ¿por qué utilizar dos clases? ¿No sería más fácil una única clase encargada de manipular la imagen? Tratemos este punto antes de seguir con la sintaxis.
Arquitectura de BitmapData y Bitmap
Aunque las dos clases se encuentran en el paquete flash.display, BitmapData no pertenece a la Display List. Si echamos un vistazo a las clases que conforman la lista de visualización, podemos comprobar que sólo Bitmap hereda de DisplayObject, y por tanto es la única que se puede representar en pantalla:
Gracias a esta separación, las operaciones que se realizan con los pixels de una imagen no entran en el motor de renderizado del Flash Player, evitando tener que redibujar constantemente el contenido, cosa que pasa con los vectores. En este concepto se basa la técnica de cachear a bitmap (cacheAsBitmap).
Hasta la llegada de Flash8, un gran número de experimentos se veían frustrados por el bajo rendimiento del player cuando se dibujaban muchos vectores en pantalla. Estos problemas desaparecieron en el momento en que se pudieron agrupar estos vectores y pasarlos a una única imagen, liberando completamente al procesador de esta dura tarea.
Y la otra gran ventaja de esta separación es que distintos objetos Bitmap pueden utilizar el mismo BitmapData. De esta manera, podríamos tener una representación rotada, otra escalada, etc. y sólo consumiría la memoria de una imagen.
Visto así, es una buena idea tener separadas ambas clases: una para manipular los datos de la imagen y otra para manipular el objeto en pantalla.
No está de más observar que Bitmap no hereda de InteractiveObject, con lo que no puede recibir eventos de teclado ni de ratón. Si queremos que esto sea así, deberemos reparentar nuestro objeto en un Sprite o MovieClip. Por otro lado, en caso de utilizar imágenes externas, deberemos cargarlas mediante un objeto Loader, que sí extiende InteractiveObject.
Sintaxis de BitmapData y Bitmap
BitmapData permite crear una imagen de cero o manipular una existente.
Nota: de momento nos centraremos en las imágenes creadas por código, que son simples rectángulos de color. En otro capítulo veremos cómo cargar imágenes o utilizarlas desde la librería.
La firma del constructor es:
[as3]BitmapData(width:int, height:int, transparent:Boolean = true, fillColor:uint = 0xFFFFFFFF)[/as3]
Los dos primeros parámetros especifican el ancho y alto que tendrá nuestro objeto. El tercer valor indica si la imagen soporta transparencia por pixel, es decir, si la imagen tendrá canal alfa. Por defecto tiene. El último corresponde al color de la imagen, por defecto blanca.
Es importante recordar los siguiente puntos:
- Una vez creada la imagen, no se pueden cambiar su anchura y altura
- Si creamos la imagen opaca (transparent = false), el canal alfa del color será ignorado (por ejemplo, 0x55FFFFFF)
- El Player renderiza más rápido las imágenes que no soportan transparencia, así que si no es necesaria, mejor forzar el parámetro a false.
Así pues, para crear una imagen azul, opaca y de 100×100 pixels escribiríamos:
[as3]import flash.display.BitmapData;
var bmpData:BitmapData = new BitmapData(100, 100, false, 0xFF0000FF);[/as3]
En este punto, tenemos nuestra imagen creada, pero solo existe en memoria, no podemos representarla visualmente (aunque ya podemos manipular sus pixels, aplicarle filtros, etc). Para ello, debemos crear un objeto Bitmap que la contenga. Veamos su constructor:
[as3]Bitmap(bitmapData:BitmapData = null, pixelSnapping:String = “auto”, smoothing:Boolean = false)[/as3]
El primer parámetro que nos pide es un objeto BitmapData, así que pasándolo ya tenemos asociados los datos en memoria al contenedor (¿fácil, no?). El segundo y tercer argumento controlan el ajuste de pixel y el suavizado al escalarse. En la gran mayoría de los casos no son relevantes, así que nos olvidaremos de ellos.
Si añadimos algunas líneas más ya tendremos nuestra imagen en pantalla:
[as3]import flash.display.BitmapData;
import flash.display.Bitmap;
var bmpData:BitmapData = new BitmapData(100, 100, false, 0xFF0000FF);
var bmp:Bitmap = new Bitmap(bmpData);
addChild(bmp);[/as3]
También es posible asociar el BitmapData al Bitmap mediante la propiedad bitmapData de este último:
[as3]var bmpData:BitmapData = new BitmapData(100, 100, false, 0xFF0000FF);
var bmp:Bitmap = new Bitmap();
bmp.bitmapData = bmpData;
addChild(bmp);[/as3]
Aquí hay un pequeño ejemplo donde se crean cuatro bitmaps de 60×40 con distintos valores en la transparencia y color. Si tienes alguna duda en este punto, te aconsejo que descargues el ejemplo y pruebes distintas combinaciones.
Hay que tener claro que los dos primeros valores hexadecimales determinan la transparencia, pero siempre que transparent sea true. Para crear una imagen completamente transparente se puede utilizar 0, que es lo mismo que 0x00000000.
Limitaciones de los bitmaps: los 2880 pixels
Si alguna vez has mirado alguna documentación o artículo sobre BitmapData, casi seguro que habrás visto una advertencia sobre que no se pueden crear imágenes con un ancho o alto superior a 2880 pixels. Esta limitación está puesta para que las películas no consuman excesiva memoria RAM, ya que una imagen de esas dimensiones consume aproximadamente 32 MB.
¿Y de dónde sale esta cantidad?
Pues como comentamos en el primer capítulo, un pixel tiene 4 bytes de información (1 byte por cada canal). Si tenemos una imagen de 2880×2880, tenemos 8.294.400 pixels, y al multiplicar por 4 tenemos que nuestra imagen consume una memoria de 33.177.600 bytes. Si lo dividimos entre 1024 (1KiloByte) tenemos 32.400 KB, y si lo volvemos a dividir entre 1024 tenemos 31’64 MB.
Personalmente, creo que los 2880 es una medida más que suficiente, y casi nunca necesitaremos una imagen de esas dimensiones. Aún así, hay que tener en cuenta que los bitmaps consumen mucha memoria, por eso hay que ser cautos cuando los utilicemos. Como referencia, una imagen de 100×100 consume cerca de 40 KB, una de 500×500 prácticamente 1 MB, y una de 1000×1000 3’8 MB.
Resumiendo
Como hemos visto, crear imágenes es una tarea muy sencilla. Sólo es importante entender cómo funciona el color (que lo vimos anteriormente) y saber que éste viene marcado por el booleano que indica si se soporta transparencia o no.
También es bueno comprender las ventajas que aporta tener las clases separadas y la limitación del tamaño de un bitmap.
Al final, este capítulo también ha sido más teórico de lo que en un principio tenía en mente. Y aunque prefiero asentar conceptos que considero importantes antes que correr mucho, no olvido que dije que me centraría en la manipulación de pixels. Así pues, el próximo artículo lo dedicaré enteramente a jugar con los pixels, y mostraré algunas técnicas para crear efectos bastante interesantes. Prometido ;)
[…] on Viernes 1 Febrero 2008 Esta tarde estaba mirando una página web que hablaba de Flash, ahí andaba yo mirando las cosas c00l que hace la gente con las clases Bitmap […]
[…] la parte final hablo sobre las clases bitmap y bitmapData, que traté en el artículo Jugando con pixels (II), y muestro algunos ejemplos prácticos donde se utiliza bitmapData para obtener distintas […]