《Java数字图像处理:编程技巧与应用实践》——第2章 Java BufferedImage对象及其支持的API操作 2.1 BufferedImage对象的构成

简介:

本节书摘来自华章计算机《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所示。

screenshot

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);
    }
}
相关文章
|
10天前
|
Java 数据挖掘
java实践
【4月更文挑战第9天】java实践
12 1
|
1天前
|
安全 Java API
java借助代理ip,解决访问api频繁导致ip被禁的问题
java借助代理ip,解决访问api频繁导致ip被禁的问题
|
1天前
|
Java
Java中如何克隆一个对象?
【4月更文挑战第13天】
8 0
|
1天前
|
安全 Java 程序员
Java中的多线程并发编程实践
【4月更文挑战第18天】在现代软件开发中,为了提高程序性能和响应速度,经常需要利用多线程技术来实现并发执行。本文将深入探讨Java语言中的多线程机制,包括线程的创建、启动、同步以及线程池的使用等关键技术点。我们将通过具体代码实例,分析多线程编程的优势与挑战,并提出一系列优化策略来确保多线程环境下的程序稳定性和性能。
|
2天前
|
负载均衡 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开发者的关键技能。
|
2天前
|
Java API 数据库
深研Java异步编程:CompletableFuture与反应式编程范式的融合实践
【4月更文挑战第17天】本文探讨了Java中的CompletableFuture和反应式编程在提升异步编程体验上的作用。CompletableFuture作为Java 8引入的Future扩展,提供了一套流畅的链式API,简化异步操作,如示例所示的非阻塞数据库查询。反应式编程则关注数据流和变化传播,通过Reactor等框架实现高度响应的异步处理。两者结合,如将CompletableFuture转换为Mono或Flux,可以兼顾灵活性和资源管理,适应现代高并发环境的需求。开发者可按需选择和整合这两种技术,优化系统性能和响应能力。
|
2天前
|
网络协议 Java API
深度剖析:Java网络编程中的TCP/IP与HTTP协议实践
【4月更文挑战第17天】Java网络编程重在TCP/IP和HTTP协议的应用。TCP提供可靠数据传输,通过Socket和ServerSocket实现;HTTP用于Web服务,常借助HttpURLConnection或Apache HttpClient。两者结合,构成网络服务基础。Java有多种高级API和框架(如Netty、Spring Boot)简化开发,助力高效、高并发的网络通信。
|
2天前
|
Java API 数据库
深入解析:使用JPA进行Java对象关系映射的实践与应用
【4月更文挑战第17天】Java Persistence API (JPA) 是Java EE中的ORM规范,简化数据库操作,让开发者以面向对象方式处理数据,提高效率和代码可读性。它定义了Java对象与数据库表的映射,通过@Entity等注解标记实体类,如User类映射到users表。JPA提供持久化上下文和EntityManager,管理对象生命周期,支持Criteria API和JPQL进行数据库查询。同时,JPA包含事务管理功能,保证数据一致性。使用JPA能降低开发复杂性,但需根据项目需求灵活应用,结合框架如Spring Data JPA,进一步提升开发便捷性。
|
3天前
|
存储 安全 Java
说说Java 8 引入的Stream API
说说Java 8 引入的Stream API
8 0
|
3天前
|
分布式计算 Java API
Java 8新特性之Lambda表达式与Stream API
【4月更文挑战第16天】本文将介绍Java 8中的两个重要新特性:Lambda表达式和Stream API。Lambda表达式是Java 8中引入的一种新的编程语法,它允许我们将函数作为参数传递给其他方法,从而使代码更加简洁、易读。Stream API是Java 8中引入的一种新的数据处理方式,它允许我们以声明式的方式处理数据,从而使代码更加简洁、高效。本文将通过实例代码详细讲解这两个新特性的使用方法和优势。