为什么 getScaledInstance() 不起作用?
Why getScaledInstance() does not work?
所以,我正在尝试创建一个垄断游戏。我正在尝试将(电路板的)图像加载到 JPanel
。
我首先想将图像缩放为 1024*1024
图像。
我已经将图片显示在 JPanel
上(因此文件地址有效)。
但是每当我使用 getScaledInstance()
方法时,图像都不会出现
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.SystemColor;
//a class that represent the board (JFrame) and all of it components
public class Board extends JFrame {
private final int SCALE;
private JPanel panel;
public Board(int scale) {
getContentPane().setBackground(SystemColor.textHighlightText);
// SCALE = scale;
SCALE = 1;
// set up the JFrame
setResizable(false);
setTitle("Monopoly");
// set size to a scale of 1080p
setSize(1920 * SCALE, 1080 * SCALE);
getContentPane().setLayout(null);
panel = new JPanel() {
public void paint(Graphics g) {
Image board = new ImageIcon(
"C:\Users\Standard\Pictures\Work\Monopoly 1.jpg")
.getImage();
board = board.getScaledInstance(1022, 1024, java.awt.Image.SCALE_SMOOTH);
g.drawImage(board, 0, 0, null);
}
};
panel.setBounds(592, 0, 1024, 1024);
getContentPane().add(panel);
}
public static void main(String[] args) {
Board board = new Board(1);
board.setVisible(true);
board.panel.repaint();
}
}
每当我删除 board.getScaledInstance()
行代码时,图像就会出现(虽然没有缩放),但是当我添加这行代码时,图像根本不会出现。
为什么会这样?
你做错了几件事:
- 您重写了 paint,而不是 paintComponent。这是危险的,因为您正在覆盖一个做了太多事情并且承担太多责任的图像。不小心执行此操作可能会导致严重的图像副作用,并且由于绘制不是双缓冲,还会导致感知动画缓慢。
- 您没有在重写中调用超级绘画方法,这会导致绘画伪像的积累和 Swing 组件绘画链的中断。
- 您可能会在绘画方法中多次读取图像,这种方法必须尽可能快,因为它是应用程序感知响应能力的主要决定因素。只读取一次,然后保存到变量中。
- 您正在使用空布局和 setBounds。虽然空布局和
setBounds()
对于 Swing 新手来说似乎是创建复杂 GUI 的最简单和最好的方法,但您创建的 Swing GUI 越多,您在使用它们时就会 运行 遇到更严重的困难。它们不会在 GUI 调整大小时调整您的组件的大小,它们是皇家 来增强或维护的,当放置在滚动窗格中时它们完全失败,当在所有平台或不同的屏幕分辨率上查看时它们看起来很糟糕从原来的。
- 您在 paint 方法中缩放图像,同样会降低 GUI 的感知响应速度。相反,只缩放图像一次,并将缩放后的图像保存到变量中。
- 重要的是,您对原始图像和缩放后的图像使用相同的变量 board,这将导致每次调用绘制时重新缩放图像。
- 正如 Mad 指出的那样,您应该将
this
传递给您的 g.drawImage(...)
方法调用,这样您就不会在完全读入之前绘制图像。
- 此外,当您不将图像用作 ImageIcon 时,请勿将其作为文件或 ImageIcon 读取。使用 ImageIO 将其作为 BufferedImage 读入,并使用资源,而不是文件。
我也会简化一些事情,例如:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
@SuppressWarnings("serial")
public class MyBoard extends JPanel {
private static final String IMG_PATH = "http://ecx.images-amazon.com/"
+ "images/I/81oC5pYhh2L._SL1500_.jpg";
// scaling constants
private static final int IMG_WIDTH = 1024;
private static final int IMG_HEIGHT = IMG_WIDTH;
// original and scaled image variables
private BufferedImage initialImg;
private Image scaledImg;
public MyBoard() throws IOException {
URL url = new URL(IMG_PATH);
initialImg = ImageIO.read(url); // read in original image
// and scale it *once* and store in variable. Can even discard original
// if you wish
scaledImg = initialImg.getScaledInstance(IMG_WIDTH, IMG_HEIGHT,
Image.SCALE_SMOOTH);
}
// override paintComponent, not paint
@Override // and don't forget the @Override annotation
protected void paintComponent(Graphics g) {
super.paintComponent(g); // call the super's painting method
// just to be safe -- check that it's not null first
if (scaledImg != null) {
// use this as a parameter to avoid drawing an image before it's
// ready
g.drawImage(scaledImg, 0, 0, this);
}
}
// so our GUI is sized the same as the image
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet() || scaledImg == null) {
return super.getPreferredSize();
}
int w = scaledImg.getWidth(this);
int h = scaledImg.getHeight(this);
return new Dimension(w, h);
}
private static void createAndShowGui() {
MyBoard mainPanel = null;
try {
mainPanel = new MyBoard();
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
JFrame frame = new JFrame("My Board");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
所以,我正在尝试创建一个垄断游戏。我正在尝试将(电路板的)图像加载到 JPanel
。
我首先想将图像缩放为 1024*1024
图像。
我已经将图片显示在 JPanel
上(因此文件地址有效)。
但是每当我使用 getScaledInstance()
方法时,图像都不会出现
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.SystemColor;
//a class that represent the board (JFrame) and all of it components
public class Board extends JFrame {
private final int SCALE;
private JPanel panel;
public Board(int scale) {
getContentPane().setBackground(SystemColor.textHighlightText);
// SCALE = scale;
SCALE = 1;
// set up the JFrame
setResizable(false);
setTitle("Monopoly");
// set size to a scale of 1080p
setSize(1920 * SCALE, 1080 * SCALE);
getContentPane().setLayout(null);
panel = new JPanel() {
public void paint(Graphics g) {
Image board = new ImageIcon(
"C:\Users\Standard\Pictures\Work\Monopoly 1.jpg")
.getImage();
board = board.getScaledInstance(1022, 1024, java.awt.Image.SCALE_SMOOTH);
g.drawImage(board, 0, 0, null);
}
};
panel.setBounds(592, 0, 1024, 1024);
getContentPane().add(panel);
}
public static void main(String[] args) {
Board board = new Board(1);
board.setVisible(true);
board.panel.repaint();
}
}
每当我删除 board.getScaledInstance()
行代码时,图像就会出现(虽然没有缩放),但是当我添加这行代码时,图像根本不会出现。
为什么会这样?
你做错了几件事:
- 您重写了 paint,而不是 paintComponent。这是危险的,因为您正在覆盖一个做了太多事情并且承担太多责任的图像。不小心执行此操作可能会导致严重的图像副作用,并且由于绘制不是双缓冲,还会导致感知动画缓慢。
- 您没有在重写中调用超级绘画方法,这会导致绘画伪像的积累和 Swing 组件绘画链的中断。
- 您可能会在绘画方法中多次读取图像,这种方法必须尽可能快,因为它是应用程序感知响应能力的主要决定因素。只读取一次,然后保存到变量中。
- 您正在使用空布局和 setBounds。虽然空布局和
setBounds()
对于 Swing 新手来说似乎是创建复杂 GUI 的最简单和最好的方法,但您创建的 Swing GUI 越多,您在使用它们时就会 运行 遇到更严重的困难。它们不会在 GUI 调整大小时调整您的组件的大小,它们是皇家 来增强或维护的,当放置在滚动窗格中时它们完全失败,当在所有平台或不同的屏幕分辨率上查看时它们看起来很糟糕从原来的。 - 您在 paint 方法中缩放图像,同样会降低 GUI 的感知响应速度。相反,只缩放图像一次,并将缩放后的图像保存到变量中。
- 重要的是,您对原始图像和缩放后的图像使用相同的变量 board,这将导致每次调用绘制时重新缩放图像。
- 正如 Mad 指出的那样,您应该将
this
传递给您的g.drawImage(...)
方法调用,这样您就不会在完全读入之前绘制图像。 - 此外,当您不将图像用作 ImageIcon 时,请勿将其作为文件或 ImageIcon 读取。使用 ImageIO 将其作为 BufferedImage 读入,并使用资源,而不是文件。
我也会简化一些事情,例如:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
@SuppressWarnings("serial")
public class MyBoard extends JPanel {
private static final String IMG_PATH = "http://ecx.images-amazon.com/"
+ "images/I/81oC5pYhh2L._SL1500_.jpg";
// scaling constants
private static final int IMG_WIDTH = 1024;
private static final int IMG_HEIGHT = IMG_WIDTH;
// original and scaled image variables
private BufferedImage initialImg;
private Image scaledImg;
public MyBoard() throws IOException {
URL url = new URL(IMG_PATH);
initialImg = ImageIO.read(url); // read in original image
// and scale it *once* and store in variable. Can even discard original
// if you wish
scaledImg = initialImg.getScaledInstance(IMG_WIDTH, IMG_HEIGHT,
Image.SCALE_SMOOTH);
}
// override paintComponent, not paint
@Override // and don't forget the @Override annotation
protected void paintComponent(Graphics g) {
super.paintComponent(g); // call the super's painting method
// just to be safe -- check that it's not null first
if (scaledImg != null) {
// use this as a parameter to avoid drawing an image before it's
// ready
g.drawImage(scaledImg, 0, 0, this);
}
}
// so our GUI is sized the same as the image
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet() || scaledImg == null) {
return super.getPreferredSize();
}
int w = scaledImg.getWidth(this);
int h = scaledImg.getHeight(this);
return new Dimension(w, h);
}
private static void createAndShowGui() {
MyBoard mainPanel = null;
try {
mainPanel = new MyBoard();
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
JFrame frame = new JFrame("My Board");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}