Resizing image in JPanel

I am trying to set an image as a background in a JPanel and resize it to the correct size.

This is MyPanel where I select the image and set it as the backgorund:

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

      

And this is my frame:

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

      

The problem is the image is not showing at first. It shows when I, for example, change the frame size a little.

Like:

enter image description here

+3


source to share


2 answers


You load your image asynchronously.

The ImageIcon (String) constructor uses Toolkit.getImage internally, which is a holdover from the 1990s when many home internet connections were so slow that it makes sense to always load images into a background thread.

Since the image is loading in the background, it img.getWidth(null)

may return the size of the image, or it may return -1. The documentation explains this .

So how do you wait until the image is loaded onto this background thread?

Usually, you watch the progress of an image using ImageObserver. It just so happens that all AWT components as well as all Swing JComponents implement ImageObserver. So you have an ImageObserver object: your instance of MyPanel.

So, instead of passing null

to all of these methods, you pass your MyPanel instance, that is this

:

double xRatio = img.getWidth(this) / 400;
double yRatio = img.getHeight(this) / 400;

      

and

g.drawImage(img, 0, 0, Color.WHITE, this);

      

However ... this correctly tracks the progress of the image loading, but still does not guarantee that it img.getWidth

will return a positive value at the time you call it.

To ensure that the image is fully loaded immediately, you can replace ImageIcon's use of ImageIO.read :

File file = fc.getSelectedFile();
try {
    img = ImageIO.read(file);
} catch (IOException e) {
    throw new RuntimeException("Could not load \"" + file + "\"", e);
}

      

This differs from using ImageIcon and Toolkit because it doesn't just return an image, it returns a BufferedImage, which is the type of image that is guaranteed to be fully loaded and present in memory. about.



Since the BufferedImage is already loaded, it has some additional methods that do not need ImageObserver, in particular methods getWidth

and getHeight

that do not require an argument:

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

      

Note that I have changed the value from 400 to 400.0. This is because dividing one int by another int results in integer arithmetic in Java. For example, it 200 / 400

returns zero because 200 and 400 are both int values. 5 / 2

outputs the int value 2.

However, if both or both numbers are doubles, Java treats the whole thing as a double expression. So it (double) 200 / 400

returns 0.5. The presence of a decimal point in a sequence of digits indicates a double value, so 200.0 / 400

and 200 / 400.0

(and of course 200.0 / 400.0

) will also return 0.5.

Finally, there is the problem of scaling. I recommend reading MadProgrammer's answer and in particular the java.net article referenced by its links, The Perils of Image.getScaledInstance () .

The short version is that Image.getScaledInstance is another hold from the 1990s and doesn't do well with scaling. There are two options:

  • Draw the image in a new image and let drawImage handle the scaling using the Graphics RenderHints Graphics2D object.
  • Use AffineTransformOp on your image to create a new scaled image.

Method 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();

      

Method 2:

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

      

You can change the RenderingHints values ​​based on your own preferred speed versus quality tradeoff. They are all documented in the RenderingHints class .

+1


source


You have to call setVisible(true)

:



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

      

+1


source







All Articles