BMP是英文Bitmap(位图)的简写,它是Windows操作系统中的标准图像文件格式,能够被多种Windows应用程序所支持。因为几乎不进行压缩,所以BMP格式是最简单的通用格式之一。
本篇文章主要介绍BMP文件的格式规范,解析及其保存。
BMP文件结构(本段代码由小组成员卿雯童鞋呈现)
位图文件可看成由4个部分组成:位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、彩色表(color table)和定义位图的字节(位图数据,即图像数据,Data Bits 或Data Body)阵列,它具有如下所示的形式。
位图文件头 (bitmap-file header) BITMAPFILEHEADER bmfh
/**
* bitmap-file header(14 byte)
*
*/
class BITMAPFILEHEADER {
short bfType;// the type of BMP file,value of 'B' or 'M' only.(1-2 byte)
int bfSize;// the size of BMP file.(3-6 byte)
short bfReserved1;// reserved word of bmp file,value of 0 only.(7-8 byte)
short bfReserved2;// reserved word of bmp file,value of 0 only.(9-10byte)
int bfOffBits;// the offset of file header(11-14 byte)
}
位图信息头 (bitmap-information header) BITMAPINFOHEADER bmih
/**
* bitmap-information header(40 byte)
*
*/
class BITMAPINFOHEADER {
int biSize;// the size of this part.(15-18 byte)
int biWidth;// the width of bmp file.(19-22 byte)
int biHeight;// the height of bmp file.(23-26 byte)
byte biPlanes;// the rank of target device.value of q only(27-28 byte)
byte biBitCount;// the bit of per pixel,value of 1,4 8 or 24 only.(29-30byte)
int biCompression;// the type of compression,value of 0(BI_RGB)(non-compression),1(BI_RLE8) or 2(BI_RLE4) only.(31-34 byte)
int biSizeImage;// the size of image.(35-38 byte)
int biXPelsPerMeter;// horizontal resolution, Pixels per meter.(39-42 byte)
int biYPelsPerMeter;// vertical resolution,Pixels per meter.(43-46 byte)
int biClrUsed;// the number of all used color.(47-50 byte)
int biClrImportant;// the number of key color.(51-54 byte)
}
彩色表 (color table) RGBQUAD aColors[]
/**
* color table
*
*/
class RGBQUAD {
byte rgbRed;// the red channel
byte rgbGreen;// the Green channel
byte rgbBlue;// the blue channel
byte rgbReserved;// reserved,value of 0 only
}
图象数据阵列字节 BYTE aBitmapBits[]
/**
* Data Bits
*
*/
class BITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors;
}
BMP文件解析(本段代码由小组成员阳超群童鞋呈现)
要点:先读取位图文件头的信息(14 byte)
然后读取位图信息头为的信息(40 byte)
注意:此段要获取信息头的2个重要的参数,位图的宽度和高度
最后读取位图的图像信息。
其中要注意数据类型的匹配。
读取位图文件头和信息头
/**
* read the image from the bitmap file
*
* @param path
* the path of bitmap file
* @throws IOException
*/
public void readBMP(String path) throws IOException {
// creat a FileInputStream object;
fis = new java.io.FileInputStream(path);
// creat a DataInputStream object;
dins = new java.io.DataInputStream(fis);
// read the bitmap loader of bitmap file
int bmploaderLen = 14;
byte[] bmploader = new byte[bmploaderLen];
dins.read(bmploader, 0, bmploaderLen);
// read the info header of bitmap file
int bmpheaderLen = 40;
byte[] bmpheader = new byte[bmpheaderLen];
dins.read(bmpheader, 0, bmpheaderLen);
biWidth = convert2Int(bmpheader, 7);
biHeight = convert2Int(bmpheader, 11);
}
将byte数组转换为int类型的数据
/**
* 4 byte convert to 1 int
* @param bytes the source byte array
* @return the converted int
*/
private int convert2Int(byte[] bytes, int index) {
return (((int) bytes[index] & 0xff) << 24)
| (((int) bytes[index - 1] & 0xff) << 16)
| (((int) bytes[index - 2] & 0xff) << 8)
| ((int) bytes[index - 3] & 0xff);
}
计算补零的长度
// calculate the length of zero-padding
if (!(biHeight * 3 % 4 == 0)) {
skip_width = 4 - biWidth * 3 % 4;
}
读取RGB信息
for (int h = biHeight - 1; h >= 0; h--) {
for (int w = 0; w < biWidth; w++) {
// 分别按照协议顺序读出字节
int blue = dins.read();
int green = dins.read();
int red = dins.read();
// 由于颜色值范围超出byte正值范围,可能存储为负值,进行位运算
int blue_temp = (blue & 0xff);
int green_temp = (green & 0xff);
int red_temp = (red & 0xff);
imageB[h][w] = blue_temp;
imageG[h][w] = green_temp;
imageR[h][w] = red_temp;
// zero-padding
if (w == 0) {
byte bytes[] = new byte[skip_width];
dins.read(bytes);
}
}
}
dins.close();
fis.close();
}
绘出位图图像
/**
* paint the image on the frame
*/
public void paint(Graphics g) {
super.paint(g);
// g = this.getGraphics();
for (int h = 0; h < biHeight; h++) {
for (int w = 0; w < biWidth; w++) {
g.setColor(new Color(imageR[h][w], imageG[h][w], imageB[h][w]));
g.fillRect(w, h, 1, 1);
}
}
}
BMP文件的保存(本段代码有小组成员曹睿超童鞋呈现)
要点:按BMP文件结构依次写入位图文件头,位图信息图,RGB颜色表,和图象数据阵列字节
注意:数据类型的匹配。
/**
* 写入bmp文件
* @param image
* @param file
*/
public void writeBMP(BufferedImage image, File file) {
try {
// 创建输入流
FileOutputStream fos = new FileOutputStream(file);
DataOutputStream dos = new DataOutputStream(fos);
//位图文件类型,2个字节,必须为'B'和'M'
dos.writeByte((int)'B');
dos.writeByte((int)'M');
//BMP头文件
biWidth = image.getWidth();
biHeight = image.getHeight();
//位图文件大小,4个字节
int skip_width = 0;
//一个扫描行所占的字节数必须为4的倍数,不足的补上0
if(skip_width * 3 % 4 != 0)
skip_width = 4 - skip_width * 3 % 4;
//得到文件的大小
bfSize = 54 + (biWidth + skip_width) * 3 * biHeight;
dos.write(changeIntToByte(bfSize, 4) , 0, 4);
//起始位置4个字节
dos.write(changeIntToByte(bfReserved1, 2), 0, 2);
dos.write(changeIntToByte(bfReserved2, 2), 0, 2);
//写入位图文件的起始位置
dos.write(changeIntToByte(bfOffBits, 4), 0, 4);
//位图信息图
biSize = (biWidth + skip_width) * 3 * biHeight;
dos.write(changeIntToByte(biSize, 4), 0, 4);
//宽度,高度
dos.write(changeIntToByte(biWidth, 4), 0, 4);
dos.write(changeIntToByte(biHeight, 4), 0, 4);
//目标设备
dos.write(changeIntToByte(biPlanes, 2), 0, 2);
//像素所需位数,24
biBitCount = 24;
dos.write(changeIntToByte(biBitCount, 2), 0 ,2);
//压缩类型
dos.write(changeIntToByte(biCompression, 4), 0 ,4);
//位图大小
dos.write(changeIntToByte(biSizeImage, 4), 0, 4);
//写入水平,垂直分辨率(150ppm)
dos.write(changeIntToByte(biXPelsPreMeter, 4), 0, 4);
dos.write(changeIntToByte(biYPelsPreMeter, 4), 0, 4);
//写入位图中实际使用的颜色表的颜色数
dos.write(changeIntToByte(biClrUsed, 4), 0, 4);
//写入重要的颜色
dos.write(changeIntToByte(biClrImportant, 4), 0, 4);
//位图数据
int color [][] = new int [biWidth][biHeight];
byte colorR[][] = new byte [biWidth][biHeight];
byte colorG[][] = new byte [biWidth][biHeight];
byte colorB[][] = new byte [biWidth][biHeight];
for(int i = 0;i < biHeight;i ++) {
for(int j = 0;j < biWidth;j ++) {
int temp = image.getRGB(j, i);
color[i][j] = temp;
colorR[i][j] = (byte)(temp >> 16);
colorG[i][j] = (byte)(temp >> 8);
colorB[i][j] = (byte)(temp >> 0);
}
}
for(int i = biHeight - 1;i >= 0;i ++) {
for(int j = biWidth - 1;j >= 0;j --){
dos.write(colorB[i][j]);
dos.write(colorG[i][j]);
dos.write(colorR[i][j]);
if(skip_width != 0 && j == 0)
dos.write(changeIntToByte(0, skip_width), 0, skip_width);
}
}
fos.flush();
fos.close();
dos.flush();
dos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
将int类型转换为Byte数组
/**
* 将int类型转换为byte数组
* @param num int类型的对象
* @param size 数组的长度
* @return byte类型数组
*/
private byte[] changeIntToByte(int num, int size) {
//定义一个byte类型数组
byte[] count = new byte[size];
//循环进行位运算
for(int i = 0;i < size;i ++) {
count[i] = (byte) (num >> (i * 8));
}
return count;
}