一、 介绍
开始之前先讲一下原始数据读写流DataOutputStream和DataInputStream
主要用来读写指定的数据类型的数据。两种数据流都以对应的文件输入
输出流为构造参数:
下面是几个数据输出流的几个方法(来至API):
writeBoolean(boolean v)
将一个 boolean
值以 1-byte 值形式写入基础输出流。
writeByte(int v)
将一个 byte 值以 1-byte 值形式写出到基础输出流中
writeChar(int v)
将一个 char 值以 2-byte 值形式写入基础输出流中,先写入高字节。
writeLong(long v)
将一个 long 值以 8-byte 值形式写入基础输出流中,先写入高字节。
以上都在数据输出流里有对应的方法,而且我们在读取数据时必须循序读取,而且类型要匹配,文件在存储数据时,是以byte形式储存的,例如,一个int 类型的数据,储存时会被拆成四个字节然后按高位到低位进行储存,所以在读取数据时必须匹配,不然会有数据丢失,储存时也是如此。
二、解析BMP文件格式:
任何文件在储存时都有自己的文件格式,而且这并不取决于扩展名,例如BMP文件,在储存时不仅包含画板上获取的数据,在储存是要提前写入一些文件头信息。
BMP文件有四部分组成:
1、 位图文件头数据结构,它包含BMP图像文件的类型、显示内容等信息;
2、 位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及自定义颜色等信息;
3、 调色板:这个部分是可选的,有些位图需要调色板,有些位 图,比如我做的24位真彩图就不需要调色板
4、 位图数据:这部分根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。
三、 实现
实现主要分为两个部分,写入和读取,必须得按照BMP本身格式类型进行操作
写入:
/** * * @param image:截取的图片 * @param file:要存储的文件 */ public void saveBMP(BufferedImage image,File file) { try { /* 创建文件输出流 */ java.io.FileOutputStream fos = new FileOutputStream(file); /* 文件数据输出流 */ java.io.DataOutputStream dos = new DataOutputStream(fos); //////////////////////////////////////////////*BMP文件头*/ int width = image.getWidth(); int height = image.getHeight(); /* 位图文件类型,2个字节 */ dos.writeByte((int) 'B'); dos.writeByte((int) 'M'); /* 位图文件大小,4个字节 */ int skip_width = 0; /* Windows规定一个扫描行所占的字节数必须为4的倍数,不足的以0补上*/ if (width * 3 % 4 != 0) skip_width = 4 - width * 3 % 4; int bfsize = 54 + (width + skip_width) * 3 * height; dos.write(changeIntTobyte(bfsize, 4), 0, 4); /* 文件的两个保留字,4个 */ dos.write(changeIntTobyte(0, 4), 0, 4); /* 起始位置4个字节 */ dos.write(54); dos.write(changeIntTobyte(0, 3), 0, 3); // ////////////////////////////////////////////*位图信息图*/ /* 本结构所占字节数40,4个字节 */ int size = 40; dos.write(changeIntTobyte(size, 4), 0, 4); /* 宽度,高度,8个字节 */ dos.write(changeIntTobyte(width, 4), 0, 4); dos.write(changeIntTobyte(height, 4), 0, 4); /* 目标设备 */ dos.write(changeIntTobyte(1, 2), 0, 2); /* 像素所需位数 24*/ dos.write(changeIntTobyte(24, 2), 0, 2); /* 压缩类型 */ dos.write(changeIntTobyte(0, 4), 0, 4); /* 位图大小 */ dos.write(changeIntTobyte(0, 4), 0, 4); /* 水平,垂直分辨率 */ dos.write(changeIntTobyte(150, 4), 0, 4); dos.write(changeIntTobyte(150, 4), 0, 4); /* 位图实际使用的颜色表,的颜色数 */ int numcolor = 0; dos.write(changeIntTobyte(numcolor, 4), 0, 4); /* 重要的颜色 */ int impcolor = 0; dos.write(changeIntTobyte(impcolor, 4), 0, 4); // ///////////////////////////////////////////位图数据 int color[][] = new int[height][width]; byte imageR[][] = new byte[height][width]; byte imageG[][] = new byte[height][width]; byte imageB[][] = new byte[height][width]; for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { int temp = image.getRGB(w, h); color[h][w] = temp; imageR[h][w] = (byte)( temp >> 16); imageG[h][w] = (byte)( temp >> 8); imageB[h][w] = (byte)( temp >> 0); } } for(int h= height - 1;h>-1;h--) for(int w =0 ;w<width;w++){ dos.writeByte(imageB[h][w]); dos.writeByte(imageG[h][w]); dos.writeByte(imageR[h][w]); if(skip_width!=0&&w == 0){ dos.write(changeIntTobyte(0,skip_width),0,skip_width); } } fos.flush(); fos.close(); } catch (IOException exception) { exception.printStackTrace(); } }
以上还有一点比较重要,前面我们已经知道文件存储数据是以byte形式,从高位到低位存储的,但对于位图文件有点特殊,它是从低位到高位存储的,所以我在存储时要手动把数据转成byte型并以从低位到高位存入文件中,系统将会读取错误
public byte[] changeIntTobyte(int num, int size) { byte[] count = new byte[size]; for (int i = 0; i < size; i++) { count[i] = (byte) (num >> (i * 8)); } return count; }
对于读取只是按照以上步骤读取即可,记住一点,要把读入的数据再按位组合成所需要的数据,即使以上转化的逆过程。通过以上的过程就可以把画图板图片保存成BMP文件了,而且可以用windows 的图片查看了。不过只限于24位的欧。
四、 文件选择器的使用
以上已经实现了功能,下面是一点拓展,在实现存储和打开文件时我使用了文件选择器
public void saveFile() { try{ FileFilter filter1 = new FileFilter() { public boolean accept(File file) {// 只显示 bmp 格式的文件或文件夹 String formatName = file.getName().toLowerCase(); return formatName.endsWith(".bmp") || file.isDirectory(); } @Override public String getDescription() {// 文件描述为 bmp // TODO Auto-generated method stub return "bmp"; } }; JFileChooser jfc = new JFileChooser();// 文件选择器 jfc.setAcceptAllFileFilterUsed(false);// 不显示所有文件 jfc.setName("保存");// 标题 jfc.addChoosableFileFilter(filter1);// 加上过滤器 jfc.setCurrentDirectory(new File("src\\MyBMP\\images"));// 文件选择器的初始目录 File outfile;//用来获得文件 int state = jfc.showSaveDialog(drawPanel);// 此句是打开文件选择器界面的触发语句 if (state == 1) { return;// 撤销则返回 } else { outfile = jfc.getSelectedFile();// f为选择到的文件名 } /* 判断是否以bmp为后缀名,不管大小 */ String formatName = "bmp";// 指定为bmp String filename = outfile.getAbsolutePath().toLowerCase();// 获得路径 if (!filename.endsWith(formatName))// 如果输入时未加上后缀则自动加上后缀 outfile = new File(filename + "." + formatName); //自定义函数 saveBMP(image, outfile); //系统函数, //javax.imageio.ImageIO.write(image, formatName, outfile); } catch (java.awt.AWTException exception) { exception.printStackTrace(); } }
加上文件选择器就可以动态写文件名了,省的一直对一个文件进行操作,也变得更人性化了,这就是我的真正BMP。。。敬请指正。。