在 JPanel 中调整图像大小
Resizing Image in JPanel
我正在尝试在 JPanel 中将图像设置为背景并将其调整为所需大小。
这是我的面板,我在其中选择图像并将其设置为背景:
public class MyPanel extends JPanel {
Image img;
public MyPanel(LayoutManager l) {
super(l);
JFileChooser fc = new JFileChooser();
int result = fc.showOpenDialog(null);
if (result == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
String sname = file.getAbsolutePath();
img = new ImageIcon(sname).getImage();
double xRatio = img.getWidth(null) / 400;
double yRatio = img.getHeight(null) / 400;
double ratio = (xRatio + yRatio) / 2;
img = img.getScaledInstance((int)(img.getWidth(null) / ratio), (int)(img.getHeight(null) / ratio), Image.SCALE_SMOOTH);
}
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img, 0, 0, Color.WHITE, null);
}
}
这是我的框架:
public class MyFrame extends JFrame {
public MyFrame () {
initUI();
}
private void initUI() {
MyPanel pnl = new MyPanel(null);
add(pnl);
setSize(600, 600);
setTitle("My component");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
MyFrame ex = new MyFrame ();
ex.setVisible(true);
}
});
}
}
问题是图像最初没有显示。它显示了我何时稍微更改帧大小。
像那样:
您必须调用 setVisible(true)
:
public MyFrame () {
initUI();
setVisible(true);
}
您正在异步加载图像。
ImageIcon(String) 构造函数在内部使用 Toolkit.getImage,这是 1990 年代的遗留问题,当时许多家庭互联网连接速度非常慢,因此始终在后台线程中加载图像是有意义的。
由于图片在后台加载,img.getWidth(null)
可能 return 图片的大小,或者 可能 return -1。 documentation 对此进行了解释。
那么,您如何等到图像在后台线程中加载完毕?
通常,您会使用 ImageObserver 观察图像的进度。恰好所有 AWT 组件,以及所有 Swing JComponents 都实现了 ImageObserver。所以你有一个 ImageObserver 对象:你的 MyPanel 实例。
因此,不是将 null
传递给所有这些方法,而是传递您的 MyPanel 实例,即 this
:
double xRatio = img.getWidth(this) / 400;
double yRatio = img.getHeight(this) / 400;
并且:
g.drawImage(img, 0, 0, Color.WHITE, this);
然而...这将正确跟踪图像的加载进度,但仍然不能保证img.getWidth
当时return为正值你怎么称呼它。
为保证图像立即完全加载,您可以将 ImageIcon 的使用替换为 ImageIO.read:
File file = fc.getSelectedFile();
try {
img = ImageIO.read(file);
} catch (IOException e) {
throw new RuntimeException("Could not load \"" + file + "\"", e);
}
这与使用 ImageIcon 和 Toolkit 不同,因为它不只是 return 一个 Image,它 return 是一个 BufferedImage,这是一种保证完全加载的 Image并存在于内存中——无需担心后台加载。
由于 BufferedImage 已经加载,它有一些不需要 ImageObserver 的额外方法,特别是不需要参数的 getWidth
和 getHeight
方法:
BufferedImage bufferedImg = (BufferedImage) img;
double xRatio = bufferedImg.getWidth() / 400.0;
double yRatio = bufferedImg.getHeight() / 400.0;
注意我将 400 更改为 400.0。这是因为将一个 int 除以另一个 int 会导致 Java 中的整数运算。例如,200 / 400
return 为零,因为 200 和 400 都是 int 值。 5 / 2
生成 int 值 2。
但是,如果其中一个或两个数字都是双精度数,Java 会将整个数字视为双精度表达式。所以,(double) 200 / 400
returns 0.5。数字序列中存在小数点表示双精度值,因此 200.0 / 400
和 200 / 400.0
(当然还有 200.0 / 400.0
)也将 return 0.5。
最后,还有缩放的问题。我建议阅读 MadProgrammer 的回答,特别是他的回答链接到的 java.net 文章 The Perils of Image.getScaledInstance().
简而言之,Image.getScaledInstance 是 1990 年代的另一个遗留物,在缩放方面做得不是很好。有两个更好的选择:
- 将您的图像绘制成新图像,并使用 Graphics2D 对象的 RenderingHints 让 drawImage 方法处理缩放。
- 在您的图像上使用 AffineTransformOp 来创建新的缩放图像。
方法一:
BufferedImage scaledImage = new BufferedImage(
img.getColorModel(),
img.getRaster().createCompatibleWritableRaster(newWidth, newHeight),
false, new Properties());
Graphics g = scaledImage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_SPEED);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g.drawImage(img, 0, 0, newWidth, newHeight, null);
g.dispose();
方法二:
RenderingHints hints = new RenderingHints();
hints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_SPEED);
hints.put(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
AffineTransform transform = AffineTransform.getScaleInstance(
(double) newWidth / img.getWidth(),
(double) newHeight / img.getHeight());
BufferedImageOp op = new AffineTransformOp(transform, hints);
BufferedImage scaledImage = op.filter(img, null);
您可能希望根据自己偏好的速度与质量权衡来更改 RenderingHints 值。它们都记录在 RenderingHints class.
中
我正在尝试在 JPanel 中将图像设置为背景并将其调整为所需大小。
这是我的面板,我在其中选择图像并将其设置为背景:
public class MyPanel extends JPanel {
Image img;
public MyPanel(LayoutManager l) {
super(l);
JFileChooser fc = new JFileChooser();
int result = fc.showOpenDialog(null);
if (result == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
String sname = file.getAbsolutePath();
img = new ImageIcon(sname).getImage();
double xRatio = img.getWidth(null) / 400;
double yRatio = img.getHeight(null) / 400;
double ratio = (xRatio + yRatio) / 2;
img = img.getScaledInstance((int)(img.getWidth(null) / ratio), (int)(img.getHeight(null) / ratio), Image.SCALE_SMOOTH);
}
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img, 0, 0, Color.WHITE, null);
}
}
这是我的框架:
public class MyFrame extends JFrame {
public MyFrame () {
initUI();
}
private void initUI() {
MyPanel pnl = new MyPanel(null);
add(pnl);
setSize(600, 600);
setTitle("My component");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
MyFrame ex = new MyFrame ();
ex.setVisible(true);
}
});
}
}
问题是图像最初没有显示。它显示了我何时稍微更改帧大小。
像那样:
您必须调用 setVisible(true)
:
public MyFrame () {
initUI();
setVisible(true);
}
您正在异步加载图像。
ImageIcon(String) 构造函数在内部使用 Toolkit.getImage,这是 1990 年代的遗留问题,当时许多家庭互联网连接速度非常慢,因此始终在后台线程中加载图像是有意义的。
由于图片在后台加载,img.getWidth(null)
可能 return 图片的大小,或者 可能 return -1。 documentation 对此进行了解释。
那么,您如何等到图像在后台线程中加载完毕?
通常,您会使用 ImageObserver 观察图像的进度。恰好所有 AWT 组件,以及所有 Swing JComponents 都实现了 ImageObserver。所以你有一个 ImageObserver 对象:你的 MyPanel 实例。
因此,不是将 null
传递给所有这些方法,而是传递您的 MyPanel 实例,即 this
:
double xRatio = img.getWidth(this) / 400;
double yRatio = img.getHeight(this) / 400;
并且:
g.drawImage(img, 0, 0, Color.WHITE, this);
然而...这将正确跟踪图像的加载进度,但仍然不能保证img.getWidth
当时return为正值你怎么称呼它。
为保证图像立即完全加载,您可以将 ImageIcon 的使用替换为 ImageIO.read:
File file = fc.getSelectedFile();
try {
img = ImageIO.read(file);
} catch (IOException e) {
throw new RuntimeException("Could not load \"" + file + "\"", e);
}
这与使用 ImageIcon 和 Toolkit 不同,因为它不只是 return 一个 Image,它 return 是一个 BufferedImage,这是一种保证完全加载的 Image并存在于内存中——无需担心后台加载。
由于 BufferedImage 已经加载,它有一些不需要 ImageObserver 的额外方法,特别是不需要参数的 getWidth
和 getHeight
方法:
BufferedImage bufferedImg = (BufferedImage) img;
double xRatio = bufferedImg.getWidth() / 400.0;
double yRatio = bufferedImg.getHeight() / 400.0;
注意我将 400 更改为 400.0。这是因为将一个 int 除以另一个 int 会导致 Java 中的整数运算。例如,200 / 400
return 为零,因为 200 和 400 都是 int 值。 5 / 2
生成 int 值 2。
但是,如果其中一个或两个数字都是双精度数,Java 会将整个数字视为双精度表达式。所以,(double) 200 / 400
returns 0.5。数字序列中存在小数点表示双精度值,因此 200.0 / 400
和 200 / 400.0
(当然还有 200.0 / 400.0
)也将 return 0.5。
最后,还有缩放的问题。我建议阅读 MadProgrammer 的回答,特别是他的回答链接到的 java.net 文章 The Perils of Image.getScaledInstance().
简而言之,Image.getScaledInstance 是 1990 年代的另一个遗留物,在缩放方面做得不是很好。有两个更好的选择:
- 将您的图像绘制成新图像,并使用 Graphics2D 对象的 RenderingHints 让 drawImage 方法处理缩放。
- 在您的图像上使用 AffineTransformOp 来创建新的缩放图像。
方法一:
BufferedImage scaledImage = new BufferedImage(
img.getColorModel(),
img.getRaster().createCompatibleWritableRaster(newWidth, newHeight),
false, new Properties());
Graphics g = scaledImage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_SPEED);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g.drawImage(img, 0, 0, newWidth, newHeight, null);
g.dispose();
方法二:
RenderingHints hints = new RenderingHints();
hints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_SPEED);
hints.put(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
AffineTransform transform = AffineTransform.getScaleInstance(
(double) newWidth / img.getWidth(),
(double) newHeight / img.getHeight());
BufferedImageOp op = new AffineTransformOp(transform, hints);
BufferedImage scaledImage = op.filter(img, null);
您可能希望根据自己偏好的速度与质量权衡来更改 RenderingHints 值。它们都记录在 RenderingHints class.
中