【问题标题】:Resizing Image in JPanel在 JPanel 中调整图像大小
【发布时间】:2015-10-25 15:10:04
【问题描述】:

我正在尝试在 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);
            }
        });
    }
}

问题是图像一开始没有显示。例如,当我稍微改变帧大小时,它会显示。

这样:

【问题讨论】:

  • 考虑使用g.drawImage(img, 0, 0, Color.WHITE, this);
  • 谢谢。它可以工作,但它会在 2 秒后显示。是不是因为处理imgae需要这么多时间?
  • 是的,或多或少,有很多事情发生,第一个是缩放操作,第二个是可能将图像转换为可以更好显示的格式(颜色型号)
  • 顺便说一句,为什么在 MyPanel pnl = new MyPanel(null); 内有 null

标签: java swing jcomponent


【解决方案1】:

您正在异步加载图像。

ImageIcon(String) 构造函数在内部使用 Toolkit.getImage,这是 1990 年代的遗留物,当时许多家庭互联网连接速度非常慢,因此总是在后台线程中加载图像是有意义的。

由于图片在后台加载,img.getWidth(null)可能返回图片的大小,或者可能返回-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 在您调用它时会返回一个正值。

为保证图像立即完全加载,您可以将 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 不同,因为它不只是返回一个 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。这是因为将一个 int 除以另一个 int 会导致 Java 中的整数运算。例如,200 / 400 返回零,因为 200 和 400 都是 int 值。 5 / 2 产生 int 值 2。

但是,如果其中一个或两个数字都是双精度数,Java 会将整个事物视为双精度表达式。所以,(double) 200 / 400 返回 0.5。数字序列中存在小数点表示双精度值,因此200.0 / 400200 / 400.0(当然还有200.0 / 400.0)也将返回 0.5。

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

简短的版本是 Image.getScaledInstance 是 1990 年代的另一个保留,在缩放方面做得不是很好。有两个更好的选择:

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

【讨论】:

    【解决方案2】:

    你必须调用setVisible(true):

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

    【讨论】:

    • 你的意思是就像操作对ex.setVisible(true);做的那样
    猜你喜欢
    • 2013-11-12
    • 1970-01-01
    • 2015-10-18
    • 2012-05-16
    • 2012-05-24
    • 1970-01-01
    • 2018-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多