本节书摘来自华章计算机《Java数字图像处理:编程技巧与应用实践》一书中的第2章,第2.1节,作者 贾志刚,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
第2章
Java BufferedImage对象及其支持的API操作
第1章我们一起学习了Java中的Graphics图形包基本概念与知识,本章将介绍Java中关于图像文件操作的基本知识。首先是Java 2D图像对象BufferedImage的组件构成、与图像文件之间的关系、格式支持,以及如何利用BufferedImage对象在Java语言中实现像素读写操作。然后通过BufferedImageOp接口介绍Java中几种非常有用的对像素操作的Buffered-ImageOp的实现类。最后将集合上述知识点,实现一个简单Java Swing的滤镜程序,帮助读者实现学以致用,加深理解。
在介绍本章内容之前,笔者假设你已经掌握了基本Java语言编程知识,学习过简单的Swing程序,同时对图像文件的格式及其特点有一些简单的了解。这些知识点可以帮助你更好地学习本章内容。
2.1 BufferedImage对象的构成
BufferedImage是一个内存对象,当通过ImageIO.read()方法读取一个图像文件时,读取到的关于图像文件的所有信息都会被存储在该API返回的BufferedImage内存对象中。此外还可以通过BufferedImage类的构造函数来创建BufferedImage内存对象。BufferedImage对象中最重要的两个组件为Raster与ColorModel,分别用于存储图像的像素数据与颜色数据,BufferedImage中的其他属性还包括宽、高、图像类型等。当需要对BufferedImage对象实现一些像素级别的操作时,调用Raster对象总是有点道理,如果做个形象的比喻,Raster就好像一个像素操作的场地,任何像素读写操作都可以通过调用Raster相关接口来完成。一个完整的BufferedImage构成类关系图如图2-1所示。
2.1.1 Raster对象的作用与像素存储
由于Raster对象是BufferedImage对象中的像素数据存储对象,因此,BufferedImage支持从Raster对象中获取任意位置(x,y)点的像素值p(x,y)。对于任意的BufferedImage对象来说,拥有越多的像素,Raster对象需要的内存空间也就越大,同时Raster对象需要的内存空间的大小还跟每个像素需要存储的字节数有一定的关系。首先来探讨一下如何从Raster对象获取像素数据,从Raster对象中读取BufferedImage全部像素数据的代码如下:
public int[] getRGB(BufferedImage image, int x, int y,
int width, int height, int[] pixels) {
int type = image.getType();
if (type == BufferedImage.TYPE_INT_ARGB
|| type == BufferedImage.TYPE_INT_RGB) {
return (int[]) image.getRaster().
getDataElements(x, y, width,height, pixels);
} else {
return image.getRGB(x, y, width,
height, pixels, 0, width);
}
}
上述方法实现了从Raster中读取像素数据,其中x, y表示开始的像素点,width与height表示像素数据的宽度与高度,pixels数组用来存放获取到的像素数据,image是一个BufferedImage的实例化引用。向BufferedImage对象实例中写入像素数据需要通过Raster来完成,其代码如下:
public void setRGB(BufferedImage image, int x, int y,
int width, int height, int[] pixels) {
int type = image.getType();
if (type == BufferedImage.TYPE_INT_ARGB
|| type == BufferedImage.TYPE_INT_RGB) {
image.getRaster().
setDataElements(x, y, width, height, pixels);
} else {
image.setRGB(x, y, width, height,
pixels, 0, width);
}
}
上面的代码演示了如何从Raster对象中读取与写入像素数据,也许有读者会问如何自己创建一个Raster对象呢?其实,Java图像的API操作中已经提供了这样的功能,实现代码如下:
Raster.createWritableRaster(sm, db, null);
其中sm指的是SampleModel对象实例,db表示DataBuffer对象实例,最后一个参数Point参数默认为null。如何创建SampleModel将在下一小节中详细解释。
2.1.2 图像类型与ColorModel
从前面的内容可以知道,BufferedImage对象中最重要的一个组件是ColorModel对象,最常用的实现类是IndexColorModel,下面就以此为例来演示如何创建与使用ColorModel对象。首先来看如何创建一个IndexColorModel对象,IndexColorModel的构造函数有五个参数,分别为:
- Bits:表示每个像素的所占的位数,对RGB单色来说是8位。
- Size:表示颜色组件数组长度,对于RGB取值范围0~255而言,值为256。
- r[]:字节数组r表示颜色组件的RED值数组。
- g[]:字节数组r表示颜色组件的GREEN值数组。
- b[]:字节数组r表示颜色组件的BLUE值数组。
通常而言,每个单色所占的位数都在1~16之间,size值必须大等于1。正确创建Index-ColorModel的代码如下:
public IndexColorModel getColorModel() {
byte[] r = new byte[256];
byte[] g = new byte[256];
byte[] b = new byte[256];
for (int i = 0; i < 256; i++)
{
r[i] = (byte) i;
g[i] = (byte) i;
b[i] = (byte) i;
}
return new IndexColorModel(8, 256, r, g, b);
}
BufferedImage对象中最重要的两个组件如何创建我们都知道了,下面一小节就一起来看看BufferedImage对象本身是如何创建的。
2.1.3 BufferedImage对象的创建与保存
根据不同的使用场景创建一个BufferedImage对象有三种常见方法,第一种方法是创建一个全新的BufferedImage对象,直接调用BufferedImage的构造函数。这在图形绘制的程序中比较常见,其代码如下:
BufferedImage bi = new BufferedImage(width, height,
BufferedImage.TYPE_BYTE_GRAY);
其中width表示图像的宽度,height表示高度,最后一个参数声明图像字节灰度图像。
第二种方法是根据已经存在的BufferedImage对象来创建一个相同的copy体。这种方法在图像处理的程序中最常见,其代码如下:
public BufferedImage createBufferedImage(BufferedImage src) {
ColorModel cm = src.getColorModel();
BufferedImage image = new BufferedImage(cm,
cm.createCompatibleWritableRaster(
src.getWidth(),
src.getHeight()),
cm.isAlphaPremultiplied(), null);
return image;
}
第三种方法是通过创建ColorModel与Raster对象实现BufferedImage对象的实例化,其代码如下:
public BufferedImage createBufferedImage(int width,
int height,
byte[] pixels) {
ColorModel cm = getColorModel();
SampleModel sm =
getIndexSampleModel((IndexColorModel) cm,
width, height);
DataBuffer db = new DataBufferByte(pixels,
width * height, 0);
WritableRaster raster =
Raster.createWritableRaster(sm, db, null);
BufferedImage image = new BufferedImage(cm,
raster, false, null);
return image;
}
上述几种方法都是关于如何创建一个BufferedImage对象的,下面来看一下如何保存BufferedImage对象为本地图像文件。Java中提供了ImageIO工具类来实现图像文件与BufferedImage对象之间的转换,读取一个图像文件时使用如下代码即可:
public BufferedImage readImageFile(File file)
{
try {
BufferedImage image = ImageIO.read(file);
return image;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
保存BufferedImage对象为图像文件的代码如下:
public void writeImageFile(BufferedImage bi)
throws IOException
{
File outputfile = new File("saved.png");
ImageIO.write(bi, "png",outputfile);
}
2.1.4 一个完整的ImageBuffered读取例子
本例将会演示前面所讲到的关于BufferedImage对象的所有知识点,包括像素的读取、Raster对象的创建、ColorModel的使用等。下面的代码演示了通过获取鼠标位置改变图像ColorModel对象索引,从而实现图像像素自动变化的方法。
package com.book.chapter.two;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BufferedImageDemo extends JPanel
implements MouseMotionListener {
private BufferedImage image = null;
private int width = 350;
private int height = 350;
public BufferedImageDemo() {
image = createImage();
addMouseMotionListener(this);
}
@Override
public void mouseMoved(MouseEvent e) {
// 创建新的图片,基于新的颜色模型索引
image = new BufferedImage(createColorModel(e.getX()),
image.getRaster(), false, null);
repaint();
}
@Override
public void mouseDragged(MouseEvent e) {
}
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
if(image != null) {
g2d.drawImage(image, 2, 2,
width, height, null);
}
}
private BufferedImage createImage() {
byte[] pixels = new byte[width * height];
DataBuffer dataBuffer = new DataBufferByte(pixels,
width*height, 0);
SampleModel sampleModel = new
SinglePixelPackedSampleModel(
DataBuffer.TYPE_BYTE,
width, height, new int[] {(byte)0xf});
WritableRaster raster = Raster.createWritableRaster(
sampleModel, dataBuffer, null);
return new BufferedImage(createColorModel(0),
raster, false, null);
}
private static ColorModel createColorModel(int n) {
byte[] r = new byte[16];
byte[] g = new byte[16];
byte[] b = new byte[16];
for (int i = 0; i < r.length; i++) {
r[i] = (byte) n;
g[i] = (byte) n;
b[i] = (byte) n;
}
return new IndexColorModel(4, 16, r, g, b);
}
public static void main(String[] args) {
JFrame ui = new JFrame("BufferedImage Demo");
ui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ui.getContentPane().setLayout(new BorderLayout());
ui.getContentPane().add(new BufferedImageDemo(),
BorderLayout.CENTER);
ui.setPreferredSize(new Dimension(380, 380));
ui.pack();
ui.setVisible(true);
}
}