Java代码变成位图是什么样子的?

前几天突发奇想,不知道如果把我们整天写的程序代码变成图片显示的话,看起来会是什么样子?

经过一番研究,编写出了这个小程序,如下图所示:



原来我们的程序看起来是如此地杂乱无章!^ ^

下面来说说这个小程序的实现过程。

我们知道,无论是文本文件还是二进制文件,都是由一个个字节组成的。如果把这些字节的值当作像素的颜色值来显示,不就能把程序变成图片了吗?

注:Java有BufferedImage类,可以在运行时修改图像的属性以及每个象素,但这里我想直接“写”一个位图,而不是使用BufferedImage这样的现有的类。

籍由此思路,我开始想到的是将Java程序转化为JPG格式,但一番考察之后,发现JPG格式很复杂。它有很多段,且各段之间有着紧密的关联,最重要的是,其中还有哈夫曼表这种难以逆推的数据结构。

因此我转而想到较为简单的BMP格式。在最简单的情况下,它仅由一个文件头和像素的颜色数据两部分组成,且保存的像素数据与显示的像素一一对应:


BMP格式的头部很简单,仅仅由54个字节组成,其中只有部分字节需要改变:

public static final int BITMAP_HEADER_LENGTH = 0x36;
public static final int[] HEADER = {
	0x42, 0x4D,   -1,   -1,   -1,   -1, 0x00, 0x00,
	0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
	0x00, 0x00,   -1,   -1,   -1,   -1,   -1,   -1,
	  -1,   -1, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
	0x00, 0x00,   -1,   -1,   -1,   -1, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
注意:BMP支持更复杂的特性(例如透明度、颜色表等),这里只是采用了最简单的一种BMP格式。

标为-1的字节是需要动态改变的,它们的含义如下:

  1. 第一行中的4个字节表示BMP文件的总字节数(包括文件头);
  2. 第三行的前4个字节表示图片的宽度,后面两个字节和第四行的两个字节表示图片的高;
  3. 第五行的4个字节则表示BMP文件的数据部分的字节数。

注意:这些数据都是采用“小端方式”存储的。即0x12345678依次存储为78 56 34 12四个字节。而如果采用相反的“大端方式”,则存储为12 34 56 78。

因此,最重要的就是要确定这些字段的值。相关代码如下:

public BitMapInputStream(File file) throws FileNotFoundException {
	long size = file.length();
	long pixelNum = size / PIXEL_BYTES;
	long widthHeight = (long) Math.sqrt(pixelNum);
	long rowBytesLength = widthHeight * PIXEL_BYTES / BYTE_ALIGNING * BYTE_ALIGNING;

	width = rowBytesLength / PIXEL_BYTES;
	height = widthHeight;
	totalBytesLength = width * height * PIXEL_BYTES;
	bitmapSize = totalBytesLength + BITMAP_HEADER_LENGTH;
	reader = new FileInputStream(file);
}

程序首先根据文件的长度初步计算出像素数(每个像素3个字节,RGB),然后再进一步粗略地计算出宽高。为什么说是粗略地呢?因为在BMP中,每一行像素的字节数必须是4字节的整数倍。上面代码中的:

long rowBytesLength = widthHeight * PIXEL_BYTES / BYTE_ALIGNING * BYTE_ALIGNING;

这一句就是用来将行字节凑成4的整数倍的。

构造方法的中的其余代码就较为简单了。

接下来是BitMapInputStream的read()方法:

@Override
public int read() throws IOException {
	++pointer;

	if (pointer >= BITMAP_HEADER_LENGTH)
		return reader.read();

	switch (pointer) {
	case 0x02: case 0x03: case 0x04: case 0x05: // bitmap total size
		return ((int)bitmapSize>>(8*(pointer-0x02))) & 0xFF;
	case 0x12: case 0x13: case 0x14: case 0x15: // width
		return ((int)width>>(8*(pointer-0x12))) & 0xFF;
	case 0x16: case 0x17: case 0x18: case 0x19: // height
		return ((int)height>>(8*(pointer-0x16))) & 0xFF;
	case 0x22: case 0x23: case 0x24: case 0x25: // data total length
		return ((int)totalBytesLength>>(8*(pointer-0x22))) & 0xFF;
	default:
		return HEADER[pointer];
	}
}
该方法依次返回BMP的每一个字节,供ImageIO使用,以构造一个BufferedImage。它根据要读取的字节在BMP文件中的偏移值来判断需要返回什么:如果偏移大于等于54(头部的长度),则返回所选择的文件中的字节;如果偏移是头部中需要动态计算的值,则用上面计算出的值代替(注意每个数值都要转化为小端方式存储的4个字节);否则就返回保存在HEADER数组中相应的字节。


下面附上全部代码:


import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.filechooser.FileFilter;

/**
 * @author Terry
 */
public class ProgramToBitmap {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new ShowPic();
	}
}

class ShowPic extends JFrame {
	private static final long serialVersionUID = -8283712863041322524L;
	//
	public static final int PIXEL_BYTES = 3;
	public static final int BYTE_ALIGNING = 4;
	public static final int BITMAP_HEADER_LENGTH = 0x36;
	public static final int[] HEADER = {
		0x42, 0x4D,   -1,   -1,   -1,   -1, 0x00, 0x00,
		0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
		0x00, 0x00,   -1,   -1,   -1,   -1,   -1,   -1,
		-1,   -1, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
		0x00, 0x00,   -1,   -1,   -1,   -1, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};

	private Canvas canvas;

	public ShowPic() {
		super("Choose your file");
		setLayout(null);
		setBounds(200, 200, 600, 560);

		canvas = new Canvas();
		canvas.setBounds(0, 0, 600, 480);
		add(canvas);

		JButton ChooseFileBtn = new JButton("Choose your file");
		ChooseFileBtn.setBounds(200, 485, 180, 30);
		ChooseFileBtn.addActionListener(new ChooseFileActionListenser());
		add(ChooseFileBtn);

		JButton helpBtn = new JButton("?");
		Font font = helpBtn.getFont();
		helpBtn.setFont(new Font(font.getName(), font.getStyle(), font.getSize()-4));
		helpBtn.setBounds(500, 490, 40, 25);
		helpBtn.addActionListener(new HelpActionListenser());
		add(helpBtn);

		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setVisible(true);
	}

	private void showBitMap(File file) throws IOException {
		BufferedImage image = ImageIO.read(new BitMapInputStream(file));
		canvas.paintBMP(image);
		setTitle(file.getName());
	}

	private class BitMapInputStream extends InputStream {
		private long width;
		private long height;
		private long totalBytesLength;
		private long bitmapSize;
		private FileInputStream reader;
		private int pointer = -1;

		public BitMapInputStream(File file) throws FileNotFoundException {
			long size = file.length();
			long pixelNum = size / PIXEL_BYTES;
			long widthHeight = (long) Math.sqrt(pixelNum);
			long rowBytesLength = widthHeight * PIXEL_BYTES / BYTE_ALIGNING * BYTE_ALIGNING;

			width = rowBytesLength / PIXEL_BYTES;
			height = widthHeight;
			totalBytesLength = width * height * PIXEL_BYTES;
			bitmapSize = totalBytesLength + BITMAP_HEADER_LENGTH;
			reader = new FileInputStream(file);
		}

		@Override
		public int read() throws IOException {
			++pointer;

			if (pointer >= BITMAP_HEADER_LENGTH)
				return reader.read();

			switch (pointer) {
			case 0x02: case 0x03: case 0x04: case 0x05: // bitmap total size
				return ((int)bitmapSize>>(8*(pointer-0x02))) & 0xFF;
			case 0x12: case 0x13: case 0x14: case 0x15: // width
				return ((int)width>>(8*(pointer-0x12))) & 0xFF;
			case 0x16: case 0x17: case 0x18: case 0x19: // height
				return ((int)height>>(8*(pointer-0x16))) & 0xFF;
			case 0x22: case 0x23: case 0x24: case 0x25: // data total length
				return ((int)totalBytesLength>>(8*(pointer-0x22))) & 0xFF;
			default:
				return HEADER[pointer];
			}
		}
	}

	private class ChooseFileActionListenser implements ActionListener {
		private File currentDir;
		@Override
		public void actionPerformed(ActionEvent event) {
			try {
				JFileChooser fileChooser = new JFileChooser();
				if (currentDir != null)
					fileChooser.setCurrentDirectory(currentDir);
				fileChooser.setFileFilter(new FileFilter() {
					@Override
					public String getDescription() {
						return "Java Files (.java, .class, .jar)";
					}
					@Override
					public boolean accept(File f) {
						if (f == null) return false;
						if (f.isDirectory()) return true;
						String name = f.getName().toLowerCase();
						if (name.endsWith(".java")
								|| name.endsWith(".class")
								|| name.endsWith(".jar"))
							return true;
						return false;
					}
				});
				int selection = fileChooser.showOpenDialog(ShowPic.this);
				if (selection == JFileChooser.APPROVE_OPTION) {
					File file = fileChooser.getSelectedFile();
					currentDir = file.getParentFile();
					showBitMap(file);
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
	}

	private class HelpActionListenser implements ActionListener {
		public static final String USAGE =
			"Choose a Java file, and then the program will show a picture by the file.";
		@Override
		public void actionPerformed(ActionEvent e) {
			JOptionPane.showMessageDialog(
					ShowPic.this, USAGE, "Help", JOptionPane.INFORMATION_MESSAGE);
		}
	}
}

class Canvas extends JPanel {
	private static final long serialVersionUID = 4177361418259831798L;
	private Image image;

	public void paintBMP(Image bmp) {
		image = bmp;
		repaint();
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		if (image != null) {
			g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
		}
	}
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值