Explicación de los valores retornados por imagecolorat()
Septiembre 21, 2009
Los que hemos trabajado alguna vez con la librería gd sobre PHP, hemos visto una singular función llamada imagecolorat que "retorna el índice del color de un pixel en una posición especificada en la imagen". Pero, ¿Qué es ese valor tan raro que nos retorna?, o ¿por qué en el manual de PHP muestran este código?
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
¿De dónde sacan esas operaciones tan extrañas?
A continuación una explicación sobre estos valores y el porque de esas operaciones binarias.
Como escribía en una publicación anterior (http://www.sinfocol.org/2008/09/solucion-reto-1-esteganografia-con-bit-menos-significativo/), para representar el color en un pixel del monitor, se usan tres posibles valores, los cuales son Rojo(R), Verde(G) y Azul(B), o como comúnmente se conoce: RGB. Cada uno de estos colores puede tomar un número de 0 a 255 (O lo mismo que decir de 0 a FF). Así que el máximo número de combinaciones posibles para colores RGB es de 256^3 = 16777216 colores.
Entonces un color RGB está representado por la unión del color rojo, el verde y el azul, formando un trío como pueden ver a continuación:
Rojo + Verde + Azul
FF + FF + FF en hexadecimal
11111111 + 11111111 + 11111111 en binario
(Si el lector conoce sobre programación con HTML, podrá notar que la notación en hexadecimal es igual a la usada para configurar colores en algunas de las etiquetas HTML.)
Ahora pasemos a la práctica con la librería, creamos una imagen de un solo pixel que contenga el color rojo, otra imagen con el color verde y una última imagen con el color azul.
Rojo
Verde
Azul
Creamos un pequeño código en PHP para obtener el índice del color y luego imprimirlo
$imagen = imagecreatefrompng('rojo.png'); //verde.png y azul.png, pueden descargar las imágenes para probar dando clic derecho, guardar imagen como...
echo imagecolorat($imagen, 0, 0);
Observamos los resultados para cada imagen
Rojo.png
16711680
Verde.png
65280
Azul.png
255
Ahora ¿cómo un número tan grande como 16711680 puede representar el color rojo y uno tan pequeño como el 255 representa el azul?
Veamos cada uno de estos números en su notación hexadecimal y binaria (Podemos convertir cada número decimal a hexadecimal con la calculadora de windows en modo científico)
Rojo.png
FF0000
111111110000000000000000
Verde.png
00FF00
000000001111111100000000
Azul.png
0000FF
000000000000000011111111
Oh!, ahora comprendemos que el valor retornado en realidad representa el trío mostrado al inicio de esta publicación. Probemos con otro color que no sea tan elemental. Yo escogí probarlo con un pixel de color ABCDEFh
Ejecutamos el script para observar que retorna el imagecolorat
Abcdef.png
11259375
Al igual que con las anteriores imágenes, convirtamos ese número decimal a su respectivo hexadecimal y binario:
ABCDEF
101010111100110111101111
Si separamos la cadena binaria en grupos de 8 bits obtenemos el valor para cada color (Probemos con la imagen del color rojo y la última del color ABCDEFh)
Para rojo.png
111111110000000000000000 se convierte en
11111111 Rojo (255)
00000000 Verde (0)
00000000 Azul (0)
Significa que la imagen contiene un valor de 255 para rojo, 0 para verde y 0 para azul (Wow, efectivamente es una imagen de puro color rojo)
Para acbdef.png
101010111100110111101111 se convierte en
10101011 Rojo (171)
11001101 Verde (205)
11101111 Azul (239)
Acá la imagen contiene un valor de 171 para rojo, 205 para verde y 239 para azul (El color con el valor más alto es el azul, por eso observamos que el color de la imagen es azul pastel)
Al haber entendido esta primera parte, podemos pasar a la segunda parte que trata sobre programación con PHP para extraer cada color.
Como base tenemos el siguiente código:
$rgb = imagecolorat($imagen, 0, 0);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
echo "Rojo = $r\nVerde = $g\nAzul = $b";
Al ejecutarlo nos muestra este contenido:
Rojo = 255
Verde = 0
Azul = 0
¿Cómo hizo?
Simulemos la ejecución del programa
$rgb = imagecolorat($imagen, 0, 0);
$rgb = 16711680; //Valor retornado por imagecolorat para la imagen roja.png
$r = (16711680 >> 16) & 0xFF;
$r = (111111110000000000000000 >> 16) & 0xFF; //El operador bitwise >> lo que hace es desplazar 16 posiciones el binario que tenemos, así que desplaza ese binario 16 posiciones a la derecha
(111111110000000000000000 >> 16) & 0xFF
(011111111000000000000000 >> 15) & 0xFF
(001111111100000000000000 >> 14) & 0xFF
(000111111110000000000000 >> 13) & 0xFF
(000111111110000000000000 >> 12) & 0xFF
...
(000000000000001111111100 >> 2) & 0xFF
(000000000000000111111110 >> 1) & 0xFF
(000000000000000011111111) & 0xFF
000000000000000011111111 AND (16711680 >> 16)
000000000000000011111111 (0xFF)
------------------------
000000000000000011111111
//El AND garantiza que se cumpla la condición de que un color solo pueda variar entre 0 y 255 valores.
$r = 255; //255 es lo mismo que 11111111
$g = (16711680 >> 8 ) & 0xFF;
$g = (111111110000000000000000 >> 8 ) & 0xFF;
(111111110000000000000000 >> 8) & 0xFF
(011111111000000000000000 >> 7) & 0xFF
(001111111100000000000000 >> 6) & 0xFF
(000111111110000000000000 >> 5) & 0xFF
(000011111111000000000000 >> 4) & 0xFF
(000001111111100000000000 >> 3) & 0xFF
(000000111111110000000000 >> 2) & 0xFF
(000000011111111000000000 >> 1) & 0xFF
(000000001111111100000000) & 0xFF
000000001111111100000000 AND (16711680 >> 8 )
000000000000000011111111 (0xFF)
------------------------
000000000000000000000000
$g = 0;
...
$b = 0;
Ahora, depronto usted notó que la operación "& 0xFF" no sirve de nada cuando se aplica para sacar el color rojo, ya que se mueven todos los bits 16 posiciones a la derecha, y a la izquierda del primer bit más significativo solo hay ceros (Bits ceros con color rojo). Pues ese no es el caso cuando tratamos imágenes con colores reales (Imágenes que además de contener color rojo, verde y azul, contienen un canal alpha para manejar transparencias), estas imágenes reciben el nombre de imágenes RGBA (Rojo, verde, azul y alfa)
Para estas imágenes, los valores son guardados de una forma diferente, en PHP con la librería GD se manejan 7 bits para los posibles valores del canal alpha, esto da lugar a 2^7 = 128 combinaciones posibles. (El 0 indica una imagen totalmente opaca, mientras que el 127 el cual es el último valor representa una total transparencia).
Así que ya no es un trío de RGB, sino un cuarteto donde el alpha va de primero
Alpha + Rojo + Verde + Azul
Así que si tenemos una imagen con color rojo, con un cincuenta% de transparencia como lo podemos ver en la siguiente imagen
Ejecutamos el script, este nos retorna el número decimal 1073676288, en sus respectivas representaciones hexadecimales y binarias:
3FFF0000 (1073676288 en hexadecimal)
3F Alpha
FF Rojo
00 Verde
00 Azul
O en su respectiva cadena binaria
0111111111111110000000000000000 donde al partirla se convierte en
0111111 Alpha
11111111 Rojo
00000000 Verde
00000000 Azul
Imaginemos el caso en que al programador se le olvidó colocar la máscara para ajustar los valores del color rojo, y quedo algo mas o menos así:
$rgb = imagecolorat($imagen, 0, 0);
$r = $rgb >> 16; //Acá se le olvidó colocar el 0xFF
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
echo "Rojo = $r\nVerde = $g\nAzul = $b";
Cuando imagecolorat retorna el resultado 1073676288, hacemos de nuevo el proceso:
$r = 1073676288 >> 16;
$r = 0111111111111110000000000000000 >> 16;
0111111111111110000000000000000 >> 16
0011111111111111000000000000000 >> 15
...
0000000000000000111111111111110 >> 1
0000000000000000011111111111111
Al final queda que $r se queda con el valor "11111111111111" que en decimal es 16383, se sale de los límites! (Los bits en rojo deberían sobrar, porque el máximo valor para el rojo es de 255 o en binario 11111111).
Si aplicamos el AND ocurre algo muy diferente:
0000000000000000011111111111111 AND (1073676288 >> 16)
0000000000000000000000011111111 (0xFF)
-------------------------------
0000000000000000000000011111111
Donde al final $r queda con el valor de 255 (Dentro de los límites, además es el color adecuado).
Para extraer el canal alpha realizamos la misma operación, el color rojo termina en la posición 24, entonces corremos el binario 24 posiciones y luego aplicamos un AND 0x7F, se puede notar que ahora no es 0xFF porque el máximo valor que toma el canal alpha es 127 que en hexadecimal es 7F
$alpha = (1073676288 >> 24) & 0xFF;
$alpha = (0111111111111110000000000000000 >> 24) & 0x7F;
0111111111111110000000000000000 >> 24
0011111111111111000000000000000 >> 23
...
0000000000000000000000001111111 >> 1
0000000000000000000000000111111
0000000000000000000000000111111 AND (1073676288 >> 24)
0000000000000000000000001111111 (0x7F)
-------------------------------
0000000000000000000000000111111
Quedando el valor para $alpha = 111111, que en decimal es 63.
Por último, en la librería gd, especificamente en el archivo gd.h podemos ver las siguientes definiciones (http://cvs.php.net/viewvc.cgi/gd/libgd/src/gd.h?revision=1.34.2.11&view=markup):
#define gdTrueColorGetAlpha(c) (((c) & 0x7F000000) >> 24)
#define gdTrueColorGetRed(c) (((c) & 0xFF0000) >> 16)
#define gdTrueColorGetGreen(c) (((c) & 0x00FF00) >> 8 )
#define gdTrueColorGetBlue(c) ((c) & 0x0000FF)
Así que en PHP (Para asemejarnos al trabajo ya realizado), podemos copiar el siguiente código obteniendo los mismos resultados:
$a = ($argb & 0x7F000000) >> 24;
$r = ($argb & 0xFF0000) >> 16;
$g = ($argb & 0x00FF00) >> 8;
$b = ($argb & 0x0000FF);
Ahora un pequeño reto para el lector que llego con éxito hasta esta parte!
El reto consiste en realizar los mismos pasos para extraer manualmente el color rojo, verde, azul y el canal alpha de la siguiente imagen:
Nota para los vagos
Los vagos podemos usar la función imagecolorsforindex() que proporciona la librería para extraer los colores.
$rgba = imagecolorat($imagen, 0, 0);
print_r(imagecolorsforindex($imagen, $rgba));
/*Array
(
[red] => 255
[green] => 0
[blue] => 0
[alpha] => 63
)*/
Archivado en: Miscelaneo |
Como siempre una gran explicacion para entender el funcionamiento interno de esta funcion de php.
Nota: Yo soy de los que usa imagecolorsforindex() jeje
jeje GENIAL, aunque esto me hubiera servido muchisimo mas antes. Pero ahora entiendo varias cosas
:D gracias por esta buena info ^^
arriba sinfocol xD
Oye mi hermano resolviste una gran duda que tenia, una ocacion imprimi los valores que me regresaba dicha funcion pero la verdad no pude comprender esas salidas, ahora me queda bastante claro, gracias !!!
Salu2...
Muchas gracias HaDeS tampoco entendia esos scripts que veia en las respuestas de algunos retos, ahora ya esta claro (y)
Lo curioso es que desde tiempo atras buscaba esta info y apenas la encuentro, creo que me toca mirar mas el blog
Un Saludo!
[...] http://www.sinfocol.org/2009/09/explicacion-de-los-valores-retornados-por-imagecolorat/ Categories: esteganografia Tags: Comentarios (0) Referencias (0) Dejar un comentario Referencia [...]
[...] artículos de este año los pueden detallar a continuación World of Wargame #Comentarios: 8 Explicación de los valores retornados por imagecolorat() #Comentarios: 5 Cómo pasar todos los niveles del juego ASCIIPortal #Comentarios: 0 Cómo [...]
Realmente te agradesco un monton, vi en varios scripts eso de los operadores de bits, y tambien en temas sobre redes neuronales y no lograba comprenderlo. Realmente te agradesco.
[...] http://www.sinfocol.org/2009/09/explicacion-de-los-valores-retornados-por-imagecolorat/ [...]