在 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 的额外方法,特别是不需要参数的 getWidthgetHeight 方法:

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 / 400200 / 400.0(当然还有 200.0 / 400.0)也将 return 0.5。

最后,还有缩放的问题。我建议阅读 MadProgrammer 的回答,特别是他的回答链接到的 java.net 文章 The Perils of Image.getScaledInstance().

简而言之,Image.getScaledInstance 是 1990 年代的另一个遗留物,在缩放方面做得不是很好。有两个更好的选择:

  1. 将您的图像绘制成新图像,并使用 Graphics2D 对象的 RenderingHints 让 drawImage 方法处理缩放。
  2. 在您的图像上使用 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.