[C con Clase] SDL operaciones bitwise y punteros.

Steven Davidson srd4121 en njit.edu
Sab Feb 26 19:36:27 CET 2011


Hola David,

On 2/26/2011 7:23 AM, David wrote:
> Steven, en serio muchísimasgracias
>

De nada; para eso estamos.

> (Uint16 *)p -> realiza el casting; una vez realizado el casting
> entonces que vuelva a apuntar a la dirección de memoria *p, juntando
> las dos cosas da como resultado la expresión *(Uint16 *)p.
>
> En base a esto, permíteme tratar de explicar la siguiente expresión:
>
> Nota: superficie->pixels está definido dentro de la estructura
> SDL_Surface como un puntero a void, o sea, "void *pixels".
>
> Uint8 *p = (Uint8 *)superficie->pixels + y * superficie->pitch + x *
> bpp;
>
> 1.- Se realiza el casting de (void *) a (Uint8 *) sobre el puntero
> superficie->pixels.
> 2.- Se multiplica x * bpp
> 3.- Se multiplica y * superficie->pitch
> 4.- Se suma el resultado del punto 2 y 3
> 5.- Y por último el resultado del punto 4 es sumado a la dirección de
> memoria de superficie->pixels.
>

Correcto.

> Otra forma de verlo sería así:
> Uint8 *p = (Uint8 *)superficie->pixels; //p apunta a la dirección
> superficie->pixels.
> p[ (y * superficie->pitch) + (x * bpp) ]; //obtener la dirección del
> pixel.
>

Esto último no es correcto. Recuerda que los corchetes [] son un 
operador de acceso. Aquí accederías al píxel del índice 
'y*superficie->pitch + x*bpp'.

Sin embargo, esto no es lo que hacemos, sino que obtenemos la dirección 
de memoria que apunta a un píxel; o mejor dicho, a un 'Uint8' que 
representa 1 solo byte.

> Así se obtiene la posición del pixel y nos permite luego ir a p[R],
> p[G] y p[B] sin mayor riesgo ya que para este caso cada uno de los
> pixeles está compuesto por 24 bits y p es de 8 bits, por lo que 8 x 3
> = 24. Entonces teniendo la posición del pixel en la superficie,
> podemos movernos entre los 24 bits que componen el pixel sin
> problemas debido a que R=0 G=1 B=2 son consecutivos.
>

Exacto.

>

[CORTE]

> Una pregunta más, la función GetPixel en mi arquitectura me devuelve
> [B, G, R], sin embargo cuando el resultado en una variable,
> llamemosle color, al verlo en hexadecimal me muestra [R, G, B]. El
> compilador hace una transformación de forma automática por ser
> little-endian?
>

Sí; claro. Al fin y al cabo, el compilador DEBE conocer la arquitectura 
del procesador al cual va a compilar y traducir el código fuente. Si 
usas un compilador para Intel, entonces debe saber que Intel usa 
"little-endian".

Ahora bien, al decir que "muestra" algo (en pantalla), entonces estamos 
hablando de las funciones, segurametne estándares, de las bibliotecas 
que controlan la consola (y la pantalla). Aquí, el compilador no tiene 
nada que ver, ya que esa funcionalidad ya está compilada, en la mayoría 
de los casos. Por consiguiente, si la función que usas muestra 
información, ésta puede ser diferente a su representación interna.

> Para que veas más claro a lo que me refiero te coloco la porción de
> código:
>
> //Defino el color que quiero mostrar en la pantalla o superficie...
> Uint32 color = SDL_MapRGB(pantalla->format, 255, 0, 0); //Color rojo..
>
> //Pinto el pixel del color rojo en la superficie:
> PutPixel(pantalla, 100, 100, color);
>
> //Obtengo el valor del color de la pantalla...
> Uint32 comprobar = GetPixel(pantalla, 100, 100); //Aquí se obtiene el
> valor rojo...
>
> //Muestro el valor hexadecimal de la variable "comprobar"...
> cout << "Valor hex: " << hex << comprobar << endl;
>
> El resultado que me da es:
> Valor hex: ff 00 00
>
> Sin embargo, si te fijas en el retorno de la función (return p[R] |
> p[G] << 8 | p[B] << 16;) lo que interpreto es lo siguiente
>
> Devuelve el rojo ff, desplázate 8 bits hacia la izquierda y devuelve
> el valor del verde 00, desplázate 16 bits y devuelve el valor de azul
> 00 dejando como resultado:
>
> 00 00 ff
>

Correcto, para una arquitectura de "little-endian", como es Intel.

> Y claro aquí me confundo porque una cosa es lo que estoy
> interpretando y otra muy distinta el valor real devuelto.
> ff 00 00 <> 00 00 ff
>

La explicación involucra, como he dicho anteriormente, en la 
funcionalidad de 'cout <<' pero ésta depende del tipo de dato que pases 
como parámetro. Al tratarse de un 'Uint32', se pasan los primeros 4 
bytes contenidos en la variable, 'comprobar', que representan el número 
entero (no negativo). Por lo tanto, convierte correctamente el número 
16711680 a hexadecimal: 0x00FF0000. Esto no tiene por qué representar 
los valores de cada byte en memoria. Por eso, el tipo de dato es muy 
importante en cuanto a la interpretación de las secuencias de bits y las 
operaciones que pueden involucrar esos valores.

En resumen, la secuencia de bytes: 0x00, 0xff, 0x00, y 0x00 es 
representada en memoria, en una arquitectura de "little-endian" como es 
Intel, como: 0x00, 0x00, 0xff, y 0x00. Su interpretación depende del 
tipo de dato:

- Si tratas esta secuencia como un entero (no negativo) de 32 bits, 
entonces el valor es: 16711680.
- Si tratas esta secuencia como dos números enteros (no negativos) de 16 
bits cada uno, entonces los valores son: 0, y 255.
- Si tratas esta secuencia como cuatro números enteros (no negativos) de 
8 bits cada uno, entonces los valores son: 0, 0, 255, y 0.

Puedes hacer esta comprobación realizando un cásting apropiado al 
puntero, 'p', o incluso a la variable 'comprobar'. Por ejemplo,

cout << "Como Uint32: " << hex << comprobar << endl;

Uint16 *p16 = ((Uint16 *)&comprobar);
cout << "Como Uint16: " << hex << p16[0] << ' ' << p16[1] << endl;

Uint8 *p8 = ((Uint8 *)&comprobar);
cout << "Como Uint8: " << hex
      << p8[0] << ' ' << p8[1] << ' ' << p8[2] << ' ' << p8[3] << endl;

> Un cordial saludo, y Steven de nuevo muchísimas gracias, cada vez
> entiendo más y mejor.
>

De nada.


Espero que lo anterior aclare más este tema.

Steven





Más información sobre la lista de distribución Cconclase