在这个项目中,我们利用的是swing开发做出的一个画图板的界面。在界面上实现了画图和保存的功能。但是由于自定义的保存方式导致只能用自定义的打开方式来打开,也就是说,我们用自定义画图板创建的图片别人根本打不开。-_-||
所以为了与国际接轨,与系统接轨,我们必须要使用系统的打开和保存方式来处理一张图片。以下用bmp格式为例。
因为bmp图片的保存格式是开源的,我们能简单的知道系统画图板是怎么保存一个bmp格式的图片的。这个网上百度可以搜到。
典型的BMP 图像文件由四部分组成:
① BMP文件头 (14 字节 )
BMP文件头数据结构含有 BMP 文件的类型、文件大小和位图起始位置等信息。其结构定义如下:
|
int bfType; // 位图文件的类型,必须为 ' B '' M '两个字母 (0-1字节 ) int bfSize; // 位图文件的大小,以字节为单位 (2-5 字节 ) int bfReserved1; // 位图文件保留字,必须为 0(6-7 字节 ) int bfReserved2; // 位图文件保留字,必须为 0(8-9 字节 ) int bfOffBits; // 位图数据的起始位置,以相对于位图 (10-13 字节 )
② 位图信息头(40 字节 )
BMP 位图信息头数据用于说明位图的尺寸等信息。
|
int Size; // 本结构所占用字节数 (14-17 字节 ) int image_width; // 位图的宽度,以像素为单位 (18-21 字节 ) int image_heigh; // 位图的高度,以像素为单位 (22-25 字节 ) int Planes; // 目标设备的级别,必须为 1(26-27 字节 ) int biBitCount;// 每个像素所需的位数,必须是 1(双色), 4(16 色 ) , 8(256 色 ) 或 24(// 真彩色 ) 之一 (28-29 字节) int biCompression; // 位图压缩类型,必须是 0( 不压缩 ), 1(BI_RLE8 压缩类型 ) 或// 2(BI_RLE4 压缩类型 ) 之一 (30-33 字节 ) int SizeImage; // 位图数据的大小,以字节为单位 (34-37 字节 ) int biXPelsPerMeter; // 位图水平分辨率,每米像素数 (38-41 字节 ) int biYPelsPerMeter; // 位图垂直分辨率,每米像素数 (42-45 字节 ) int biClrUsed;// 位图实际使用的颜色表中的颜色数 (46-49 字节 ) int biClrImportant;// 位图显示过程中重要的颜色数 (50-53 字节 )
3、调色板 ,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24 位的 BMP )就不需要调色板。所以,想当然,我选的是不需要调色板的24位的。所以下面做保存的时候直接跳过啦。
|
class BITMAPINFO { BITMAPINFOHEADER bmiHeader; // 位图信息头 RGBQUAD bmiColors[1]; // 颜色表 }
4、位图数据 ,这部分的内容根据BMP 位图使用的位数不同而不同,在 24 位图中直接使用 RGB ,而其他的小于 24 位的使用调色板中颜色索引值。
准备完成,下面是写代码阶段。
首先得swing界面中添加画布和菜单栏,对画布实现鼠标监听器,对菜单项实现事件监听器。然后在画布监听器中,实现鼠标按下,拖动,释放的方法,写的简陋,就只实现了这三个方法。在这个监听器中,我们把鼠标对画布的操作保存在缓存中,使用int数组保存一张图片中所有的像素点,在监听器的构造方法中初始化这个画图为白色。
public static int array[][]; //定义数组保存图片的像素点 public DrawListener(JPanel panel){ This.panel=panel; Dimension dim=panel.getPreferredSize(); array=new int[dim.height][dim.width]; for(int i=0;i<array.length;i++){ for(int j=0;j<array[i].length;j++){ //画布初始化,设置画布为白色 array[i][j]=Color.WHITE.getRGB(); } } }
在鼠标监听器里面,鼠标按下拖动的方法里面可以画连续的线段,可是,当你把画图板最小化在重新打开时你会发现你先前画的线条都没有了。这个怎么解决呢?办法是把这张图片的像素已经保存在电脑缓存中,在释放的方法里面把画布中的像素点保存在int数组当中,然后在每一次重新调用这个面板的时候(比如最小化后再打开就是一次重新调用)重绘这个画布中的像素点。如下面的代码所示:
public void mouseReleased(MouseEvent e) { //当鼠标释放的时候保存 Point p=panel.getLocationOnScreen(); Dimension dim=panel.getPreferredSize(); Rectangle rect=new Rectangle(p,dim); BufferedImage img=robot.createScreenCapture(rect); array=new int[img.getHeight()][img.getWidth()]; for(int i=0;i<array.length;i++){ for(int j=0;j<array[i].length;j++){ //遍历图片的宽高,把图片的像素点保存在int数组中,图片的宽高和数组的下表是相反的。 array[i][j]=img.getRGB(j, i); } } }
public void paint(Graphics g) { super.paint(g); for(int i=0;i<DrawListener.array.length;i++){ for(int j=0;j<DrawListener.array[i].length;j++){ g.setColor(new Color(DrawListener.array[i][j])); //重新绘制保存好的int数组中的像素。 g.drawLine(j, i, j, i); } } }
此时,自定义的画图板实现了简陋的绘画的功能了,接下来就遇到一开始说的问题了,画完之后的如何保存到硬盘中,并且让系统的画图软件也能打开我们保存的图片呢?实际上,我们在电脑上看到的图片真的是一张图片吗?不是的,它只是一组数据的集合。只是画图软件能知道怎么把这组数据还还原成一张图片显示在画图板中而已。
接下来,以bmp的24位保存格式为例。
首先是如何打开一个bmp格式的图片,这个先在自己系统的画图板中画好,并以bmp24位的格式保存。而从前面我们已经能够知道它是怎么个保存方法了。只要读取到我们所需要的图片的高度和宽度还有每一个点的像素点就能在自定义的画图板中把图片显示出来了。
FileInputStream fis=new FileInputStream(file1); fis.skip(18); int width=changeInt(fis);//跳过不需要的,读取宽度和高度 int height=changeInt(fis); fis.skip(28); //跳过,直接读取位图数据。 DrawListener.array=new int[height][width]; int t=0; if(width*3%4>0){ t=4-width*3%4; } for(int i=height-1;i>=0;i--){ for(int j=0;j<width;j++){ //调用自定义方法,得到图片的像素点并保存到int数组中 int c=readColor(fis); DrawListener.array[i][j]=c; } fis.skip(t); } fis.close(); //刷新,重绘面板,打开系统保存的图片。 panel.setPreferredSize(new Dimension(width, height)); SwingUtilities.updateComponentTreeUI(panel);
//由于读取的是字节,把读取到的4个byte转化成1个int public int changeInt(FileInputStream 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(FileInputStream 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; }
以上就完成打开的操作了,保存图片比打开图片复杂一点,不过也只要你依据正确的存储方式把数据存储起来系统的画图板就能识别你保存的数据打开你的图片了。部分代码如下。
if("savebmp".equals(commend)){ FileOutputStream fos=new FileOutputStream(file2); savebmpTop(fos); savebmpInfo(fos); savebmpDate(fos); fos.flush(); fos.close(); }
public void savebmpTop(OutputStream ops) throws Exception{ 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)*height; //位图文件的大小 size=14+40+hight*width*3+(4-width*3%4)*255 writeInt(ops,size); writeShort(ops,(short) 0); writeShort(ops,(short) 0); writeInt(ops,54); }
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); writeShort(ops, (short) 1); writeShort(ops, (short) 24); writeInt(ops,0); writeInt(ops,height*width*3+(4-width*3%4)*height); writeInt(ops,0); writeInt(ops,0); writeInt(ops,0); writeInt(ops,0); }
public void savebmpDate(OutputStream ops) throws Exception{ int height=DrawListener.array.length; int width=DrawListener.array[0].length; int m=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); } } }
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); } 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); } public void writeShort(OutputStream ops,short t) throws Exception{ int c=(t>>8)&0xff; int d=t&0xff; ops.write(d); ops.write(c); }
至此,整个保存bmp24位格式的代码就写完了,虽然只是部分代码,但还是感觉很多啊╮(╯▽╰)╭。每次我都是写完之后才感觉整个项目的难度系数不大。本项目就是系统是C语言的存储的字节使用Java处理起来有点小复杂而已。掌握了整个bmp格式的数据结构其实很容易实现。