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; }