Ir al contenido


Foto

Como crear una imagen de escala de grises, a 8bits reales.


  • Por favor identifícate para responder
11 respuestas en este tema

#1 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 21 febrero 2011 - 04:53

Hola,
Aquí dejo un pequeño truco que nos permite generar imágenes en escala de grises a 8 bits.

 

Revisión 22/08/16:

NOTA: Este truco tiene un error. Se ha asignado de manera errónea los valores de la paleta a la imagen resultante.

Para un código óptimo y probado se sugiere seguir este hilo. Lo que puede ser de utilidad aquí es la discusión de como proceder a calcular el valor de gris en base a dos modos:

Como bien sabemos, el esquema RGB trabaja con 24 bits, 1 byte para cada canal: Rojo, Verde y Azul. Y también conocemos que para lograr imágenes en escala de grises debemos recurrir a una "transformación" de color.

Básicamente hay dos ecuaciones que calculan el valor de gris para un valor RGB.

Basada en el brillo

Valor = 0.3 * Rojo + 0.59 * Verde + 0.11 * Azul

Basada en la Intensidad

Valor = 1/3 * (Rojo + Verde + Azul)

Cuando ya tenemos el color, que no es más que un valor en rango [0,255] debemos alterar el color de cada canal por este. Es decir:

Rojo = Valor
Verde = Valor
Azul = Valor

El defecto de esta técnica es que la imagen sigue siendo de formato a 24 bits y estamos desperdiciando 16 de ellos por cada pixel, ¡estamos guarda el mismo valor en cada canal!. Uno piensa que bastaría entonces con hacer algo como:
 


delphi
  1. Bitmap.PixelFormat := pf8bit;
  2. Bitmap.SaveToFile(...);

Pero no. No es tan simple, y NO... NO es una imagen de escala de gris. La imagen que hemos pintado si está en tonos de grises... y si vemos las propiedades de la imagen parece está todo en orden ¡leemos bit de profundidad: 8!
¿Porqué dices que no funciona?

Pues déjame contarte que en realidad eso es un camuflaje, intermente esa imagen de mapa de bits sigue utilizando una paleta de colores basada en el tradicional RGB. Una prueba bastará para demostrarlo.

Deje (o asigne) al bitmap el formato pf8bit.
Luego modifique algunos pixeles, para unas pruebas yo he asignado valores al azar:
 


delphi
  1.   bmp := TBitmap.Create;
  2.  
  3.   //asignamos formato de bits y tamaño
  4.   bmp.PixelFormat := pf8bit;
  5.   bmp.Width := 92;
  6.   bmp.Height := 112;
  7.  
  8.   // "pintamos"
  9.   for j := 0 to bmp.Height - 1 do
  10.     begin
  11.       p := bmp.ScanLine[j];
  12.       for i := 0 to bmp.Width - 1 do
  13.         begin
  14.           b := Random(256);
  15.           p^[1] := b;
  16.           Inc(p);
  17.         end;
  18.     end;
  19.  
  20.   bmp.SaveToFile('...');
  21.   ie1.Picture.Bitmap := bmp;
  22.  
  23.   FreeAndNil(bmp);

Aquí una explicación de las variables:
bmp es de la clase TBitmap.
ie1 es un TImage, ubicado en el form.
i,j: son dos variables enteros.
b es de tipo byte.

La variable que quizá desconcierte a muchos es p:
 


delphi
  1. p: P8BitsChannel;

Ese tipo especial no es más que un puntero:
 


delphi
  1. type
  2.  
  3. T8bitsChannel = array[1..1] of Byte;
  4. P8BitsChannel = ^T8BitsChannel;

Cuando asignamos un valor a PixelFormat lo más adecuado es trabajar con SanLine ya que está preparado para aceptar el formato establecido.

Si todo sale como lo planeado dicha imagen tiene la forma de estática de cuando perdemos señal en el TV a todo color. ¡Un momento... pero si es formato a 8 bits! ¿Cómo es posible ver esa colorida imagen?

Ya le avisé: todo está en la paleta de color por defecto que está utilizando el bitmap.

La solución: debemos crear una propia y asociar los niveles de grises. Para ello nos apoyaremos en un tipo especial tagLOGPALETTE.

Defina la paleta como un record:
 


delphi
  1. type
  2.   // Mi tipo de paleta de colores
  3.   MiPaleta = record
  4.   lpal: TLogPalette;
  5.   // espacio de colores de grises de 8 bits
  6.   EspacioColor: array[0..255] of TPaletteEntry;
  7.   end;

Declare una variable:
 


delphi
  1. pal: MiPaleta;

Ahora debemos "crearla" y llenarla:
 


delphi
  1. // creamos la paleta!
  2.   pal.lpal.palVersion := $300;  // ¡constante!
  3.   pal.lpal.palNumEntries := 256; // 256 tonos de grises
  4.   for i := 0 to 255 do
  5.   begin
  6.     with pal.lpal.palPalEntry[i] do
  7.     begin
  8.       peRed := i;
  9.       peGreen := i;
  10.       peBlue := i;
  11.     end;
  12.   end;

La explicación es simple. Se hace corresponder cada valor RGB con el tono de gris.

Sólo nos resta asociar la paleta al bitmap para que cada vez que el bitmap lea un valor RGB lo pinte como queremos:
 


delphi
  1. bmp.Palette := CreatePalette(Pal.lpal);

A todo esto el código final resulta en un:
 


delphi
  1. procedure TForm1.bn2Click(Sender: TObject);
  2. type
  3.   // Mi tipo de paleta de colores
  4.   MiPaleta = record
  5.   lpal: TLogPalette;
  6.   // espacio de colores de grises de 8 bits
  7.   EspacioColor: array[0..255] of TPaletteEntry;
  8.   end;
  9.  
  10. var i,j: Integer;
  11.     bmp: TBitmap;
  12.     p: P8BitsChannel;
  13.     //paleta de colores a 8 bits
  14.     pal: MiPaleta;
  15.  
  16.     b: Byte;
  17. begin
  18.   // creamos la paleta!
  19.   pal.lpal.palVersion := $300;  // ¡constante!
  20.   pal.lpal.palNumEntries := 256; // 256 tonos de grises
  21.   for i := 0 to 255 do
  22.   begin
  23.     with pal.lpal.palPalEntry[i] do
  24.     begin
  25.       peRed := i;
  26.       peGreen := i;
  27.       peBlue := i;
  28.     end;
  29.   end;
  30.  
  31.   bmp := TBitmap.Create;
  32.  
  33.   //asignamos formato de bits y tamaño
  34.   bmp.PixelFormat := pf8bit;
  35.   bmp.Width := 92;
  36.   bmp.Height := 112;
  37.  
  38.   bmp.Palette := CreatePalette(Pal.lpal);
  39.  
  40.   // "pintamos"
  41.   for j := 0 to bmp.Height - 1 do
  42.     begin
  43.       p := bmp.ScanLine[j];
  44.       for i := 0 to bmp.Width - 1 do
  45.         begin
  46.           b := Random(256);
  47.           p^[1] := b;
  48.           Inc(p);
  49.         end;
  50.     end;
  51.  
  52.   bmp.SaveToFile('...');
  53.   ie1.Picture.Bitmap := bmp;
  54.  
  55.   FreeAndNil(bmp);
  56. end;

Ahora si tenemos una imagen a todas luces en formato 8bits y escala de grises. Lo lindo de esta técnica es que es muy rápida y tenemos bmps mucho más livianos. Muchas veces en el tratamiento de imagen se utilizan imágenes en escala de grises, nos podemos ahorrar mucho al trabajar con un único canal.

Esto también es válido para repintar una imagen de forma directa, cree la paleta como en el ejemplo y vinculada al bmp. Luego directamente pinte el original sobre el bitmap temporal. Por ejemplo suponga que la imagen original está en un TImage1:
 


delphi
  1. bmp.Canvas.Draw(0,0,Image1.Picture.Graphic);

Espero que le sea de utilidad.

Saludos,


  • 0

#2 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14.448 mensajes
  • LocationMéxico

Escrito 21 febrero 2011 - 04:58

Muy interesante amigo Marcelo, gracias (y)

Salud OS
  • 0

#3 felipe

felipe

    Advanced Member

  • Administrador
  • 3.283 mensajes
  • LocationColombia

Escrito 21 febrero 2011 - 05:03

Gracias por el aporte, se nota que lo estudiaste bastante (y)



[off-topic]En estos casos es más rápido Photoshop :D :D[/off-topic]



Saludos!
  • 0

#4 Wilson

Wilson

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.137 mensajes

Escrito 21 febrero 2011 - 05:31

Gracias Delphius, buen aporte. (y)
  • 0

#5 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 21 febrero 2011 - 05:35

Gracias a ustedes amigos.

Es que justo necesitaba explorar el tema de la conversión real a 8 bits y con escala (con delphi).

Lo bueno de asignar una paleta al bitmap es que podemos hacer conversiones directas, no hay que explorar pixels, etc. Con sólo hacer un Draw y la paleta apropiada podemos hacer muchas creaciones.

Prueben alterando el color de peRed, peGreen, y peBlue de otras formas y vean sus resultados. Por ejemplo esta:



delphi
  1. peBlue := i;
  2. peRed := Round((255 - i) * 0.1);
  3. peGreen := Round((255 - i) * 0.2);



Eso debería llevar todo al azul (tendencia al oscuro) y negro 

Saludos,
  • 0

#6 felipe

felipe

    Advanced Member

  • Administrador
  • 3.283 mensajes
  • LocationColombia

Escrito 22 febrero 2011 - 10:26

Una de las cosas importantes de este truco es el manejo de recursos y bytes al hacer este cambio.


Saludos!
  • 0

#7 sergiocapillera2013

sergiocapillera2013

    Newbie

  • Miembros
  • Pip
  • 5 mensajes

Escrito 29 abril 2013 - 05:05

Marcelo, recien estoy familiarizandome con delphi y me encantaria llevar a cabo mi idea de proyecto este código lo coloco una vez que en el form una imagen??? , me salen errores como ; unit1.pas(31,5) Error: Illegal expression y  unit1.pas(31,15) Fatal: Syntax error, ";" expected but "identifier TFORM1" found


  • 0

#8 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 29 abril 2013 - 08:01

Hola sergiocapillera2013,
¡Bienvenido a Delphiaccess! Espero que esta comunidad sea un segundo hogar para ti.

No es posible saber cual es el verdadero error que tienes, hace falta ver tu código para eso.
Te invito a que inicies un hilo propio para tratar tus dudas al respecto.
Este foro, Trucos y Consejos, es una sección en donde uno puede ir colocando aportes personales que considera útiles compartir en sus momentos de descubrimientos e investigaciones.

Como tu duda hacen a algo puntual, considero oportuno que se le dedique un hilo propio. En todo caso en tus post haz referencia a éste.

Saludos,
  • 0

#9 ELKurgan

ELKurgan

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 566 mensajes
  • LocationEspaña

Escrito 30 abril 2013 - 03:02

Muchas gracias por el aporte, maestro

(y) (y)
  • 0

#10 poliburro

poliburro

    Advanced Member

  • Administrador
  • 4.945 mensajes
  • LocationMéxico

Escrito 30 abril 2013 - 08:01

Gracias por compartirnos este excelente truco. Un abrazo amigo
  • 0

#11 sergiocapillera2013

sergiocapillera2013

    Newbie

  • Miembros
  • Pip
  • 5 mensajes

Escrito 06 mayo 2013 - 07:06

Marcelo tengo éste código y no me funciona quizas coloco las lineas mal ??? en form1 coloco una imagen junto a un boton y deberia pasarla escala gris y no pasa nada , podr{ias darme una orientación , lo que quiero lograr es hacer el promedio RGB y pasarla a escala de gris ,acordate que estoy empezando podrías orientarme paso a paso?? saludos.
begin
BMP1 := TBitmap.Create;
  BMP1.LoadFromFile('/home/mariposa.bmp'); //Imagen de 8 bits
  BMP2 := TBitmap.Create;
  BMP2.PixelFormat := pf1bit;
  BMP2.Height := BMP1.Height;
  BMP2.Width := BMP1.Width;
  for X := 0 to BMP1.Width - 1 do
    for Y := 0 to BMP1.Height - 1 do
      BMP2.Canvas.Pixels[X, Y] := BMP1.Canvas.Pixels[X, Y];
  BMP1.Free;

end;                     

supuestamente explora toda la imagen esta en formato BMP en  home y no pasa nada podrás probar éste codigo si realmente funciona, saludos, espero tu respuesta a la brevedad.
  • 0

#12 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 06 mayo 2013 - 07:27

Cito y repito:

Como tu duda hacen a algo puntual, considero oportuno que se le dedique un hilo propio. En todo caso en tus post haz referencia a éste.


Por favor inicia un hilo propio para tratar tus dudas puntuales y explica detalladamente el problema. Partamos de tres cosas:
1. ¿Te aseguraste de que efectivamente la imagen leída sea de escala de grises a 8bits? Porque esto me lleva al punto 2.
2. En tu código se aprecia que asignas el valor pf1bit.
3. Si resulta ser que el BMP2 muestra una imagen a todo color es resultado a como he expresado en este "truco" que el problema está en la paleta de colores por defecto que emplea la clase TBitmap.

No voy a responder tus dudas aquí. Incisto en que abras tu propio hilo en el foro apropiado. Respeta las normas y sugerencias, porque tal parece que no has tenido en cuenta mis palabras anteriores.

Saludos,
  • 0




IP.Board spam blocked by CleanTalk.