图像
最后修改于 2023 年 7 月 17 日
在本部分的Java 2D教程中,我们将处理图像。
BufferedImage是Java 2D中处理图像的基础类。它是在内存中存储的像素矩形。
显示图像
在第一个示例中,我们在面板上显示一个图像。
package com.zetcode;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel {
private Image mshi;
public Surface() {
loadImage();
setSurfaceSize();
}
private void loadImage() {
mshi = new ImageIcon("mushrooms.jpg").getImage();
}
private void setSurfaceSize() {
Dimension d = new Dimension();
d.width = mshi.getWidth(null);
d.height = mshi.getHeight(null);
setPreferredSize(d);
}
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(mshi, 0, 0, null);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
public class DisplayImageEx extends JFrame {
public DisplayImageEx() {
initUI();
}
private void initUI() {
add(new Surface());
pack();
setTitle("Mushrooms");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
DisplayImageEx ex = new DisplayImageEx();
ex.setVisible(true);
}
});
}
}
在示例中,我们在面板上显示一个图像。窗口会调整大小以适应图像的大小。
private void loadImage() {
mshi = new ImageIcon("mushrooms.jpg").getImage();
}
我们使用ImageIcon类加载图像。图像位于当前工作目录中。
private void setSurfaceSize() {
Dimension d = new Dimension();
d.width = mshi.getWidth(null);
d.height = mshi.getHeight(null);
setPreferredSize(d);
}
我们确定加载图像的大小。使用setPreferredSize方法,我们设置Surface面板的首选大小。JFrame容器的pack方法将使框架适应其子对象的大小。在我们的例子中是Surface面板。因此,窗口的大小将正好显示加载的图像。
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(mshi, 0, 0, null);
}
图像使用drawImage方法在面板上绘制。最后一个参数是ImageObserver类。它有时用于异步加载。当我们不需要异步加载图像时,可以将null放在那里。
private void initUI() {
...
pack();
...
}
pack方法将容器的大小调整为适合子面板的大小。
灰度图像
在计算学中,数字灰度图像是指其中每个像素的值都是一个单一的样本,即它承载了关于其强度的全部(且唯一)信息。此类图像完全由中性灰度的阴影组成,从最弱强度的黑色到最强白色的变化。(维基百科)
在下一个示例中,我们将使用Java 2D创建一个灰度图像。
package com.zetcode;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel {
private Image mshi;
private BufferedImage bufimg;
private Dimension d;
public Surface() {
loadImage();
determineAndSetSize();
createGrayImage();
}
private void determineAndSetSize() {
d = new Dimension();
d.width = mshi.getWidth(null);
d.height = mshi.getHeight(null);
setPreferredSize(d);
}
private void createGrayImage() {
bufimg = new BufferedImage(d.width, d.height,
BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g2d = bufimg.createGraphics();
g2d.drawImage(mshi, 0, 0, null);
g2d.dispose();
}
private void loadImage() {
mshi = new ImageIcon("mushrooms.jpg").getImage();
}
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(bufimg, null, 0, 0);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
public class GrayScaleImageEx extends JFrame {
public GrayScaleImageEx() {
initUI();
}
private void initUI() {
Surface dpnl = new Surface();
add(dpnl);
pack();
setTitle("GrayScale image");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
GrayScaleImageEx ex = new GrayScaleImageEx();
ex.setVisible(true);
}
});
}
}
有几种方法可以创建灰度图像。我们通过将图像数据写入BufferedImage.TYPE_BYTE_GRAY类型的缓冲图像来实现。
bufimg = new BufferedImage(d.width, d.height,
BufferedImage.TYPE_BYTE_GRAY);
我们创建一个类型为BufferedImage.TYPE_BYTE_GRAY的BufferedImage类。
Graphics2D g2d = bufimg.createGraphics(); g2d.drawImage(mshi, 0, 0, null);
这里我们将蘑菇图像绘制到缓冲图像中。
g2d.dispose();
使用createGraphics方法创建的Graphics对象应手动释放。作为组件的paint和update方法的参数提供的Graphics对象在这些方法返回时由系统自动释放。
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(bufimg, null, 0, 0);
}
缓冲图像使用drawImage方法在面板上绘制。
翻转图像
下面的示例翻转图像。我们将过滤图像。有一个filter方法可以转换图像。
package com.zetcode;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel {
private Image mshi;
private BufferedImage bufimg;
private final int SPACE = 10;
public Surface() {
loadImage();
createFlippedImage();
setSurfaceSize();
}
private void loadImage() {
mshi = new ImageIcon("mushrooms.jpg").getImage();
}
private void createFlippedImage() {
bufimg = new BufferedImage(mshi.getWidth(null),
mshi.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics gb = bufimg.getGraphics();
gb.drawImage(mshi, 0, 0, null);
gb.dispose();
AffineTransform tx = AffineTransform.getScaleInstance(-1, 1);
tx.translate(-mshi.getWidth(null), 0);
AffineTransformOp op = new AffineTransformOp(tx,
AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
bufimg = op.filter(bufimg, null);
}
private void setSurfaceSize() {
int w = bufimg.getWidth();
int h = bufimg.getHeight();
Dimension d = new Dimension(3*SPACE+2*w, h+2*SPACE);
setPreferredSize(d);
}
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(mshi, SPACE, SPACE, null);
g2d.drawImage(bufimg, null, 2*SPACE + bufimg.getWidth(), SPACE);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
public class FlippedImageEx extends JFrame {
public FlippedImageEx() {
initUI();
}
private void initUI() {
add(new Surface());
pack();
setTitle("Flipped image");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
FlippedImageEx ex = new FlippedImageEx();
ex.setVisible(true);
}
});
}
}
在我们的代码示例中,我们将图像水平翻转。
AffineTransform tx = AffineTransform.getScaleInstance(-1, 1); tx.translate(-castle.getWidth(null), 0);
翻转图像意味着对其进行缩放和平移。因此,我们进行AffineTransform操作。
AffineTransformOp op = new AffineTransformOp(tx,
AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
bufferedImage = op.filter(bufferedImage, null)
这是可用的过滤操作之一。也可以通过像素操作来完成。但是Java 2D提供了高级类,可以更轻松地操作图像。在我们的例子中,AffineTransformOp类对图像像素执行缩放和平移。
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(mshi, SPACE, SPACE, null);
g2d.drawImage(bufimg, null, 2*SPACE + bufimg.getWidth(), SPACE);
}
两个图像都绘制在面板上。
private void setSurfaceSize() {
int w = bufimg.getWidth();
int h = bufimg.getHeight();
Dimension d = new Dimension(3*SPACE+2*w, h+2*SPACE);
setPreferredSize(d);
}
我们为面板设置了首选大小。我们计算大小是为了能够在面板上放置两个图像,并在它们之间以及图像与窗口边框之间留出一些空间。
模糊图像
下一个代码示例模糊图像。模糊意味着图像不清晰。要模糊图像,我们使用卷积操作。它是一种数学运算,也用于边缘检测或噪声消除。模糊操作可用于各种图形效果。例如,创建速度错觉或显示人的模糊视野。
模糊滤镜操作会用像素及其邻居的平均值替换图像中的每个像素。卷积是逐像素操作。相同的算术运算会为图像中的每个像素重复执行。核可以被认为是数字的二维网格,它依次遍历图像的每个像素,并在过程中执行计算。由于图像也可以被认为是数字的二维网格,将核应用于图像可以被可视化为一个小的网格(核)在很大程度上更大的网格(图像)上移动。(developer.apple.com)
package com.zetcode;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel {
private BufferedImage mshi;
private BufferedImage bluri;
public Surface() {
loadImage();
createBlurredImage();
setSurfaceSize();
}
private void loadImage() {
try {
mshi = ImageIO.read(new File("mushrooms.jpg"));
} catch (IOException ex) {
Logger.getLogger(Surface.class.getName()).log(
Level.WARNING, null, ex);
}
}
private void createBlurredImage() {
float[] blurKernel = {
1 / 9f, 1 / 9f, 1 / 9f,
1 / 9f, 1 / 9f, 1 / 9f,
1 / 9f, 1 / 9f, 1 / 9f
};
BufferedImageOp blur = new ConvolveOp(new Kernel(3, 3, blurKernel));
bluri = blur.filter(mshi, new BufferedImage(mshi.getWidth(),
mshi.getHeight(), mshi.getType()));
}
private void setSurfaceSize() {
Dimension d = new Dimension();
d.width = mshi.getWidth(null);
d.height = mshi.getHeight(null);
setPreferredSize(d);
}
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(bluri, null, 0, 0);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
public class BlurredImageEx extends JFrame {
public BlurredImageEx() {
setTitle("Blurred image");
add(new Surface());
pack();
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
BlurredImageEx ex = new BlurredImageEx();
ex.setVisible(true);
}
});
}
}
在代码示例中,我们从磁盘加载图像,对图像执行模糊操作,并在窗口上显示结果。
private void loadImage() {
try {
mshi = ImageIO.read(new File("mushrooms.jpg"));
} catch (IOException ex) {
Logger.getLogger(Surface.class.getName()).log(
Level.WARNING, null, ex);
}
}
ImageIO类的read方法从磁盘读取图像并返回一个BufferedImage。
float[] blurKernel = {
1 / 9f, 1 / 9f, 1 / 9f,
1 / 9f, 1 / 9f, 1 / 9f,
1 / 9f, 1 / 9f, 1 / 9f
};
这个矩阵称为核。这些值是应用于要更改的像素的邻居值的权重。
BufferedImageOp blur = new ConvolveOp(new Kernel(3, 3, blurKernel));
bluri = blur.filter(mshi, new BufferedImage(mshi.getWidth(),
mshi.getHeight(), mshi.getType()));
在这里,我们将模糊滤镜应用于图像。
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(bluri, null, 0, 0);
}
模糊图像在窗口上绘制。
反射
在下一个示例中,我们展示了一个反射图像。这种效果给人一种图像在水中反射的错觉。下面的代码示例受到jhlabs.com代码的启发。
package com.zetcode;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel {
private BufferedImage image;
private BufferedImage refImage;
private int img_w;
private int img_h;
private final int SPACE = 30;
public Surface() {
loadImage();
getImageSize();
createReflectedImage();
}
private void loadImage() {
try {
image = ImageIO.read(new File("rotunda.jpg"));
} catch (Exception ex) {
Logger.getLogger(Surface.class.getName()).log(
Level.WARNING, null, ex);
}
}
private void getImageSize() {
img_w = image.getWidth();
img_h = image.getHeight();
}
private void createReflectedImage() {
float opacity = 0.4f;
float fadeHeight = 0.3f;
refImage = new BufferedImage(img_w, img_h,
BufferedImage.TYPE_INT_ARGB);
Graphics2D rg = refImage.createGraphics();
rg.drawImage(image, 0, 0, null);
rg.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN));
rg.setPaint(new GradientPaint(0, img_h * fadeHeight,
new Color(0.0f, 0.0f, 0.0f, 0.0f), 0, img_h,
new Color(0.0f, 0.0f, 0.0f, opacity)));
rg.fillRect(0, 0, img_w, img_h);
rg.dispose();
}
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
int win_w = getWidth();
int win_h = getHeight();
int gap = 20;
g2d.setPaint(new GradientPaint(0, 0, Color.black, 0,
win_h, Color.darkGray));
g2d.fillRect(0, 0, win_w, win_h);
g2d.translate((win_w - img_w) / 2, win_h / 2 - img_h);
g2d.drawImage(image, 0, 0, null);
g2d.translate(0, 2 * img_h + gap);
g2d.scale(1, -1);
g2d.drawImage(refImage, 0, 0, null);
g2d.dispose();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(img_w + 2 * SPACE, 2 * img_h + 3 * SPACE);
}
}
public class ReflectionEx extends JFrame {
public ReflectionEx() {
initUI();
}
private void initUI() {
add(new Surface());
pack();
setTitle("Reflection");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
ReflectionEx ex = new ReflectionEx();
ex.setVisible(true);
}
});
}
}
在示例中,我们创建了一个反射图像的错觉。
refImage = new BufferedImage(img_w, img_h,
BufferedImage.TYPE_INT_ARGB);
Graphics2D rg = refImage.createGraphics();
rg.drawImage(image, 0, 0, null);
创建了加载图像的一个副本。
rg.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN));
rg.setPaint(new GradientPaint(0, img_h * fadeHeight,
new Color(0.0f, 0.0f, 0.0f, 0.0f), 0, img_h,
new Color(0.0f, 0.0f, 0.0f, opacity)));
rg.fillRect(0, 0, img_w, img_h);
这是代码中最重要的部分。我们将第二个图像设置为透明。但透明度不是恒定的;图像会逐渐淡出。这是通过GradientPaint类实现的。
g2d.setPaint(new GradientPaint(0, 0, Color.black, 0,
win_h, Color.darkGray));
g2d.fillRect(0, 0, win_w, win_h);
窗口的背景填充了渐变画笔。画笔是从黑色到深灰色的平滑混合。
g2d.translate((win_w - img_w) / 2, win_h / 2 - img_h); g2d.drawImage(image, 0, 0, null);
普通图像被移动到窗口的中心并绘制。
g2d.translate(0, 2 * imageHeight + gap); g2d.scale(1, -1);
这段代码翻转图像并将其平移到原始图像下方。平移操作是必需的,因为缩放操作会使图像上下颠倒并将图像向上平移。要理解发生了什么,只需拿一张照片放在桌子上然后翻过来。
g2d.drawImage(refImage, 0, 0, null);
绘制了反射图像。
@Override
public Dimension getPreferredSize() {
return new Dimension(img_w + 2 * SPACE, 2 * img_h + 3 * SPACE);
}
设置组件首选大小的另一种方法是覆盖getPreferredSize方法。
在本部分Java 2D教程中,我们处理了图像。