[C con Clase] SDL operaciones bitwise y punteros.

David xdrtas en yahoo.es
Sab Feb 26 13:23:34 CET 2011


Steven, en serio muchísimas gracias

(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.

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.

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.

En cuanto al último punto, cuando escribiste la siguiente expresión me quedó 
clara la idea:

return ((Uint32)p[R]) | ((Uint32)p[G]) << 8 | ((Uint32)p[B]) << 16;

Para mí ahora si tiene sentido, aunque me diste la clave también cuando me 
recordaste que la función devuelve un valor de 32 bits, con lo que el return se 
encargaría de empaquetar el resultado, permitiendo la expresión como sigue:

return p[R] | p[G] << 8 | p[B] << 16; //Aquí se pude construir un bloque de 32 
bits.

Todo está entendido, muchas gracias Steven, si en realidad es muy fácil, sólo 
hay que tener los conceptos claros.

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?

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

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

Un cordial saludo, y Steven de nuevo muchísimas gracias, cada vez entiendo más y 
mejor.
http://xdrtas.coolpage.biz/
http://xdrtas.blogspot.com/




________________________________
De: Steven Davidson <srd4121 en njit.edu>
Para: Lista de correo sobre C y C++ <cconclase en listas.conclase.net>
Enviado: vie,25 febrero, 2011 14:14
Asunto: Re: [C con Clase] SDL operaciones bitwise y punteros.

Hola David,

On 2/25/2011 7:24 AM, David wrote:
> Hola, buenas a todos, estoy siguiendo el tutorial de SDL:

[CORTE]

> Mi primera pregunta es:
> ¿Cómo se interpreta la siguiente sentencia?: *(Uint16 *)p = pixel.
> La primera parte lo entiendo, si el sistema trabaja con una
> profundidad de color de 16 bits o 2 bytes entonces se hace un cast a
> *p para que apunte a variables del tipo Uint16 y así *p en vez de

Técnicamente, no hacemos un cásting a '*p', sino a 'p', el puntero.

> usar un byte o 8 bits para apuntar a el valor, ahora podrá apuntar a
> variables de2 bytes o 16 bits, pero qué pasa con el otro operador de
> indirección "*" el que está de rojo, *(Uint16 *)p, no entiendo a que
> apunta aquí.
> 

Apunta al valor que está en esa misma dirección guardada en 'p'. Lo que pasa es 
que ahora conseguirá un valor de 2 bytes en lugar de un valor de 1 byte, como 
indicaba 'p', originalmente. El operador * es de indirección y por tanto está 
accediendo al valor apuntado. En otras palabras, estamos asignando el valor de 
'pixel' a la memoria apuntada por 'p'. Esto es similar al caso 1 del 
'switch/case':
*p = pixel;

Lo que pasa es que, en el caso de 16 bits, tenemos que hacer un cásting. 
Podríamos haber hecho esto:

Uint16 *p16 = (Uint16 *)p;
*p16 = pixel;

Si quieres, te pongo un vector descriptor, para que veas lo que ocurre en 
memoria. Anterior a esta sentencia, tenemos lo siguiente en memoria:

Dirección
de Memoria    Nombre    Tipo        Valor
--------------------------------------------------
0x1122FF00    pixel    Uint32        0xFF770000  (color RGB)
...
0x11AAEE00    p    Uint8 *        0x33FFCC00
...
0x33FFCC00    ----    Uint8        0x00
0x33FFCC01    ----    Uint8        0x00
0x33FFCC02    ----    Uint8        0x00
0x33FFCC03    ----    Uint8        0x00
0x33FFCC04    ----    Uint8        0x00
0x33FFCC05    ----    Uint8        0x00
...

Después de la asignación, veríamos lo siguiente en memoria:

Dirección
de Memoria    Nombre    Tipo        Valor
--------------------------------------------------
0x1122FF00    pixel    Uint32        0xFF770000  (color RGB)
...
0x11AAEE00    p    Uint8 *        0x33FFCC00
...
0x33FFCC00    ----    Uint8        0xFF
0x33FFCC01    ----    Uint8        0x77
0x33FFCC02    ----    Uint8        0x00
0x33FFCC03    ----    Uint8        0x00
0x33FFCC04    ----    Uint8        0x00
0x33FFCC05    ----    Uint8        0x00
...

El puntero 'p' sigue apuntando al mismo lugar, pero ahora hemos copiado el valor 
en 'pixel' a la memoria apuntada por 'p'. Previamente, tuvimos que indicar a 
C/C++ que tratase 'p' como un puntero a 'Uint16'.

La verdad es que deberíamos hacer un cásting de 'pixel' (de tipo 'Uin32') al 
tipo 'Uint16', ya que perderíamos datos y por tanto el compilador no puede 
realizar esta operación implícitamente. Esto es,

*(Uint16 *)p = (Uint16)pixel;

> Mi segunda pregunta es sobre el siguiente bloque:
> 
> p[R] = pixel & 0xFF;
> p[G] = (pixel >> 8) & 0xFF;
> p[B] = (pixel >> 16) & 0xFF;
> 
> Tratándose de punteros, sé que se pueden hacer cosas como esa, pero
> cómo sabe el compilador que p[1] y p[2], (p[G] y p[B]
> respectivamente) no están ocupadas con información de otra
> aplicación, no es esto peligroso?, manipular direcciones de memoria
> que no se han declarado?

En general, sí. Por eso el programador de C/C++ debe poner especial atención y 
mucho cuidado al usar punteros.

> Sé que declare *p, pero no declaré p[1], ni p[2], ni p[..n].

Creo que estás confundiendo el operador [] con arrays. El operador [] puede 
servir tanto para arrays como para punteros. Por ejemplo,

int lista[10];

lista[0] = 10;
lista[1] = 100;
...

Pero también podemos usar la nomenclatura de punteros:

*lista = 10;
*(lista+1) = 100;
...

Estas operaciones son equivalentes entre sí. También podemos hacer lo mismo con 
punteros:

int *p = lista;

*p = 10;
*(p+1) = 100;
...

o incluso,

int *p = array;

p[0] = 10;
p[1] = 100;
...

La diferencia está en que 'lista' es un array y por tanto el compilador creará 
la memoria necesaria para que "exista". Además, un array no puede ser 
modificado, pero sus elementos sí. Por lo tanto, podemos decir que un array es 
como un puntero constante. Esto es,

lista = new int[100];  // incorrecto: 'lista' es constante
p = new int[100];      // correcto: 'p' apunta a una nueva dirección

Deberías revisar este tema de punteros de C/C++ en la documentación. En nuestro 
curso, puedes dirigirte al capítulo 12: 
http://c.conclase.net/curso/index.php?cap=012b#PUNT_Correspondencia

Por consiguiente, 'p' apunta a una dirección de memoria basada en 
'superficie->pixels'. Al comienzo de esta función, escribes:

Uint8 *p = (Uint8 *)superficie->pixels + y * superficie->pitch + x * bpp;

Por lo tanto, 'p[0]', 'p[1]', etc. son los valores en esas direcciones de 
memoria.

> p[R], p[G], p[B], son de 32 bits o 4 bytes cada uno? O p[R], p[G],
> p[B] representa cada uno un byte de los 4 que tiene p. Me explico con

Para responder a esta pregunta, simplemente mira el tipo del dato apuntado por 
'p'. Como 'p' es de tipo 'Uint8 *', entonces los valores apuntados son de tipo 
'Uint8' que seguramente se define como 'unsigned char' en alguna parte.

> un esquema:
> 
> Recordemos que *p es de 4 bytes y para el caso de profundidad de

No. 'p' es de tipo 'Uint *', así que '*p' es de tipo 'Uint8', que como acabo de 
explicar, es 1 byte.

Lo que seguramente es cierto es que 'p', como puntero, ocupa 32 bits, si estamos 
en una plataforma de 32 bits y por tanto las direcciones de memoria son de 32 
bits.

> 

[CORTE]

> Mi tercera pregunta viene en relación a la siguiente función:
> 
> Uint32 GetPixel(SDL_Surface *superficie, int x, int y)

[CORTE]

> El siguiente "return " no lo entiendo bien, alguien me podría
> explicar a nivel de bits que hace lo siguiente:
> 
> return p[R] | p[G] << 8 | p[B] << 16;
> 
> Porque si p[R], p[G] y p[B] son cada uno de un Byte o 8 bits entonces
> no tiene sentido el desplazamiento de bits.
> O, lo que hace es que: escribe los 8 bits del rojo, luego que
> desplace 8 bits el verde y y escriba el valor en bits del verde y por
> último que desplace 16 bits el azul para que el return devuelva los
> 24 bits como [B, G, R], o sea, una concatenación de bits.

Esta última explicación es la correcta para aclarar tu duda.

En parte, tienes razón en la primera duda que planteas. Los operandos son de 
tipo 'Uint8' y por tanto estos operadores darán resultados de tipo 'Uint8'. Sin 
embargo, el tipo resultante es de 'Uint32', por lo que el compilador entiende 
que el resultado será de 32 bits.

De todas maneras, estoy de acuerdo contigo; esto no sería la forma correcta de 
escribir esta operación. Aconsejaría hacer cástingas a cada operando para que 
las expresiones fueren del tipo correcto. Esto es,

return ((Uint32)p[R]) | ((Uint32)p[G]) << 8 | ((Uint32)p[B]) << 16;

Ciertamente, esto es algo engorroso y poco legible, pero al menos nos aseguramos 
que las expresiones son correctas.

> Aunque lo que yo veo es que se está usando un OR entre los tres.
> 

Correcto. Realizamos dos operaciones de OR a nivel de bits. Esto es mejor verlo 
con un ejemplo en binario,

   11001110
OR 00111000
-----------
   11111110

En el caso de la expresión anterior, obtendríamos algo así:

   1100111000000000
OR 0000000000111000
-------------------
   1100111000111000

Básicamente, agregamos una parte a la parte "superior" del resultado y la otra 
parte a la parte "inferior" del resultado. Es casi como empaquetar la 
información en otro tipo de dato - en otro formato. Visto de una forma general:

   aaaaaaaa00000000
OR 00000000bbbbbbbb
-------------------
   aaaaaaaabbbbbbbb

donde 'a' y 'b' son cualesquier bits.

Te aconsejo que consultes el capítulo 18 de nuestro curso de C++, yendo a: 
http://c.conclase.net/curso/index.php?cap=018#inicio


Espero que todo esto te aclare las dudas.

Steven


_______________________________________________
Lista de correo Cconclase Cconclase en listas.conclase.net
http://listas.conclase.net/mailman/listinfo/cconclase_listas.conclase.net
Bajas: http://listas.conclase.net/index.php?gid=2&mnu=FAQ



      
------------ próxima parte ------------
Se ha borrado un adjunto en formato HTML...
URL: <http://listas.conclase.net/pipermail/cconclase_listas.conclase.net/attachments/20110226/6df5b1c8/attachment.html>


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