通过前面两节的学习,我们已经大致了解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可以很好地说明它们之间的关系。
图2-4 LookupTable接口与实现类之间关系
运用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是实现模板卷积功能操作的类,通过简单设置卷积核/卷积模板就可以实现图像模糊功能,实现代码如下:
f
loat 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刷新。