Ir al contenido


Foto

Formatos BMP indexados en Lazarus y BitmapToGrayscale


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

#1 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 29 agosto 2016 - 05:19

Como ya sabréis, Lazarus no soporta formatos de Bitmap indexados, solo soporta color verdadero. Esto implica que no sirve de nada usar PixelFormat para cambiar el formato de un TBitmap, como en delphi.
 
Como curiosidad y ejercicio he hecho una función que devuelve en un TMemoryStream el formato de un Bitmap tipo DIB, tal como se escribe en un archivo. Cuando Lazarus carga un Bitmap indexado desde un archivo o un TStream, respeta su formato hasta que trabaja con él, momento en el que lo convierte a color real prescindiendo de la paleta y escribiendo en el buffer de la imagen los tripletes RGB.
 
Cuando queremos reducir una imagen a blanco y negro (escala de 256 grises) siguiendo el código publicado en el tema "Convertir una imagen de Color a Escala de Grises" nos llevamos la desagradable sorpresa de que no podemos pasarlo a una imagen indexada usando TBitmap. Si nos vamos a bajo nivel, podemos hacerlo pero el Handle del bitmap creado no lo podremos asignar a un TBitmap Lazarus, aunque si hacerle cargarse desde un TStream.
 
Este truco presenta dos funciones, la primera es como crear un TStream desde un Bitmap y una Paleta, la función que os presento es válida en Lazarus y delphi:
 


delphi
  1. function BitmapToStream(Bitmap: HBITMAP; Palette: HPALETTE): TMemoryStream;
  2. var
  3. bmp: BITMAP;
  4. bmpFileHeader: PBITMAPFILEHEADER;
  5. bmiHeader: PBITMAPINFOHEADER;
  6. bmiColors: PRGBQUAD;
  7. nColors: WORD;
  8. SizeImage: DWORD;
  9. begin
  10. // Obteniendo datos del Bitmap
  11. GetObject(Bitmap, sizeof(bmp), @bmp);
  12. GetObject(Palette, 2, @nColors);
  13. SizeImage:= bmp.bmWidthBytes * bmp.bmHeight;
  14.  
  15. // Preparando Memoria TMemoryStream y punteros
  16. Result:= TMemoryStream.Create;
  17. Result.Size:= sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + nColors * sizeof(RGBQUAD) + SizeImage;
  18. bmpFileHeader:= Result.Memory;
  19. bmiHeader:= Result.Memory; inc(PBYTE(bmiHeader), sizeof(BITMAPFILEHEADER));
  20. bmiColors:= PRGBQUAD(bmiHeader); inc(PBYTE(bmiColors), sizeof(BITMAPINFOHEADER));
  21.  
  22. // Escribiendo FileHeader (BITMAPFILEHEADER)
  23. bmpFileHeader^.bfType:= PWORD(PCHAR('BM'))^;
  24. bmpFileHeader^.bfSize:= sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + nColors * sizeof(RGBQUAD) + SizeImage;
  25. bmpFileHeader^.bfReserved1:= 0;
  26. bmpFileHeader^.bfReserved2:= 0;
  27. bmpFileHeader^.bfOffBits:= sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + nColors * sizeof(RGBQUAD);
  28.  
  29. // Escribiendo InfoHeader (BITMAPINFOHEADER)
  30. bmiHeader^.biSize:= sizeof(BITMAPINFOHEADER);
  31. bmiHeader^.biWidth:= bmp.bmWidth;
  32. bmiHeader^.biHeight:= bmp.bmHeight;
  33. bmiHeader^.biPlanes:= bmp.bmPlanes;
  34. bmiHeader^.biBitCount:= bmp.bmBitsPixel;
  35. bmiHeader^.biCompression:= BI_RGB;
  36. bmiHeader^.biSizeImage:= SizeImage;
  37. bmiHeader^.biXPelsPerMeter:= 0;
  38. bmiHeader^.biYPelsPerMeter:= 0;
  39. bmiHeader^.biClrUsed:= nColors;
  40. bmiHeader^.biClrImportant:= 0;
  41.  
  42. // Escribiendo la paleta: bmiColors (RGBQUAD)
  43. GetPaletteEntries(Palette, 0, nColors, bmiColors^);
  44.  
  45. // Escribiendo la imagen de bits
  46. Result.Position:= bmpFileHeader^.bfOffBits;
  47. Result.Write(bmp.bmBits^, bmiHeader^.biSizeImage);
  48. Result.Position:= 0;
  49. end;

Ahora podemos reescribir la función BitmapToGrayscale usando el bajo nivel de la GDI y obligar a que devuelva un TMemoryStream compatible con los TBitmap de Lazarus. Esta función también funciona en delphi


delphi
  1. // Creando de forma directa en una sola pasada.
  2. function BitmapToGrayscale(Bitmap: TBitmap): TMemoryStream;
  3. var
  4. TempBitmap: HANDLE;
  5. bmp: BITMAP;
  6. X,Y,i: Integer;
  7. Gray: Byte;
  8. Color: DWORD;
  9. bmpInfo: BITMAPINFO;
  10. TempBits, D: PBYTE;
  11. Pixel: PRGBTRIPLE;
  12. LPalette: PLOGPALETTE;
  13. Palette: HPALETTE;
  14. DC: HDC;
  15. begin
  16.  
  17. // Creando una paleta de grises
  18. GetMem(LPalette, sizeof(LOGPALETTE) + 255*sizeof(DWORD));
  19. LPalette^.palVersion:= $300;
  20. LPalette^.palNumEntries:= 256;
  21. for i:= 0 to 255 do
  22. begin
  23. Color:= $FF000000 or i or (i shl 8) or (i shl 16);
  24. LPalette^.palPalEntry[i]:= PPALETTEENTRY(@Color)^;
  25. end;
  26.  
  27. // Creando un bitmap temporal de 8 bits
  28. bmpInfo.bmiHeader.biSize:= sizeof(BITMAPINFOHEADER);
  29. bmpInfo.bmiHeader.biWidth:= Bitmap.Width;
  30. bmpInfo.bmiHeader.biHeight:= Bitmap.Height;
  31. bmpInfo.bmiHeader.biPlanes:= 1;
  32. bmpInfo.bmiHeader.biBitCount:= 8;
  33. bmpInfo.bmiHeader.biCompression:= BI_RGB;
  34. bmpInfo.bmiHeader.biSizeImage:= (((bmpInfo.bmiHeader.biWidth * (bmpInfo.bmiHeader.biBitCount shr 3)) + 3) and $FFFFFFFC) * bmpInfo.bmiHeader.biHeight;
  35.  
  36. GetMem(TempBits, bmpInfo.bmiHeader.biSizeImage);
  37. TempBitmap:= CreateDIBSection(0, bmpInfo, DIB_RGB_COLORS, TempBits, 0, 0);
  38. GetObject(TempBitmap, sizeof(bmp), @bmp);
  39.  
  40. // Pasando la imagen a escala de grises
  41. for Y := 0 to Bitmap.Height - 1 do
  42. begin
  43. Pixel := Bitmap.ScanLine[Y];
  44. D:= TempBits; inc(D, bmp.bmWidthBytes * (Bitmap.Height-1-Y));
  45. for X := 0 to Bitmap.Width - 1 do
  46. begin
  47. Gray := Round((0.299 * Pixel^.rgbtRed) + (0.587 * Pixel^.rgbtBlue) + (0.114 * Pixel^.rgbtGreen));
  48. for i:= 0 to 255 do
  49. if Gray = LPalette^.palPalEntry[i].peRed then break;
  50. D^:= i;
  51. Inc(Pixel); inc(D);
  52. end;
  53. end;
  54.  
  55. Result:= BitmapToStream(TempBitmap, CreatePalette(LPalette^));
  56.  
  57. // Limpiando
  58. DeleteObject(TempBitmap);
  59. FreeMem(LPalette);
  60. end;

Un ejemplo de uso puede ser el siguiente:


delphi
  1. var
  2. Stream: TStream;
  3. begin
  4. Stream:= BitmapToGrayscale(Image2.Picture.Bitmap);
  5. Image1.Picture.Bitmap.LoadFromStream(Stream);
  6.  
  7. // Ahora si se guarda una imagen con formato indexado de 8 bits
  8. Image1.Picture.Bitmap.SaveToFile('prueba.bmp');
  9.  
  10. Stream.free;
  11. end;

Como BitmapToGrayscale devuelve un TMemoryStream, se puede guardar en un archivo directamente o usarlo para cargar un Bitmap tranquilamente, eso sí, habrá que liberarlo cuando no nos haga más falta.

 

Espero que sea de utilidad.

 

 

 

Saludos.


  • 2

#2 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 29 agosto 2016 - 07:30

Asi es amigo, en Lazarus no existe por defecto el bmp basado en paletas o indexados.

No estoy seguro pero creo que se debe a una cuestión de multiplataforma.

 

No me puse a revisar como es que logra hacer este trabajo la suite BGRABitmap. Esta es 100% multiplataforma y ofrece de base muchos filtros y variados métodos de tratamiento de imagenes. Y si no me falla la memoria, hasta da soporte a OpenGL. Es la suite más recomendada en los foros de Lazarus.

 

Esta suite es algo diferente en algunos aspectos a como lo conocemos el tBitmap tradicional. Por empezar siempre trabaja en formato BGRA, independientemente si uno abre una imagen BMP, o un PNG. Y esto hace que cualquier intento de establecer el formato de bits sea inútil, como tu comentas.

 

Saludos,


  • 0

#3 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 29 agosto 2016 - 07:55

Es curioso que Lazarus no soporte BMP indexado con paletas cuando no es el único formato que lo usa, sin ir muy lejos Gif es multiplataforma y se en paleta de colores y un mapa comprimido.

 

 

Saludos.


  • 0




IP.Board spam blocked by CleanTalk.