图片的打开与保存(基于画图板)
上次的博客中可以看出,我们已经基本实现了画图板的基本功能。那么最后的一步,就是打开和保存。
在此之前,我们首先要加一个菜单条(我们的画图板是模仿win自带的)。菜单条的代码如下:
JMenuBar menubar = new JMenuBar();
// 创建一个菜单的数组
String[] menu1 = { "文件", "编辑", "关于" };
String[][] menu2 = { { "新建", "打开", "保存" }, { "撤销", "还原" }, { "关于" } };
String[][] commands = { { "xinjian", "dakai", "baocun" },
{ "chexiao", "huanyuan" }, { "guanyu" } };
// 创建一个菜单监听器
Filelistener listener1 = new Filelistener(centre);
// 遍历
for (int i = 0; i < menu1.length; i++) {
// 创建菜单对象
JMenu menu = new JMenu(menu1[i]);
menubar.add(menu);
for (int j = 0; j < menu2[i].length; j++) {
// 创建菜单选项
JMenuItem item = new JMenuItem(menu2[i][j]);
// 加在菜单上
menu.add(item);
// 设置选项的动作命令
item.setActionCommand(commands[i][j]);
// 给每个选项加监听器,注意,这是是item
item.addActionListener(listener1);
}
}
this.setJMenuBar(menubar);
在此要注意两点:
1:加监听器时候,是给item加,也即每一个选项加。
2:菜单以数组的形式创建,例如,文件下面包括了新建,保存,和打开。
3:设置动作命令时候,要把数组commands[i][j]传进来。
下面就是菜单的监听器,代码如下:
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.filechooser.FileNameExtensionFilter;
public class Filelistener implements ActionListener {
//要把画板centre传过来
private JPanel centre;
//声明机器人对象
java.awt.Robot robot;
//创建一个重绘的二维数组
int[][] keep;
public Filelistener(JPanel centre) {
this.centre=centre;
//robot
try {
robot=new java.awt.Robot();
} catch (AWTException e) {
e.printStackTrace();
}
}
public void actionPerformed(ActionEvent e) {
String commen = e.getActionCommand();
if ("xinjian".equals(commen)) {
System.out.print("xinjian");
}
/**
* 这里是保存
*/
if ("baocun".equals(commen)) {
// 弹出文件选择框
javax.swing.JFileChooser c = new javax.swing.JFileChooser();
// 过滤
javax.swing.filechooser.FileNameExtensionFilter f = new javax.swing.filechooser.FileNameExtensionFilter(
"BMP images", "BMP");
c.setFileFilter(f);
c.showSaveDialog(null);
//得到选择文件的路径
String path1=c.getSelectedFile().getAbsolutePath();
//保存图像
baocun b=new baocun(path1);
b.keep(path1);
System.out.print("baocun");
/************************************保存************************************************/
//得到centre左上角的坐标,相对于屏幕
Point left=centre.getLocationOnScreen();
//得到宽度 高度
Dimension dimension=centre.getPreferredSize();
//创建截取的区域对象
Rectangle rectangle=new Rectangle(left,dimension);
//截图
java.awt.image.BufferedImage image=robot.createScreenCapture(rectangle);
//创建二维数组
keep=new int[image.getHeight()][image.getWidth()];
//遍历
for(int i=0;i<keep.length;i++){
for(int j=0;j<keep[i].length;j++){
//获取坐标点的颜色
keep[i][j]=image.getRGB(j, i);
}
}
}
/**
* 这里是读取
*/
if ("dakai".equals(commen)) {
// 弹出文件选择框
JFileChooser chooser = new JFileChooser();
// 过滤
FileNameExtensionFilter filter = new FileNameExtensionFilter(
"BMP Images", "BMP");
chooser.setFileFilter(filter);
System.out.print("过滤");
chooser.showOpenDialog(null);
// 得到选择文件的路径
String path = chooser.getSelectedFile().getAbsolutePath();
// 解析,显示图像
jiexi jie = new jiexi( path, centre);
jie.show(path);
}
}
}
下面我们要做的,就是写一个解析(就是打开)的类和保存的类。
下面先来说明解析类。
首先,要做保存和打开,我们要知道bmp图片的格式信息。
此段转自百度:
位图文件可看成由4个部分组成:位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、彩色表(color table)和定义位图的字节阵列,它具有如下所示的形式。
http://baike.baidu.com/view/189487.htm
网址如下,我就不在这里黏贴一次了。
打开有两步:
1:显示出bmp文件。
2:将其转成int。
1:显示出,代码如下:
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JPanel;
public class jiexi {
// 画布
private JPanel centre;
// 窗口的画笔
private java.awt.Graphics g;
// 文件打开的路径
private String path = null;
// 图像大小
//private int size;
// 读取的两个保留字
//private int c1, c2;
// 数据偏移量
//private int dataoffset;
// 图片的宽高
private int width, heigh;
// 存放像素点的颜色
private int R[][], G[][], B[][];
// 宽取模??
int skipwidth = 0;
public jiexi(String path, JPanel centre) {
this.path = path;
this.centre = centre;
}
// public void paint(java.awt.Graphics g) {pubi
// for (int i = 0; i < heigh; i++) {
// for (int j = 0; j < width; j++) {
// g.setColor(new java.awt.Color(R[i][j], G[i][j], B[i][j]));
// g.fillOval(j, i, 1, 1);
// }
//
// }
// }
/**
* 显示出BMP文件
*/
public void show(String path) {
try {
// 文件输入流
java.io.FileInputStream input = new java.io.FileInputStream(path);
// 将文件流包装成可写基本数据类型的输出流
java.io.DataInputStream input1 = new java.io.DataInputStream(input);
// 读取14字节bmp文件头
// int bflen = 14;
// 定义数组来存放
// byte bf[] = new byte[bflen];
// input1.read(bf, 0, bflen);
// 读取40字节的信息头
// int bilen = 40;
// byte bi[] = new byte[bilen];
// input1.read(bi, 0, bilen);
// 获取重要数据
// 原图宽
// width = ChangeInt(bi, 7);
// 原图高
// heigh = ChangeInt(bi, 11);
//bmp图片的信息头
int len = 54;
byte b[] = new byte[len];
input1.read(b, 0, len);
width = ChangeInt(b, 21);
heigh = ChangeInt(b, 25);
// 存入重绘的数组
Drawlistener.keep = new int[heigh][width];
centre.setPreferredSize(new Dimension(width, heigh));
// 按照行读取
if (width * 3 % 4 != 0) {
input1.skip(width * 3 % 4);
}
//
// (从下往上,从左往右)
for (int i = heigh - 1; i >= 0; i--) {
for (int j = 0; j < width; j++) {
// 读取三原色
int blue = input1.read();
int green = input1.read();
int red = input1.read();
// 将三原色转换为颜色
Color color = new Color(red, green, blue);
// 将相对应的颜色转为整形存入数组中(下标极为重要)
Drawlistener.keep[i][j] = color.getRGB();
}
}
centre.repaint();// 调用重绘的方法
// ???
// int nbitcount = (((int) bi[15] & 0xff) << 8) | (int) bi[14] &
// 0xff;
// / System.out.println("位数:" + nbitcount);
// int nsizeimage = ChangeInt(bi, 23);
// System.out.println("源图大小:" + nsizeimage);
// 关闭文件流
input.close();
input1.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// public void showRGB24(DataInputStream input1) {
// this.setTitle(path);
// 弹出一个图片的窗口的大小
// this.setSize(width, heigh);
// this.setResizable(false);
// this.setVisible(true);
// g = this.getGraphics();
// 如果图像的宽度不为0
// //if (!(width * 3 % 4 == 0)) {
// skipwidth = 4 - width * 3 % 4;
// }
// R = new int[heigh][width];
// G = new int[heigh][width];
// B = new int[heigh][width];
// 按行读取,如果i,j为正则倒着来 i=heigh,j=width
// for (int i = heigh - 1; i >= 0; i--) {
// for (int j = 0; j < width; j++) {
// 读入三原色
// try {
// int red = input1.read();
// int green = input1.read();
// int blue = input1.read();
// R[i][j] = red;
// G[i][j] = green;
// B[i][j] = blue;
// 这里小心
// if(j==width-1){
// System.out.print(input1.skipBytes(skipwidth));
// }
// / } catch (IOException e) {
// e.printStackTrace();
// / }
// }repaint();
// }
// }
我将第一次做的代码也贴上来,方便对比。(就是屏蔽掉的部分)
1:我第一次写的时候,将bmp的文件头拆开来写,实际上,并没有必要。因为现在是读取,没有必要将文件头和信息头分 开两个数组来存放。我们直接将其存在同一个数组里面就可。
2:关于颜色的储存方式。首先,没必要去弹窗口神马的。第二,我第一次写的时候,将三原色分别储存在一个二维的数组 里面,这样显得十分麻烦(并且有时候也会出bug)。实际上,我们直接用的文件流来读取(在前一篇blog里面有关于 文件流的详细分析和代码示例),所以直接用文件流来读取三原色就可以了。然后,将颜色存入同一个数组就可以。这 就和文件头和信息头一样,既无必要分开,也不容易出错。
最后一步,就是转成int,因为bmp格式的图片是以byte保存的,我们要读取出来,就要专程成int,代码如下:
/**
* 转成int
*
* @param bi
* @param i
* @return
*/
public int ChangeInt(byte[] bi, int i) {
return (((int) bi[i] & 0xff) << 24) | (((int) bi[i - 1] & 0xff) << 16)
| (((int) bi[i - 2] & 0xff) << 8) | (int) bi[i - 3] & 0xff;
}
}
至此,我们的打开就完成了。
下面,是将图片保存成bmp格式。首先,代码如下:
import java.awt.Color;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class baocun {
private String path1;
private int width, heigh;
public baocun(String path1) {
this.path1 = path1;
}
public void keep(String path1) {
// 输出
try {
java.io.FileOutputStream output = new java.io.FileOutputStream(
path1);
java.io.BufferedOutputStream op1 = new java.io.BufferedOutputStream(
output);
/**
* 文件头 14个字节
*/
op1.write((byte) 66);// b
op1.write((byte) 77);// m
// 图片文件的大小,这里要自己算出来
int size = (4 - (width * 3) % 4) * heigh + heigh * width * 3 + 54;
// 2--5字节,转成int
op1.write(turnByte((int) size));
// 有4个字节的保留字
op1.write(0);
op1.write(0);
op1.write(0);
op1.write(0);
/**
* 信息头 54字节
*/
// 10--13字节,起始位置
op1.write(turnByte((int) 54));
// 存入信息头数据40
// 14--17,4个字节
op1.write(turnByte((int) 40));
op1.write(turnByte(width)); // 18-21 4个字节 图片宽度
op1.write(turnByte(heigh));// 22-25 4个字节 图片高度
// 26-27 2个字节 必须1
op1.write((byte) 1);
op1.write((byte) 0);
// 28-29 2个字节 必须24
op1.write((byte) 24);
op1.write((byte) 0);
op1.write(turnByte((int) 0)); // 30-34 4个字节 必须0
// 未完成
op1.write(turnByte((int) (size - 54))); // 34-37 4个字节 位图的大小,以字节为单位
op1.write(turnByte((int) 0)); // 38-41 4个字节 位图水平分辨率,
op1.write(turnByte((int) 0)); // 42-45 4个字节 位图垂直分辨率,
op1.write(turnByte((int) 0)); // 46-49 4个字节 位图实际使用的颜色表中的颜色数(全选)
op1.write(turnByte((int) 0)); // 50-53 4个字节 位图显示过程中重要的颜色数(都重要)
// 因为是24bmp图像,不需要颜色表
// 位图数据
int wid = 0;
// 判断是否后面有补0 的情况
if ((width * 3) % 4 != 0) {
wid = 4 - (width * 3) % 4;
}
// (从下往上,从左往右)
for (int i = heigh - 1; i >= 0; i--) {
for (int j = 0; j < width; j++) {
int[][] aa = new int[heigh][width];
Color c = new Color(aa[i][j]);
op1.write((byte) c.getBlue());
op1.write((byte) c.getGreen());
op1.write((byte) c.getRed());
}
for (int j = 0; j < wid; j++) {
op1.write((byte) 0);
}
}
// 关闭流
op1.flush();
op1.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 将其转成byte保存
private byte[] turnByte(int a) {
byte[] b = new byte[4];
for (int i = 0; i < 4; i++) {
b[i] = (byte) ((a >>> (8 * i)) & 0xFF); // 将int从低8位到高8位加入到byte数组中
}
return b;
}
}
我觉得保存的重点,1是要写出他的文件头和信息头,2是要记得转成byte来保存!