import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

import javax.swing.JFrame;

/**
 * 打开BMP格式图片的程序
 * 
 * @author XiongMinghua
 * 
 *         主函数
 */
public class OpenBMP extends JFrame {
 public static void main(String[] args) {
  OpenBMP obmp = new OpenBMP();
  String path = "D:\\My Documents\\StudySample\\src\\images\\Sunset.bmp";
  obmp.openFile(path);
 }

 private void openFile(String path) {
  try {
   // 创建文件输入流
   FileInputStream fis = new FileInputStream(path);
   // 把文件输入流包装成输入缓冲流
   BufferedInputStream bis = new BufferedInputStream(fis);
   // 读入BMP头文件的基本信息
   int bflen = 14;
   byte[] bf = new byte[bflen];
   bis.read(bf, 0, bflen);// 读取14字节的BMP文件头

   // 读入位图信息头
   int bilen = 40;
   byte[] bi = new byte[bilen];
   bis.read(bi, 0, bilen);// 读取40字节的BMP位图信息头

   /**
    * 获取一些重要信息: 位图的宽度、高度、每个像素点所需的位数(即图像的深度)、源图大小(测试时值为0)
    */
   image_width = changeInt(bi, 7);// 源图宽度
   image_height = changeInt(bi, 11);// 源图高度
   // 每个像素所需的位数,必须是1(双色)、4(16色)、8(256色)、24(真彩色)之一,(29-30字节)
   biBitCount = (((int) bi[15] & 0xff) << 8) | ((int) bi[14] & 0xff);

   sizeImage = changeInt(bi, 23);// 位图的大小,以字节为单位(35-38字节),测试结果为sizeImage=0;
   // 调用读取位图数据的方法,将数据存入imageR, imageB, imageG三个二维数组中
   readRGB(bis);
   // 关闭流
   bis.close();
   fis.close();
   showUI(path);

  } catch (Exception e) {
   e.printStackTrace();
  }

 }

 // 读取位图数据的方法
 public void readRGB(BufferedInputStream bis) {

  if (!(image_width * 3 % 4 == 0)) {
   skip_width = 4 - image_width * 3 % 4;
  }

  imageR = new int[image_height][image_width];
  imageG = new int[image_height][image_width];
  imageB = new int[image_height][image_width];
  for (int h = image_height - 1; h >= 0; h--) {
   for (int w = 0; w < image_width; w++) {
    int blue;
    try {
     blue = bis.read();// 先读取出蓝色
     int green = bis.read();// 接着读取出绿色
     int red = bis.read();// 最后读取的是红色
     imageR[h][w] = red;
     imageG[h][w] = green;
     imageB[h][w] = blue;
    } catch (IOException e) {
     e.printStackTrace();
    }

    if (w == 0) {
     // 跳过补0项
     try {
      bis.skip(skip_width);
     } catch (IOException e) {
      e.printStackTrace();
     }
    }
   }
  }
 }

 // 显示窗体的方法
 void showUI(String path) {
  this.setTitle(path);
  this.setSize(image_width, image_height);
  this.setDefaultCloseOperation(3);
  this.setVisible(true);
  this.getGraphics();
  repaint();// 调用重绘方法
 }

 // 重绘方法
 public void paint(java.awt.Graphics g) {
  super.paint(g);// 调用父类的重绘方法
  for (int h = 0; h < image_height; h++) {
   for (int w = 0; w < image_width; w++) {
    g.setColor(new java.awt.Color(imageR[h][w], imageG[h][w],
      imageB[h][w]));
    g.drawLine(w, h, w, h);
   }
  }
 }

 private 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));
 }

 /**
  * 1.BMP文件头(占14个字节)
  */
 int bfType;// 位图文件类型,占两个字节,第一个字节为字母'B',第二个字节为字母'M'(1-2字节)
 int bfSize;// 位图文件的大小,以字节为单位(3-6字节)
 // 第7-10字节为位图的保留字,其Int值为0
 // 位图数据的起始位置,以相对与位图(11-14字节)
 int bfOffBits;
 /**
  * 2.位图信息头(占40字节)
  */
 int size;// 位图信息头所占用字节数,为40个字节(15-18字节)
 int image_width;// 位图的宽度,以像素为单位(19-22字节)
 int image_height;// 位图的高度,以像素为单位(23-26字节)
 int planes;// 目标设备的级别,必须为1(27-28字节)
 int biBitCount;// 每个像素所需的位数,必须是1(双色)、4(16色)、8(256色)、24(真彩色)之一,(29-30字节)
 int biCompression;// 位图压缩类型,必须是0(不压缩)、1(BI_RLE8压缩类型)、2(BI_RLE4压缩类型)之一。(31-34字节)

 int sizeImage;// 位图的大小,以字节为单位(35-38字节)
 int biXPelsPerMeter;// 位图水平分辨率,每米像素数(39-42字节)
 int biYPelsPerMeter;// 位图垂直分辨率,每米像素数(43-46字节)
 int biClrUsed;// 位图实际使用的颜色表中的颜色数(47-50字节)
 int biClrImportant;// 位图显示过程中重要的颜色数(51-54字节)

 // 跳过补0项
 int skip_width;

 /**
  * 颜色表
  * 颜色表中的RGBQUAD结构数据的个数由biBitCount来确定。当biBitCount分别取1,4,8时,分别有2,16,256个表项;
  * 当biBitCount=24时,没有颜色表项
  */
 class RGBQUAD {
  byte rgbBlue;// 蓝色的深度(值范围为0-255)
  byte rgbGreen;// 绿色的深度(值范围为0-255)
  byte rgbRed;// 红色的深度(值范围为0-255)
  byte rgbReserved;// 保留,必须为0
 }

 // 用于存储RGB颜色数据的数组
 int[][] imageR, imageB, imageG;

}