Java:如何维护JPanel背景图像的纵横比

26

我有一个带有背景图片的 JPanel 和一个布局管理器,其中包含其他较小的图片,所有这些都在一个 JFrame 中。背景图片很大,我想让它在大屏幕或小屏幕上保持其纵横比。

最终,我希望我的 LayoutManager 和其单元格中的较小图像“粘”在背景图片上。

我查找了一些资源,看起来许多示例使用了 BufferedImage,但我没有使用;这会造成问题吗?我将在下面发布我的代码以绘制图像,如果我缺少任何信息,请告诉我。

public class MonitorPanel extends JPanel {
    Image img;
    public MonitorPanel() throws MalformedURLException {
        //add components

        try {
            img = ImageIO.read(new File("src/customer_vlans.jpg"));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
protected void paintComponent(Graphics g)
{
    //paint background image
    super.paintComponent(g);
    //g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
    g.drawImage(img, 0, 0, this);

}

}

编辑:我应该提到我知道宽高比公式:原始高度/原始宽度x新宽度=新高度。然而,我不知道如何正确地利用它。

4个回答

49

最快最简单的解决方案是使用Image.getScaledInstance

g.drawImage(img.getScaledInstance(newWidth, -1, Image. SCALE_SMOOTH), x, y, this);

如果您对负数感到困惑,Java文档说:
如果宽度或高度为负数,则会替换一个值以保持原始图像尺寸的纵横比。如果宽度和高度都是负数,则使用原始图像尺寸。
更新
只是顺便提一句(我的谷歌出了点问题)。
getScaledInstance既不是最快也不是最高质量的方法,但它是最简单的方法。
阅读The Perils of Image.getScaledInstance获取更多想法。
更新
将图像缩放以适应区域比简单缩放长宽比稍微复杂些。您必须选择是否要使图像“适合”该区域(可能在其周围留下空白区域)或覆盖该区域(使其最小维度适合该区域的最大维度)。

FitFill

适应与填充

基本上,我使用比例因子来工作

这将返回特定尺寸的缩放因子。我使用它来决定我想要使用哪个因子,基于我需要的算法

public static double getScaleFactor(int iMasterSize, int iTargetSize) {

    double dScale = 1;
    if (iMasterSize > iTargetSize) {

        dScale = (double) iTargetSize / (double) iMasterSize;

    } else {

        dScale = (double) iTargetSize / (double) iMasterSize;

    }

    return dScale;

}

这两个方法使用了它。它们只需要两个Dimension,原始尺寸和目标尺寸。

public static double getScaleFactorToFit(Dimension original, Dimension toFit) {

    double dScale = 1d;

    if (original != null && toFit != null) {

        double dScaleWidth = getScaleFactor(original.width, toFit.width);
        double dScaleHeight = getScaleFactor(original.height, toFit.height);

        dScale = Math.min(dScaleHeight, dScaleWidth);

    }

    return dScale;

}

public static double getScaleFactorToFill(Dimension masterSize, Dimension targetSize) {

    double dScaleWidth = getScaleFactor(masterSize.width, targetSize.width);
    double dScaleHeight = getScaleFactor(masterSize.height, targetSize.height);

    double dScale = Math.max(dScaleHeight, dScaleWidth);

    return dScale;

}

将图像传递到程序中相对简单(可以直接传递或通过支持方法传递)。例如,您可以在paint方法中调用此函数。

double factor getScaledFactorToFit(new Dimension(image.getWidth(), image.getHeight()), getSize());

int scaledWidth = image.getWidth() * scale;
int scaledHeight *= image.getWidth() * scale;

This will automatically take care of the aspect ratio for you ;)

更新并扩展示例

public double getScaleFactor(int iMasterSize, int iTargetSize) {

    double dScale = 1;
    if (iMasterSize > iTargetSize) {

        dScale = (double) iTargetSize / (double) iMasterSize;

    } else {

        dScale = (double) iTargetSize / (double) iMasterSize;

    }

    return dScale;

}

public double getScaleFactorToFit(Dimension original, Dimension toFit) {

    double dScale = 1d;

    if (original != null && toFit != null) {

        double dScaleWidth = getScaleFactor(original.width, toFit.width);
        double dScaleHeight = getScaleFactor(original.height, toFit.height);

        dScale = Math.min(dScaleHeight, dScaleWidth);

    }

    return dScale;

}

@Override
protected void paintComponent(Graphics g) {

    super.paintComponent(g);

    double scaleFactor = Math.min(1d, getScaleFactorToFit(new Dimension(image.getWidth(), image.getHeight()), getSize()));

    int scaleWidth = (int) Math.round(image.getWidth() * scaleFactor);
    int scaleHeight = (int) Math.round(image.getHeight() * scaleFactor);

    Image scaled = image.getScaledInstance(scaleWidth, scaleHeight, Image.SCALE_SMOOTH);

    int width = getWidth() - 1;
    int height = getHeight() - 1;

    int x = (width - scaled.getWidth(this)) / 2;
    int y = (height - scaled.getHeight(this)) / 2;

    g.drawImage(scaled, x, y, this);

}

1
太棒了!我使用了你们两个的答案来达到想要的效果。我希望我能够选中你们两个的答案,非常感谢你们的时间和知识。 - exit_1
@FagnerBrack 我知道这个。 - MadProgrammer
@FagnerBrack 我不想争论这个事实,这段代码是从我多年未查看的工作库代码中提取的。与其去搞那些我知道能正常工作的东西,我选择保持原样... - MadProgrammer
@FagnerBrack 我现在选择选项1。如果我没记错的话(代码已经有5年了),我曾经根据目标和主尺寸使用不同的功能,但我决定将其删除或进行测试... - MadProgrammer
我想到了一些非常简单而有趣的东西,值得一试 https://gist.github.com/FagnerMartinsBrack/6913946 - Fagner Brack
显示剩余13条评论

4
尝试像这样做:

尝试像这样做:

import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class SG2B2 {

    JFrame frame;

    public static void main(String[] args) {
        SG2B2 gui = new SG2B2();
        gui.createUI();
    }

    public void createUI() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        MyDrawPanel drawPanel = new MyDrawPanel();
        frame.getContentPane().add(BorderLayout.CENTER, drawPanel);
        frame.setSize(300, 400);
        frame.setVisible(true);
    }

    class MyDrawPanel extends JPanel {

        Image image;
        private final String pic = "Logo.jpg";

        public MyDrawPanel() {
            image = new ImageIcon(pic).getImage();
            image = scaleImage(image);
        }

        @Override
        public void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;
            g2.drawImage(image, 0, 0, this);
        }

        private Image scaleImage(Image rawImage) {
            Image scaledImage = null;
            System.out.println("Scaling");
            try {
                int rawImageWidth = rawImage.getWidth(this);
                int rawImageHeight = rawImage.getHeight(this);
                int paneWidth = (int) getWidth();
                int paneHeight = (int) getHeight();
                System.out.println("Image W = " + rawImageWidth
                        + ", H = " + rawImageHeight
                        + "; Pane W = " + paneWidth
                        + ", H = " + paneHeight);
                // preserve the original ratio  
                float widthRatio = (float) rawImageWidth / (float) paneWidth;
                float heightRatio = (float) rawImageHeight / (float) paneHeight;
                int widthFactor = -1;
                int heightFactor = -1;
                if ((widthRatio > heightRatio) && (widthRatio > 1.0)) {
                    widthFactor = paneWidth;
                } else if ((heightRatio > widthRatio) && (heightRatio > 1.0)) {
                    heightFactor = paneHeight;
                }
                System.out.println("widthRatio = "
                        + String.format("%.3f", widthRatio)
                        + ", heightRatio = "
                        + String.format("%.3f", heightRatio));
                System.out.println("widthFactor = " + widthFactor
                        + ", heightFactor = " + heightFactor);
                if ((widthFactor < 0) && (heightFactor < 0)) {
                    scaledImage = rawImage;
                } else {
                    scaledImage = rawImage.getScaledInstance(widthFactor, heightFactor,
                            Image.SCALE_SMOOTH);
                    // load the new image, 'getScaledInstance' loads asynchronously  
                    MediaTracker tracker = new MediaTracker(this);
                    tracker.addImage(scaledImage, 0);
                    tracker.waitForID(0);
                }
            } catch (InterruptedException ie) {
                System.err.println("load interrupt: " + ie.getMessage());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return (scaledImage);
        }
    }
}

这将通过使用 getScaledInstance(int width, int height, ImageObserver io) 最终将图像按比例缩放到 JPanel 的大小。


1
如果我要使用 MediaTracker,我可能会将其转移到第二个 Thread(显然这只是一个例子😉)。 另一个有趣的事实要记住的是,每个Java Component都是它自己的ImageObserver,因此您可以省去MediaTracker,因为Component已经在做这件事(某种程度上)。 除非您有一个非常庞大的图像需要加载,否则我可能会在单独的Thread中执行相同的操作;)在我看来 - MadProgrammer

3

对于任何对修改MadProgrammer的PaintComponent方法感兴趣的人,以下修改可以实现更快的显示更新。

        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2d = (Graphics2D) g;
            super.paintComponent(g);

            double scaleFactor = Math.min(1d, getScaleFactorToFit(new Dimension(image.getWidth(), image.getHeight()), getSize()));

            int scaleWidth = (int) Math.round(image.getWidth() * scaleFactor);
            int scaleHeight = (int) Math.round(image.getHeight() * scaleFactor);

            //Image scaled = image.getScaledInstance(scaleWidth, scaleHeight, Image.SCALE_SMOOTH);

            int width = getWidth() - 1;
            int height = getHeight() - 1;

            int x = (width - scaleWidth) / 2;
            int y = (height - scaleHeight) / 2;

            g2d.drawImage(image, x, y, scaleWidth, scaleHeight, this);

        }

这样会快得多。getScaledInstance非常慢。 - vovahost

1
我想出了这个解决方案:
public class ImageLabel extends JPanel {

    private Image image = null;

    public void setImage(Image img) {
        image = img;
        repaint();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        if (image != null) {

            int imgWidth, imgHeight;
            double contRatio = (double) getWidth() / (double) getHeight();
            double imgRatio =  (double) image.getWidth(this) / (double) image.getHeight(this);

            //width limited
            if(contRatio < imgRatio){
                imgWidth = getWidth();
                imgHeight = (int) (getWidth() / imgRatio);

            //height limited
            }else{
                imgWidth = (int) (getHeight() * imgRatio);
                imgHeight = getHeight();
            }

            //to center
            int x = (int) (((double) getWidth() / 2) - ((double) imgWidth / 2));
            int y = (int) (((double) getHeight()/ 2) - ((double) imgHeight / 2));

            g.drawImage(image, x, y, imgWidth, imgHeight, this);
        }
    }
}

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