接下,为了对输入输出流有更多的了解,我们练习了对BMP格式文件的解析。
BMP格式的文件解析跟其他一样,也是按照字节的顺序一个一个字节的读取,在bmp格式的文件中,写有关于此类文件解析的协议,这也是我们初识“协议”这个东西,这在我们之后学过的通信中也有用到。
BMP文件要用我们自己写的东西解析出来,就要了解它的字节写入的方法即“协议”,这样才能顺利读出来。
因此,我们查到了BMP文件的格式:
BMP的4个组成部分:
位图文件头(bitmap-file header) | BITMAPFILEHEADER | bmfh |
位图信息头(bitmap-information header) | BITMAPINFOHEADER | bmih |
彩色表(color table) | RGBQUAD | aColors[] |
图象数据阵列字节 | BYTE | aBitmapBits[] |
然后按照字节的排列,他的每个字节所代表的含义如下:
<!--EndFragment-->
1.位图文件头
0000-0001:文件标识,为字母ASCII码“BM”。
0002-0005:文件大小。
0006-0009:保留,每字节以“00”填写。
000A-000D:记录图像数据区的起始位置。各字节的信息依次含义为:文件头信息块大小,图像描述信息块的大小,图像颜色表的大小,保留(为01)。
2.图像描述信息块
000E-0011:图像描述信息块的大小,常为28H。
0012-0015:图像宽度。
0016-0019:图像高度。
001A-001B:图像的plane总数(恒为1)。
001C-001D:记录像素的位数,很重要的数值,图像的颜色数由该值决定。
001E-0021:数据压缩方式(数值位0:不压缩;1:8位压缩;2:4位压缩
0022-0025:图像区数据的大小。
0026-0029:水平每米有多少像素,在设备无关位图(.DIB)中,每字节以00H填写。
002A-002D:垂直每米有多少像素,在设备无关位图(.DIB)中,每字节以00H填写。
002E-0031:此图像所用的颜色数,如值为0,表示所有颜色一样重要。
由于我们使用的是24位的图像,因此之后两个表示颜色的都只用RGB来表示。
所以,根据协议的顺序,就可以一步步的解析BMP文件把它的数据给读出来了,而有一些固定不变的信息就不需要我们读取,我们只需要读取就像颜色长度宽度这类的重要信息。
package lyw.summer0628V3;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.swing.JFileChooser;
/**
* 按钮的监听器类
* @author liuyuwen
*/
public class ButtonListener implements ActionListener {
int picture_width;//图片宽度
int picture_height;//图片高度
int flen=14;//头文件信息长度
int bilen=40;//位图信息头长度
int skip_width;//因是补齐而要跳过的宽度
Color c;
int[][] pictureR,pictureG,pictureB;//分别存储R,G,B值的int二维数组
Graphics g;
public ButtonListener(Graphics g){
this.g=g;
}
public void actionPerformed(ActionEvent e) {
String str=e.getActionCommand();
if("打开文件".equals(str)){
//文件选择
JFileChooser jfc=new JFileChooser();
//显示对话框
jfc.showOpenDialog(null);
//返回选中的文件
File file=jfc.getSelectedFile();
try {
//文件输入流
java.io.FileInputStream fis=new java.io.FileInputStream(file);
//数据输入流
java.io.DataInputStream dis=new java.io.DataInputStream(fis);
//读取头文件信息长度数组
byte[] fb=new byte[flen];
dis.read(fb);
//读取位图信息头长度数组
byte[] bib=new byte[bilen];
dis.read(bib);
//调用change方法得到图片的宽度和高度
picture_width=change(bib,7);
picture_height=change(bib,11);
//读取位图的RGB颜色数据
readRGB(dis);
//关闭文件
dis.close();
} catch (Exception e1) {
e1.printStackTrace();
System.out.println("文件读取出错啦~~~~");
}
paint(g);
}
}
/**
*将byte数组的byte信息并转化为int型返回的方法
* @param b:byte数组
* @param n:数组中的位置
* @return:所得的int型数值
*/
public int change(byte[] b,int n){
int t=(((int)b[n]&0xff)<<24)|
(((int)b[n-1]&0xff)<<16)|
(((int)b[n-2]&0xff)<<8)|
((int)b[n-3]&0xff);
return t;
}
/**
* 读取RGB数据的方法
* @param dis:数据输入流
*/
public void readRGB(java.io.DataInputStream dis){
//判断每行是否后面有补0 的情况(是否为4的倍数)
if(!(picture_width*3%4==0)){//图片的宽度不为0
skip_width=4-picture_width*3%4;
}
//实例化存储R,G,B数据的数组,行数和列数分别与图片的宽度和高度对应
pictureR=new int[picture_height][picture_width];
pictureG=new int[picture_height][picture_width];
pictureB=new int[picture_height][picture_width];
//按行读取,从左下开始倒着读
for(int h=picture_height-1;h>=0;h--){
for(int w=0;w<picture_width;w++){
try {
//读取R,G,B三原色的数据,注意原是按蓝绿红的顺序
int blue=dis.read();
int green=dis.read();
int red=dis.read();
//将数据存放进R,G,B数组
pictureR[h][w]=red;
pictureG[h][w]=green;
pictureB[h][w]=blue;
//跳过补0项
if(w==0)
dis.skip(skip_width);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
*
* 绘制图片的方法
* @param g:画布
*/
public void paint(Graphics g){
for(int h=picture_height-1;h>=0;h--){
for(int w=0;w<picture_width;w++){
g.setColor(new Color(pictureR[h][w],pictureG[h][w],pictureB[h][w]));
g.drawLine(w+200, h+100, w+200, h+100);
}
}
}
}
以上步骤就是对Bmp文件的一个读取过程,其中读取RGB颜色是一个困扰点,有好几次颜色读取跟原本图像不一致,只好好多个小组也遇到了同样的问题,最后发现是RGB三个色素的读取的顺序不一致导致的。在读取完数据后,我们调用paint方法,再重新调用刚才读取到的数据,用自己的方式画出之前一样的图形。
我们重绘出之前图形的原理就是,从某一个边开始一个像素点一个像素点的画点,每个点有自己的一个RGB属性,构成了它的颜色,这样一个点一个点拼凑出来就成了之前一样的图像BMP格式的文件了。
package lyw.summer0628V3;
import java.awt.FlowLayout;
import java.awt.Graphics;
import javax.swing.JButton;
import javax.swing.JFrame;
/**
* BMP图片的读取与显示
* @author liuyuwen
*/
public class BMPPicture {
/**
* 图片显示面板的方法
*/
public void showUI(){
JFrame jf=new JFrame();
jf.setTitle("BMP文件读取");
jf.setSize(800, 650);
jf.setDefaultCloseOperation(3);
jf.setVisible(true);
FlowLayout fl=new FlowLayout(); //设置布局为流布局
jf.setLayout(fl);
//通过按钮打开文件
JButton jb=new JButton("打开文件");
jb.setSize(30, 30);
jf.add(jb);
Graphics g=jf.getGraphics();
ButtonListener bl=new ButtonListener(g);
jb.addActionListener(bl);
}
/**
* 主函数@param args
*/
public static void main(String[] args) {
BMPPicture bp=new BMPPicture();
bp.showUI();
}
}
BLABLA~~~这些是用自己写的打开的图片,没有失真。。
<!--EndFragment-->在之后听到关于图片马赛克的事,然后就考虑了一下,发现其实跟图片解析没什么大的关系,只是重绘时画图的问题,就试着写了一下代码^O^....本来的想法是,把图片的每个像素点放大,然后就会出现马赛克的效果。。。。结果忘记考虑像素点放大图片也会放大,所以这个马赛克的效果只是把小的图片弄成”马赛克风“。。然后图片也变大了。。。+_+...
其实只是改了一下paint里面的方法。。。
public void paint(Graphics g){
for(int h=picture_height-1;h>=0;h--){
for(int w=0;w<picture_width;w++){
g.setColor(new Color(pictureR[h][w],pictureG[h][w],pictureB[h][w]));
System.out.println("颜色="+ pictureG[h][w]+" "+ pictureB[h][w]+" "+ pictureR[h][w]);
g.fillRect(w+100+9*w,h+100+9*h,10,10);
System.out.println("x="+w+"y="+h);
System.out.println("我执行啦");
}
}
}
但是。。本来打开是这样的图片……
。
。
。
变成了……
放大版。。
所以只能把图片全部缩小……
成这个样子。。。
才能出来一般大小的马赛克。。。
所以马赛克效果的制作还没我想象的那么简单,他需要不改变图像的大小,在某一范围内或者某个N*N的像素点的范围内,把那个范围内的不同颜色的像素点变成同一个颜色值。。这样应该才能算是真正的马赛克效果吧(不然像“可以给我脸打上马赛克吗”之类的。。。那么脸该会被放成多大啊。。。=_=|||)
<!--EndFragment-->