0

I'm trying to get some images to scale for a chess program I'm writing. I tried writing up a small program just to get a feel for scaling images and I ran into a problem with the image displaying. Basically the image will only display properly if that ImageIcon (icon) is there and it is above pawn in the code. If it doesn't exist, it is below pawn in the code, or the image sources are different pawn displays as a black square. If I don't try and scale pawn then the whole thing works just fine regardless of whether icon is there or not. This whole thing just really perplexes me because the only association pawn and icon have is that they share a source image. Can anyone shed some light on this?

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.swing.*;



public class resizeImg extends JFrame{

     public void generateGui(){
        JPanel mainPanel=new JPanel();
        getContentPane().add(mainPanel);

        JPanel main=new JPanel();
        main.setBorder(BorderFactory.createLineBorder(new Color(0,0,0)));
        main.setSize(400,400);
        mainPanel.add(main);
        mainPanel.setLayout(null);

        ImageIcon icon=new ImageIcon(Toolkit.getDefaultToolkit().getImage("resources/pawn.jpg"));
        //if you remove the above ImageIcon the image just displays as a black square

        Image pawn=scale(Toolkit.getDefaultToolkit().getImage("resources/pawn.jpg"));
        JLabel pawnContainer=new JLabel(new ImageIcon(pawn));
        main.add(pawnContainer);

        setSize(400,400);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public BufferedImage scale(Image i){
        BufferedImage resized=new BufferedImage(50,50,BufferedImage.TYPE_INT_RGB);
        Graphics2D g=resized.createGraphics();
        g.drawImage(i,0,0,50,50,null);
        g.dispose();
        return resized;
    }
    public static void main(String[] args){
        SwingUtilities.invokeLater(new Runnable(){
            public void run(){
                resizeImg imgObject=new resizeImg();
                imgObject.generateGui();
                imgObject.setVisible(true);
            }
        });
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
Leo Rossi
  • 5
  • 1
  • 3
  • 1
    Try using `ImageIO` instead, it will 1- Throw an error if something goes wrong with reading the image and 2- Return only after the image has begin read (`ImageIcon` will use a background thread to load the image). Make use of appropriate layout managers. `drawImage` should have an image observer passed to it, in your case, `this` should suffice – MadProgrammer Aug 08 '13 at 05:59
  • See also this [approach](http://stackoverflow.com/a/2563350/230513) using Unicode. – trashgod Aug 08 '13 at 09:11

1 Answers1

2

The original image loading code was designed to load to images over slow networks, so it uses a background thread to load the image. This means that when Toolkit.getImage returns, the image may not have actually begin loaded.

When you included the ImageIcon call, ImageIcon is using a MediaTracker to waitFor the image to become loaded. Because of the caching mechanism of Toolkit.getImage, repeated calls for the same image can use the cached image instead.

This is, also, why it's important to use an ImageObserver when painting images, but it may not help you in this case.

Instead, use ImageIO

public class Scale extends JFrame {

    public void generateGui() {
        JPanel main = new JPanel();
        main.setBorder(BorderFactory.createLineBorder(new Color(0, 0, 0)));
        main.setLayout(new BorderLayout());

        try {
            BufferedImage image = ImageIO.read(new File("path/to/image"));

            Image pawn = scale(image);
            JLabel pawnContainer = new JLabel(new ImageIcon(pawn));
            main.add(pawnContainer);
        } catch (IOException exp) {
            exp.printStackTrace();
        }

        add(main);

        setSize(400, 400);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public BufferedImage scale(Image i) {
        BufferedImage resized = new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = resized.createGraphics();
        g.drawImage(i, 0, 0, 50, 50, this);
        g.dispose();
        return resized;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Scale imgObject = new Scale();
                imgObject.generateGui();
                imgObject.setVisible(true);
            }
        });
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Actually, it's no race condition. The `ImageIcon` constructor makes sure the `Image` is fully loaded before it finishes (it's a feature and an old hack: ["Images ... are preloaded using MediaTracker to monitor the loaded state of the image."](http://docs.oracle.com/javase/7/docs/api/javax/swing/ImageIcon.html)). However I fully agree with using `BufferedImage`s and `ImageIO` :-). – Harald K Aug 08 '13 at 11:50
  • @haraldK Yes, you are correct (after reading through the code), `ImageIcon` uses `getImage` which uses a `MediaTracker` to `waitFor` the image to be loaded, the actually problem is with the call to `Toolkit#getImage` for the scale method, the image is still being loaded after this call is made. – MadProgrammer Aug 08 '13 at 21:51
  • Ok, thanks. That worked. As a quick follow up question is it always better to use ImageIO or are there situations where using the Toolkit approach is preferable? – Leo Rossi Aug 08 '13 at 23:11
  • 1
    Personally, I prefer `ImageIO`. Apart from gurenteeing the image is loaded when it returns and throwing `IOException` which provides more details about things when they go wrong, the API also supports a wide range of formats and provides for plugins to included to support future formats – MadProgrammer Aug 09 '13 at 00:02