Parece que fue ayer cuando empecé a escribir la primera entrega y ya vamos por la séptima. Este será el último capítulo dedicado a los gráficos en 2D y nos centraremos en la utilización de los efectos sobre texturas que ofrece GRRLIB. Para ello desarrollaremos un programa que me he permitido llamar Photoshop, que como habréis imaginado pretende hacer una versión muy sencilla de un programa de retoque fotográfico.
El programa permitirá aplicar ocho efectos diferentes sobre el archivo "foto.png" que deberá estar situado en la raiz de la SD. Quizás más adelante podamos completar el programa seleccionando el archivo, cuando veamos las funciones de acceso al sistema de archivos de la SD, pero de momento debemos conformarnos con esto.
Para controlar la aplicación de los efectos sobre la foto dispondremos de 10 botones a la derecha de la pantalla, ocho de los cuales nos permitirán seleccionar el efecto deseado y dos adicionales para las funciones de rehacer y deshacer.
Una vez seleccionada la función deseada cambiará el puntero del wiimote al estado correspondiente a dicha función y permitira aplicar el efecto pulsando el botón A sobre la foto.
Tres de las funciones disponibles permiten seleccionar el nivel de intensidad del efecto mediante las flechas de arriba y abajo del wiimote, modificando de esta manera el valor numérico que aparece en el botón.
Una vez explicado el programa, veremos cual sería el método para "fabricar" nuestra propia función de efectos de textura, una función que nos permitirá modificar el brillo de la foto.
Seguidamente podéis ver una imagen y un video del efecto final del programa.
Tenemos un total de 8 funciones en GRRLIB para modificar mediante diversos efectos las texturas. Todas ellas comienzan por GRRLIB_BMFX y tienen como mínimo dos parámetros: Textura origen y textura destino, aunque algunas de ellas tienen un tercer parámetro para seleccionar la intensidad del efecto.
Seguidamente expongo un listado de las funciones disponibles:
• void GRRLIB_BMFX_FlipH (const GRRLIB_texImg *texsrc, GRRLIB_texImg *texdest)
Voltea horizontalmente una textura
• void GRRLIB_BMFX_FlipV (const GRRLIB_texImg *texsrc, GRRLIB_texImg *texdest)
Voltea verticalmente una textura
• void GRRLIB_BMFX_Grayscale (const GRRLIB_texImg *texsrc, GRRLIB_texImg *texdest)
Cambia a escala de grises
• void GRRLIB_BMFX_Sepia (const GRRLIB_texImg *texsrc, GRRLIB_texImg *texdest)
Cambia la textura a color sepia como si fuera una foto antigua.
• void GRRLIB_BMFX_Invert (const GRRLIB_texImg *texsrc, GRRLIB_texImg *texdest)
Invierte los colores de una textura.
• void GRRLIB_BMFX_Blur (const GRRLIB_texImg *texsrc, GRRLIB_texImg *texdest, const u32 factor)
Provoca efecto de emborronado o desenfocado
• void GRRLIB_BMFX_Scatter (const GRRLIB_texImg *texsrc, GRRLIB_texImg *texdest, const u32 factor)
Provoca efecto de dispersión
• void GRRLIB_BMFX_Pixelate (const GRRLIB_texImg *texsrc, GRRLIB_texImg *texdest, const u32 factor)
Pixela la textura
Adicionalmente deberemos conocer la función GRRLIB_FlushTex que fuerza a volcar la textura en la memoria principal. Esta función es necesaria ya que la CPU guarda temporalmente las texturas en una memoria intermedia (caché) por razones de rendimiento.
La sintaxis de dicha función es:
void GRRLIB FlushTex ( GRRLIB_texImg * tex )
Donde tex es la textura que queremos volcar sobre la memoria principal.
Es conveniente llamar a esta función después de la llamada a las funciones de efecto antes de que volvamos a operar con ellas.
La mejor manera de ver cómo funcionan dichas funciones es verlas aplicadas en un programa práctico, y cómo hemos dicho antes, que mejor programa para verlo que uno de retoque fotográfico.
Vamos a plantear nuestro programa distibuyendo los botones en dos filas a la derecha. Como tenemos 8 funciones posibles necesitaremos un total de 10 botones, ya que incluiremos las funciones rehacer y dehacer.
Cada vez que seleccionemos uno de los botones activaremos una función de efecto y por lo tanto deberemos mostrar al usuario la función que está activa. Esto último lo conseguiremos mediante el cambio de la imagen del puntero de nuestro Wiimote.
Para ello habilitaremos por un lado un tileset de 10 elementos para representar los botones:
Por otro lado habilitaremos un tileset adicional para los punteros de los wiimotes, con un total de 11 elementos, uno por cada botón más uno adicional para cuando no tenemos seleccionada ninguna función. Por razones de facilidad de implementación repetiremos el primer puntero en las posiciones corresppondientes a rehacer y deshacer aunque no sea necesario cursor para dichas funciones.
Por último necesitaremos mostrar el parámetro de intensidad que necesitan algunas funciones, por lo que necesitaremos otro tileset para la fuente de letras. He utilizado un tipo Arial black de tamaño 12.
A la izquierda de la pantalla aparecerá la fotografía sobre la que aplicaremos nuestros efectos. Dicha fotografía procederá de un archivo llamado "foto.png" que leeremos de la raiz de la tarjeta SD, debiendo tener una resolución de 640x480.
Comenzaremos nuestro programa con los habituales includes y las constantes de colores.
#include <grrlib.h> #include <stdlib.h> #include <wiiuse/wpad.h> #include "botones_png.h" #include "pointers_png.h" #include "arial_black_12_png.h" // Colores RGBA #define GRRLIB_BLACK 0x000000FF #define GRRLIB_MAROON 0x800000FF #define GRRLIB_GREEN 0x008000FF #define GRRLIB_OLIVE 0x808000FF #define GRRLIB_NAVY 0x000080FF #define GRRLIB_PURPLE 0x800080FF #define GRRLIB_TEAL 0x008080FF #define GRRLIB_GRAY 0x808080FF #define GRRLIB_SILVER 0xC0C0C0FF #define GRRLIB_RED 0xFF0000FF #define GRRLIB_LIME 0x00FF00FF #define GRRLIB_YELLOW 0xFFFF00FF #define GRRLIB_BLUE 0x0000FFFF #define GRRLIB_FUCHSIA 0xFF00FFFF #define GRRLIB_AQUA 0x00FFFFFF #define GRRLIB_WHITE 0xFFFFFFFF #define GRRLIB_LBLUE 0x5050FFFF
Luego definiremos una serie de constantes sobre la anchura y altura de los tiles utilizados que nos servirán de ayuda en el programa, además de tres de constantes adicionales cuya función veremos más adelante.
Como hemos dicho antes vamos a añadir las funciones rehacer y deshacer, dichas funciones necesitan memorizar los estados anteriores para poder ser efectivas. Una forma de conseguir esto es memorizar las texturas resultantes en un array de texturas. El tamaño de dicho array lo hemos definido en una de las constantes del bloque anterior ("MAXCOLA").
Teniendo disponible la información en este array, las funciones deshacer y rehacer se reducen a recorrer el array adelante y atrás manteniendo un puntero de posición que llamaremos head.
GRRLIB_texImg* cola[MAXCOLA]; int head = 0;
Como en programas anteriores, necesitaremos consultar la resolución de la pantalla:
int resx = 640; int resy=528;
Declararemos las variables de textura que vamos a utilizar en el programa (fuente de caracteres, botones y punteros):
GRRLIB_texImg* tex_font; GRRLIB_texImg* tex_botones; GRRLIB_texImg* tex_pointers;
Algunas variables de control adicionales y finalmente un array con los factores de intensidad que aplicaremos sobre los efectos.
int i, src, dst; int selection = 0; u32 factor[] = {3,3,0,0,0,3,0,0,0,0};
Como es habitual inicializamos GRRLIB y los wiimotes:
// Inicializar gráficos GRRLIB_Init(); // Inicializar wiimotes WPAD_Init(); WPAD_SetDataFormat(WPAD_CHAN_ALL, WPAD_FMT_BTNS_ACC_IR); WPAD_SetVRes(WPAD_CHAN_ALL, resx, resy);
En esta ocasión, en lugar de asignar la resolución manualmente, recurriremos a las variables rmode->fbWidth y rmode->efbHeight, que nos devuelven automáticamente los valores de anchura y altura del framebuffer. Utilizaremos la variable resy para calcular el factor de escalado que deberemos aplicar a la textura de nuestros botones para que se ajusten a la resolución seleccionada.
resx = rmode->fbWidth; resy = rmode->efbHeight; float sy = ((float) resy / HBOTON) / FBOTONES;
Cargamos e inicializamos los tilesets y cargamos la foto en el elemento número 0 de nuestro array de estado:
tex_botones = GRRLIB_LoadTexture(botones_png); tex_pointers = GRRLIB_LoadTexture(pointers_png); GRRLIB_InitTileSet (tex_botones, WBOTON, HBOTON, 0); GRRLIB_InitTileSet (tex_pointers, WPOINTER, HPOINTER, 0); head = 0; cola[head] = GRRLIB_LoadTextureFromFile("sd:/foto.png");
Inicializamos todos los elementos de nuestro array de estado con texturas vacías (excepto el 0 en el que hemos cargado la foto). Así quedarán preparados para poder utilizarlos en nuestras funciones de efectos.
// llena la cola de texturas con texturas vacías del mismo tamaño // que la foto original. for (i=1; i<MAXCOLA; i++) cola[i]=GRRLIB_CreateEmptyTexture(cola[head]->w, cola[head]->h);
Tras lo cual entramos en el bucle principal del programa en el que actualizamos los mandos y comprobamos si queremos terminar:
while(1){ WPAD_ScanPads(); // Leer mandos WPAD_IR(0, &ir); // Si pulsamos home salimos del bucle if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME) break;
Comprobaremos después tres posibles acciones del wiimote:
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_UP && (factor[selection-1] > 0)) factor[selection-1]++; if (WPAD_ButtonsDown(0) & WPAD_BUTTON_DOWN && (factor[selection-1] > 1)) factor[selection-1]--;
Con DrawImg mostraremos la foto a la izquierda de la pantalla, teniendo en cuenta que debemos escalar la imagen respecto del espacio que tenemos disponible. En el caso del eje vertical dependerá de la resolución de la pantalla y en el caso del eje horizontal será constante pero se verá reducido por el espacio ocupado por los botones, por lo que también deberemos escalarlo.
GRRLIB_DrawImg(0,0,cola[head],0,(float)(resx-203)/resx,(float)resy/480,GRRLIB_WHITE);
El siguiente bloque se ejecutará sólo si pulsamos el botón A cuando tenemos el puntero sobre la imagen, para detectarlo utilizaremos la función ptInRect que vimos en entregas anteriores.
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A) { if ((selection > 0) && (selection < 9)&& GRRLIB_PtInRect(0,0,resx-203, resy,ir.x,ir.y)){
Ya dentro del condicional deberemos aplicar el efecto deseado sonbre la imagen.
Debido a que tenemos que memorizar las distintas imagenes para permitir deshacer las operaciones, habíamos declarado un array llamado "cola" que debemos ir recorriendo según aplicamos los efectos.
El elemento que almacena la imagen que mostramos actualmente es el que indica la variable head. Es decir que si head es igual a 1 la imagen mostrada en pantalla será la correspondiente a la textura contenida en cola[1].
Para el nuevo efecto que vamos a aplicar utilzaremos como origen la imagen actual apuntada por head y como destino el siguiente elemento del array. Para facilitar el trabajo almacenaremos ambas posiciones en las variables src y dst que representan fuente y destino.
src = head; if (head < MAXCOLA-1) dst = head+1; else dst = 0; head = dst;
La variable que contiene el efecto seleccionado es seleccion. Veremos más adelante como almacenamos dicho valor a partir de nuestras acciones con el wiimote, pero ahora mismo nos basta con saber cual es su función.
Los efectos a aplicar están numerados de 1 a 8 en el mismo orden que aparecen los botones a la derecha de la pantalla empezando a contar arriba a la izquierda.
Para poder ejecutar la opción adecuada necesitaríamos ocho funciones if anidadas de la forma "if (seleccion==1)...." (una por cada efecto).
Esto sería perfectamente válido, aunque en C existe una estructura llamada switch/case que nos facilita bastante la tarea y clarifica el código.
La instrucción switch() es una instrucción de decisión múltiple, donde el compilador prueba o busca el valor contenido en una variable (en nuestro caso seleccion) contra una lista de constantes ints o chars, cuando el ordenador encuentra el valor de igualdad entre variable y constante, entonces ejecuta el grupo de instrucciones asociados a dicha constante, si no encuentra el valor de igualdad entre variable y constante, entonces ejecuta un grupo de instrucciones asociados a un default, aunque este ultimo es opcional.
El formato de esta instrucción es el siguiente:
switch(var int o char) { case const1: instrucción(es); break; case const2: instrucción(es); break; case const3: instrucción(es); break; ……………… default: instrucción(es); };
En nuestro caso deberemos comparar con la variable seleccion y dependiendo de su valor llamaremos a cada una de las funciones correspondientes:
switch (selection){
case 1:
GRRLIB_BMFX_Blur (cola[src],cola[dst],factor[selection-1]);
break;
case 2:
GRRLIB_BMFX_Scatter (cola[src],cola[dst],factor[selection-1]);
break;
case 3:
GRRLIB_BMFX_FlipH (cola[src],cola[dst]);
break;
case 4:
GRRLIB_BMFX_FlipV (cola[src],cola[dst]);
break;
case 5:
GRRLIB_BMFX_Grayscale (cola[src],cola[dst]);
break;
case 6:
GRRLIB_BMFX_Pixelate (cola[src], cola[dst],factor[selection-1]);
break;
case 7:
GRRLIB_BMFX_Sepia (cola[src], cola[dst]);
break;
case 8:
GRRLIB_BMFX_Invert (cola[src], cola[dst]);
break;
}
Sea cual sea el efecto aplicado es conveniente que llamemos a la función GRRLIB_FlushTex para asegurarnos que aplica el efecto:
GRRLIB_FlushTex(cola[dst]);
Seguidamente pasamos al fragmento de código que dibuja y gestiona los botones de la derecha.
Lo primero que haremos será agrupar todos los botones dibujando un rectangulo de color verde.
GRRLIB_Rectangle(resx-203, 0, 203, resy, GRRLIB_LIME, true);
El dibujo y gestión de los botones lo haremos de una manera genérica para lo que utilizaremos un bucle for cuya variable de control "i" recorrerá todos los valores desde 0 hasta el número máximo de botones-1.
Para obtener la posición de los botones utilizaremos un pequeño cálculo que explico a continuación.
Distribuyendo los botones en dos columnas de 5 botones las fórmulas para calcular la posición de cada botón serán las que aparecen en el código.
En dichas fórmulas hemos tenido en cuenta lo siguiente:
for (i=0; i < _MAXBOTONES; i++) { int x = resx-202+(i/FBOTONES)*100; int y = (i%FBOTONES)*88*sy; GRRLIB_DrawTile(x,y,tex_botones,0,1,sy,GRRLIB_WHITE,i);
En el caso de que el efecto que le corresponde al botón necesite un parámetro de factor de intensidad dibujaremos dicho factor en la parte superior del botón. En el array factor declarado al principio del programa, utilizaremos valores diferentes de 0 en el caso de que sea posible utilizar dicho factor de intensidad, en caso contrario no se mostrará nada.
if (factor[i] != 0){
char sf[10];
sprintf(sf, "%d",factor[i]);
int tw = strlen(sf) * 16;
GRRLIB_Printf(x+WBOTON/2-tw/2,y+5,tex_font,GRRLIB_BLACK,1,"%d",factor[i]);
}
Aprovecharemos el mismo bucle que hemos utilizado para pintar los botones, para comprobar si pulsamos el botón correspondiente con el puntero del wiimote+boton A.
En dicho bucle trataremos de forma genérica los botones del 0 al 7 ya que corresponden a la selección de efectos y lo único que deberemos hacer es asignar la variable seleccion para que pueda ser interpretada por el bloque switch antes comentado.
Los botones 8 y 9 (rehacer y deshacer) se tratan de una manera diferenciada ya que deben modificar el valor de la variable que sirve de puntero (head) dentro del array de texturas cola.
Pulsar el botón deshacer significa volver a la textura anterior a la actual, es decir a la que se encuentra en la posición anterior del array cola (teniendo en cuenta que cola la tratamos como una estructura circular, osea que la posición siguiente de la 9 es la 0 y la anterior a la 0 es la 9).
Pulsar el botón rehacer significa volver de nuevo a la posición que acababamos de deshacer por lo que simplemente incrementaremos la variable head, siempre considerando el array como una estructura circular.
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A){ if (GRRLIB_PtInRect(x,y,WBOTON,HBOTON*sy,ir.x,ir.y)){ if (i < 8) selection = i+1; else { if (i==8) { // rehacer if (head < MAXCOLA-1) head++; else head = 0; } else if (i==9){ //deshacer if (head > 0) head--; else head = MAXCOLA-1; } }
Ya para finalizar el programa solo nos quedaría dibujar el puntero correspondiente a la opción seleccionada y una vez fuera del bucle liberar la memoria utilizada.
GRRLIB_DrawTile(ir.x,ir.y,tex_pointers,ir.angle,1,1, GRRLIB_WHITE,selection); GRRLIB_Render(); // Refrescamos la pantalla } GRRLIB_FreeTexture(tex_botones); GRRLIB_FreeTexture(tex_pointers); for (i=0; i<MAXCOLA; i++) GRRLIB_FreeTexture(cola[i]); GRRLIB_Exit(); // Liberamos memoria exit(0); // Usar exit para salir del program (no usar return desde el main)
Quedando al final el siguiente código:
#include <grrlib.h>
#include <stdlib.h>
#include <wiiuse/wpad.h>
#include "botones_png.h"
#include "pointers_png.h"
#include "arial_black_12_png.h"
// Colores RGBA
#define GRRLIB_BLACK 0x000000FF
#define GRRLIB_MAROON 0x800000FF
#define GRRLIB_GREEN 0x008000FF
#define GRRLIB_OLIVE 0x808000FF
#define GRRLIB_NAVY 0x000080FF
#define GRRLIB_PURPLE 0x800080FF
#define GRRLIB_TEAL 0x008080FF
#define GRRLIB_GRAY 0x808080FF
#define GRRLIB_SILVER 0xC0C0C0FF
#define GRRLIB_RED 0xFF0000FF
#define GRRLIB_LIME 0x00FF00FF
#define GRRLIB_YELLOW 0xFFFF00FF
#define GRRLIB_BLUE 0x0000FFFF
#define GRRLIB_FUCHSIA 0xFF00FFFF
#define GRRLIB_AQUA 0x00FFFFFF
#define GRRLIB_WHITE 0xFFFFFFFF
#define GRRLIB_LBLUE 0x5050FFFF
#define _MAXBOTONES 10
#define HBOTON 88
#define WBOTON 100
#define HPOINTER 44
#define WPOINTER 32
#define FBOTONES 5
#define MAXCOLA 10
GRRLIB_texImg* cola[MAXCOLA];
int head = 0;
int resx = 640; int resy=528;
GRRLIB_texImg* tex_font;
GRRLIB_texImg* tex_botones;
GRRLIB_texImg* tex_pointers;
int i, src, dst;
int selection = 0;
u32 factor[] = {3,3,0,0,0,3,0,0,0,0};
int main(int argc, char **argv) {
ir_t ir;
// Inicializar gráficos
GRRLIB_Init();
resx = rmode->fbWidth;
resy = rmode->efbHeight;
float sy = ((float) resy / HBOTON) / FBOTONES;
// Inicializar wiimotes
WPAD_Init();
WPAD_SetDataFormat(WPAD_CHAN_ALL, WPAD_FMT_BTNS_ACC_IR);
WPAD_SetVRes(WPAD_CHAN_ALL, resx, resy);
tex_font = GRRLIB_LoadTexture(arial_black_12_png);
GRRLIB_InitTileSet (tex_font, 16, 23, 32);
tex_botones = GRRLIB_LoadTexture(botones_png);
tex_pointers = GRRLIB_LoadTexture(pointers_png);
GRRLIB_InitTileSet (tex_botones, WBOTON, HBOTON, 0);
GRRLIB_InitTileSet (tex_pointers, WPOINTER, HPOINTER, 0);
head = 0;
cola[head] = GRRLIB_LoadTextureFromFile("sd:/foto.png");
// llena la cola de texturas con texturas vacías del mismo tamaño
// que la foto original.
for (i=1; i<MAXCOLA; i++)
cola[i]=GRRLIB_CreateEmptyTexture(cola[head]->w, cola[head]->h);
while(1){
WPAD_ScanPads(); // Leer mandos
WPAD_IR(0, &ir);
// Si pulsamos home salimos del bucle
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME) break;
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_B)
selection = 0;
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_UP && (factor[selection-1] > 0))
factor[selection-1]++;
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_DOWN && (factor[selection-1] > 1))
factor[selection-1]--;
GRRLIB_DrawImg(0,0,cola[head],0,(float)(resx-203)/resx,(float)resy/480,GRRLIB_WHITE);
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A) {
if ((selection > 0) && (selection < 9)&&
GRRLIB_PtInRect(0,0,resx-203, resy,ir.x,ir.y)){
src = head;
if (head < MAXCOLA-1) dst = head+1;
else dst = 0;
head = dst;
switch (selection){
case 1:
GRRLIB_BMFX_Blur (cola[src],cola[dst],factor[selection-1]);
break;
case 2:
GRRLIB_BMFX_Scatter (cola[src],cola[dst],factor[selection-1]);
break;
case 3:
GRRLIB_BMFX_FlipH (cola[src],cola[dst]);
break;
case 4:
GRRLIB_BMFX_FlipV (cola[src],cola[dst]);
break;
case 5:
GRRLIB_BMFX_Grayscale (cola[src],cola[dst]);
break;
case 6:
GRRLIB_BMFX_Pixelate (cola[src], cola[dst],factor[selection-1]);
break;
case 7:
GRRLIB_BMFX_Sepia (cola[src], cola[dst]);
break;
case 8:
GRRLIB_BMFX_Invert (cola[src], cola[dst]);
break;
}
GRRLIB_FlushTex(cola[dst]);
}
}
GRRLIB_Rectangle(resx-203, 0, 203, resy, GRRLIB_LIME, true);
for (i=0; i < _MAXBOTONES; i++) {
int x = resx-202+(i/FBOTONES)*100;
int y = (i%FBOTONES)*88*sy;
GRRLIB_DrawTile(x,y,tex_botones,0,1,sy,GRRLIB_WHITE,i);
if (factor[i] != 0){
char sf[10];
sprintf(sf, "%d",factor[i]);
int tw = strlen(sf) * 16;
GRRLIB_Printf(x+WBOTON/2-tw/2,y+5,tex_font,GRRLIB_BLACK,1,"%d",factor[i]);
}
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A){
if (GRRLIB_PtInRect(x,y,WBOTON,HBOTON*sy,ir.x,ir.y)){
if (i < 8)
selection = i+1;
else {
if (i==8) { // rehacer
if (head < MAXCOLA-1) head++;
else head = 0;
} else if (i==9){ //deshacer
if (head > 0) head--;
else head = MAXCOLA-1;
}
}
}
}
}
GRRLIB_DrawTile(ir.x,ir.y,tex_pointers,ir.angle,1,1, GRRLIB_WHITE,selection);
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_1)
GRRLIB_ScrShot("CAP7");
GRRLIB_Render(); // Refrescamos la pantalla
}
GRRLIB_FreeTexture(tex_botones);
GRRLIB_FreeTexture(tex_pointers);
for (i=0; i<MAXCOLA; i++)
GRRLIB_FreeTexture(cola[i]);
GRRLIB_Exit(); // Liberamos memoria
exit(0); // Usar exit para salir del program (no usar return desde el main)
}
Llegados a este punto, algunos de vosotros quizás os estéis preguntando cómo podríamos crear nuestras propios efectos especiales sobre texturas.
Sin ir más lejos, la redacción de este curso ha despertado mi curiosidad en este sentido por lo que me he permitido investigar un poco sobre los fuentes de la biblioteca y desarrollar una función que permite modificar la luminosidad de una textura.
Pero antes de empezar a programar una función de este tipo deberemos conocer algunas cosas adicionales.
Por un lado deberemos conocer un poco más la estructura de una textura, pero además, para poder comprender la funcionalidad de la función que vamos a poner como ejemplo debemos comprender, aunque sea por encima, los distintos modelos de representación del color que se pueden utilizar.
En una textura hay tres campos fundamentales que deberemos conocer si queremos manipularla:
Dentro del campo Data de una textura tenemos todos los píxeles de la misma ordenados de izquierda a derecha y de arriba a abajo, como si estuvieramos leyendo un libro.
Cada uno de esos píxeles consta de un valor RGBA como los que hemos tratado hasta ahora (ejemplo: 0xFFFFFFFF corresponde al blanco opaco).
De forma genérica, para poder tratar cada uno de los píxeles de la textura deberemos recorrerlos mediante dos bucles for anidados que recorran el eje x (de 0 a w) y el eje y (de 0 a h) respectivamente.
Para la lectura y escritura del valor de los píxeles echaremos mano de dos funciones que nos permiten dibujar puntos (píxeles) de manera individual:
u32 GRRLIB_GetPixelFromtexImg (const int x, const int y, const GRRLIB_texImg *tex) Parametros: x,y: coordeandas x e y dentro de la textura tex: textura sobre la que trabajamos Devuelve el color en formato RGBA void GRRLIB_SetPixelTotexImg (const int x, const int y, GRRLIB_texImg *tex, const u32 color) Parametros: x,y: coordeandas x e y dentro de la textura tex: textura sobre la que trabajamos color: Color RGBA a asignar Asigna el color indicado en la posición x,y de la textura tex
Al final la estructura general que deberiamos tener en una función de este tipo sería la siguiente:
void GRRLIB_BMFX_DUMMY (const GRRLIB_texImg *texsrc, GRRLIB_texImg *texdest) { unsigned int x, y; u32 color; for (y = 0; y < texsrc->h; y++) { for (x = 0; x < texsrc->w; x++) { color = GRRLIB_GetPixelFromtexImg(x, y, texsrc); ............ modificar la variable color a voluntad....... ............ GRRLIB_SetPixelTotexImg(x, y, texdest,color); } } }
Un modelo de color no es ni más ni menos que una manera de representar un color mediante una serie de valores.
El modelo que hemos utilizado hasta ahora (RGBA) se basa en esa premisa utilizando un byte para cada color básico (rojo, verde y azul). Se añade un byte más para lo que llamamos el canal alfa que corresponde a las transparencias, aunque esto no es estrictamente parte del color.
Esto es una forma de representarlo pero no la única. En nuestra vida diaria no manejamos de una manera intuitiva los colores en formato RGB. Es complicado saber mentalmente cual es el color resultante de mezclar un 32% de rojo con un 16% de verde más un 40% de azul.
Mas bien pensamos en cosas como; este color es un azul turquesa claro. En nuestra mente es mas sencillo manejar diferentes tonos de color aplicandoles niveles de brillo y saturación.
Pues bien, aplicando ese principio se crearon diferentes modelos de codificación del color entre los que se encuentra el modelo HSL.
Las siglas HSL significan (Hue: tono; Saturation: Saturación; L: Light), es decir exactamente la manera en la que modelamos en nuestra mente un color.
Existen una serie de complicadas ecuaciones que nos permiten convertir un valor RGB en un valor HSL y que os voy a ahorrar. Aportaré sin embargo un par de funciones que harán esto automáticamente por vosotros.
float max(float a, float b){ if (a > b) return a; else return b; } float min(float a, float b){ if (a < b) return a; else return b; } u32 GRRLIB_RGBToHSL(u32 cRGB){ float r= (float) R(cRGB)/0xFF; //RGB from 0 to 255 float g= (float) G(cRGB)/0xFF; float b= (float) B(cRGB)/0xFF; float maximo = max(r,max(g,b)); float minimo = min(r,min(g,b)); float delta = maximo-minimo; float h=0,s=0; float l = (maximo+minimo)/2; if (delta == 0){ //This is a gray, no chroma... h = 0; //HSL results from 0 to s = 0; } else { //Chromatic data... if (l < 0.5) s = delta/(maximo + minimo); else s = delta/(2.0-maximo-minimo); float del_R=(((maximo-r)/6.0)+(delta/2.0))/delta; float del_G=(((maximo-g)/6.0)+(delta/2.0))/delta; float del_B=(((maximo-b)/6.0)+(delta/2.0))/delta; if(r == maximo) h = del_B - del_G; else if (g == maximo) h = (1.0/3.0)+del_R-del_B; else if (b == maximo) h = (2.0/3.0)+del_G-del_R; if ( h < 0.0 ) h += 1.0; if ( h > 1.0 ) h -= 1.0; } return HSLA(h*0xFF,s*0xFF,l*0xFF,A(cRGB)); } float Hue_2_RGB(float v1, float v2, float vH){ //Function Hue_2_RGB if ( vH < 0 ) vH += 1.0; if ( vH > 1 ) vH -= 1.0; if ( ( 6.0 * vH ) < 1 ) return ( v1 + ( v2 - v1 ) * 6.0 * vH ); if ( ( 2.0 * vH ) < 1 ) return ( v2 ); if ( ( 3.0 * vH ) < 2 ) return ( v1 + ( v2 - v1 ) * ( ( 2.0 / 3.0 ) - vH ) * 6.0 ); return ( v1 ); } u32 GRRLIB_HSLToRGB(u32 cHSL){ float h= (float) H(cHSL)/0xFF; //RGB from 0 to 255 float s= (float) S(cHSL)/0xFF; float l= (float) L(cHSL)/0xFF; float var_1 = 0, var_2 = 0; float r,g,b; if (s == 0 ){ //HSL from 0 to 1 r = l; g = l; b = l; } else { if (l < 0.5) var_2 = l*(1.0+s); else var_2 = (l+s)-(s*l); var_1 = 2.0*l-var_2; r = Hue_2_RGB(var_1, var_2, h+(1.0/3.0)); g = Hue_2_RGB(var_1, var_2, h); b = Hue_2_RGB(var_1, var_2, h-(1.0/3.0)); } return RGBA(r*0xFF,g*0xFF,b*0xFF,A(cHSL)); }
Como habréis podido deducir, las funciones GRRLIB_RGBToHSL y GRRLIB_HSLToRGB permiten convertir un valor RGBA a HSLA y vicevesa.
El formato HSL es muy util en retoque fotográfico ya que nos permite manipular los parámetros tono, saturación y luminosidad de manera independiente.
Volviendo a nuestra función, la codificación HSL nos va a venir de maravilla para nuestros objetivos.
Esquemáticamente, si codificamos el color con HSL, lo único que debemos hacer para aumentar el brillo de una textura es:
Teniendo en cuenta la estructura general que hemos visto para una función de este tipo y aplicando para cada pixel estos cinco puntos llegamos al código que podemos ver abajo.
#define H( c ) (((c) >> 24) &0xFF) #define S( c ) (((c) >> 16) &0xFF) #define V( c ) (((c) >> 8) &0xFF) #define L( c ) (((c) >> 8) &0xFF) #define HSLA(h,s,l,a) ( (u32)( ( ((u32)(h)) <<24) | \ ((((u32)(s)) &0xFF) <<16) | \ ((((u32)(l)) &0xFF) << 8) | \ ( ((u32)(a)) &0xFF ) ) ) float max(float a, float b){ if (a > b) return a; else return b; } float min(float a, float b){ if (a < b) return a; else return b; } u32 GRRLIB_RGBToHSV(u32 cRGB){ float h = 0,s = 0,v = 0; float r = R(cRGB/0xFF); float g = G(cRGB/0xFF); float b = B(cRGB/0xFF); float maximo = max(r,max(g,b)); float minimo = min(r,min(g,b)); float delta = maximo-minimo; if (s==0) h = -1; else if (r==maximo) h = (g-b)/delta; else if (g==maximo) h = 2+(b-r)/delta; else h = 4+(r-g)/delta; h = h * 60; if (h<0) h=h/360; if (maximo != 0) s = delta/maximo; else s = 0; v = maximo; return RGBA(h*0xFF,s*0xFF,v*0xFF,A(cRGB)); } u32 GRRLIB_HSVToRGB(u32 cHSV){ float r,g,b; float h = H(cHSV/0xFF); float s = S(cHSV/0xFF); float v = V(cHSV/0xFF); if (s == 0) { //HSV from 0 to 1 r = v; g = v; b = v; } else { int var_h = h * 6; if (var_h == 6) var_h = 0; //H must be < 1 int var_i = var_h; //Or ... var_i = floor( var_h ) float var_1 = v*(1-s); float var_2 = v*(1-s*(var_h-var_i)); float var_3 = v*(1-s*(1-(var_h-var_i))); if (var_i == 0) { r = v; g = var_3; b = var_1;} else if (var_i == 1) {r = var_2; g = v; b = var_1;} else if (var_i == 2) {r = var_1; g = v; b = var_3;} else if (var_i == 3) {r = var_1; g = var_2; b = v;} else if (var_i == 4) {r = var_3; g = var_1; b = v;} else { r = v; g = var_1; b = var_2;} } return RGBA(r*0xFF,g*0xFF,b*0xFF,A(cHSV)); } u32 GRRLIB_RGBToHSL(u32 cRGB){ float r= (float) R(cRGB)/0xFF; //RGB from 0 to 255 float g= (float) G(cRGB)/0xFF; float b= (float) B(cRGB)/0xFF; float maximo = max(r,max(g,b)); float minimo = min(r,min(g,b)); float delta = maximo-minimo; float h=0,s=0; float l = (maximo+minimo)/2; if (delta == 0){ //This is a gray, no chroma... h = 0; //HSL results from 0 to s = 0; } else { //Chromatic data... if (l < 0.5) s = delta/(maximo + minimo); else s = delta/(2.0-maximo-minimo); float del_R=(((maximo-r)/6.0)+(delta/2.0))/delta; float del_G=(((maximo-g)/6.0)+(delta/2.0))/delta; float del_B=(((maximo-b)/6.0)+(delta/2.0))/delta; if(r == maximo) h = del_B - del_G; else if (g == maximo) h = (1.0/3.0)+del_R-del_B; else if (b == maximo) h = (2.0/3.0)+del_G-del_R; if ( h < 0.0 ) h += 1.0; if ( h > 1.0 ) h -= 1.0; } return HSLA(h*0xFF,s*0xFF,l*0xFF,A(cRGB)); } float Hue_2_RGB(float v1, float v2, float vH){ //Function Hue_2_RGB if ( vH < 0 ) vH += 1.0; if ( vH > 1 ) vH -= 1.0; if ( ( 6.0 * vH ) < 1 ) return ( v1 + ( v2 - v1 ) * 6.0 * vH ); if ( ( 2.0 * vH ) < 1 ) return ( v2 ); if ( ( 3.0 * vH ) < 2 ) return ( v1 + ( v2 - v1 ) * ( ( 2.0 / 3.0 ) - vH ) * 6.0 ); return ( v1 ); } u32 GRRLIB_HSLToRGB(u32 cHSL){ float h= (float) H(cHSL)/0xFF; //RGB from 0 to 255 float s= (float) S(cHSL)/0xFF; float l= (float) L(cHSL)/0xFF; float var_1 = 0, var_2 = 0; float r,g,b; if (s == 0 ){ //HSL from 0 to 1 r = l; g = l; b = l; } else { if (l < 0.5) var_2 = l*(1.0+s); else var_2 = (l+s)-(s*l); var_1 = 2.0*l-var_2; r = Hue_2_RGB(var_1, var_2, h+(1.0/3.0)); g = Hue_2_RGB(var_1, var_2, h); b = Hue_2_RGB(var_1, var_2, h-(1.0/3.0)); } return RGBA(r*0xFF,g*0xFF,b*0xFF,A(cHSL)); } void GRRLIB_BMFX_Brightness (const GRRLIB_texImg *texsrc, GRRLIB_texImg *texdest, int value) { unsigned int x, y; u32 hsl; u32 color; for (y = 0; y < texsrc->h; y++) { for (x = 0; x < texsrc->w; x++) { color = GRRLIB_GetPixelFromtexImg(x, y, texsrc); hsl = GRRLIB_RGBToHSL(color); int l = L(hsl)+value; if (l>0xFF) l = 0xFF; color = GRRLIB_HSLToRGB(RGBA(H(hsl),S(hsl),l,A(hsl))); GRRLIB_SetPixelTotexImg(x, y, texdest,color); } } GRRLIB_SetHandle(texdest, 0, 0); }
El corazón de dicho código son las operaciones que hacemos sobre cada pixel en el interior de los bucles anidados:
color = GRRLIB_GetPixelFromtexImg(x, y, texsrc); hsl = GRRLIB_RGBToHSL(color); int l = L(hsl)+value; if (l>0xFF) l = 0xFF; color = GRRLIB_HSLToRGB(RGBA(H(hsl),S(hsl),l,A(hsl)));
Debemos tener en cuenta que si saturamos el valor de brillo (es decir superamos el valor 0xFF) debemos asignar dicho valor.
Por último asignamos el punto de referencia para giros y escalado en 0,0 en la nueva textura, para lo que utilizaremos la función GRRLIB_SetHandle.
Hasta aquí hemos llegado con la entrega actual y con las funciones en 2D.
Hemos visto la gran mayoría de las funciones en 2D que suministra GRRLIB aunque aun nos quedarían cosas interesantes como son la composición de texturas y los diferentes modos de blending. Quizás volvamos sobre ello un poco más adelante.
Ahora mismo debemos continuar con otros aspectos de la programación. Unos están directamente relacionados con GRRLIB como son los gráficos en 3D y otros no tanto aunque son complementos imprescindibles, como son el sonido y el acceso al sistema de archivos. Pero esto ya será a partir de la siguiente entrega.
¡¡¡Nos vemos en la próxima entrega!!!!
Comentarios
Felicidades por la nueva
Felicidades por la nueva entrega y muchas gracias!!super util, con lo que hay ya se podria hacer un juego decente.
Yo ahora estoy trasteando con reproducir varios mp3 a la vez, es complicado jejejje.
Un saludo!
7ma. entrega curso GRRLIB
Está hiper que extraordinaria ésta entrega, me quedó asombrado, Wilco2009 eres lo máximo en ser programador y en todo, voy a ver si en cuestión de días empezaré a programar con la librería GRRLIB, porque es bastante pesado el material de las anteriores entregas desde la priemra hasta la sexta.
Se ve más dificil todavía de lo que estudio lenguaje de programación, pero aún así, lo voy a intentar, es mi carrera y quiero aprovecharlo al máximo.
Un saludo!!! Wilco2009
Miiverse ID: BrianRC07 | Mii y Mario Kart 8: BRKart 07
¿Tenés la Wii U y te gustaría jugar backups de Wii?, entra aquí.
Muchas gracias por el
Muchas gracias por el reconocimiento, pero yo como todos, sigo aprendiendo día a día. A partir de la novena entrega espero comenzar con las funciones en 3D, y ahí tengo mucho que aprender todavía.
A ver si es verdad que que te animas a seguir el curso y entre todos conseguimos una buena comunidad de programadores.
Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11
Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3 - Parte 4 (Balanceboard) - Parte 5 (Miis)
Homebrew - WiiTriis - LifemiiWii