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:
Bitmap.PixelFormat := pf8bit; 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:
bmp := TBitmap.Create; //asignamos formato de bits y tamaño bmp.PixelFormat := pf8bit; bmp.Width := 92; bmp.Height := 112; // "pintamos" for j := 0 to bmp.Height - 1 do begin p := bmp.ScanLine[j]; for i := 0 to bmp.Width - 1 do begin b := Random(256); p^[1] := b; Inc(p); end; end; bmp.SaveToFile('...'); ie1.Picture.Bitmap := bmp; 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:
p: P8BitsChannel;
Ese tipo especial no es más que un puntero:
type T8bitsChannel = array[1..1] of Byte; 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:
type // Mi tipo de paleta de colores MiPaleta = record lpal: TLogPalette; // espacio de colores de grises de 8 bits EspacioColor: array[0..255] of TPaletteEntry; end;
Declare una variable:
pal: MiPaleta;
Ahora debemos "crearla" y llenarla:
// creamos la paleta! pal.lpal.palVersion := $300; // ¡constante! pal.lpal.palNumEntries := 256; // 256 tonos de grises for i := 0 to 255 do begin with pal.lpal.palPalEntry[i] do begin peRed := i; peGreen := i; peBlue := i; end; 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:
bmp.Palette := CreatePalette(Pal.lpal);
A todo esto el código final resulta en un:
procedure TForm1.bn2Click(Sender: TObject); type // Mi tipo de paleta de colores MiPaleta = record lpal: TLogPalette; // espacio de colores de grises de 8 bits EspacioColor: array[0..255] of TPaletteEntry; end; var i,j: Integer; bmp: TBitmap; p: P8BitsChannel; //paleta de colores a 8 bits pal: MiPaleta; b: Byte; begin // creamos la paleta! pal.lpal.palVersion := $300; // ¡constante! pal.lpal.palNumEntries := 256; // 256 tonos de grises for i := 0 to 255 do begin with pal.lpal.palPalEntry[i] do begin peRed := i; peGreen := i; peBlue := i; end; end; bmp := TBitmap.Create; //asignamos formato de bits y tamaño bmp.PixelFormat := pf8bit; bmp.Width := 92; bmp.Height := 112; bmp.Palette := CreatePalette(Pal.lpal); // "pintamos" for j := 0 to bmp.Height - 1 do begin p := bmp.ScanLine[j]; for i := 0 to bmp.Width - 1 do begin b := Random(256); p^[1] := b; Inc(p); end; end; bmp.SaveToFile('...'); ie1.Picture.Bitmap := bmp; FreeAndNil(bmp); 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:
bmp.Canvas.Draw(0,0,Image1.Picture.Graphic);
Espero que le sea de utilidad.
Saludos,