Consejo 60 de Java: Guardar archivos de mapa de bits en Java

Este consejo complementa el Consejo 43 de Java, que demostró el proceso de carga de archivos de mapa de bits en aplicaciones Java. Este mes, sigo con un tutorial sobre cómo guardar imágenes en archivos de mapa de bits de 24 bits y un fragmento de código que puede usar para escribir un archivo de mapa de bits desde un objeto de imagen.

La capacidad de crear un archivo de mapa de bits abre muchas puertas si está trabajando en un entorno de Microsoft Windows. En mi último proyecto, por ejemplo, tuve que conectar Java con Microsoft Access. El programa Java permitió al usuario dibujar un mapa en la pantalla. Luego, el mapa se imprimió en un informe de Microsoft Access. Debido a que Java no es compatible con OLE, mi única solución fue crear un archivo de mapa de bits del mapa e indicarle al informe de Microsoft Access dónde recogerlo. Si alguna vez ha tenido que escribir una aplicación para enviar una imagen al portapapeles, este consejo puede serle útil, especialmente si esta información se pasa a otra aplicación de Windows.

El formato de un archivo de mapa de bits

El formato de archivo de mapa de bits admite RLE de 4 bits (codificación de longitud de ejecución), así como codificación de 8 y 24 bits. Debido a que solo estamos tratando con el formato de 24 bits, echemos un vistazo a la estructura del archivo.

El archivo de mapa de bits se divide en tres secciones. Te las he puesto a continuación.

Sección 1: encabezado del archivo de mapa de bits

Este encabezado contiene información sobre el tamaño del tipo y el diseño del archivo de mapa de bits. La estructura es la siguiente (tomada de una definición de estructura de lenguaje C):

typedef struct tagBITMAPFILEHEADER {UINT bfType; DWORD bfSize; UINT bfReserved1; UINT bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER;

Aquí hay una descripción de los elementos de código de la lista anterior:

  • bfType: Indica el tipo de archivo y siempre se establece en BM.
  • bfSize: Especifica el tamaño de todo el archivo en bytes.
  • bfReserved1: Reservado: debe establecerse en 0.
  • bfReserved2: Reservado: debe establecerse en 0.
  • bfOffBits: Especifica el desplazamiento de bytes desde BitmapFileHeaderel inicio de la imagen.

Aquí ha visto que el propósito del encabezado del mapa de bits es identificar el archivo de mapa de bits. Cada programa que lee archivos de mapa de bits utiliza el encabezado del mapa de bits para la validación del archivo.

Sección 2: Encabezado de información de mapa de bits

El siguiente encabezado, llamado encabezado de información, contiene todas las propiedades de la imagen en sí.

A continuación, se explica cómo se especifica la información sobre la dimensión y el formato de color de un mapa de bits independiente del dispositivo (DIB) de Windows 3.0 (o superior):

typedef struct tagBITMAPINFOHEADER {DWORD biSize; LARGO biAncho; LONG biHeight; PALABRA biPlanos; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER;

Cada elemento de la lista de códigos anterior se describe a continuación:

  • biSize: Especifica el número de bytes requeridos por la BITMAPINFOHEADERestructura.
  • biWidth: Especifica el ancho del mapa de bits en píxeles.
  • biHeight: Especifica la altura del mapa de bits en píxeles.
  • biPlanes: Especifica el número de planos para el dispositivo de destino. Este miembro debe establecerse en 1.
  • biBitCount: Especifica el número de bits por píxel. Este valor debe ser 1, 4, 8 o 24.
  • biCompression: Especifica el tipo de compresión de un mapa de bits comprimido. En un formato de 24 bits, la variable se establece en 0.
  • biSizeImage: especifica el tamaño en bytes de la imagen. Es válido establecer este miembro en 0 si el mapa de bits tiene el BI_RGBformato.
  • biXPelsPerMeter: especifica la resolución horizontal, en píxeles por metro, del dispositivo de destino para el mapa de bits. Una aplicación puede utilizar este valor para seleccionar un mapa de bits de un grupo de recursos que mejor se adapte a las características del dispositivo actual.
  • biYPelsPerMeter: especifica la resolución vertical, en píxeles por metro, del dispositivo de destino para el mapa de bits.
  • biClrUsed: especifica el número de índices de color en la tabla de colores que realmente utiliza el mapa de bits. Si biBitCountse establece en 24, biClrUsedespecifica el tamaño de la tabla de colores de referencia que se utiliza para optimizar el rendimiento de las paletas de colores de Windows.
  • biClrImportant: especifica el número de índices de color que se consideran importantes para mostrar el mapa de bits. Si este valor es 0, todos los colores son importantes.

Ahora se ha definido toda la información necesaria para crear la imagen.

Sección 3: Imagen

En el formato de 24 bits, cada píxel de la imagen está representado por una serie de tres bytes de RGB almacenados como BRG. Cada línea de exploración se rellena hasta un límite uniforme de 4 bytes. Para complicar un poco más el proceso, la imagen se almacena de abajo hacia arriba, lo que significa que la primera línea de escaneo es la última línea de escaneo de la imagen. La siguiente figura muestra los encabezados ( BITMAPHEADER) y ( BITMAPINFOHEADER) y parte de la imagen. Cada sección está delimitada por una barra vertical:

0000000000 4D42 B536 0002 0000 0000 0036 0000 | 0028 0000000020 0000 0107 0000 00E0 0000 0001 0018 0000 0000000040 0000 B500 0002 0EC4 0000 0EC4 0000 0000 0000000060 0000 0000 0000 | FFFF FFFF FFFF FFFF FFFF 0000000100 FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF *

Ahora, pasemos al código

Ahora que sabemos todo sobre la estructura de un archivo de mapa de bits de 24 bits, esto es lo que estaba esperando: el código para escribir un archivo de mapa de bits a partir de un objeto de imagen.

importar java.awt. *; importar java.io. *; importar java.awt.image. *; public class BMPFile extends Component {// --- Constantes privadas private final static int BITMAPFILEHEADER_SIZE = 14; privado final estático int BITMAPINFOHEADER_SIZE = 40; // --- Declaración de variable privada // --- Encabezado del archivo de mapa de bits bitmapFileHeader [] = nuevo byte [14]; byte privado bfType [] = {'B', 'M'}; privado int bfSize = 0; private int bfReserved1 = 0; private int bfReserved2 = 0; privado int bfOffBits = BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE; // --- encabezado de información del mapa de bits byte privado bitmapInfoHeader [] = new byte [40]; privado int biSize = BITMAPINFOHEADER_SIZE; privado int biWidth = 0; privado int biHeight = 0; privados int biPlanes = 1; privado int biBitCount = 24; private int biCompression = 0; private int biSizeImage = 0x030000;privado int biXPelsPerMeter = 0x0; privado int biYPelsPerMeter = 0x0; private int biClrUsed = 0; private int biClrImportant = 0; // --- mapa de bits de datos brutos private int bitmap []; // --- sección de archivo privada FileOutputStream fo; // --- Constructor predeterminado public BMPFile () {} public void saveBitmap (String parFilename, Image parImage, int parWidth, int parHeight) {try {fo = new FileOutputStream (parFilename); guardar (parImage, parWidth, parHeight); fo.close (); } captura (excepción saveEx) {saveEx.printStackTrace (); }} / * * SaveMethod es el método principal del proceso. Este método * llamará al método convertImage para convertir la imagen de la memoria en * una matriz de bytes; el método writeBitmapFileHeader crea y escribe * el encabezado del archivo de mapa de bits; writeBitmapInfoHeader crea el * encabezado de información; y writeBitmap escribe la imagen.* * / guardar vacío privado (imagen parImage, int parWidth, int parHeight) {try {convertImage (parImage, parWidth, parHeight); writeBitmapFileHeader (); writeBitmapInfoHeader (); writeBitmap (); } captura (excepción saveEx) {saveEx.printStackTrace (); }} / * * convertImage convierte la imagen de la memoria al formato de mapa de bits (BRG). * También calcula cierta información para el encabezado de información del mapa de bits. * * / convertImage booleana privada (imagen parImage, int parWidth, int parHeight) {int pad; mapa de bits = nuevo int [parWidth * parHeight]; PixelGrabber pg = new PixelGrabber (parImage, 0, 0, parWidth, parHeight, bitmap, 0, parWidth); intente {pg.grabPixels (); } captura (InterruptedException e) {e.printStackTrace (); falso retorno); } pad = (4 - ((parWidth * 3)% 4)) * parHeight; biSizeImage = ((parWidth * parHeight) * 3) + pad;bfSize = biSizeImage + BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE; biWidth = parWidth; biHeight = parHeight; return (verdadero); } / * * writeBitmap convierte la imagen devuelta por el capturador de píxeles al * formato requerido. Recuerde: las líneas de escaneo están invertidas * en un archivo de mapa de bits. * * Cada línea de exploración debe rellenarse hasta un límite uniforme de 4 bytes. * / private void writeBitmap () {int tamaño; valor int; int j; int i; int rowCount; int rowIndex; int lastRowIndex; int pad; int padCount; byte rgb [] = nuevo byte [3]; tamaño = (biWidth * biHeight) - 1; pad = 4 - ((biWidth * 3)% 4); if (pad == 4) // <==== corrección de errores pad = 0; // <==== Corrección de errores rowCount = 1; padCount = 0; rowIndex = tamaño - biWidth; lastRowIndex = rowIndex; intente {para (j = 0; j> 8) & 0xFF); rgb [2] = (byte) ((valor >> 16) & 0xFF); fo.write (rgb);if (rowCount == biWidth) {padCount + = pad; para (i = 1; i> 8) & 0x00FF); return (retValue); } / * * * intToDWord convierte un int en una palabra doble, donde el valor * devuelto se almacena en una matriz de 4 bytes. * * / byte privado [] intToDWord (int parValue) {byte retValue [] = nuevo byte [4]; retValue [0] = (byte) (parValue & 0x00FF); retValue [1] = (byte) ((parValue >> 8) & 0x000000FF); retValue [2] = (byte) ((parValue >> 16) & 0x000000FF); retValue [3] = (byte) ((parValue >> 24) & 0x000000FF); return (retValue); }}0x00FF); retValue [1] = (byte) ((parValue >> 8) & 0x000000FF); retValue [2] = (byte) ((parValue >> 16) & 0x000000FF); retValue [3] = (byte) ((parValue >> 24) & 0x000000FF); return (retValue); }}0x00FF); retValue [1] = (byte) ((parValue >> 8) & 0x000000FF); retValue [2] = (byte) ((parValue >> 16) & 0x000000FF); retValue [3] = (byte) ((parValue >> 24) & 0x000000FF); return (retValue); }}

Conclusión

Eso es todo al respecto. Estoy seguro de que encontrará esta clase muy útil, ya que, a partir de JDK 1.1.6, Java no admite guardar imágenes en ninguno de los formatos populares. JDK 1.2 ofrecerá soporte para crear imágenes JPEG, pero no soporte para mapas de bits. Entonces, esta clase aún llenará un vacío en JDK 1.2.

Si juegas con esta clase y encuentras formas de mejorarla, ¡házmelo saber! Mi correo electrónico aparece a continuación, junto con mi biografía.

Jean-Pierre Dubé es un consultor independiente de Java. Fundó Infocom, registrada en 1988. Desde entonces, Infocom ha desarrollado varias aplicaciones personalizadas que van desde la fabricación, la gestión de documentos y la gestión de líneas eléctricas a gran escala. Tiene una amplia experiencia en programación en C, Visual Basic y, más recientemente, en Java, que ahora es el lenguaje principal utilizado por su empresa. Uno de los proyectos recientes de Infocom es una API de diagrama que pronto estará disponible como versión beta.

Esta historia, "Java Tip 60: Guardar archivos de mapa de bits en Java" fue publicada originalmente por JavaWorld.