1.BMP文件格式
BMP文件格式组成部分:bmp文件头(14个字节) + 位图信息头(40个字节) + 调色板(由颜色索引数决定) + 位图数据(由图像尺寸决定)
位图文件头BITMAPFILEHEADER |
位图信息头BITMAPINFOHEADER |
调色板Palette(可选) |
实际的位图数据ImageDate |
第一部分为位图文件头BITMAPFILEHEADER,是一个结构,其定义如下:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
这个结构的长度是固定的,为14个字节(WORD为无符号16位整数,DWORD为无符号32位整数),各个域的说明如下:
bfType | 指定文件类型,必须是0x424D,即字符串“BM”,也就是说所有.bmp文件的头两个字节都是“BM”。 |
bfSize | 指定文件大小,包括这14个字节。 |
bfReserved1 | 保留字,不用考虑 |
bfReserved2 | 保留字,不用考虑 |
bfOffBits | 为从文件头到实际的位图数据的偏移字节数,前三个部分的长度之和。 |
第二部分为位图信息头BITMAPINFOHEADER,也是一个结构,其定义如下:
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;
这个结构的长度是固定的,为40个字节(LONG为32位整数),各个域的说明如下:
biSize | 指定这个结构的长度,为40。 |
biWidth | 指定图象的宽度,单位是像素。 |
biHeight | 指定图象的高度,单位是像素。 |
biPlanes | 必须是1,不用考虑。 |
biBitCount | 指定表示颜色时要用到的位数,常用的值为1(黑白二色图), 4(16色图), 8(256色), 24(真彩色图), 32(带透明通道的真彩色图) |
biCompression | 指定位图是否压缩,有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定义好的常量)。一般只使用第一种不压缩的情况,即biCompression为BI_RGB的情况。 |
biSizeImage | 指定实际的位图数据占用的字节数,其实也可以从以下的公式中计算出来: biSizeImage=biWidth’ × biHeight 要注意的是:上述公式中的biWidth’必须是4的整倍数(所以不是biWidth,而是biWidth’,表示大于或等于biWidth的,最接近4的整倍数。举个例子,如果biWidth=240,则biWidth’=240;如果biWidth=241,biWidth’=244)。 如果biCompression为BI_RGB,则该项可能为零 |
biXPelsPerMeter | 指定目标设备的水平分辨率,单位是每米的像素个数。 |
biYPelsPerMeter | 指定目标设备的垂直分辨率,单位同上。 |
biClrUsed | 指定本图象实际用到的颜色数,如果该值为零,则用到的颜色数为2的biBitCount次方。 |
biClrImportant | 指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的。 |
第三部分为调色板Palette,当然,这里是对那些需要调色板的位图文件而言的。有些位图,如真彩色图,前面已经讲过,是不需要调色板的,BITMAPINFOHEADER后直接是位图数据。
调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有 2的biBitCount次方 个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节,其定义如下:
typedef struct tagRGBQUAD {
BYTE rgbBlue; //该颜色的蓝色分量
BYTE rgbGreen; //该颜色的绿色分量
BYTE rgbRed; //该颜色的红色分量
BYTE rgbReserved; //保留值
} RGBQUAD;
第四部分就是实际的图像数据了。对于用到调色板的位图,图像数据就是该像素颜在调色板中的索引值。对于真彩色图,图象数据就是实际的R、G、B值。
要注意两点:
(1) 每一行的字节数必须是4的整倍数,如果不是,则需要补齐。这在前面介绍biSizeImage时已经提到了。
(2) 一般来说,.bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图像最下面一行的左边第一个像素,然后是左边第二个像素……接下来是倒数第二行左边第一个像素,左边第二个像素……依次类推 ,最后得到的是最上面一行的最右一个象素。
2.Java读写BMP代码
package com.miaxis.util;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class BmpUtil {
/**
* @author mickey
* @category 读取BMP图像
* @param strFileName - [in]BMP文件路径
* ucImgBuf - [out]图象数据
* iWidth - [out]图像宽度
* iHeight - [out]图像高度
* iChannels - [out]图像通道数
* @return 0 - 成功
* 其他 - 失败
* @notes 暂时只支持灰度BMP文件格式
* */
public static int ReadBMP(String strFileName,byte[] ucImgBuf,int[] iWidth,int[] iHeight,int[] iChannels)
{
byte[] bBmp = ReadData(strFileName);
if(bBmp==null)
return -1;
return Bmp2Raw(bBmp,ucImgBuf,iWidth,iHeight,iChannels);
}
/**
* @author mickey
* @category 保存BMP图像
* @param strFileName - [in]BMP文件路径
* ucImgBuf - [in]]图象数据
* iWidth - [in]图像宽度
* iHeight - [in]图像高度
* iChannels - [in]图像通道数
* @return 0 - 成功
* 其他 - 失败
* @notes 暂时只支持灰度BMP文件格式
* */
public static int SaveBMP(String strFileName,byte[] ucImgBuf,int iWidth,int iHeight,int iChannels)
{
int iNewWidth = (iChannels*iWidth+3)/4*4;
byte[] bBmp = new byte[iNewWidth*iHeight + 1078];
int[] iBmpLen = new int[1];
Raw2Bmp(bBmp, iBmpLen,ucImgBuf, iWidth, iHeight,iChannels);
return SaveData(strFileName, bBmp, iBmpLen[0]);
}
public static int JUnsigned(int x)
{
if (x>=0)
return x;
else
return (x+256);
}
/**
* @author mickey
* @category BMP格式图像数据 -> 图像裸数据
* @param pBmp - [in]BMP格式图像数据
* pRaw - [out]图像裸数据
* iWidth - [out]图像宽度
* iHeight - [out]图像高度
* iChannels - [out]图像通道数
* @return 0 - 成功
* 其他 - 失败
* */
public static int Bmp2Raw(byte[] pBmp,byte[] pRaw,int[] iWidth,int[] iHeight,int[] iChannels)
{
int i,X,Y,channels;
byte[] head = new byte[1078];
System.arraycopy(pBmp, 0, head, 0, 54);
if(head[0]!=0x42 || head[1]!=0x4D)
return -1;
int head18=(int)head[18];
int head19=(int)(head[19]<<8);
int head20=(int)(head[20]<<8);
int head21=(int)(head[21]<<8);
int head22=(int)head[22];
int head23=(int)(head[23]<<8);
int head24=(int)(head[24]<<8);
int head25=(int)(head[25]<<8);
X=JUnsigned(head18) + JUnsigned(head19) + JUnsigned(head20) + JUnsigned(head21);
Y=JUnsigned(head22) + JUnsigned(head23) + JUnsigned(head24) + JUnsigned(head25);
int head28=(int)(head[28]);
int head29=(int)(head[29]<<8);
channels = JUnsigned(head28) + JUnsigned(head29);
channels = channels/8;
//System.out.println("channels="+channels);
iWidth[0] = X;
iHeight[0] = Y;
iChannels[0] = channels;
if(channels == 4)
iChannels[0] = 3;
int iNewWidth = (channels * X + 3) / 4 * 4;
if (channels==1){
for( i=0;i<Y; i++ ) {
System.arraycopy(pBmp,1078+(Y-1-i)*iNewWidth, pRaw,i*X, X);
}
}else if(channels==3) {
for( i=0;i<Y; i++ ) {
System.arraycopy(pBmp,54+(Y-1-i)*iNewWidth, pRaw,i*3*X, 3*X);
}
}else if(channels==4) {
for (i = 0; i < Y; i++) {
for (int j = 0; j < X; j++) {
pRaw[i * 3 * X + 3 * j] = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j];
pRaw[i * 3 * X + 3 * j + 1] = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j + 1];
pRaw[i * 3 * X + 3 * j + 2] = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j + 2];
}
}
}
else{
return -2;
}
return 0;
}
/**
* @author mickey
* @category 读取文件数据到byte数组
* @param filepath - [in]文件路径
* @return byte数组
* */
public static byte[] ReadData(String filepath){
File f = new File(filepath);
if(!f.exists()){
System.out.println("filepath=" + null);
return null;
}
ByteArrayOutputStream bos = new ByteArrayOutputStream((int)f.length());
BufferedInputStream in = null;
try{
in = new BufferedInputStream(new FileInputStream(f));
int buf_size = 1024;
byte[] buffer = new byte[buf_size];
int len = 0;
while(-1 != (len = in.read(buffer,0,buf_size))){
bos.write(buffer,0,len);
}
return bos.toByteArray();
}catch (IOException e) {
e.printStackTrace();
}finally{
try{
in.close();
}catch (IOException e) {
e.printStackTrace();
}
try {
bos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
/**
* @author mickey
* @category 图像裸数据-> BMP格式图像数据
* @param pBmp - [out]BMP格式图像数据
* iBMPLen - [out]BMP格式图像数据长度
* pRaw - [in]图像裸数据
* iImgX - [in]图像宽度
* iImgY - [in]图像高度
* iChannels - [in]图像通道数
* @return 0 - 成功
* 其他 - 失败
* */
public static int Raw2Bmp(byte[] pBmp,int[] iBMPLen, byte[] pRaw, int X, int Y,int iChannels) {
int num;
int i, j;
byte[] head = new byte[1078];
byte[] temp = { 0x42, 0x4d, // file header
0x00, 0x00, 0x00, 0x00, // file size***
0x00, 0x00, // reserved
0x00, 0x00,// reserved
0x36, 0x4, 0x00, 0x00,// head byte***
0x28, 0x00, 0x00, 0x00,// struct size
0x00, 0x00, 0x00, 0x00,// map width***
0x00, 0x00, 0x00, 0x00,// map height***
0x01, 0x00,// must be 1
0x08, 0x00,// color count***
0x00, 0x00, 0x00, 0x00, // compression
0x00, 0x00, 0x00, 0x00,// data size***
0x00, 0x00, 0x00, 0x00, // dpix
0x00, 0x00, 0x00, 0x00, // dpiy
0x00, 0x00, 0x00, 0x00,// color used
0x00, 0x00, 0x00, 0x00,// color important
};
System.arraycopy(temp, 0, head, 0, temp.length);
int iNewWidth = (iChannels*X+3)/4*4;
int iHeadLen = 54;
if (iChannels == 1)
{
iHeadLen = 1078;
}
int iFileSize = iHeadLen + iNewWidth*Y;
num=iFileSize;
head[2] = (byte) (num & 0xFF);
num = num >> 8;
head[3] = (byte) (num & 0xFF);
num = num >> 8;
head[4] = (byte) (num & 0xFF);
num = num >> 8;
head[5] = (byte) (num & 0xFF);
num = iHeadLen;
head[10] = (byte) (num & 0xFF);
num = num >> 8;
head[11] = (byte) (num & 0xFF);
num = num >> 8;
head[12] = (byte) (num & 0xFF);
num = num >> 8;
head[13] = (byte) (num & 0xFF);
// 确定图象宽度数值
num = X;
head[18] = (byte) (num & 0xFF);
num = num >> 8;
head[19] = (byte) (num & 0xFF);
num = num >> 8;
head[20] = (byte) (num & 0xFF);
num = num >> 8;
head[21] = (byte) (num & 0xFF);
// 确定图象高度数值
num = Y;
head[22] = (byte) (num & 0xFF);
num = num >> 8;
head[23] = (byte) (num & 0xFF);
num = num >> 8;
head[24] = (byte) (num & 0xFF);
num = num >> 8;
head[25] = (byte) (num & 0xFF);
//BitCount
num = iChannels*8;
head[28] = (byte) (num & 0xFF);
num = num >> 8;
head[29] = (byte) (num & 0xFF);
//Data size
num = Y*iNewWidth;
head[34] = (byte) (num & 0xFF);
num = num >> 8;
head[35] = (byte) (num & 0xFF);
num = num >> 8;
head[36] = (byte) (num & 0xFF);
num = num >> 8;
head[37] = (byte) (num & 0xFF);
// 确定调色板数值
j = 0;
for (i = 54; i < 1078; i = i + 4) {
head[i] = head[i + 1] = head[i + 2] = (byte) j;
head[i + 3] = 0;
j++;
}
// 写入文件头
System.arraycopy(head, 0, pBmp, 0, iHeadLen);
// 写入图象数据
for (i = 0; i < Y; i++) {
System.arraycopy(pRaw, i * iChannels * X, pBmp,
iHeadLen + (Y - 1 - i) * iNewWidth, iChannels * X);
}
//输出文件大小
iBMPLen[0] = iFileSize;
return 0;
}
/**
* @author mickey
* @category 保存数据为文件
* @param filepath - [in]文件路径
* buffer - [in]数据缓存
* size - [in]数据长度
* @return 0 - 成功
* 其他 - 失败
* */
public static int SaveData(String filepath, byte[] buffer, int size) {
File f = new File(filepath);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(f);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return -1;
}
try {
fos.write(buffer, 0, size);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return -2;
}
return 0;
}
}
3. Java测试工程源代码
编译器版本: IntelliJ IDEA 2020.3.2 x64
JDK版本: java 1.8.0_111