在概述一节中,图像使用宽度和高度描述,以像素为单位,有自己独立于绘图平面的坐标系。
使用图像时有很多普通的任务。
l 将图像外部的GIF, PNG JPEG 图像文件中加载到Java 2D 内部表示中。
l 直接创建Java 2D 图像听且绘制它。
l 在绘制平面中绘制Java 2D 图像的内容。
l 将Java 2D 图像保存在外部的GIF, PNG, 或 JPEG 图像文件中。
本课教您基本的加载,展示和保存图像的方法。
使用图像有两个类必须知道:
l java.awt.Image 是超类,它以矩形像素数组的方式表示图像。
l java.awt.image.BufferedImage 扩展了Image 类,允许应用程序直接操作图像数据(例如,获取或设置像素的颜色)。应用程序可以直接创建这个类的实例。
BufferedImage 类是Java 2D 即时模式图像API 的基础。它在内存中保存数据,同时提供了存储,解析和获取像素数据的方法。因为BufferedImage 是Image 的子类,它可以使用Graphics 和Graphics2D 中接受Image 参数的方法渲染。
BufferedImage 的本质是一个带有可访问数据缓存的Image 。直接操作BufferedImage 会更高效。BufferedImage 有图像数据的ColorModel 和光栅。ColorModel 提供了图像像素数据的颜色解释。
光栅执行以下功能:
l 表示图像的矩形坐标。
l 在内存中维护图像数据
l 提供从单一图像数据缓冲区中创建多个子图像的机制。
l 提供了访问图像内特定像素的方法。
图像的基本操作在以下几节中介绍:
l 读取/ 加载图像
本节描述如何从外部图像文件中,使用图像I/O API 将图像加载到Java 应用程序中。
l 绘制图像
本节介绍如何使用Graphics 和Graphics2D 类中的drawImage 方法显示图像。
l 创建并向图像中进行绘制
本节介绍如何创建图像,并且如何将图像作为绘制平面。
l 保存图像
本节介绍如何将图像保存成合适的格式。
1.1 读取加载图像
当您想象数字图像时,想一下数码相机中使用的JPEG 格式,和在web 页面中常用的GIF 格式。所有使用这些图像的程序必须首先将外部格式转换成内部格式。
Java 2D 支持将这些外部图像格式加载到BufferedImage 格式,使用javax.imageio 包中的I/O API 进行。Image I/O 对GIF, PNG, JPEG, BMP, 和 WBMP 直接支持。Image I/O 同时允许程序员和管理员添加其他格式的支持。例如,支持TIFF 和JPEG 2000 的插件就是单独提供的。
要加载制定图像,需要如下代码:
BufferedImage img = null; try { img = ImageIO.read(new File("strawberry.jpg")); } catch (IOException e) { } |
Image I/O 将文件内容当做JPEG 格式解析,同时将他们解码到Java 2D 可以直接使用的BufferedImage 中。
LoadImageApp.java 展示了如何展示图像。
如果代码在applet 中运行,那么可以很容易的从applet 的codebase 中得到图像。
try { URL url = new URL(getCodeBase(), "strawberry.jpg"); img = ImageIO.read(url); } catch (IOException e) { } |
例子中的getCodeBase 方法返回包含applet 的目录的URL 。
下面的例子展示了如何使用getCodeBase 方法加载strawberry.jpg 文件。
LoadImageApp.java 包含这个例子的完整代码,同时appet 需要strawberry.jpg 图像文件。除了从文件或URL 中读取之外,Image I/O 还可以从其他源中读取,例如InputStream 。
ImageIO.read() 是大多数应用程序最常用的API ,但javax.imageio.ImageIO 包含了其他静态方法,提供更高级的Image I/O API 。这个类中的方法表示了从图像中获取信息同时控制图像解码的API 的一小部分。
在保存图像一节将会介绍一些其他的Image I/O 。更详细的信息请参考Image I/O 的指导。
1.2 绘制图像
您已经知道,Graphics.drawImage 方法在指定位置绘制图像:
boolean Graphics.drawImage(Image img, int x, int y, ImageObserver observer); |
X, y 位置表示图像的左上角。Observer 参数放应用程序异步更新图像。Observer 参数不经常直接使用,并且对于BufferedImage 类来说是不需要的,所以通常是null 。
上面的方法只有在绘制整个图像时才有用,它按照1:1 的比例将图像的像素映射到用户空间坐标中。有时,应用程序需要绘制一部分图像(或子图像),或者是拉伸了图像,以覆盖绘图平面的一部分区域,或者在绘制前进行转换或过滤。
drawImage() 方法的重载形式执行这些操作。例如,以下drawImage() 方法可以在特定的区域绘制特定的图像,同时进行拉伸以符合目标绘制平面的要求:
boolean Graphics.drawImage(Image img, int dstx1, int dsty1, int dstx2, int dsty2, int srcx1, int srcy1, int srcx2, int srcy2, ImageObserver observer); |
Src 参数表示需要拷贝和绘制的区域。Dest 参数展示了需要由源数据覆盖的区域。dstx1, dsty1 坐标定义了绘制的位置。通过以下公式计算目标区域的宽和高:(dstx2-dstx1), (dsty2-dsty1) 。如果源区域和目标区域的大小不同,Java 2D API 将自动执行图像拉伸或收缩。
以下代码将图片剪切成4 个部分,同时随机的将每个部分绘制到目标区域。
这个applet 的完整代码在JumbledImageApplet.java 中。这个例子使用以下代码绘制duke_skateboard.jpg 图片。它遍历四个子图像,每次随机选择一个进行绘制。
/* divide the image 'bi' into four rectangular areas and draw each * of these areas in to a different part of the image, so as to * jumble up the image. * 'cells' is an array which has been populated with values * which redirect drawing of one subarea to another subarea. */ int cellWidth = bi.getWidth(null)/2; int cellHeight = bi.getHeight(null)/2; for (int x=0; x<2; x++) { int sx = x*cellWidth; for (int y=0; y<2; y++) { int sy = y*cellHeight; int cell = cells[x*2+y]; int dx = (cell / 2) * cellWidth; int dy = (cell % 2) * cellHeight; g.drawImage(bi, dx, dy, dx+cellWidth, dy+cellHeight, sx, sy, sx+cellWidth, sy+cellHeight, null); } } |
1.2.1 过滤图像
除了拷贝和拉伸图像外,Java 2D API 也可以过滤图像。过滤是在绘制或产生新图像的过程中,对原有图像的像素应用的一种算法。图像过滤器可以使用下面的方法进行使用:
void Graphics2D.drawImage(BufferedImage img, BufferedImageOp op, int x, int y) |
BufferedImageOp 参数实现了过滤器。以下applet 表示一个在左上角绘制的文本。拖动滑块可以透过图像显示文本,也可以让图像变得透明。
以下代码展示了过滤操作通过在BufferedImage 上使用alpha channel 进行操作,同时使用RescaleOp 对象重新拉伸这个alpha channel 的过程。它同时指定了图像覆盖的度。
/* Create an ARGB BufferedImage */ BufferedImage img = ImageIO.read(imageSrc); int w = img.getWidth(null); int h = img.getHeight(null); BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics g = bi.getGraphics(); g.drawImage(img, 0, 0, null);
/* Create a rescale filter op that makes the image 50% opaque */ float[] scales = { 1f, 1f, 1f, 0.5f }; float[] offsets = new float[4]; RescaleOp rop = new RescaleOp(scales, offsets, null);
/* Draw the image, applying the filter */ g2d.drawImage(bi, rop, 0, 0); |
完成的例子在SeeThroughImageApplet.java 中,包含了使用滑块从开始的50 %调整透明度的代码。这个例子需要duke_skateboard.jpg 图像。
RescaleOp 对象只是多种过滤器之一。Java 2D API 有以下多种内建的过滤器:
l ConvolveOp :每个输出像素都围绕源图像中像素计算。可以用来让图像变得模糊或尖锐。
l AffineTransformOp :这个过滤器通过在像素位置上进行转换,将源像素映射到目标的不同位置。
l LookupOp :这个过滤器使用用用程序提供的查询表重新映射像素颜色。
l RescaleOp :这个过滤器在颜色上乘以一定参数。可以用来让图像加亮或变暗,增加或减少透明度等。
下面的例子展示了上述的每种过滤器:
这个applet 的完整源代码在ImageDrawingApplet.java ,这个applet 需要bld.jpg 图片。
使用下拉列表可以选择可以选择对图像进行拉伸或过滤操作。
1.3 创建和绘制图像
我们已经知道如何加载一个已经存在的图像,它在您的系统或网络的任何位置创建和保存。但是,你可能想要创建一个新的图像作为像素数据的缓冲区。
这种情况下,您可以手动创建BufferedImage 对象,使用以下三个构造函数:
l new BufferedImage(width, height, type) :使用预定义的图像类型之一创建BufferedImage 。
l new BufferedImage(width, height, type, colorModel) :使用预定义的图像类型创建BufferedImage ,可以是TYPE_BYTE_BINARY 或 TYPE_BYTE_INDEXED 。
l new BufferedImage(colorModel, raster, premultiplied, properties) :使用指定的ColorModel 和Raster 创建新的BufferedImage 。
同时,我们也可以使用Component 类的方法。这些方法可以分析给定Component 的显示分辨率,或GraphicsConfiguration ,同时创建合适类型的图像。
l Component.createImage(width, height)
l GraphicsConfiguration.createCompatibleImage(width, height)
l GraphicsConfiguration.createCompatibleImage(width, height, transparency)
GraphicsConfiguration 返回BufferedImage 类型的对象,但Component 返回Image 类型的对象,如果您需要一个BufferedImage 对象,这样可以执行在代码中instanceof 然后转换成BufferedImage 。
在以前的章节中已经提到,我们不止是在屏幕中渲染图像。图像自己可以被当做绘制平面。您可以使用BufferedImage 类的createGraphics() 干下面的事:
...
BufferedImage off_Image = new BufferedImage(100, 50, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = off_Image.createGraphics(); |
另一个屏幕外的图像时自动的双缓存。这种特性可以在后台缓冲器中绘制图像,然后拷贝到屏幕中,而不是直接绘制在屏幕中,以避免动画闪烁。
Java 2D 也允许离线图像的硬件加速,这可以为从这些图像进行渲染和拷贝提供更好的性能。您可以使用Image 类的下列方法完成这项功能:
l getCapabilities 方法决定图像是否已经被加速。
l setAccelerationPriority 方法让您指定为图像使用的加速方式。
l getAccelerationPriority 方法得到图像的加速重要性。
1.4 读 / 写图像
本节以javax.imageio 包的说明开始,它可以从外部图像格式的文件中家在图像,并转换成Java 2D 内部的BufferedImage 格式。然后说明如何使用Graphics.drawImage() 方法绘制图像,同时增加可选的过滤器。
最后的部分是将BufferedImage 对象保存为外部图像格式。这可以是最初由Image I/O 类从外部图像文件中加载的,同时可以使用Java 2D API 修改,或者可以是由Java 2D 创建的。
Image I/O 类提供了保存多种文件格式的简单方法,如下所示:
static boolean ImageIO.write(RenderedImage im, String formatName, File output) throws IOException
|
注意:BufferedImage 类实现了RenderedImage 接口。
formatName 参数选择要保存BufferedImage 的格式,如下:
try { BufferedImage bi = getMyImage(); // retrieve image File outputfile = new File("saved.png"); ImageIO.write(bi, "png", outputfile); } catch (IOException e) { ... } |
ImageIO.write 方法调用了实现写PNG 文件的代码。名词“插件”用来表示Image I/O 是可扩展的,同时可以支持多种格式。
通常可以使用以下标准图像格式:JPEG, PNG, GIF, BMP 和 WBMP 。
每种图像格式都有自己的优缺点:
| 优点 | 缺点 |
GIF | 支持动画和透明像素 | 只支持256 色,不支持半透明效果 |
PNG | 比GIF 和JPG 有更低的色彩丢失率,支持半透明 | 不支持动画 |
JPG | 对于照片图像处理很好 | 会因为压缩丢失精度,不应该作为文本,快照以及其他需要精确保存原来图形的应用程序 |
对于大多数应用程序来说,使用这些标准插件之一已经足够了。他们的优点是立即可用。Image I/O 类提供了支持其他可用的格式的插件式方法。如果你对一些文件格式感兴趣,并且需要在系统中读取或保存他们,您必须使用ImageIO 类的getReaderFormatNames 和getWriterFormatNames 方法。这些方法返回当前JRE 中支持的所有格式列表。
String writerNames[] = ImageIO.getWriterFormatNames(); |
返回值将包含所有已经安装的插件,同时他们都可以作为选择图像writer 的格式名。下面的代码展示了整个图像编辑/ 修改程序的建议版本,它使用ImageDrawingApplet.java 示例程序的修订版,如下:
l 首先使用Image I/O 加载图像。
l 用户从下拉列表中选择过滤器,然后绘制更新后的图像。
l 用户从下拉列表中选择保存格式。
l 然后文件选择器显示出来,同时用户选择在哪保存图像。
l 修改后的图像现在可以被其他桌面应用程序查看。
完整的代码在SaveImage.java 中。
本课您已经学习了Image I/O 的基本信息,它为写图像提供了扩展的支持,以及支持使用ImageWriter 插件最终控制编码过程。ImageIO 可以写多种图像,图像元数据,决定质量和大小的关系。更详细的信息请参考Java Image I/O API Guide.