欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

BMP文件读取修改保存_画图板_缓冲绘图

程序员文章站 2022-06-01 23:52:23
...

作者:杨振林

 

  本文将对BMP格式文件进行分析,实现对BMP文件读取、修改、保存的功能。

  首先对BMP文件进行分析,分析材料引自互联网。16进制。

 

偏移量

域的名称

大小

内容

 

 

 

图象文件

0000h

文件标识

2 bytes

两字节的内容用来识别位图的类型:

‘BM’ : Windows 3.1x, 95, NT, …

‘BA’ :OS/2 Bitmap Array

‘CI’ :OS/2 Color Icon

‘CP’ :OS/2 Color Pointer

‘IC’ : OS/2 Icon

‘PT’ :OS/2 Pointer

注:因为OS/2系统并没有被普及开,所以在编程时,你只需判断第一个标识“BM”就行。

 

0002h

File Size

1 dword

用字节表示的整个文件的大小

 

0006h

Reserved

1 dword

保留,必须设置为0

 

000Ah

Bitmap Data Offset

1 dword

从文件开始到位图数据开始之间的数据(bitmap data)之间的偏移量

 

000Eh

Bitmap Header Size

1 dword

位图信息头(Bitmap Info Header)的长度,用来描述位图的颜色、压缩方法等。下面的长度表示:

28h - Windows 3.1x, 95, NT, …

0Ch - OS/2 1.x

F0h - OS/2 2.x

注:在Windows95、98、2000等操作系统中,位图信息头的长度并不一定是28h,因为微软已经制定出了新的BMP文件格式,其中的信息头结构变化比较大,长度加长。所以最好不要直接使用常数28h,而是应该从具体的文件中读取这个值。这样才能确保程序的兼容性。

 

0012h

Width

1 dword

位图的宽度,以象素为单位

 

0016h

Height

1 dword

位图的高度,以象素为单位

 

001Ah

Planes

1 word

位图的位面数(注:该值将总是1)


图象

信息

 

 

001Ch

Bits Per Pixel

1 word

每个象素的位数

1 - 单色位图(实际上可有两种颜色,缺省情况下是黑色和白色。你可以自己定义这两种颜色)

4 - 16 色位图

8 - 256 色位图

16 - 16bit 高彩色位图

24 - 24bit 真彩色位图

32 - 32bit 增强型真彩色位图

 

001Eh

Compression

1 dword

压缩说明:

0 - 不压缩 (使用BI_RGB表示)

1 - RLE 8-使用8位RLE压缩方式(用BI_RLE8表示)

2 - RLE 4-使用4位RLE压缩方式(用BI_RLE4表示)

3 - Bitfields-位域存放方式(用BI_BITFIELDS表示)

 

0022h

Bitmap Data Size

1 dword

用字节数表示的位图数据的大小。该数必须是4的倍数

 

0026h

HResolution

1 dword

用象素/米表示的水平分辨率

 

002Ah

VResolution

1 dword

用象素/米表示的垂直分辨率

 

002Eh

Colors

1 dword

位图使用的颜色数。如8-比特/象素表示为100h或者 256.

 

0032h

Important Colors

1 dword

指定重要的颜色数。当该域的值等于颜色数时(或者等于0时),表示所有颜色都一样重要

调色板数据

根据BMP版本的不同而不同

Palette

N * 4 byte

调色板规范。对于调色板中的每个表项,这4个字节用下述方法来描述RGB的值:

1字节用于蓝色分量

1字节用于绿色分量

1字节用于红色分量

1字节用于填充符(设置为0)

图象数据

根据BMP版本及调色板尺寸的不同而不同

Bitmap Data

xxx bytes

该域的大小取决于压缩方法及图像的尺寸和图像的位深度,它包含所有的位图数据字节,这些数据可能是彩色调色板的索引号,也可能是实际的RGB值,这将根据图像信息头中的位深度值来决定。

 

第一步:读取

  将文件内容读取一个BMP文件,在文件中提取出“宽”、“高”、“图像数据”三项重要数据。将“图像数据”保存到数组中。这样我们就可以得出每一个点的R、G、B值。注意处理宽度时,由于每四个连续点保存在一起,当宽度到达边界时,如果不足四个,则该行由0补齐。所以在读的时候要跳过可能出现的0。skipWide变量表示的就是需要跳过的像素数(可能为0(不用跳)、1、2、3)。

 

	public void myRead(String str) {

		File f = new File(str);

		DataInputStream dis = null;
		try {

			FileInputStream fis = new FileInputStream(f);
			dis = new DataInputStream(fis);

			// 0-17 跳过
			byte[] bs1 = new byte[18];
			dis.read(bs1);

			// 0x0012h-0x0015h 18-21 宽
			Width = NewInt.readInt(dis);
			// 0x0016h-0x0019h 22-25 高
			Height = NewInt.readInt(dis);

			// 26-53 跳过
			byte[] bs2 = new byte[28];
			dis.read(bs2);

			// 图像数据区

			R = new int[Height][Width];
			G = new int[Height][Width];
			B = new int[Height][Width];

			if (!(Width * 3 % 4 == 0)) {
				skipWide = 4 - Width * 3 % 4;
			}

			for (int h = 0; h < Height; h++) {
				for (int w = 0; w < Width; w++) {
					int blue = dis.read();
					int green = dis.read();
					int red = dis.read();

					R[h][w] = red;
					G[h][w] = green;
					B[h][w] = blue;

					// 跳过补0
					if (w == Width - 1) {
						dis.skipBytes(skipWide);
					}
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				dis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

   需要注意的是:BMP文件中数据的保存格式是:以字节为单位,多字节数据低位字节在前,高位字节在后。例如 1B 0A 其实表示的是0x0A1Bh,即十进制2587。

  我在这里用了一个转换方法。

	/**
	 * 高低位反转
	 */
	public static int readInt(DataInputStream dis)throws IOException {
		int ch1 = 0, ch2 = 0, ch3 = 0, ch4 = 0;

			ch1 = dis.read();
			ch2 = dis.read();
			ch3 = dis.read();
			ch4 = dis.read();

		return ((ch1 << 0) + (ch2 << 0) + (ch3 << 16) + (ch4 << 24));
	}
	

 

  当文件读取到数据中之后,就可以遍历数组中的所有值,进行绘制了。如果不需要修改和保存,那么直接绘制到画布上就可以了。如果想实现修改和保存,就要用到缓冲绘图这个工具,因为缓冲绘图中有getRGB(x,y),这个方法,可以获取缓冲绘图中某个坐标点(x,y)的RGB值。此方法框架JFrame中没有。当绘制到缓冲绘图之后,将缓冲绘图中的内容复制到当前画布即可看到读取的BMP文件。

 

	public void drawBMP() {

		for (int h = 0; h < Height; h++) {
			for (int w = 0; w < Width; w++) {
				gg.setColor(new Color(R[h][w], G[h][w], B[h][w]));
				gg.drawLine(w, Height-h-1, w, Height-h-1);

			}
		}
	}

 

  将缓冲绘图中的内容复制到当前画布的代码是:g.drawImage(bf, 0, 0, null);

  至此,完成了读取操作。

 
BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 
 
BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 
 
BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 
 

第二步:修改

  本工程使用的修改方法是画图板的基本方法,可以参照 http://yangzhenlin.iteye.com/blog/1774822 由于不是本文重点,这里略写。需要注意的一点是,修改中绘制的图形都要先保存到缓冲绘图中,再复制到画布上;而不是直接画到画布上。使用MouseListener监听器。本文列出代码。

 

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import javax.swing.ButtonGroup;

/**
 * 画图板的监听器,实现鼠标监听器接口
 * 
 * @author YangZhenlin
 * 
 */
public class DrawListener implements MouseListener {

	private int x1, y1, x2, y2;
	private java.awt.Graphics g;
	private javax.swing.ButtonGroup group;
	private javax.swing.ButtonGroup typegroup;
	private String type = "rect";// 要绘制的形状类型:line直线 rect矩形 oval椭圆
	private String typetype = "draw";
	private Color color;// 要绘制的颜色
	private Draw du;
	private Graphics gg;
	private BufferedImage bf;

	public DrawListener(Graphics g, Graphics gg, ButtonGroup group,
			ButtonGroup typegroup, Draw du, BufferedImage bf) {
		this.g = g;
		this.gg = gg;
		this.group = group;
		this.typegroup = typegroup;
		this.du = du;
		this.bf = bf;

	}

	public void mousePressed(MouseEvent e) {
		// 要绘制的时候才需要知道即将绘制的形状
		// 得到按钮族中被选中的按钮
		javax.swing.ButtonModel bm = group.getSelection();
		// 得到按钮的动作命令,作为要绘制的形状类型
		type = bm.getActionCommand();

		javax.swing.ButtonModel tm = typegroup.getSelection();
		typetype = tm.getActionCommand();

		// 得到选中的颜色
		color = du.selectColor;
		// System.out.println(color);
		// 设置要绘制的颜色
		gg.setColor(color);


		x1 = e.getX();
		y1 = e.getY();
	}

	public void mouseReleased(MouseEvent e) {

		x2 = e.getX();
		y2 = e.getY();

		if (type.equals("line")) {
			// 画直线
			gg.drawLine(x1, y1, x2, y2);
			g.drawImage(bf, 0, 0, null);

		} else if (type.equals("rect")) {
			/**
			 * 画矩形
			 */
			if (typetype.equals("draw")) {
				gg.drawRect(Math.min(x1, x2), Math.min(y1, y2),
						Math.abs(x1 - x2), Math.abs(y1 - y2));
				g.drawImage(bf, 0, 0, null);
			} else if (typetype.equals("fill")) {
				gg.fillRect(Math.min(x1, x2), Math.min(y1, y2),
						Math.abs(x1 - x2), Math.abs(y1 - y2));
				g.drawImage(bf, 0, 0, null);

			} else if (typetype.equals("clear")) {
				gg.setColor(Color.WHITE);
				gg.fillRect(Math.min(x1, x2), Math.min(y1, y2),
						Math.abs(x1 - x2), Math.abs(y1 - y2));
				g.drawImage(bf, 0, 0, null);
			}

		} else if (type.equals("oval")) {
			if (typetype.equals("draw")) {
				gg.drawOval(Math.min(x1, x2), Math.min(y1, y2),
						Math.abs(x1 - x2), Math.abs(y1 - y2));
				g.drawImage(bf, 0, 0, null);
			} else if (typetype.equals("fill")) {
				gg.fillOval(Math.min(x1, x2), Math.min(y1, y2),
						Math.abs(x1 - x2), Math.abs(y1 - y2));
				g.drawImage(bf, 0, 0, null);
			} else if (typetype.equals("clear")) {
				gg.setColor(Color.WHITE);
				gg.fillOval(Math.min(x1, x2), Math.min(y1, y2),
						Math.abs(x1 - x2), Math.abs(y1 - y2));
				g.drawImage(bf, 0, 0, null);
			}
		} else if (type.equals("arc")) {
			if (typetype.equals("draw")) {
				gg.drawArc(Math.min(x1, x2), Math.min(y1, y2),
						Math.abs(x1 - x2), Math.abs(y1 - y2), 90, 90);
				g.drawImage(bf, 0, 0, null);
			} else if (typetype.equals("fill")) {
				gg.fillArc(Math.min(x1, x2), Math.min(y1, y2),
						Math.abs(x1 - x2), Math.abs(y1 - y2), 90, 90);
				g.drawImage(bf, 0, 0, null);
			} else if (typetype.equals("clear")) {
				gg.setColor(Color.WHITE);

				gg.fillArc(Math.min(x1, x2), Math.min(y1, y2),
						Math.abs(x1 - x2), Math.abs(y1 - y2), 90, 90);
				g.drawImage(bf, 0, 0, null);
			}
		} else if (type.equals("triangle")) {
			if (typetype.equals("draw")) {

				gg.drawLine(Math.min(x1, x2), Math.min(y1, y2),
						Math.max(x1, x2), Math.max(y1, y2));
				gg.drawLine(Math.min(x1, x2), Math.min(y1, y2),
						Math.min(x1, x2), Math.max(y1, y2));
				gg.drawLine(Math.min(x1, x2), Math.max(y1, y2),
						Math.max(x1, x2), Math.max(y1, y2));
				g.drawImage(bf, 0, 0, null);
			} else if (typetype.equals("fill")) {
				int xPoints[] = { Math.min(x1, x2), Math.min(x1, x2),
						Math.max(x1, x2) };
				int yPoints[] = { Math.min(y1, y2), Math.max(y1, y2),
						Math.max(y1, y2) };
				gg.fillPolygon(xPoints, yPoints, 3);
				g.drawImage(bf, 0, 0, null);
			} else if (typetype.equals("clear")) {
				int xPoints[] = { Math.min(x1, x2), Math.min(x1, x2),
						Math.max(x1, x2) };
				int yPoints[] = { Math.min(y1, y2), Math.max(y1, y2),
						Math.max(y1, y2) };
				gg.setColor(Color.WHITE);
				gg.fillPolygon(xPoints, yPoints, 3);
				g.drawImage(bf, 0, 0, null);
			}
		}

	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}

	public void mouseClicked(MouseEvent e) {
	}
}

 
BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 
 

第三步:保存

  经过前两步,修改好的数据目前保存在缓冲绘图中。需要先获取缓冲绘图中每个像素的RGB值,然后将RGB值整理好,并在宽度不为4整数的边缘地方补充0,最后做成一个一位数组填到新文件的图像数据区。同样,需要进行高低位转化。

 

高低位转化

	/**
	 * 写四位高低反转
	 */
	public static void writeInt(DataOutputStream dos, int i) throws IOException {
		dos.write(i);
		dos.write(i >> 8);
		dos.write(i >> 16);
		dos.write(i >> 24);
	}

	/**
	 * 写两位高低反转
	 */
	public static void writeShort(DataOutputStream dos, short i) throws IOException {
		dos.write(i);
		dos.write(i >> 8);
	}

 

保存

	public void write(BufferedImage bf, String out) {

		// 获取宽
		int width = bf.getWidth();
		// 三倍宽
		int triWidth = width * 3;
		// 获取高
		int height = bf.getHeight();
		// 三倍宽补0
		int fullTriWidth = 0;
		if (triWidth % 4 == 0) {
			fullTriWidth = triWidth;
		} else if (triWidth % 4 != 0) {
			// 整除取整,加1后变成四倍
			fullTriWidth = 4 * ((triWidth / 4) + 1);
		}
		// px是一个宽乘以高的二维数组
		int[][] px = new int[height][width];

		for (int h = 0; h < height; h++) {
			for (int w = 0; w < width; w++) {
				px[h][w] = bf.getRGB(w, h);
			}
		}

		// rgbcolor数组
		byte[][][] rgbcolor = new byte[height][width][3];

		// r, g, b数组,把px拆分开
		for (int h = 0; h < height; h++) {
			for (int w = 0; w < width; w++) {
				// 分拆24位色
				rgbcolor[h][w][0] = (byte) px[h][w];
				rgbcolor[h][w][1] = (byte) (px[h][w] >>> 8);
				rgbcolor[h][w][2] = (byte) (px[h][w] >>> 16);

			}
		}

		int num = 0;
		byte[] rgbs = new byte[triWidth * height];
		for (int h = 0; h < height; h++) {
			for (int w = 0; w < width; w++) {
				rgbs[num++] = (byte) rgbcolor[h][w][0];
				rgbs[num++] = (byte) rgbcolor[h][w][1];
				rgbs[num++] = (byte) rgbcolor[h][w][2];
			}
		}

		// 补齐扫描行长度为4的倍数
		byte[] fullrgbs = new byte[fullTriWidth * height];
		for (int h = 0; h < height; h++) {
			for (int w = 0; w < fullTriWidth; w++) {
				if (w < triWidth) {
					fullrgbs[fullTriWidth * h + w] = rgbs[(height-h-1) * triWidth + w];
				} else {
					fullrgbs[fullTriWidth * h + w] = 0;
				}
			}
		}

		DataOutputStream dos = null;
		try {
			FileOutputStream fos = new FileOutputStream(out);
			dos = new DataOutputStream(fos);
			dos.write('B');// 0
			dos.write('M');// 1
			NewInt.writeInt(dos, width * height * 3 + 54);// 2-5文件大小
			NewInt.writeInt(dos, 0);// 6-9保留
			NewInt.writeInt(dos, 54);// 10-13偏移量
			NewInt.writeInt(dos, 40);// 14-17头信息
			NewInt.writeInt(dos, width);// 18-21宽
			NewInt.writeInt(dos, height);// 22-25高
			NewInt.writeShort(dos, (short) 1);// 26-27 1帧数
			NewInt.writeShort(dos, (short) 24);// 28-29 24位数
			NewInt.writeInt(dos, 0);// 30-33 压缩
			NewInt.writeInt(dos, 4);// 34-37 size
			NewInt.writeInt(dos, 3800);// 38-41 水平分辨率
			NewInt.writeInt(dos, 3800);// 42-45 垂直分辨率
			NewInt.writeInt(dos, 0);// 46-49 颜色索引 0为所有
			NewInt.writeInt(dos, 0);// 50-53 重要颜色索引 0为所有

			// 写入所有图像数据
			dos.write(fullrgbs);
			dos.flush();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				dos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}


BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 

BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 
 

附JAR包

 

  • BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 
  • 大小: 90.5 KB
  • BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 
  • 大小: 24 KB
  • BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 
  • 大小: 91.2 KB
  • BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 
  • 大小: 78.9 KB
  • BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 
  • 大小: 59.8 KB
  • BMP文件读取修改保存_画图板_缓冲绘图
            
    
    博客分类: 文件BMPJava JAVABMP文件读写位操作缓冲绘图 
  • 大小: 80.7 KB
  • BMP.jar (10.3 KB)
  • 下载次数: 4