Java 图像编程全解析
1. Java 图像编程基础概述
在 Java 编程中,图像的处理是一个重要的部分,它涵盖了图像的创建、加载、显示等操作。Java 提供了
Image
类和
java.awt.image
包来支持图像的显示和操作。图像是一种矩形的图形对象,在网页设计中是关键元素之一。早在 1993 年,NCSA 的 Mosaic 浏览器引入
<img>
标签,使得网页开始迅速发展。Java 在此基础上进行了扩展,允许在程序控制下管理图像。
Image
类属于
java.awt
包,而图像的操作则使用
java.awt.image
包中的类。
java.awt.image
定义了大量的图像类和接口,这里主要关注构成图像基础的部分:
-
类
:
-
CropImageFilter
-
MemoryImageSource
-
FilteredImageSource
-
PixelGrabber
-
ImageFilter
-
RGBImageFilter
-
接口
:
-
ImageConsumer
-
ImageObserver
-
ImageProducer
此外,还会涉及到
java.awt
包中的
MediaTracker
类。
2. 图像文件格式
最初,网页图像只能是 GIF 格式。GIF 格式由 CompuServe 于 1987 年创建,适合在线查看,不过每个 GIF 图像最多只能有 256 种颜色。这一限制促使主要浏览器厂商在 1995 年开始支持 JPEG 图像。JPEG 格式由一组摄影专家创建,用于存储全彩色光谱、连续色调的图像,在适当创建的情况下,比相同源图像的 GIF 编码具有更高的保真度和更高的压缩率。另一种文件格式是 PNG,它也是 GIF 的替代方案。在大多数情况下,Java 图像类通过简洁的接口抽象了这些格式的差异,无需过多关注具体使用的格式。
3. 图像基本操作
在处理图像时,通常会涉及到三种常见操作:创建图像、加载图像和显示图像。
3.1 创建图像对象
不能直接使用
new Image()
来创建内存图像,例如下面的代码是错误的:
Image test = new Image(200, 100); // Error -- won’t work
因为图像最终要在窗口上绘制才能显示,
Image
类没有足够的环境信息来为屏幕创建合适的数据格式。因此,
java.awt
中的
Component
类有一个工厂方法
createImage()
用于创建
Image
对象。所有 AWT 组件都是
Component
的子类,都支持该方法。
createImage()
方法有两种形式:
Image createImage(ImageProducer imgProd);
Image createImage(int width, int height);
第一种形式返回由
imgProd
生成的图像,
imgProd
是实现
ImageProducer
接口的类的对象;第二种形式返回具有指定宽度和高度的空白图像。示例如下:
Canvas c = new Canvas();
Image test = c.createImage(200, 100);
3.2 加载图像
可以使用
Applet
类的
getImage()
方法来加载图像,它有以下两种形式:
Image getImage(URL url);
Image getImage(URL url, String imageName);
第一种形式返回封装了
url
位置图像的
Image
对象;第二种形式返回封装了
url
位置且名为
imageName
的图像的
Image
对象。
3.3 显示图像
使用
Graphics
类的
drawImage()
方法来显示图像,这里使用的形式如下:
boolean drawImage(Image imgObj, int left, int top, ImageObserver imgOb);
该方法将
imgObj
图像的左上角显示在
left
和
top
指定的位置。
imgOb
是实现
ImageObserver
接口的类的引用,所有 AWT(和 Swing)组件都实现了该接口,图像观察者可以在图像加载时进行监控。
下面是一个加载并显示单个图像的示例小程序:
/*
* <applet code="SimpleImageLoad" width=400 height=345>
* <param name="img" value="Lilies.jpg">
* </applet>
*/
import java.awt.*;
import java.applet.*;
public class SimpleImageLoad extends Applet
{
Image img;
public void init() {
img = getImage(getDocumentBase(), getParameter("img"));
}
public void paint(Graphics g) {
g.drawImage(img, 0, 0, this);
}
}
在
init()
方法中,
img
变量被赋值为
getImage()
返回的图像。
getImage()
方法使用
getParameter("img")
返回的字符串作为图像文件名,该图像从相对于
getDocumentBase()
结果的 URL 加载。当小程序运行时,在
init()
方法中开始加载图像,由于
Applet
实现了
ImageObserver
接口,每次有更多图像数据到达时会调用
paint()
方法,因此可以在屏幕上看到图像从网络加载的过程。
4. ImageObserver 接口
ImageObserver
是一个用于在图像生成时接收通知的接口,它只定义了一个方法
imageUpdate()
。使用图像观察者可以在得知下载进度时执行其他操作,如显示进度指示器或吸引人的屏幕,在通过慢速网络加载图像时非常有用。
imageUpdate()
方法的一般形式如下:
boolean imageUpdate(Image imgObj, int flags, int left, int top, int width, int height);
其中,
imgObj
是正在加载的图像,
flags
是一个整数,用于传达更新报告的状态。
left
、
top
、
width
和
height
四个整数表示一个矩形,其值根据
flags
的值而不同。如果图像加载完成,
imageUpdate()
应返回
false
;如果还有更多图像需要处理,则返回
true
。
flags
参数包含一个或多个在
ImageObserver
接口中定义为静态变量的位标志,具体如下表所示:
| Flag | Meaning |
| ---- | ---- |
| WIDTH | 宽度参数有效,包含图像的宽度。 |
| HEIGHT | 高度参数有效,包含图像的高度。 |
| PROPERTIES | 现在可以使用
imgObj.getProperty()
获取与图像关联的属性。 |
| SOMEBITS | 已接收到绘制图像所需的更多像素。
left
、
top
、
width
和
height
参数定义了包含新像素的矩形。 |
| FRAMEBITS | 已接收到多帧图像中之前绘制的完整帧,该帧可以显示。
left
、
top
、
width
和
height
参数不使用。 |
| ALLBITS | 图像现在已完成。
left
、
top
、
width
和
height
参数不使用。 |
| ERROR | 异步跟踪的图像发生了错误,图像不完整且无法显示,不会再收到更多图像信息。
ABORT
标志也会被设置以指示图像生成已中止。 |
| ABORT | 异步跟踪的图像在完成之前被中止。但是,如果没有发生错误,访问图像数据的任何部分将重新启动图像生成。 |
下面是一个简单的
imageUpdate()
方法示例:
public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) {
if ((flags & ALLBITS) == 0) {
System.out.println("Still processing the image.");
return true;
} else {
System.out.println("Done processing the image.");
return false;
}
}
5. 双缓冲技术
图像不仅可用于存储图片,还可以用作离屏绘图表面,即双缓冲技术。绘制复杂图像可能需要几毫秒甚至更长时间,用户可能会看到闪烁,使用离屏图像减少闪烁的方法称为双缓冲,因为屏幕被视为像素缓冲区,离屏图像是第二个缓冲区,可以在其中准备像素以进行显示。
首先创建一个空白
Image
对象,然后使用
getGraphics()
方法获取用于在图像上绘制的
Graphics
对象。以下代码片段创建一个新图像,获取其图形上下文,并将整个图像填充为红色像素:
Canvas c = new Canvas();
Image test = c.createImage(200, 100);
Graphics gc = test.getGraphics();
gc.setColor(Color.red);
gc.fillRect(0, 0, 200, 100);
创建并填充离屏图像后,还需要调用
drawImage()
方法来显示图像。以下示例绘制一个耗时的图像,展示双缓冲在感知绘图时间上的差异:
/*
* <applet code=DoubleBuffer width=250 height=250>
* </applet>
*/
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class DoubleBuffer extends Applet {
int gap = 3;
int mx, my;
boolean flicker = true;
Image buffer = null;
int w, h;
public void init() {
Dimension d = getSize();
w = d.width;
h = d.height;
buffer = createImage(w, h);
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent me) {
mx = me.getX();
my = me.getY();
flicker = false;
repaint();
}
public void mouseMoved(MouseEvent me) {
mx = me.getX();
my = me.getY();
flicker = true;
repaint();
}
});
}
public void paint(Graphics g) {
Graphics screengc = null;
if (!flicker) {
screengc = g;
g = buffer.getGraphics();
}
g.setColor(Color.blue);
g.fillRect(0, 0, w, h);
g.setColor(Color.red);
for (int i = 0; i < w; i += gap)
g.drawLine(i, 0, w - i, h);
for (int i = 0; i < h; i += gap)
g.drawLine(0, i, w, h - i);
g.setColor(Color.black);
g.drawString("Press mouse button to double buffer", 10, h / 2);
g.setColor(Color.yellow);
g.fillOval(mx - gap, my - gap, gap * 2 + 1, gap * 2 + 1);
if (!flicker) {
screengc.drawImage(buffer, 0, 0, null);
}
}
public void update(Graphics g) {
paint(g);
}
}
这个小程序的
paint()
方法比较复杂,它先将背景填充为蓝色,然后绘制红色的莫尔图案,再绘制黑色文本和黄色圆圈。
mouseMoved()
和
mouseDragged()
方法用于跟踪鼠标位置,通过设置
flicker
变量来控制是否使用双缓冲。当
flicker
为
true
时,在屏幕上可以看到每个绘图操作的执行过程;当
flicker
为
false
时,所有绘图操作在离屏画布上进行,最后一次性显示结果。
6. MediaTracker 类
MediaTracker
可以并行检查任意数量图像的状态。使用时,创建一个新实例并使用其
addImage()
方法跟踪图像的加载状态。
addImage()
方法有以下两种形式:
void addImage(Image imgObj, int imgID);
void addImage(Image imgObj, int imgID, int width, int height);
imgObj
是要跟踪的图像,
imgID
是其标识号,ID 号不需要唯一,可以使用相同的编号将多个图像标识为一组,加载时较低 ID 的图像优先于较高 ID 的图像。第二种形式指定了图像显示时的尺寸。
注册图像后,可以使用
checkID()
方法检查图像是否已加载:
boolean checkID(int imgID);
该方法返回
true
表示具有指定 ID 的所有图像都已加载(或因错误或用户中止而终止加载),否则返回
false
。也可以使用
checkAll()
方法检查所有跟踪的图像是否都已加载。
以下是一个加载三张图像幻灯片并显示加载进度条形图的示例:
/*
* <applet code="TrackedImageLoad" width=400 height=345>
* <param name="img"
* value="Lilies+SunFlower+ConeFlowers">
* </applet>
*/
import java.util.*;
import java.applet.*;
import java.awt.*;
public class TrackedImageLoad extends Applet implements Runnable {
MediaTracker tracker;
int tracked;
int frame_rate = 5;
int current_img = 0;
Thread motor;
static final int MAXIMAGES = 10;
Image img[] = new Image[MAXIMAGES];
String name[] = new String[MAXIMAGES];
volatile boolean stopFlag;
public void init() {
tracker = new MediaTracker(this);
StringTokenizer st = new StringTokenizer(getParameter("img"), "+");
while (st.hasMoreTokens() && tracked <= MAXIMAGES) {
name[tracked] = st.nextToken();
img[tracked] = getImage(getDocumentBase(), name[tracked] + ".jpg");
tracker.addImage(img[tracked], tracked);
tracked++;
}
}
public void paint(Graphics g) {
String loaded = "";
int donecount = 0;
for (int i = 0; i < tracked; i++) {
if (tracker.checkID(i, true)) {
donecount++;
loaded += name[i] + " ";
}
}
Dimension d = getSize();
int w = d.width;
int h = d.height;
if (donecount == tracked) {
frame_rate = 1;
Image i = img[current_img++];
int iw = i.getWidth(null);
int ih = i.getHeight(null);
g.drawImage(i, (w - iw) / 2, (h - ih) / 2, null);
if (current_img >= tracked)
current_img = 0;
} else {
int x = w * donecount / tracked;
g.setColor(Color.black);
g.fillRect(0, h / 3, x, 16);
g.setColor(Color.white);
g.fillRect(x, h / 3, w - x, 16);
g.setColor(Color.black);
g.drawString(loaded, 10, h / 2);
}
}
public void start() {
motor = new Thread(this);
stopFlag = false;
motor.start();
}
public void stop() {
stopFlag = true;
}
public void run() {
motor.setPriority(Thread.MIN_PRIORITY);
while (true) {
repaint();
try {
Thread.sleep(1000 / frame_rate);
} catch (InterruptedException e) {
System.out.println("Interrupted");
return;
}
if (stopFlag)
return;
}
}
}
在
init()
方法中,使用
MediaTracker
跟踪图像的加载状态。
paint()
方法根据图像的加载情况显示进度条或图像。当所有图像加载完成后,开始循环显示图像。
综上所述,Java 提供了丰富的工具和类来处理图像,通过掌握这些知识和技术,可以在 Java 程序中实现各种图像相关的功能。
Java 图像编程全解析
7. 总结与实际应用建议
在 Java 图像编程中,我们已经了解了多个关键方面,下面总结一下各个知识点,并给出实际应用中的建议。
7.1 知识点总结
-
图像类与包
:
Image类和java.awt.image包提供了成像支持,MediaTracker类用于并行检查图像加载状态。 - 文件格式 :常见的图像文件格式有 GIF、JPEG 和 PNG,Java 图像类抽象了它们的差异。
-
基本操作
:创建图像使用
Component类的createImage()方法;加载图像使用Applet类的getImage()方法;显示图像使用Graphics类的drawImage()方法。 -
ImageObserver 接口
:用于在图像加载时接收通知,通过
imageUpdate()方法监控加载进度。 - 双缓冲技术 :使用离屏图像减少闪烁,提高绘图的视觉效果。
7.2 实际应用建议
-
图像加载优化
:当加载多个图像时,使用
MediaTracker类可以更好地管理加载过程,避免在图像未加载完成时进行不必要的操作。例如,在游戏开发中,加载大量的游戏资源图像时,可以使用MediaTracker确保所有图像加载完成后再开始游戏。 - 双缓冲的使用 :对于绘制复杂图像的场景,如动画、图形编辑器等,使用双缓冲技术可以显著减少闪烁,提升用户体验。
- 图像格式选择 :根据实际需求选择合适的图像格式。如果需要透明背景或简单的图形,PNG 是不错的选择;如果是照片等需要高保真度的图像,JPEG 更合适;对于简单的图标或动画,GIF 可以考虑。
8. 流程图示例
下面是一个简单的 Java 图像加载和显示的流程图:
graph TD;
A[开始] --> B[创建 MediaTracker 实例];
B --> C[获取图像 URL 和名称];
C --> D[使用 getImage() 方法加载图像];
D --> E[使用 addImage() 方法将图像添加到 MediaTracker];
E --> F{图像是否加载完成};
F -- 否 --> G[等待并继续检查];
G --> F;
F -- 是 --> H[使用 drawImage() 方法显示图像];
H --> I[结束];
9. 常见问题及解决方案
在 Java 图像编程中,可能会遇到一些常见问题,以下是一些问题及解决方案:
| 问题 | 解决方案 |
|---|---|
| 图像加载缓慢 |
使用
MediaTracker
监控加载进度,在加载过程中显示进度条或提示信息,避免用户等待时的焦虑。同时,优化图像文件大小,选择合适的图像格式。
|
| 图像闪烁 | 使用双缓冲技术,将图像绘制到离屏画布上,然后一次性显示到屏幕上,减少闪烁。 |
| 图像无法显示 |
检查图像文件路径是否正确,确保文件存在。同时,检查
getImage()
方法的参数是否正确。
|
10. 代码优化建议
对于前面给出的示例代码,可以进行一些优化。例如,在
TrackedImageLoad
类中,可以添加异常处理,增强代码的健壮性。以下是优化后的代码:
/*
* <applet code="TrackedImageLoad" width=400 height=345>
* <param name="img"
* value="Lilies+SunFlower+ConeFlowers">
* </applet>
*/
import java.util.*;
import java.applet.*;
import java.awt.*;
public class TrackedImageLoad extends Applet implements Runnable {
MediaTracker tracker;
int tracked;
int frame_rate = 5;
int current_img = 0;
Thread motor;
static final int MAXIMAGES = 10;
Image img[] = new Image[MAXIMAGES];
String name[] = new String[MAXIMAGES];
volatile boolean stopFlag;
public void init() {
tracker = new MediaTracker(this);
StringTokenizer st = new StringTokenizer(getParameter("img"), "+");
try {
while (st.hasMoreTokens() && tracked <= MAXIMAGES) {
name[tracked] = st.nextToken();
img[tracked] = getImage(getDocumentBase(), name[tracked] + ".jpg");
tracker.addImage(img[tracked], tracked);
tracked++;
}
} catch (Exception e) {
System.out.println("Error loading images: " + e.getMessage());
}
}
public void paint(Graphics g) {
String loaded = "";
int donecount = 0;
try {
for (int i = 0; i < tracked; i++) {
if (tracker.checkID(i, true)) {
donecount++;
loaded += name[i] + " ";
}
}
} catch (Exception e) {
System.out.println("Error checking image status: " + e.getMessage());
}
Dimension d = getSize();
int w = d.width;
int h = d.height;
if (donecount == tracked) {
frame_rate = 1;
Image i = img[current_img++];
int iw = i.getWidth(null);
int ih = i.getHeight(null);
g.drawImage(i, (w - iw) / 2, (h - ih) / 2, null);
if (current_img >= tracked)
current_img = 0;
} else {
int x = w * donecount / tracked;
g.setColor(Color.black);
g.fillRect(0, h / 3, x, 16);
g.setColor(Color.white);
g.fillRect(x, h / 3, w - x, 16);
g.setColor(Color.black);
g.drawString(loaded, 10, h / 2);
}
}
public void start() {
motor = new Thread(this);
stopFlag = false;
motor.start();
}
public void stop() {
stopFlag = true;
}
public void run() {
motor.setPriority(Thread.MIN_PRIORITY);
while (true) {
try {
repaint();
Thread.sleep(1000 / frame_rate);
} catch (InterruptedException e) {
System.out.println("Interrupted: " + e.getMessage());
return;
}
if (stopFlag)
return;
}
}
}
11. 总结
通过本文的介绍,我们深入了解了 Java 图像编程的各个方面,包括图像的创建、加载、显示,以及相关的技术和类的使用。掌握这些知识和技术,可以在 Java 程序中实现各种图像相关的功能,提升程序的视觉效果和用户体验。在实际应用中,根据具体需求选择合适的方法和技术,并注意代码的优化和异常处理,以确保程序的稳定性和性能。
希望本文对你在 Java 图像编程方面有所帮助,让你能够更加熟练地处理图像相关的任务。
超级会员免费看
10万+

被折叠的 条评论
为什么被折叠?



