1

So, I am trying to create a monopoly game. I am trying to load an image (of the board) onto a JPanel.

I first want to scale the image to a 1024*1024 image.

I already got the image to appear on the JPanel (so the file address works).

But whenever I use the getScaledInstance() method, the image doesn't appear

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.SystemColor;

//a class that represent the board (JFrame) and all of it components 
public class Board extends JFrame {
private final int SCALE;
private JPanel panel;

public Board(int scale) {
    getContentPane().setBackground(SystemColor.textHighlightText);
    // SCALE = scale;
    SCALE = 1;
    // set up the JFrame
    setResizable(false);
    setTitle("Monopoly");
    // set size to a scale of 1080p
    setSize(1920 * SCALE, 1080 * SCALE);
    getContentPane().setLayout(null);

    panel = new JPanel() {
        public void paint(Graphics g) {
            Image board = new ImageIcon(
                    "C:\\Users\\Standard\\Pictures\\Work\\Monopoly 1.jpg")
                    .getImage();
            board = board.getScaledInstance(1022, 1024, java.awt.Image.SCALE_SMOOTH);

            g.drawImage(board, 0, 0, null);
        }
    };
    panel.setBounds(592, 0, 1024, 1024);
    getContentPane().add(panel);
}

public static void main(String[] args) {
    Board board = new Board(1);
    board.setVisible(true);
    board.panel.repaint();
 }
}

Whenever I remove the board.getScaledInstance() line of code, the image appears (though not scaled), but when I add the line of code, the image doesn't appear at all.

Why does this happen?

Manos Nikolaidis
  • 21,608
  • 12
  • 74
  • 82
Vineet Patel
  • 477
  • 7
  • 17
  • 2
    You're doing several things wrong: overriding paint, not paintComponent, not calling the super painting method, reading in an image multiple times and within a painting method, using null layouts and setBounds. Scaling the image within the paint method... Yow. Have you searched for similar questions on this site, as much better ways to do this sort of thing can be found here. – Hovercraft Full Of Eels Oct 24 '15 at 02:32
  • 1
    [The Perils of Image.getScaledInstance()](https://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html) – MadProgrammer Oct 24 '15 at 02:32
  • 1
    `g.drawImage(board, 0, 0, null);` should be `g.drawImage(board, 0, 0, this);` – MadProgrammer Oct 24 '15 at 02:33
  • 2
    Avoid using `null` layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify – MadProgrammer Oct 24 '15 at 02:33
  • @MadProgrammer It worked!. But? Most of the examples i found online, used null. Also i will try to use other layouts, though i do need coordinates to get the pieces to move to their designated places – Vineet Patel Oct 24 '15 at 02:52
  • @VineetPatel: see edit to answer with code. – Hovercraft Full Of Eels Oct 24 '15 at 02:55
  • Most of online examples don't understand the importance of the ImageObserver when dealing with Image, which dies slot if it's work in the background. I'd also avoid loading resources in the paintXxx methods and storing the result of the scale so you only have to do it once – MadProgrammer Oct 24 '15 at 02:58
  • Also, you don't need use layout managers to achieve what you want, you can use custom painting, but if use something like BorderLayout to layout your initial pane – MadProgrammer Oct 24 '15 at 02:59

1 Answers1

2

You're doing several things wrong:

  • You're overriding paint, not paintComponent. This is dangerous as you're overriding an image that does too much and has too much responsibility. Doing this without care can lead to significant image side effects, and will also lead to slow perceived animation due to paint not double buffering.
  • You're not calling the super painting method within your override, something that will lead to accumulation of painting artifacts and breaking of the Swing component painting chain.
  • You're reading in an image potentially multiple times and within a painting method, a method that must be as fast as possible since it's a major determinant in the perceived responsiveness of your application. Read it in once only, and then save it to a variable.
  • You're using null layouts and setBounds. While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one.
  • You're scaling the image within the paint method, again doing something that will slow down the perceived responsiveness of the GUI. Instead, scale the image once only, and save that scaled image to a variable.
  • Importantly, you're using the same variable, board, for both the original image and the scaled image, and this will lead to re-scaling of the image every time that paint is called.
  • As Mad points out, you should pass in this to your g.drawImage(...) method call, so that you don't draw an image before it's completely read in.
  • Also, don't read the image in as a File or as an ImageIcon when you're not using it as an ImageIcon. Use ImageIO to read it in as a BufferedImage, and use resources, not Files.

I would also simplify things, for example:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;

@SuppressWarnings("serial")
public class MyBoard extends JPanel {
    private static final String IMG_PATH = "http://ecx.images-amazon.com/"
            + "images/I/81oC5pYhh2L._SL1500_.jpg";

    // scaling constants
    private static final int IMG_WIDTH = 1024;
    private static final int IMG_HEIGHT = IMG_WIDTH;

    // original and scaled image variables
    private BufferedImage initialImg;
    private Image scaledImg;

    public MyBoard() throws IOException {
        URL url = new URL(IMG_PATH);
        initialImg = ImageIO.read(url); // read in original image

        // and scale it *once* and store in variable. Can even discard original
        // if you wish
        scaledImg = initialImg.getScaledInstance(IMG_WIDTH, IMG_HEIGHT,
                Image.SCALE_SMOOTH);
    }

    // override paintComponent, not paint
    @Override   // and don't forget the @Override annotation
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); // call the super's painting method

        // just to be safe -- check that it's not null first
        if (scaledImg != null) {
            // use this as a parameter to avoid drawing an image before it's
            // ready
            g.drawImage(scaledImg, 0, 0, this);
        }
    }

    // so our GUI is sized the same as the image
    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet() || scaledImg == null) {
            return super.getPreferredSize();
        }
        int w = scaledImg.getWidth(this);
        int h = scaledImg.getHeight(this);
        return new Dimension(w, h);
    }

    private static void createAndShowGui() {
        MyBoard mainPanel = null;
        try {
            mainPanel = new MyBoard();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }

        JFrame frame = new JFrame("My Board");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGui();
            }
        });
    }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • Thanks for the suggestions, I will most certainly make changes to my code. Though i have some questions: does the initial image have to be a bufferedImage?, and what layout manger would you recommend for me? – Vineet Patel Oct 24 '15 at 02:57
  • @VineetPatel: No, the initial image variable can be declared using the interface -- as an Image, but understand that `ImageIO.read(...)` will return a BufferedImage. As for layout managers, much will depend on what you want to achieve. I would nest JPanels each using its own layout manager including BorderLayout for the overall layout, possibly nesting BorderLayout using JPanels, and then GridLayout for the non-square cells along the edge. I'd make sure that all overlying JPanels are not opaque so that the image shows through. – Hovercraft Full Of Eels Oct 24 '15 at 03:02