Java-Tipp 60: Speichern von Bitmap-Dateien in Java

Dieser Tipp ergänzt Java-Tipp 43, in dem das Laden von Bitmap-Dateien in Java-Anwendungen demonstriert wurde. Diesen Monat habe ich ein Tutorial zum Speichern von Bildern in 24-Bit-Bitmap-Dateien und einen Code-Snip, mit dem Sie eine Bitmap-Datei aus einem Bildobjekt schreiben können.

Die Möglichkeit, eine Bitmap-Datei zu erstellen, öffnet viele Türen, wenn Sie in einer Microsoft Windows-Umgebung arbeiten. Bei meinem letzten Projekt musste ich beispielsweise Java mit Microsoft Access verbinden. Mit dem Java-Programm konnte der Benutzer eine Karte auf dem Bildschirm zeichnen. Die Karte wurde dann in einem Microsoft Access-Bericht gedruckt. Da Java OLE nicht unterstützt, bestand meine einzige Lösung darin, eine Bitmap-Datei der Karte zu erstellen und dem Microsoft Access-Bericht mitzuteilen, wo sie abgerufen werden soll. Wenn Sie jemals eine Anwendung schreiben mussten, um ein Bild in die Zwischenablage zu senden, kann dieser Tipp für Sie von Nutzen sein - insbesondere, wenn diese Informationen an eine andere Windows-Anwendung übergeben werden.

Das Format einer Bitmap-Datei

Das Bitmap-Dateiformat unterstützt 4-Bit-RLE (Run Length Coding) sowie 8-Bit- und 24-Bit-Codierung. Da es sich nur um das 24-Bit-Format handelt, werfen wir einen Blick auf die Struktur der Datei.

Die Bitmap-Datei ist in drei Abschnitte unterteilt. Ich habe sie unten für Sie ausgelegt.

Abschnitt 1: Bitmap-Dateikopf

Dieser Header enthält Informationen zur Typgröße und zum Layout der Bitmap-Datei. Die Struktur ist wie folgt (entnommen aus einer C-Sprachstrukturdefinition):

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

Hier ist eine Beschreibung der Codeelemente aus der obigen Liste:

  • bfType: Gibt den Dateityp an und ist immer auf BM eingestellt.
  • bfSize: Gibt die Größe der gesamten Datei in Bytes an.
  • bfReserved1: Reserviert - muss auf 0 gesetzt sein.
  • bfReserved2: Reserviert - muss auf 0 gesetzt sein.
  • bfOffBits: Gibt den Byte-Versatz vom BitmapFileHeaderbis zum Bildanfang an.

Hier haben Sie gesehen, dass der Zweck des Bitmap-Headers darin besteht, die Bitmap-Datei zu identifizieren. Jedes Programm, das Bitmap-Dateien liest, verwendet den Bitmap-Header zur Dateiüberprüfung.

Abschnitt 2: Bitmap-Informationsheader

Der nächste Header, der als Informationsheader bezeichnet wird, enthält alle Eigenschaften des Bildes.

So geben Sie Informationen zur Abmessung und zum Farbformat einer geräteunabhängigen Windows 3.0-Bitmap (DIB) an:

typedef struct tagBITMAPINFOHEADER {DWORD biSize; LANGE Breite; LANGE biHeight; WORD BiPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER;

Jedes Element der obigen Codeliste wird unten beschrieben:

  • biSize: Gibt die Anzahl der von der BITMAPINFOHEADERStruktur benötigten Bytes an .
  • biWidth: Gibt die Breite der Bitmap in Pixel an.
  • biHeight: Gibt die Höhe der Bitmap in Pixel an.
  • biPlanes: Gibt die Anzahl der Ebenen für das Zielgerät an. Dieses Mitglied muss auf 1 gesetzt sein.
  • biBitCount: Gibt die Anzahl der Bits pro Pixel an. Dieser Wert muss 1, 4, 8 oder 24 sein.
  • biCompression: Gibt die Art der Komprimierung für eine komprimierte Bitmap an. In einem 24-Bit-Format wird die Variable auf 0 gesetzt.
  • biSizeImage: Gibt die Größe des Bildes in Byte an. Es ist gültig, dieses Mitglied auf 0 zu setzen, wenn die Bitmap das BI_RGBFormat hat.
  • biXPelsPerMeter: Gibt die horizontale Auflösung des Zielgeräts für die Bitmap in Pixel pro Meter an. Eine Anwendung kann diesen Wert verwenden, um eine Bitmap aus einer Ressourcengruppe auszuwählen, die den Eigenschaften des aktuellen Geräts am besten entspricht.
  • biYPelsPerMeter: Gibt die vertikale Auflösung des Zielgeräts für die Bitmap in Pixel pro Meter an.
  • biClrUsed: Gibt die Anzahl der Farbindizes in der Farbtabelle an, die tatsächlich von der Bitmap verwendet werden. Wenn biBitCountauf 24 biClrUsedfestgelegt, wird die Größe der Referenzfarbtabelle angegeben, die zur Optimierung der Leistung von Windows-Farbpaletten verwendet wird.
  • biClrImportant: Gibt die Anzahl der Farbindizes an, die für die Anzeige der Bitmap als wichtig angesehen werden. Wenn dieser Wert 0 ist, sind alle Farben wichtig.

Jetzt wurden alle zum Erstellen des Bildes erforderlichen Informationen definiert.

Abschnitt 3: Bild

Im 24-Bit-Format wird jedes Pixel im Bild durch eine Reihe von drei als BRG gespeicherten RGB-Bytes dargestellt. Jede Scanlinie wird bis zu einer geraden 4-Byte-Grenze aufgefüllt. Um den Vorgang etwas zu verkomplizieren, wird das Bild von unten nach oben gespeichert, was bedeutet, dass die erste Scanlinie die letzte Scanlinie im Bild ist. Die folgende Abbildung zeigt sowohl die Überschriften ( BITMAPHEADER) und ( BITMAPINFOHEADER) als auch einen Teil des Bildes. Jeder Abschnitt wird durch einen vertikalen Balken begrenzt:

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 *

Nun zum Code

Nachdem wir nun alles über die Struktur einer 24-Bit-Bitmap-Datei wissen, haben Sie auf Folgendes gewartet: den Code zum Schreiben einer Bitmap-Datei aus einem Bildobjekt.

import java.awt. *; import java.io. *; import java.awt.image. *; öffentliche Klasse BMPFile erweitert Komponente {// --- Private Konstanten private final static int BITMAPFILEHEADER_SIZE = 14; private final static int BITMAPINFOHEADER_SIZE = 40; // --- Private Variablendeklaration // --- Bitmap-Dateikopf privates Byte bitmapFileHeader [] = neues Byte [14]; privates Byte bfType [] = {'B', 'M'}; private int bfSize = 0; private int bfReserved1 = 0; private int bfReserved2 = 0; private int bfOffBits = BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE; // --- Bitmap-Info-Header privates Byte bitmapInfoHeader [] = neues Byte [40]; private int biSize = BITMAPINFOHEADER_SIZE; private int biWidth = 0; private int biHeight = 0; private int biPlanes = 1; private int biBitCount = 24; private int biCompression = 0; private int biSizeImage = 0x030000;private int biXPelsPerMeter = 0x0; private int biYPelsPerMeter = 0x0; private int biClrUsed = 0; private int biClrImportant = 0; // --- Bitmap-Rohdaten private int bitmap []; // --- Dateibereich private FileOutputStream fo; // --- Standardkonstruktor public BMPFile () {} public void saveBitmap (Zeichenfolge parFilename, Image parImage, int parWidth, int parHeight) {try {fo = new FileOutputStream (parFilename); save (parImage, parWidth, parHeight); fo.close (); } catch (Ausnahme saveEx) {saveEx.printStackTrace (); }} / * * Die saveMethod ist die Hauptmethode des Prozesses. Diese Methode * ruft die convertImage-Methode auf, um das Speicherabbild in * ein Byte-Array zu konvertieren. Methode writeBitmapFileHeader erstellt und schreibt * den Bitmap-Dateikopf; writeBitmapInfoHeader erstellt den * Informationsheader. und writeBitmap schreibt das Bild.* * / private void save (Bild parImage, int parWidth, int parHeight) {try {convertImage (parImage, parWidth, parHeight); writeBitmapFileHeader (); writeBitmapInfoHeader (); writeBitmap (); } catch (Ausnahme saveEx) {saveEx.printStackTrace (); }} / * * convertImage konvertiert das Speicherbild in das Bitmap-Format (BRG). * Es werden auch einige Informationen für den Bitmap-Info-Header berechnet. * * / private boolean convertImage (Bild parImage, int parWidth, int parHeight) {int pad; bitmap = new int [parWidth * parHeight]; PixelGrabber pg = neuer PixelGrabber (parImage, 0, 0, parWidth, parHeight, Bitmap, 0, parWidth); try {pg.grabPixels (); } catch (InterruptedException e) {e.printStackTrace (); falsch zurückgeben); } pad = (4 - ((parWidth * 3)% 4)) * parHeight; biSizeImage = ((parWidth * parHeight) * 3) + pad;bfSize = biSizeImage + BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE; biWidth = parWidth; biHeight = parHeight; return (true); } / * * writeBitmap konvertiert das vom Pixel Grabber zurückgegebene Bild in das * erforderliche Format. Denken Sie daran: Scanlinien werden in * einer Bitmap-Datei invertiert! * * Jede Scanlinie muss bis zu einer geraden 4-Byte-Grenze aufgefüllt werden. * / private void writeBitmap () {int size; int value; int j; int i; int rowCount; int rowIndex; int lastRowIndex; int pad; int padCount; Byte rgb [] = neues Byte [3]; Größe = (biWidth * biHeight) - 1; pad = 4 - ((biWidth * 3)% 4); if (pad == 4) // <==== Fehlerkorrekturpad = 0; // <==== Fehlerkorrektur rowCount = 1; padCount = 0; rowIndex = size - biWidth; lastRowIndex = rowIndex; try {for (j = 0; j> 8) & 0xFF); rgb [2] = (Byte) ((Wert >> 16) & 0xFF); fo.write (rgb);if (rowCount == biWidth) {padCount + = pad; für (i = 1; i> 8) & 0x00FF); return (retValue); } / * * * intToDWord konvertiert ein int in ein Doppelwort, wobei der Rückgabewert * in einem 4-Byte-Array gespeichert wird. * * / privates Byte [] intToDWord (int parValue) {Byte retValue [] = neues 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); }}

Fazit

Das ist alles dazu. Ich bin sicher, dass Sie diese Klasse sehr nützlich finden werden, da Java ab JDK 1.1.6 das Speichern von Bildern in keinem der gängigen Formate unterstützt. JDK 1.2 bietet Unterstützung für das Erstellen von JPEG-Bildern, jedoch keine Unterstützung für Bitmaps. Diese Klasse wird also immer noch eine Lücke in JDK 1.2 füllen.

Wenn Sie mit dieser Klasse herumspielen und Möglichkeiten finden, sie zu verbessern, lassen Sie es mich wissen! Meine E-Mail wird unten zusammen mit meiner Biografie angezeigt.

Jean-Pierre Dubé ist ein unabhängiger Java-Berater. Er gründete Infocom, das 1988 registriert wurde. Seitdem hat Infocom verschiedene kundenspezifische Anwendungen entwickelt, die von der Herstellung über das Dokumentenmanagement bis hin zum großtechnischen Management von Stromleitungen reichen. Er verfügt über umfangreiche Programmiererfahrung in C, Visual Basic und zuletzt in Java, der heute von seinem Unternehmen verwendeten Hauptsprache. Eines der jüngsten Projekte von Infocom ist eine Diagramm-API, die bald als Beta-Version verfügbar sein soll.

Diese Geschichte "Java-Tipp 60: Speichern von Bitmap-Dateien in Java" wurde ursprünglich von JavaWorld veröffentlicht.