在JPanel中调整图像大小

3

我正在尝试在JPanel中设置一张图片作为背景,并将其调整为所需的大小。

这是我的面板(MyPanel),我在其中选择图片并将其设置为背景:

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);
            }
        });
    }
}

问题在于图片一开始不显示,只有当我更改帧大小时才会显示。就像这样: enter image description here

3
考虑使用 g.drawImage(img, 0, 0, Color.WHITE, this);。这行代码的意思是在当前组件上绘制一幅指定的图像,并将其放置在左上角,背景颜色设置为白色。 - MadProgrammer
1
你可能也想看一下调整大小后图像质量非常低 - MadProgrammer
谢谢。它可以工作,但是需要大约2秒才能显示。这是因为图像处理需要太长时间吗? - Krzysztof Majewski
2
是的,或多或少,有许多事情正在进行,第一件事是缩放操作,第二件事是将图像转换为更好显示的格式(颜色模型)。 - MadProgrammer
作为旁注,为什么在MyPanel pnl = new MyPanel(null);中会有null - mKorbel
@Krzysztof Majewski null != 布局管理器 - mKorbel
2个回答

2
您正在异步加载图像。
ImageIcon(String)构造函数在内部使用Toolkit.getImage,这是上世纪90年代的遗留问题,当时许多家庭互联网连接速度非常慢,因此始终在后台线程中加载图像是有意义的。
由于图像是在后台加载的,img.getWidth(null)可能会返回图像的大小,也可能返回-1。文档解释了这一点。
那么,如何等待图像在后台线程中加载完成?
通常,您会使用ImageObserver观察图像的进度。恰好所有AWT组件,以及所有Swing JComponent都实现了ImageObserver。因此,您有一个ImageObserver对象:您的MyPanel实例。
因此,您可以将MyPanel实例(即this)传递给所有这些方法,而不是将null传递给它们。
double xRatio = img.getWidth(this) / 400;
double yRatio = img.getHeight(this) / 400;

"而且:"
g.drawImage(img, 0, 0, Color.WHITE, this);

然而... 这将正确地跟踪图像的加载进度,但仍不能保证在调用img.getWidth时会返回正值。
为了立即确保图像已完全加载,您可以使用ImageIO.read替换ImageIcon的使用:
File file = fc.getSelectedFile();
try {
    img = ImageIO.read(file);
} catch (IOException e) {
    throw new RuntimeException("Could not load \"" + file + "\"", e);
}

这与使用ImageIcon和Toolkit不同,因为它不仅返回一个Image,而是返回一个BufferedImage,它是一种保证完全加载并存在于内存中的Image类型,无需担心后台加载。

由于BufferedImage已经加载,它有一些不需要ImageObserver的附加方法,特别是不需要参数的getWidthgetHeight方法:

BufferedImage bufferedImg = (BufferedImage) img;
double xRatio = bufferedImg.getWidth() / 400.0;
double yRatio = bufferedImg.getHeight() / 400.0;

注意我将400更改为400.0。这是因为在Java中,将一个整数除以另一个整数会产生整数算术运算。例如,200/400返回零,因为200和400都是int值。5/2产生int值2。
然而,如果其中一个或两个数字是double类型,Java将整个表达式视为double表达式。因此,(double) 200/400返回0.5。在数字序列中出现小数点表示double值,因此200.0/400200/400.0(当然,200.0/400.0)也将返回0.5。
最后,还有缩放问题。我建议阅读MadProgrammer的答案,特别是他的答案链接到的java.net文章The Perils of Image.getScaledInstance()
简要概述:Image.getScaledInstance是上世纪90年代的遗留物,缩放效果不佳。有两个更好的选项:
  1. 将图像绘制到新图像中,让drawImage方法处理缩放,使用Graphics2D对象的RenderingHints。
  2. 在图像上使用AffineTransformOp创建一个新的缩放图像。

方法1:

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类中有文档记录。

0

你需要调用 setVisible(true) 方法:

public MyFrame () {    
    initUI();
    setVisible(true);
}

3
你的意思是像操作者正在使用 ex.setVisible(true); 这样的代码吗? - MadProgrammer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接