《Java数字图像处理:编程技巧与应用实践》——2.3 基于BufferedImageOp的图像滤镜演示

简介:

本节书摘来自华章计算机《Java数字图像处理:编程技巧与应用实践》一书中的第2章,第2.3节,作者 贾志刚,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.3 基于BufferedImageOp的图像滤镜演示

通过前面两节的学习,我们已经大致了解BufferedImageOp接口及其实现类的功能。实践出真知,本节将演示BufferedImageOp接口中每个实现类的实际使用场景,达到知行合一、学以致用的目的,帮助大家解决项目中遇到的实际问题。为了让大家对应用效果有更加深刻的印象,下面会使用BufferedImageOp的实现类来实现如下几个滤镜特效功能。

  • 黑白滤镜:将彩色图像自动转换为黑白两色图像。
  • 灰度滤镜:将彩色图像自动转换为灰度图像。
  • 模糊滤镜:使图像产生模糊效果。
  • 放缩滤镜:使图像放大或缩小。

1 . UI实现部分

在介绍基于Swing的UI实现时,关于Swing UI部分的编程知识将在下一章中详细剖析与解释,本节的重点放在滤镜实现部分,大致的UI布局如图2-3所示。

screenshot

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可以很好地说明它们之间的关系。

screenshot

运用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刷新。

相关文章
|
18天前
|
Java 调度
Java并发编程:深入理解线程池的原理与实践
【4月更文挑战第6天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将从线程池的基本原理入手,逐步解析其工作过程,以及如何在实际开发中合理使用线程池以提高程序性能。同时,我们还将关注线程池的一些高级特性,如自定义线程工厂、拒绝策略等,以帮助读者更好地掌握线程池的使用技巧。
|
15天前
|
Java 数据挖掘
java实践
【4月更文挑战第9天】java实践
12 1
|
1天前
|
安全 Java 调度
Java线程:深入理解与实战应用
Java线程:深入理解与实战应用
9 0
|
1天前
|
Java
Java中的并发编程:理解和应用线程池
【4月更文挑战第23天】在现代的Java应用程序中,性能和资源的有效利用已经成为了一个重要的考量因素。并发编程是提高应用程序性能的关键手段之一,而线程池则是实现高效并发的重要工具。本文将深入探讨Java中的线程池,包括其基本原理、优势、以及如何在实际开发中有效地使用线程池。我们将通过实例和代码片段,帮助读者理解线程池的概念,并学习如何在Java应用中合理地使用线程池。
|
6天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
|
6天前
|
设计模式 算法 Java
Java中的设计模式及其应用
【4月更文挑战第18天】本文介绍了Java设计模式的重要性及分类,包括创建型、结构型和行为型模式。创建型模式如单例、工厂方法用于对象创建;结构型模式如适配器、组合关注对象组合;行为型模式如策略、观察者关注对象交互。文中还举例说明了单例模式在配置管理器中的应用,工厂方法在图形编辑器中的使用,以及策略模式在电商折扣计算中的实践。设计模式能提升代码可读性、可维护性和可扩展性,是Java开发者的必备知识。
|
6天前
|
安全 Java API
函数式编程在Java中的应用
【4月更文挑战第18天】本文介绍了函数式编程的核心概念,包括不可变性、纯函数、高阶函数和函数组合,并展示了Java 8如何通过Lambda表达式、Stream API、Optional类和函数式接口支持函数式编程。通过实际应用案例,阐述了函数式编程在集合处理、并发编程和错误处理中的应用。结论指出,函数式编程能提升Java代码的质量和可维护性,随着Java语言的演进,函数式特性将更加丰富。
|
6天前
|
安全 Java 程序员
Java中的多线程并发编程实践
【4月更文挑战第18天】在现代软件开发中,为了提高程序性能和响应速度,经常需要利用多线程技术来实现并发执行。本文将深入探讨Java语言中的多线程机制,包括线程的创建、启动、同步以及线程池的使用等关键技术点。我们将通过具体代码实例,分析多线程编程的优势与挑战,并提出一系列优化策略来确保多线程环境下的程序稳定性和性能。
|
6天前
|
Java 测试技术 索引
滚雪球学Java(14):快速入门JavaSE-for循环语句,轻松掌握编程技巧
【4月更文挑战第3天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
20 1
滚雪球学Java(14):快速入门JavaSE-for循环语句,轻松掌握编程技巧
|
7天前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。