Java Tip 60: Zapisywanie plików bitmap w Javie

Ta wskazówka uzupełnia Java Tip 43, w której przedstawiono proces ładowania plików bitmapowych w aplikacjach Java. W tym miesiącu przedstawiam samouczek dotyczący zapisywania obrazów w 24-bitowych plikach bitmapowych oraz fragment kodu, którego można użyć do napisania pliku mapy bitowej z obiektu obrazu.

Możliwość tworzenia pliku mapy bitowej otwiera wiele drzwi, jeśli pracujesz w środowisku Microsoft Windows. Na przykład w moim ostatnim projekcie musiałem połączyć Javę z Microsoft Access. Program Java umożliwiał użytkownikowi rysowanie mapy na ekranie. Mapa została następnie wydrukowana w raporcie Microsoft Access. Ponieważ Java nie obsługuje OLE, moim jedynym rozwiązaniem było utworzenie pliku mapy bitowej mapy i poinformowanie raportu programu Microsoft Access, gdzie go pobrać. Jeśli kiedykolwiek musiałeś napisać aplikację, aby wysłać obraz do schowka, ta wskazówka może ci się przydać - szczególnie jeśli ta informacja jest przekazywana do innej aplikacji Windows.

Format pliku mapy bitowej

Format pliku mapy bitowej obsługuje 4-bitowe kodowanie RLE (kodowanie długości przebiegu), a także kodowanie 8-bitowe i 24-bitowe. Ponieważ mamy do czynienia tylko z formatem 24-bitowym, przyjrzyjmy się strukturze pliku.

Plik mapy bitowej jest podzielony na trzy sekcje. Przedstawiłem je poniżej.

Sekcja 1: Nagłówek pliku mapy bitowej

Ten nagłówek zawiera informacje o rozmiarze typu i układzie pliku mapy bitowej. Struktura jest następująca (zaczerpnięta z definicji struktury języka C):

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

Oto opis elementów kodu z powyższej listy:

  • bfType: Wskazuje typ pliku i zawsze jest ustawiony na BM.
  • bfSize: Określa rozmiar całego pliku w bajtach.
  • bfReserved1: Zarezerwowane - musi być ustawione na 0.
  • bfReserved2: Zarezerwowane - musi być ustawione na 0.
  • bfOffBits: Określa przesunięcie bajtów od początku BitmapFileHeaderdo początku obrazu.

Widziałeś tutaj, że celem nagłówka mapy bitowej jest identyfikacja pliku mapy bitowej. Każdy program odczytujący pliki bitmapowe używa nagłówka mapy bitowej do weryfikacji pliku.

Sekcja 2: nagłówek informacji bitmapy

Następny nagłówek, zwany nagłówkiem informacyjnym, zawiera wszystkie właściwości samego obrazu.

Oto sposób określania informacji o wymiarze i formacie kolorów niezależnej od urządzenia mapy bitowej (DIB) w systemie Windows 3.0 (lub nowszym):

typedef struct tagBITMAPINFOHEADER {DWORD biSize; LONG biWidth; LONG biHeight; WORD dwupłatowce; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER;

Każdy element powyższej listy kodów jest opisany poniżej:

  • biSize: Określa liczbę bajtów wymaganych przez BITMAPINFOHEADERstrukturę.
  • biWidth: Określa szerokość bitmapy w pikselach.
  • biHeight: Określa wysokość bitmapy w pikselach.
  • biPlanes: Określa liczbę płaszczyzn dla urządzenia docelowego. Ten element członkowski musi mieć wartość 1.
  • biBitCount: Określa liczbę bitów na piksel. Ta wartość musi wynosić 1, 4, 8 lub 24.
  • biCompression: Określa typ kompresji dla skompresowanej mapy bitowej. W formacie 24-bitowym zmienna jest ustawiona na 0.
  • biSizeImage: określa rozmiar obrazu w bajtach. Prawidłowe jest ustawienie tego elementu członkowskiego na 0, jeśli mapa bitowa jest w BI_RGBformacie.
  • biXPelsPerMeter: Określa rozdzielczość poziomą w pikselach na metr urządzenia docelowego dla mapy bitowej. Aplikacja może użyć tej wartości, aby wybrać mapę bitową z grupy zasobów, która najlepiej pasuje do charakterystyki bieżącego urządzenia.
  • biYPelsPerMeter: Określa rozdzielczość pionową w pikselach na metr urządzenia docelowego dla mapy bitowej.
  • biClrUsed: Określa liczbę indeksów kolorów w tabeli kolorów faktycznie używanych przez mapę bitową. Jeśli biBitCountjest ustawiona na 24, biClrUsedokreśla rozmiar tabeli kolorów odniesienia używanej do optymalizacji wydajności palet kolorów systemu Windows.
  • biClrImportant: Określa liczbę indeksów kolorów uznawanych za ważne przy wyświetlaniu mapy bitowej. Jeśli ta wartość wynosi 0, wszystkie kolory są ważne.

Teraz wszystkie informacje potrzebne do stworzenia obrazu zostały zdefiniowane.

Sekcja 3: Obraz

W formacie 24-bitowym każdy piksel obrazu jest reprezentowany przez serię trzech bajtów RGB przechowywanych jako BRG. Każda linia skanowania jest dopełniona do równej 4-bajtowej granicy. Aby nieco bardziej skomplikować proces, obraz jest przechowywany od dołu do góry, co oznacza, że ​​pierwsza linia skanowania jest ostatnią linią skanowania w obrazie. Poniższy rysunek przedstawia nagłówki ( BITMAPHEADER) i ( BITMAPINFOHEADER) oraz część obrazu. Każda sekcja jest ograniczona pionową kreską:

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 *

A teraz przejdźmy do kodu

Teraz, gdy wiemy już wszystko o strukturze 24-bitowego pliku mapy bitowej, oto, na co czekałeś: kod do napisania pliku mapy bitowej z obiektu obrazu.

import java.awt. *; import java.io. *; import java.awt.image. *; public class BMPFile extends Component {// --- Prywatne stałe private final static int BITMAPFILEHEADER_SIZE = 14; prywatne końcowe statyczne int BITMAPINFOHEADER_SIZE = 40; // --- Deklaracja zmiennej prywatnej // --- Prywatny bajt nagłówka pliku bitmapy bitmapFileHeader [] = nowy bajt [14]; bajt prywatny bfType [] = {'B', 'M'}; private int bfSize = 0; private int bfReserved1 = 0; private int bfReserved2 = 0; private int bfOffBits = BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE; // --- Prywatny bajt nagłówka bitmapy bitmapInfoHeader [] = nowy bajt [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; // --- Bitmapa surowych danych private int bitmap []; // --- Sekcja pliku prywatna FileOutputStream fo; // --- Domyślny konstruktor public BMPFile () {} public void saveBitmap (String parFilename, Image parImage, int parWidth, int parHeight) {try {fo = new FileOutputStream (parFilename); save (parImage, parWidth, parHeight); fo.close (); } catch (Exception saveEx) {saveEx.printStackTrace (); }} / * * SaveMethod jest główną metodą procesu. Ta metoda * wywoła metodę convertImage, aby przekonwertować obraz pamięci na * tablicę bajtów; metoda writeBitmapFileHeader tworzy i zapisuje * nagłówek pliku mapy bitowej; writeBitmapInfoHeader tworzy * nagłówek informacji; a writeBitmap zapisuje obraz.* * / private void save (Image parImage, int parWidth, int parHeight) {try {convertImage (parImage, parWidth, parHeight); writeBitmapFileHeader (); writeBitmapInfoHeader (); writeBitmap (); } catch (Exception saveEx) {saveEx.printStackTrace (); }} / * * convertImage konwertuje obraz pamięci do formatu mapy bitowej (BRG). * Oblicza również pewne informacje dla nagłówka informacji o bitmapie. * * / private boolean convertImage (obraz parImage, int parWidth, int parHeight) {int pad; bitmap = new int [parWidth * parHeight]; PixelGrabber pg = nowy PixelGrabber (parImage, 0, 0, parWidth, parHeight, bitmap, 0, parWidth); try {pg.grabPixels (); } catch (InterruptedException e) {e.printStackTrace (); return (false); } pad = (4 - ((parWidth * 3)% 4)) * parHeight; biSizeImage = ((parWidth * parHeight) * 3) + pad;bfSize = biSizeImage + BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE; biWidth = parWidth; biHeight = parHeight; return (prawda); } / * * writeBitmap konwertuje obraz zwrócony z chwytaka pikseli na * wymagany format. Pamiętaj: linie skanowania są odwrócone w * pliku bitmapy! * * Każda linia skanowania musi być dopełniona do równej 4-bajtowej granicy. * / private void writeBitmap () {int size; wartość int; int j; int i; int rowCount; int rowIndex; int lastRowIndex; int pad; int padCount; bajt rgb [] = nowy bajt [3]; size = (biWidth * biHeight) - 1; pad = 4 - ((biWidth * 3)% 4); if (pad == 4) // <==== Pad korekcji błędów = 0; // <==== Korekta błędów rowCount = 1; padCount = 0; rowIndex = size - biWidth; lastRowIndex = rowIndex; try {for (j = 0; j> 8) & 0xFF); rgb [2] = (bajt) ((wartość >> 16) & 0xFF); fo.write (rgb);if (rowCount == biWidth) {padCount + = pad; dla (i = 1; i> 8) & 0x00FF); return (retValue); } / * * * intToDWord konwertuje int na podwójne słowo, gdzie zwracana * wartość jest przechowywana w 4-bajtowej tablicy. * * / private byte [] intToDWord (int parValue) {byte retValue [] = nowy bajt [4]; retValue [0] = (bajt) (parValue & 0x00FF); retValue [1] = (bajt) ((parValue >> 8) & 0x000000FF); retValue [2] = (bajt) ((parValue >> 16) & 0x000000FF); retValue [3] = (byte) ((parValue >> 24) & 0x000000FF); return (retValue); }}0x00FF); retValue [1] = (bajt) ((parValue >> 8) & 0x000000FF); retValue [2] = (bajt) ((parValue >> 16) & 0x000000FF); retValue [3] = (byte) ((parValue >> 24) & 0x000000FF); return (retValue); }}0x00FF); retValue [1] = (bajt) ((parValue >> 8) & 0x000000FF); retValue [2] = (bajt) ((parValue >> 16) & 0x000000FF); retValue [3] = (byte) ((parValue >> 24) & 0x000000FF); return (retValue); }}

Wniosek

To wszystko. Jestem pewien, że ta klasa okaże się bardzo przydatna, ponieważ od JDK 1.1.6 Java nie obsługuje zapisywania obrazów w żadnym z popularnych formatów. JDK 1.2 będzie obsługiwać tworzenie obrazów JPEG, ale nie obsługuje map bitowych. Więc ta klasa nadal będzie wypełniać lukę w JDK 1.2.

Jeśli bawisz się tą klasą i znajdziesz sposoby na jej ulepszenie, daj mi znać! Mój e-mail pojawia się poniżej, wraz z moją biografią.

Jean-Pierre Dubé jest niezależnym konsultantem Java. Założył firmę Infocom, zarejestrowaną w 1988 roku. Od tego czasu Infocom opracował kilka niestandardowych aplikacji, takich jak produkcja, zarządzanie dokumentami i zarządzanie liniami elektrycznymi na dużą skalę. Ma bogate doświadczenie w programowaniu w C, Visual Basic, a ostatnio w Javie, która jest obecnie podstawowym językiem używanym przez jego firmę. Jednym z ostatnich projektów Infocom jest interfejs API diagramów, który wkrótce powinien zostać udostępniony jako wersja beta.

Artykuł „Java Tip 60: zapisywanie plików bitmap w Javie” został pierwotnie opublikowany przez JavaWorld.