读取24位bitmap(bmp)格式图片文件
//读取bmp图片文件
package pcm24;
import java.awt.Color;
import java.awt.Graphics;
import java.io.IOException;
public class BmpRead24 extends javax.swing.JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* 位图的宽
*/
private static int width;
/**
* 位图的高
*/
private static int height;
/**
* 位图数据数组,即一个像素的三个分量的数据数组
*/
private static int[][] red, green, blue;
Graphics g;
public static void main(String args[]) {
BmpRead24 bmp = new BmpRead24();
bmp.init();
}
public void init() {
try {
// 通过bmp文件地址创建文件输入流对象
java.io.FileInputStream fin = new java.io.FileInputStream(
"C:\\Documents and Settings\\专属于我\\桌面\\未命名1.bmp");
// 根据文件输入流对象创建原始数据输入对象
// 这里既可以用原始数据输入流来读取数据,也可以用缓冲输入流来读取,后者速度相比较快点。
// java.io.DataInputStream bis = new java.io.DataInputStream(fin);
java.io.BufferedInputStream bis = new java.io.BufferedInputStream(
fin);
// 建立两个字节数组来得到文件头和信息头的数据
byte[] array1 = new byte[14];
bis.read(array1, 0, 14);
byte[] array2 = new byte[40];
bis.read(array2, 0, 40);
// 翻译bmp文件的数据,即将字节数据转化为int数据
// 通过翻译得到位图数据的宽和高
width = ChangeInt(array2, 7);
height = ChangeInt(array2, 11);
// 调用可以将整个位图数据读取成byte数组的方法
getInf(bis);
fin.close();
bis.close();
// 创建BMP对象来显示图画
showUI();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 实现可将四个字节翻译int数据的方法
*
* @param array2
* 存储字节的字节数组
* @param start
* 起始字节
* @return 返回翻译后的int数据
*/
public int ChangeInt(byte[] array2, int start) {
// 因为char,byte,short这些数据类型经过运算符后会自动转为成int数据类,
// 所以array2[start]&0xff的实际意思就是通过&0xff将字符数据转化为正int数据,然后在进行位运算。
// 这里需要注意的是<<的优先级别比&高,所以必须加上括号。
int i = (int) ((array2[start] & 0xff) << 24)
| ((array2[start - 1] & 0xff) << 16)
| ((array2[start - 2] & 0xff) << 8)
| (array2[start - 3] & 0xff);
return i;
}
/**
* 得到位图数据的int数组
*
* @param dis
* 数据输入流对象
*/
public void getInf(java.io.BufferedInputStream bis) {
// 给数组开辟空间
red = new int[height][width];
green = new int[height][width];
blue = new int[height][width];
// 通过计算得到每行计算机需要填充的字符数。
// 为什么要填充?这是因为windows系统在扫描数据的时候,每行都是按照4个字节的倍数来读取的。
// 因为图片是由每个像素点组成。而每个像素点都是由3个颜色分量来构成的,而每个分量占据1个字节。
// 因此在内存存储中实际图片数据每行的长度是width*3。
int skip_width = 0;
int m = width * 3 % 4;
if (m != 0) {
skip_width = 4 - m;
}
// 通过遍历给数组填值
// 这里需要注意,因为根据bmp的保存格式。
// 位图数据中height的值如果是正数的话:
// 那么数据就是按从下到上,从左到右的顺序来保存。这个称之为倒向位图。
// 反之就是按从上到下,从左到右的顺序来保存。这个则称之为正向位图。
for (int i = height - 1; i >= 0; i--) {
for (int j = 0; j < width; j++) {
try {
// 这里遍历的时候,一定要注意本来像素是有RGB来表示,
// 但是在存储的时候由于windows是小段存储,所以在内存中是BGR顺序。
blue[i][j] = bis.read();
green[i][j] = bis.read();
red[i][j] = bis.read();
// 这里一定要知道,其实系统在给位图数据中添加填充0的时候,都是加在每行的最后。
// 但是我们在使用dis.skipBytes()这个方法的时候,却不一定要在最后一列。
// 系统在填充数据的时候,在数据上加了标记。
// 所以dis.skipBytes()这个方法只要调用了,那么系统就会自动不读取填充数据。
if (j == 0) {
bis.skip(skip_width);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void showUI() {
// 对窗体的属性进行设置
this.setTitle("BMP解析");//设置标题
this.setSize(width, height);//设置窗体大小
this.setDefaultCloseOperation(3);//点击关闭,程序自动退出。
this.setResizable(false);//设置窗体大小不可以调节
this.setLocationRelativeTo(null);//设置窗体出现在屏幕中间
//创建自己的panel,用其来显示图形。
//因为如果将图片设置到窗体上显示时,因为jframe是一个复合组件,上面的组件有多个paint方法,所以在paint的时候会画两次,
//而panel是只需画一次。
MyPanel panel = new MyPanel();
java.awt.Dimension di = new java.awt.Dimension(width, height);//设置panel大小
panel.setPreferredSize(di);
this.add(panel);//窗体添加panel
this.setVisible(true);//使窗体可见。
}
public class MyPanel extends javax.swing.JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* 重写paint方法
*/
public void paint(Graphics g) {
// 这句话可写可不写,因为这句话是用来画jframe的contentPane的。
// 而这里我们已经在下面定义了contentPane的方法了
super.paint(g);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
g.setColor(new Color(red[i][j], green[i][j], blue[i][j]));
// 如果这里画点的话,是不能使用下面注释掉的方法的,不行的话,亲,自己试试吧
// 因为系统在画椭圆的时候,是先画出椭圆的外切矩形。而矩形的边框刚好是占据一个像素点。
// 因此也就出现了,jdk api中说g.drawOval的像素点是width+1,height+1。
// 如果亲,你有更好的理解,请告诉我们。欢迎交流!!!
// g.fillOval(j, i, 1, 1);
g.fillRect(j, i, 1, 1);// 这里可以使用画点的任何方法,除了上面那种特例。
}
}
}
}
}
编写bmp
package pcm24;
import java.awt.Color;
/**
*
* 类说明:实现BMP文件格式的保存
*
* @author 彭晨明 E-mail:[email protected]
*
* @version 创建时间:2012-2-5下午9:06:28
*
*/
public class BmpWrite24 {
/**
* 图形数据数组
*/
private Color[][] pointArray;
/**
* 图形的宽
*/
int width;
/**
* 图形的高
*/
int height;
/**
* BMPWrite构造器的重载,传入图形数据数组
*
* @param pointArray
*/
public BmpWrite24(Color[][] pointArray) {
this.pointArray = pointArray;
this.width = pointArray.length;
this.height = pointArray[0].length;
this.write();
}
/**
* 将数据传入内存
*/
public void write() {
try {
// 创建输出流文件对象
java.io.FileOutputStream fos = new java.io.FileOutputStream(
"C:\\Documents and Settings\\专属于我\\桌面\\未命名1.bmp");
// 创建原始数据输出流对象
java.io.DataOutputStream dos = new java.io.DataOutputStream(fos);
// 给文件头的变量赋值
int bfType = 0x424d; // 位图文件类型(0—1字节)
int bfSize = 54 + width * height * 3;// bmp文件的大小(2—5字节)
int bfReserved1 = 0;// 位图文件保留字,必须为0(6-7字节)
int bfReserved2 = 0;// 位图文件保留字,必须为0(8-9字节)
int bfOffBits = 54;// 文件头开始到位图实际数据之间的字节的偏移量(10-13字节)
// 输入数据的时候要注意输入的数据在内存中要占几个字节,
// 然后再选择相应的写入方法,而不是它自己本身的数据类型
// 输入文件头数据
dos.writeShort(bfType); // 输入位图文件类型'BM'
dos.write(changeByte(bfSize),0,4); // 输入位图文件大小
dos.write(changeByte(bfReserved1),0,2);// 输入位图文件保留字
dos.write(changeByte(bfReserved2),0,2);// 输入位图文件保留字
dos.write(changeByte(bfOffBits),0,4);// 输入位图文件偏移量
// 给信息头的变量赋值
int biSize = 40;// 信息头所需的字节数(14-17字节)
int biWidth = width;// 位图的宽(18-21字节)
int biHeight = height;// 位图的高(22-25字节)
int biPlanes = 1; // 目标设备的级别,必须是1(26-27字节)
int biBitcount = 24;// 每个像素所需的位数(28-29字节),必须是1位(双色)、4位(16色)、8位(256色)或者24位(真彩色)之一。
int biCompression = 0;// 位图压缩类型,必须是0(不压缩)(30-33字节)、1(BI_RLEB压缩类型)或2(BI_RLE4压缩类型)之一。
int biSizeImage = width * height;// 实际位图图像的大小,即整个实际绘制的图像大小(34-37字节)
int biXPelsPerMeter = 0;// 位图水平分辨率,每米像素数(38-41字节)这个数是系统默认值
int biYPelsPerMeter = 0;// 位图垂直分辨率,每米像素数(42-45字节)这个数是系统默认值
int biClrUsed = 0;// 位图实际使用的颜色表中的颜色数(46-49字节),如果为0的话,说明全部使用了
int biClrImportant = 0;// 位图显示过程中重要的颜色数(50-53字节),如果为0的话,说明全部重要
// 因为java是大端存储,那么也就是说同样会大端输出。
// 但计算机是按小端读取,如果我们不改变多字节数据的顺序的话,那么机器就不能正常读取。
// 所以首先调用方法将int数据转变为多个byte数据,并且按小端存储的顺序。
// 输入信息头数据
dos.write(changeByte(biSize),0,4);// 输入信息头数据的总字节数
dos.write(changeByte(biWidth),0,4);// 输入位图的宽
dos.write(changeByte(biHeight),0,4);// 输入位图的高
dos.write(changeByte(biPlanes),0,2);// 输入位图的目标设备级别
dos.write(changeByte(biBitcount),0,2);// 输入每个像素占据的字节数
dos.write(changeByte(biCompression),0,4);// 输入位图的压缩类型
dos.write(changeByte(biSizeImage),0,4);// 输入位图的实际大小
dos.write(changeByte(biXPelsPerMeter),0,4);// 输入位图的水平分辨率
dos.write(changeByte(biYPelsPerMeter),0,4);// 输入位图的垂直分辨率
dos.write(changeByte(biClrUsed),0,4);// 输入位图使用的总颜色数
dos.write(changeByte(biClrImportant),0,4);// 输入位图使用过程中重要的颜色数
// 因为是24位图,所以没有颜色表
// 通过遍历输入位图数据
// 这里遍历的时候注意,在计算机内存中位图数据是从左到右,从下到上来保存的,
// 也就是说实际图像的第一行的点在内存是最后一行
for (int i = height - 1; i >= 0; i--) {
for (int j = 0; j < width; j++) {
// 这里还需要注意的是,每个像素是有三个RGB颜色分量组成的,
// 而数据在windows操作系统下是小端存储,对多字节数据有用。
int red = pointArray[i][j].getRed();// 得到位图点的红色分量
int green = pointArray[i][j].getGreen();// 得到位图点的绿色分量
int blue = pointArray[i][j].getBlue();// 得到位图点的蓝色分量
byte[] red1 = changeByte(red);
byte[] green1 = changeByte(green);
byte[] blue1 = changeByte(blue);
dos.write(blue1,0,1);
dos.write(green1,0,1);
dos.write(red1,0,1);
}
}
//关闭数据的传输
dos.flush();
dos.close();
fos.close();
System.out.println("success!!!");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 将一个int数据转为按小端顺序排列的字节数组
* @param data int数据
* @return 按小端顺序排列的字节数组
*/
public byte[] changeByte(int data){
byte b4 = (byte)((data)>>24);
byte b3 = (byte)(((data)<<8)>>24);
byte b2= (byte)(((data)<<16)>>24);
byte b1 = (byte)(((data)<<24)>>24);
byte[] bytes = {b1,b2,b3,b4};
return bytes;
}
}