1.我们先来了解BMP文件的格式:
(很重要的一点就是,存储完特定的BMP文件格式之后,记下来便是位图数据存储,这个时候我们只需要保存图片每一个点的颜色信息,这样就可以完成了保存过程)
1.(格式参考学长的) 2.① BMP文件头(14)字节 3.byte bfType1;//位图文件的类型,必须位’B’’1个字母(’B ‘ 1个字节) 4.byte bfType2;//位图文件的类型,必须位’B’1个个字母(’M ‘ 1个字节) 5. 6.int bfSize;//位图文件的大小,以字节位单位(4个字节) 7.short usignedshort bfReserved1;//位图文件保留字,必须为0(2个字节) 8.short usignedshort bfReserved2;//位图文件保留字,必须为0(2个字节) 9.int bfOffBits;//位图数据的起始位置,以相对于位图(文件头的偏移量表示,以字节为单位)(4个字节) 10.① BMP位图信息头(40)字节 11.BMP位图信息头数据用于说明位图的尺寸等信息 12.int Size;//本结构所占用字节数(4个字节) 13.int image_width;//位图的宽度,以像素为单位(4个字节) 14.int image_heigh;//位图的高度,以像素位单位(4个字节) 15.short Planes;//目标设备的级别,必须位1(2个字节) 16.short biBitCount;//每个像素所需的位数,必须是1(双色),4(16色), 17.8(256色)或24(//真彩色)之一(2个字节) 18.int biCompression;//位图压缩类型,必须是0(不压缩),1(BI_RLE8 压缩类型)或2(BI_RLE4)之一(4个字节) 19.int SizeImage;//位图的大小,以字节位单位(4个字节) 20.int biXPelsPerMeter;//位图水平分辨率,每米像素数(4个字节) 21.int biYPelsPerMeter;//位图垂直分辨率,每米像素数(4个字节) 22.int biClrUsed;//位图实际使用的颜色表中的颜色数(4个字节) 23.int biClrImportant;//位图显示过程中重要的颜色数(4个字节)
2.我先给出保存图片的代码然后再对照上面的格式要求来分析:
String command = e.getActionCommand(); JFileChooser chooser = new JFileChooser(); if (command.equals("保存")) { int t = chooser.showSaveDialog(null); if (t == JFileChooser.APPROVE_OPTION) { String path = chooser.getSelectedFile().getAbsolutePath(); try { FileOutputStream fos = new FileOutputStream(path); DataOutputStream dos = new DataOutputStream(fos); savebmpTop(dos);// 存储文件头 savebmpInfo(dos);// 存储位图信息头 savebmpDate(dos);//// 图像数据阵列 fos.flush(); fos.close(); } catch (Exception ef) { JOptionPane.showMessageDialog(null, "文件保存失败!!"); ef.printStackTrace(); } } }
我们会发现,保存图片调用了三个方法:
savebmpTop(dos);// 存储文件头
savebmpInfo(dos);// 存储位图信息头
savebmpDate(dos);// 图像数据阵列
分别用来按顺序存储文件头,位图信息头,就像开始给出的bmp图片格式要求,,,,接下来就是存我们图像数据阵列了;
savebmpTop(dos);// 存储文件头的方法:
// bmp文件头 public void savebmpTop(OutputStream ops) throws Exception { // 位图文件的类型,必须为BM ops.write('B'); ops.write('M'); int height = DrawListener.array.length; int width = DrawListener.array[0].length; // 位图文件的大小,以字节为单位 int size = 14 + 40 + height * width * 3 + (4 - width * 3 % 4) * 3 * height; writeInt(ops, size); // 保留字节,必须为零 writeShort(ops, (short) 0); writeShort(ops, (short) 0); // 位图偏移量 writeInt(ops, 54); }
savebmpInfo(dos);// 存储位图信息头
// 位图信息头 public void savebmpInfo(OutputStream ops) throws Exception { int height = DrawListener.array.length; int width = DrawListener.array[0].length; // 位图信息头长度 writeInt(ops, 40); // 位图宽 writeInt(ops, width); // 位图高 writeInt(ops, height); // 位图位面数总是为1 writeShort(ops, (short) 1); // 位图24位像素 writeShort(ops, (short) 24); // 位图是否被压缩,0为不压缩 writeInt(ops, 0); // 字节数代表位图大小 writeInt(ops, height * width * 3 + (4 - width * 3 % 4) * 3 * height); // 水平分辨率 每米像素数 writeInt(ops, 0); // 垂直分辨率 每米像素数 writeInt(ops, 0); // 颜色索引,0为所有调色板 writeInt(ops, 0); // 对图象显示有重要影响的颜色索引的数目。如果是0,表示都重要 writeInt(ops, 0); }
savebmpDate(dos);// 图像数据阵列方法(其中还有一些数据进制转换的方法我没有吧代码列出来,文件里面有完整代码,在后面我会写这个进制转换)
// 图像数据阵列 public void savebmpDate(OutputStream ops) throws Exception { int height = DrawListener.array.length; int width = DrawListener.array[0].length; int m = 0; // 进行补0 if (width * 3 % 4 > 0) { m = 4 - width * 3 % 4; } for (int i = height - 1; i >= 0; i--) { for (int j = 0; j < width; j++) { int t = DrawListener.array[i][j]; writeColor(ops, t); } for (int k = 0; k < m; k++) { ops.write(0); } } }
到这里我们的存储过程就完成了
然而我们存储Int类型的数据需要用到这个代码:
// 传入一个int值,转化成4个8位的二进制,刚好四个字节 public void writeInt(OutputStream ops, int t) throws Exception { int a = (t >> 24) & 0xff; int b = (t >> 16) & 0xff; int c = (t >> 8) & 0xff; int d = t & 0xff; ops.write(d); ops.write(c); ops.write(b); ops.write(a); }
上面的t是一个int类型(32位的)的,0xff是16进制它的二进制是
1111 1111 t>>24表示t的二进制右移24位与1111 1111做位运算,得到t的二进制的前8位,同理t>>16 右移16得到t的次8位,这里右移16,剩下16位怎么会是得到t的次8位呢,注意啊0xff的二进制是1111 1111 只有8位,在计算的b的时候它会把0xff前面用0补成16位就是0000 00
00 1111 1111,前面8个0在进行与位运算的时候不管怎么样都是0,所以是得到t的次8位的数据,就这样将一个int值的t按照8位8位的一次取出数据。
为什么要进行位运算呢?
Windows规定一个扫描行所占的字节数必须是4的倍数,不足的以0补充。关键就是要搞清楚Windows里面数据是怎么存的?Windows储存数据是从地位到高位来储存的,我们一般都是从高位到地位进行保存。所以就需要我们进行位运算将数据来转化成Windows储存数据的方式。
然而:
如果不进行位运算取出4个8位的byte类型,int是32位不会进行补0,而8位的byte会进行补0;
整个位图数据存储,就只需要存储每一个点的颜色:
// 总共是24位的色图,分成3个字节来存储(每8位二进制数据为一个字节) public void writeColor(OutputStream ops, int t) throws Exception { int b = (t >> 16) & 0xff; int c = (t >> 8) & 0xff; int d = t & 0xff; ops.write(d); ops.write(c); ops.write(b); }
保存文件最重要的步骤都在上面了;
-------------------------------------------------------------------------
3.怎么读取BMP格式的文件呢?
这个最简单了,就是你怎么存的数据,就按那个顺序来读取,当然还有一些位运算需要进行,就是存储的时候我们把int转化成byte类型,现在我们要转换回来:
// 由于读取的是字节,把读取到的4个byte转化成1个int public int changeInt(InputStream ips) throws IOException { int t1 = ips.read() & 0xff; int t2 = ips.read() & 0xff; int t3 = ips.read() & 0xff; int t4 = ips.read() & 0xff; int num = (t4 << 24) + (t3 << 16) + (t2 << 8) + t1; System.out.println(num); return num; } // 24位的图片是1个像素3个字节。 public int readColor(InputStream ips) throws IOException { int b = ips.read() & 0xff; int g = ips.read() & 0xff; int r = ips.read() & 0xff; int c = (r << 16) + (g << 8) + b; return c; }
4.使用的时候要注意(如图):保存图片后缀名为 .bmp 这样其他的照片查看器都可以打开,你自己也可以在菜单栏里面打开(还有一些其他的功能没有实现)!