2.3 基于BufferedImageOp的图像滤镜演示
通过前面两节的学习,我们已经大致了解BufferedImageOp接口及其实现类的功能。实践出真知,本节将演示BufferedImageOp接口中每个实现类的实际使用场景,达到知行合一、学以致用的目的,帮助大家解决项目中遇到的实际问题。为了让大家对应用效果有更加深刻的印象,下面会使用BufferedImageOp的实现类来实现如下几个滤镜特效功能。
- 黑白滤镜:将彩色图像自动转换为黑白两色图像。
- 灰度滤镜:将彩色图像自动转换为灰度图像。
- 模糊滤镜:使图像产生模糊效果。
- 放缩滤镜:使图像放大或缩小。
1 . UI实现部分
在介绍基于Swing的UI实现时,关于Swing UI部分的编程知识将在下一章中详细剖析与解释,本节的重点放在滤镜实现部分,大致的UI布局如图2-3所示。
2.滤镜部分的实现
(1)ColorConvertOp实现灰度功能
ColorConvertOp主要用于实现各种色彩空间的转换,从而达到转换BufferedImage对象类型的目的,也可以在实例化ColorConvertOp对象时指定色彩空间。当前支持的色彩空间有五种,实现灰度功能时,只需在实例化ColorConvertOp时指定色彩空间为ColorSpace.CS_GRAY,然后调用它的filter方法得到返回图像即可。灰度化的源代码如下:
public BufferedImage doColorGray(BufferedImage bi)
{
ColorConvertOp filterObj = new ColorConvertOp(
ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
return filterObj.filter(bi, null);
}
(2)LookupOp 实现黑白功能
LookupOp在实例化时需要传入LookupTable实例,当前LookupTable接口的两个实现类分别为ByteLookupTable与ShortLookupTable。类关系图2-4可以很好地说明它们之间的关系。
运用LookupOp实现彩色图像变成黑白单色图像的功能时,首先要将图像灰度化,然后针对灰度图像在LookupTable中根据像素值进行索引查找,以便设置新的像素值,从而得到黑白单色图像,代码如下:
public BufferedImage doBinaryImage(BufferedImage bi)
{
bi = doColorGray(bi);
byte[] threshold = new byte[256];
for (int i = 0; i < 256; i++)
{
threshold[i] = (i < 128) ? (byte)0 : (byte)255;
}
BufferedImageOp thresholdOp =
new LookupOp(new ByteLookupTable(0, threshold), null);
return thresholdOp.filter(bi, null);
}
(3)ConvolveOp 实现模糊功能
ConvolveOp是实现模板卷积功能操作的类,通过简单设置卷积核/卷积模板就可以实现图像模糊功能,实现代码如下:
float ninth = 1.0f / 9.0f;
float[] blurKernel = {
ninth, ninth, ninth,
ninth, ninth, ninth,
ninth, ninth, ninth
};
BufferedImageOp blurFilter =
new ConvolveOp(new Kernel(3, 3, blurKernel));
return blurFilter.filter(bi, null);
但是当你想对大多数JPG格式图片的BufferedImage对象实现模糊功能时,很多情况下Java会抛出如下错误消息:
unable to convolve src image
原因在于JDK读入JPG格式图像时,多数情况下使用了TYPE_3BYTE_BGR存储方式,而BufferedImageOp实现的滤镜不支持操作该存储方式的BufferedImage对象,这样就导致了上面的错误。解决之道很简单,就是通过ColorConvertOp把图像从类型TYPE_3BYTE_BGR转换为TYPE_INT_RGB的BufferedImage对象。所以模糊功能的完整源代码如下:
public BufferedImage doBlur(BufferedImage bi)
{
// fix issue - unable to convolve src image
if (bi.getType()==BufferedImage.TYPE_3BYTE_BGR)
{
bi=convertType(bi,
BufferedImage.TYPE_INT_RGB);
}
float ninth = 1.0f / 9.0f;
float[] blurKernel = {
ninth, ninth, ninth,
ninth, ninth, ninth,
ninth, ninth, ninth
};
BufferedImageOp blurFilter =
new ConvolveOp(new Kernel(3, 3, blurKernel));
return blurFilter.filter(bi, null);
}
convertType方法的代码如下:
ColorConvertOp cco=new ColorConvertOp(null);
BufferedImage dest=new BufferedImage(
src.getWidth(), src.getHeight(), type);
cco.filter(src, dest);
return dest;
(4)AffineTransformOp实现图像zoom in/out的功能
AffineTransformOp支持的操作包括图像的错切、旋转、放缩、平移。要实现图像的放缩功能,首先要通过AffineTransform.getScaleInstance来获取Scale实例,然后作为参数初始化AffineTransformOp对象实例,最后调用filter方法即可。实现图像放缩功能的代码如下:
public BufferedImage doScale(BufferedImage bi,
double sx, double sy)
{
AffineTransformOp atfFilter = new AffineTransformOp(
AffineTransform.getScaleInstance(sx, sy),
AffineTransformOp.TYPE_BILINEAR);
//计算放缩后图像的宽与高
int nw = (int)(bi.getWidth() * sx);
int nh = (int)(bi.getHeight() * sy);
BufferedImage result = new BufferedImage(
nw, nh, BufferedImage.TYPE_3BYTE_BGR);
//实现图像放缩
atfFilter.filter(bi, result);
return result;
}
需要传入的三个参数包括:bi是BufferedImage对象实例,代表要放缩的图像;sx表示在X方向的放缩比率;sy表示在Y方向的放缩比率。
完整的UI部分代码如下:
package com.book.chapter.two;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class MyFilterUI extends JFrame
implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 1L;
public static final String GRAY_CMD = "灰度";
public static final String BINARY_CMD = "黑白";
public static final String BLUR_CMD = "模糊";
public static final String ZOOM_CMD = "放缩";
public static final String BROWSER_CMD = "选择...";
private JButton grayBtn;
private JButton binaryBtn;
private JButton blurBtn;
private JButton zoomBtn;
private JButton browserBtn;
private MyFilters filters;
// image
private BufferedImage srcImage;
public MyFilterUI()
{
this.setTitle("JAVA 2D BufferedImageOp - 滤镜演示");
grayBtn = new JButton(GRAY_CMD);
binaryBtn = new JButton(BINARY_CMD);
blurBtn = new JButton(BLUR_CMD);
zoomBtn = new JButton(ZOOM_CMD);
browserBtn = new JButton(BROWSER_CMD);
// buttons
JPanel btnPanel = new JPanel();
btnPanel.setLayout(new
FlowLayout(FlowLayout.RIGHT));
btnPanel.add(grayBtn);
btnPanel.add(binaryBtn);
btnPanel.add(blurBtn);
btnPanel.add(zoomBtn);
btnPanel.add(browserBtn);
// filters
filters = new MyFilters();
getContentPane().setLayout(new BorderLayout());
getContentPane().add(filters, BorderLayout.CENTER);
getContentPane().add(btnPanel, BorderLayout.SOUTH);
// setup listener
setupActionListener();
}
private void setupActionListener() {
grayBtn.addActionListener(this);
binaryBtn.addActionListener(this);
blurBtn.addActionListener(this);
zoomBtn.addActionListener(this);
browserBtn.addActionListener(this);
}
@Override
public void actionPerformed(ActionEvent e) {
if(srcImage == null)
{
JOptionPane.showMessageDialog(this,
"请先选择图像源文件");
try {
JFileChooser chooser = new JFileChooser();
chooser.showOpenDialog(null);
File f = chooser.getSelectedFile();
srcImage = ImageIO.read(f);
filters.setImage(srcImage);
filters.repaint();
} catch (IOException e1) {
e1.printStackTrace();
}
return;
}
if(GRAY_CMD.equals(e.getActionCommand()))
{
filters.doColorGray(srcImage);
filters.repaint();
}
else if(BINARY_CMD.equals(e.getActionCommand()))
{
filters.doBinaryImage(srcImage);
filters.repaint();
}
else if(BLUR_CMD.equals(e.getActionCommand()))
{
filters.doBlur(srcImage);
filters.repaint();
}
else if(ZOOM_CMD.equals(e.getActionCommand()))
{
filters.doScale(srcImage, 1.5, 1.5);
filters.repaint();
}
else if(BROWSER_CMD.equals(e.getActionCommand()))
{
try {
JFileChooser chooser = new JFileChooser();
chooser.showOpenDialog(null);
File f = chooser.getSelectedFile();
srcImage = ImageIO.read(f);
filters.setImage(srcImage);
filters.repaint();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
public static void main(String[] args) {
MyFilterUI ui = new MyFilterUI();
ui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ui.setPreferredSize(new Dimension(800, 600));
ui.pack();
ui.setVisible(true);
}
}
这里主要是基于JFrame对象实现UI部分,通过重载JPanel的paintComponent()方法来显示原图与处理后的效果图。按钮动作响应通过监听ActionListener来实现,处理完以后通过调用repaint()方法来实现UI刷新。