《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);
    }
}
相关文章
|
4天前
|
SQL Java 程序员
Java 8中的Stream API:简介与实用案例
【5月更文挑战第23天】本文将深入探讨Java 8中的Stream API,这是一种能够极大提升Java程序员生产力的新特性。我们将从基础概念开始,然后通过一些实用的案例来展示如何使用Stream API进行数据处理和操作。无论你是Java的初学者还是经验丰富的开发者,本文都将为你提供有价值的信息。
|
16小时前
|
分布式计算 Java API
Java 8新特性之Stream API详解
【5月更文挑战第27天】 本文将详细解析Java 8中的一个重要新特性——Stream API。Stream API是Java 8中引入的一种新的数据处理方式,它允许我们以声明性和函数式的方式处理数据。我们将深入探讨Stream API的基本概念,使用方法以及一些高级技巧。无论你是Java初学者还是资深开发者,本文都将帮助你更好地理解和使用Stream API。
|
17小时前
|
Java 程序员 API
Java 8新特性深度解析:Stream API和Lambda表达式
【5月更文挑战第27天】 在Java 8中,引入了两个重要的新特性:Stream API和Lambda表达式。这两个特性不仅提高了Java程序员的生产力,也使得Java代码更加简洁易读。本文将深入探讨这两个特性的使用方法和优势,以及如何在实际应用中结合使用它们。
|
1天前
|
存储 算法 Java
滚雪球学Java(42):探索对象的奥秘:解析Java中的Object类
【5月更文挑战第17天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
10 2
滚雪球学Java(42):探索对象的奥秘:解析Java中的Object类
|
1天前
|
Java 大数据 程序员
使用Java Stream API实现高效数据处理
在大数据和复杂业务逻辑的时代,高效的数据处理成为了软件开发的关键。本文将探讨如何使用Java 8引入的Stream API,通过函数式编程的方式,实现高效、简洁、可读的数据处理。我们将介绍Stream API的基本概念、核心操作,并通过一个具体的实例,展示如何利用Stream API解决复杂的数据处理问题,同时提升代码的可读性和可维护性。
|
3天前
|
存储 Java
Java语言中的类与对象:深入解析与实战应用
Java语言中的类与对象:深入解析与实战应用
|
3天前
|
安全 算法 Java
Java Stream API:原理、应用与深入解析
Java Stream API:原理、应用与深入解析
|
3天前
|
SQL Java 关系型数据库
JavaWeb(JDBC编程)看这一篇就够了 —— 如何使用Java操作mysql数据库
JavaWeb(JDBC编程)看这一篇就够了 —— 如何使用Java操作mysql数据库
7 0
|
4天前
|
XML Web App开发 JavaScript
软件测试 -- Selenium常用API全面解答(java)
软件测试 -- Selenium常用API全面解答(java)
14 0
|
4天前
|
存储 Java 编译器
Java文件IO操作基础
Java文件IO操作基础
6 0