82、Java 图像编程全解析

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 图像编程方面有所帮助,让你能够更加熟练地处理图像相关的任务。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值