摘要
雖然 Java 提供了幾種打開(kāi)圖像的機(jī)制,但保存圖像并不是它的強(qiáng)項(xiàng)。這篇技巧將講述如何將圖像保存在 24 位位圖文件中。另外,Jean-Pierre 還提供了將圖像文件寫(xiě)入位圖文件所需的全部代碼。
這篇技巧是 "在 Java 應(yīng)用程序中加載位圖文件的逐步指南" 的補(bǔ)充,那篇技巧說(shuō)明了在 Java 應(yīng)用程序中加載位圖文件的過(guò)程。本月我再提供一篇教程,說(shuō)明如何將圖像保存在 24 位位圖文件中,其中還包含將圖像對(duì)象寫(xiě)入位圖文件的代碼片斷。
如果您在 Microsoft Windows 環(huán)境中工作,那么創(chuàng)建位圖文件的功能將為您提供許多方便。例如,在我的上一個(gè)項(xiàng)目中,我必須將 Java 與 Microsoft Access 對(duì)接。Java 程序允許用戶在屏幕上繪圖。這幅圖隨后被打印到 Microsoft Access 報(bào)表中。由于 Java 不支持 OLE,我的唯一選擇就是創(chuàng)建該圖的一個(gè)位圖文件,并通知 Microsoft Access 報(bào)表在何處能找到這個(gè)位圖文件。如果您寫(xiě)過(guò)向剪貼板發(fā)送圖像的應(yīng)用程序,則這個(gè)技巧可能對(duì)您有用 -- 尤其是當(dāng)您將這個(gè)信息傳遞給另一個(gè)應(yīng)用程序時(shí)。
位圖文件的格式
位圖文件格式支持 4 位 RLE(行程長(zhǎng)度編碼)以及 8 位和 24 位編碼。因?yàn)槲覀冎惶幚?nbsp;24 位格式,所以下面我們查看一下該文件的結(jié)構(gòu)。
位圖文件分為三個(gè)部分。我已將它們列在下面。
第 1 部分:位圖文件的標(biāo)頭
標(biāo)頭包含位圖文件的類型大小信息和版面信息。結(jié)構(gòu)如下(摘自 C 語(yǔ)言結(jié)構(gòu)定義):


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


下面是對(duì)這個(gè)清單中的代碼元素的說(shuō)明:


bfType:指定文件類型,其值始終為 BM。

bfSize:指定整個(gè)文件的大?。ㄒ宰止?jié)為單位)。

bfReserved1:保留 -- 必須為 0。

bfReserved2:保留 -- 必須為 0。

bfOffBits:指定從 BitmapFileHeader 到圖像首部的字節(jié)偏移量。
現(xiàn)在您已經(jīng)明白位圖標(biāo)頭的用途就是標(biāo)識(shí)位圖文件。讀取位圖文件的每個(gè)程序都使用位圖標(biāo)頭來(lái)進(jìn)行文件驗(yàn)證。

第 2 部分:位圖信息標(biāo)頭
隨后的標(biāo)頭稱為信息標(biāo)頭,其中包含圖像本身的屬性。

下面說(shuō)明如何指定 Windows 3.0(或更高版本)設(shè)備獨(dú)立位圖 (DIB) 的大小和顏色格式:


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


以上代碼清單的每個(gè)元素說(shuō)明如下:


biSize:指定 BITMAPINFOHEADER 結(jié)構(gòu)所需的字節(jié)數(shù)。

biWidth:指定位圖的寬度(以象素為單位)。

biHeight:指定位圖的高度(以象素為單位)。

biPlanes:指定目標(biāo)設(shè)備的位面數(shù)。這個(gè)成員變量的值必須為 1。

biBitCount:指定每個(gè)象素的位數(shù)。其值必須為 1、4、8 或 24。

biCompression:指定壓縮位圖的壓縮類型。在 24 位格式中,該變量被設(shè)置為 0。

biSizeImage:指定圖像的大小(以字節(jié)為單位)。如果位圖的格式是 BI_RGB,則將此成員變量設(shè)置為 0 是有效的。

biXPelsPerMeter:為位圖指定目標(biāo)設(shè)備的水平分辨率(以“象素/米”為單位)。應(yīng)用程序可用該值從最符合當(dāng)前設(shè)備特征的資源群組中選擇一個(gè)位圖。

biYPelsPerMeter:為位圖指定目標(biāo)設(shè)備的垂直分辨率(以“象素/米”為單位)。

biClrUsed:指定位圖實(shí)際所用的顏色表中的顏色索引數(shù)。如果 biBitCount 設(shè)為 24,則 biClrUsed 指定用來(lái)優(yōu)化 Windows 調(diào)色板性能的參考顏色表。

biClrImportant:指定對(duì)位圖的顯示有重要影響的顏色索引數(shù)。如果此值為 0,則所有顏色都很重要。
現(xiàn)在已定義了創(chuàng)建圖像所需的全部信息。

第 3 部分:圖像
在 24 位格式中,圖像中的每個(gè)象素都由存儲(chǔ)為 BRG 的三字節(jié) RGB 序列表示。每個(gè)掃描行都被補(bǔ)足到 4 位。為了使這個(gè)過(guò)程稍復(fù)雜一點(diǎn),圖像是自底而上存儲(chǔ)的,即第一個(gè)掃描行是圖像中的最后一個(gè)掃描行。下圖顯示了標(biāo)頭 (BITMAPHEADER) 和 (BITMAPINFOHEADER) 以及部分圖像。各個(gè)部分由垂線分隔:

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
*


現(xiàn)在,我們開(kāi)始檢視代碼
現(xiàn)在我們已經(jīng)知道了 24 位位圖文件的結(jié)構(gòu),下面就是您期待已久的內(nèi)容:用來(lái)將圖像對(duì)象寫(xiě)入位圖文件的代碼。

import java.awt.*;
import java.io.*;
import java.awt.image.*;


public class BMPFile extends Component
{

//--- 私有常量
private final static int BITMAPFILEHEADER_SIZE = 14;
private final static int BITMAPINFOHEADER_SIZE = 40;

//--- 私有變量聲明

//--- 位圖文件標(biāo)頭
private byte bitmapFileHeader [] = new byte [14];

private byte bfType [] =
{'B', 'M'};
private int bfSize = 0;
private int bfReserved1 = 0;
private int bfReserved2 = 0;
private int bfOffBits = BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE;

//--- 位圖信息標(biāo)頭
private byte bitmapInfoHeader [] = new 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;

//--- 位圖原始數(shù)據(jù)
private int bitmap [];

//--- 文件部分
private FileOutputStream fo;

//--- 缺省構(gòu)造函數(shù)

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 是該進(jìn)程的主方法。該方法
* 將調(diào)用 convertImage 方法以將內(nèi)存圖像轉(zhuǎn)換為
* 字節(jié)數(shù)組;writeBitmapFileHeader 方法創(chuàng)建并寫(xiě)入
* 位圖文件標(biāo)頭;writeBitmapInfoHeader 創(chuàng)建
* 信息標(biāo)頭;writeBitmap 寫(xiě)入圖像。
*
*/

private void save (Image parImage, int parWidth, int parHeight)
{


try
{
convertImage (parImage, parWidth, parHeight);
writeBitmapFileHeader ();
writeBitmapInfoHeader ();
writeBitmap ();
}

catch (Exception saveEx)
{
saveEx.printStackTrace ();
}
}



/**//*
* convertImage 將內(nèi)存圖像轉(zhuǎn)換為位圖格式 (BRG)。
* 它還計(jì)算位圖信息標(biāo)頭所用的某些信息。
*
*/

private boolean convertImage (Image parImage, int parWidth, int parHeight)
{

int pad;
bitmap = new int [parWidth * parHeight];

PixelGrabber pg = new 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 (true);
}


/**//*
* writeBitmap 將象素捕獲器返回的圖像轉(zhuǎn)換為
* 所需的格式。請(qǐng)記?。簰呙栊性谖粓D文件中是
* 反向存儲(chǔ)的!
*
* 每個(gè)掃描行必須補(bǔ)足為 4 個(gè)字節(jié)。
*/

private void writeBitmap ()
{

int size;
int value;
int j;
int i;
int rowCount;
int rowIndex;
int lastRowIndex;
int pad;
int padCount;
byte rgb [] = new byte [3];


size = (biWidth * biHeight) - 1;
pad = 4 - ((biWidth * 3) % 4);
if (pad == 4) // <==== 錯(cuò)誤修正
pad = 0; // <==== 錯(cuò)誤修正
rowCount = 1;
padCount = 0;
rowIndex = size - biWidth;
lastRowIndex = rowIndex;


try
{

for (j = 0; j < size; j++)
{
value = bitmap [rowIndex];
rgb [0] = (byte) (value & 0xFF);
rgb [1] = (byte) ((value >> 8) & 0xFF);
rgb [2] = (byte) ((value >> 16) & 0xFF);
fo.write (rgb);

if (rowCount == biWidth)
{
padCount += pad;

for (i = 1; i <= pad; i++)
{
fo.write (0x00);
}
rowCount = 1;
rowIndex = lastRowIndex - biWidth;
lastRowIndex = rowIndex;
}
else
rowCount++;
rowIndex++;
}

//--- 更新文件大小
bfSize += padCount - pad;
biSizeImage += padCount - pad;
}

catch (Exception wb)
{
wb.printStackTrace ();
}

}


/**//*
* writeBitmapFileHeader 將位圖文件標(biāo)頭寫(xiě)入文件中。
*
*/

private void writeBitmapFileHeader ()
{


try
{
fo.write (bfType);
fo.write (intToDWord (bfSize));
fo.write (intToWord (bfReserved1));
fo.write (intToWord (bfReserved2));
fo.write (intToDWord (bfOffBits));

}

catch (Exception wbfh)
{
wbfh.printStackTrace ();
}

}


/**//*
*
* writeBitmapInfoHeader 將位圖信息標(biāo)頭
* 寫(xiě)入文件中。
*
*/


private void writeBitmapInfoHeader ()
{


try
{
fo.write (intToDWord (biSize));
fo.write (intToDWord (biWidth));
fo.write (intToDWord (biHeight));
fo.write (intToWord (biPlanes));
fo.write (intToWord (biBitCount));
fo.write (intToDWord (biCompression));
fo.write (intToDWord (biSizeImage));
fo.write (intToDWord (biXPelsPerMeter));
fo.write (intToDWord (biYPelsPerMeter));
fo.write (intToDWord (biClrUsed));
fo.write (intToDWord (biClrImportant));
}

catch (Exception wbih)
{
wbih.printStackTrace ();
}

}



/**//*
*
* intToWord 將整數(shù)轉(zhuǎn)換為單字,返回值
* 存儲(chǔ)在一個(gè)雙字節(jié)數(shù)組中。
*
*/

private byte [] intToWord (int parValue)
{

byte retValue [] = new byte [2];

retValue [0] = (byte) (parValue & 0x00FF);
retValue [1] = (byte) ((parValue >> 8) & 0x00FF);

return (retValue);

}


/**//*
*
* intToDWord 將整數(shù)轉(zhuǎn)換為雙字,返回值
* 存儲(chǔ)在一個(gè) 4 字節(jié)數(shù)組中。
*
*/

private byte [] intToDWord (int parValue)
{

byte retValue [] = new 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);

}

}

小結(jié)
這就是所要做的全部工作。我確信您將會(huì)發(fā)現(xiàn)這個(gè)類很有用,因?yàn)榈?nbsp;JDK 1.1.6 為止,Java 不支持用任何常用的格式保存圖像。JDK 1.2 將支持創(chuàng)建 JPEG 圖像,但不支持創(chuàng)建位圖。所以這個(gè)類仍將填補(bǔ) JDK1.2 中的空白。
如果您使用這個(gè)類并發(fā)現(xiàn)了改進(jìn)它的方法,請(qǐng)通知我!下面的個(gè)人簡(jiǎn)歷中有我的電子郵件地址。
作者簡(jiǎn)介
jeanpierre.dube Jean-Pierre Dubé 是一名自由 Java 咨詢者。他于 1988 年注冊(cè)創(chuàng)辦了 Infocom公司。從那時(shí)起,Infocom 已承接并開(kāi)發(fā)了幾套應(yīng)用程序,涉及的領(lǐng)域包括制造業(yè)、文檔管理和大規(guī)模輸電線管理。他有豐富的 C 語(yǔ)言、Visual Basic 和 Java(最近)編程經(jīng)驗(yàn),他的公司現(xiàn)在主要使用 Java 語(yǔ)言。 Infocom 最近的一個(gè)項(xiàng)目是一種圖形 API,其測(cè)試版很快就會(huì)面市。