Search code examples
javaimageswingbufferedimagepaintcomponent

Drawing an image on top of an image in a JComponent erases part of the bottom image


I am making a 2d game and I need to draw an image on top of another. After I draw the first image(the larger one, jpg), the second image(the smaller one,png) erases from where the second image is to the lower right hand corner. Like this:

enter image description here

I have looked into this a bit, and it was suggested that I use buffered images, so I did that with both images and the problem remains. Here is one post I looked at: How to draw an image over another image?. I have also seen some people suggesting graphics2d, though I did not really understand the reason to use them or how to use them. I am new to java graphics and images, so it is probably a silly mistake. Here is my code. Thank you.

import java.awt.*;
import java.awt.event.*;
import java.net.*;
import javax.swing.*;
import java.util.ArrayList;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.IOException;

public class DisplayExample extends JComponent
{
private BufferedImage backgroundImage;
private String backgroundName;

private BufferedImage image;  //image to draw
private int imageX;  //position of left edge of image
private int imageY;  //position of top edge of image

private JFrame frame;

public static void main(String[] args)
{
    DisplayExample example = new DisplayExample();
    example.run();
}

public DisplayExample()
{
    imageX = 200;
    imageY = 200;

    backgroundName = "backgroundShip.jpg";
    URL backgroundURL = getClass().getResource(backgroundName);
    if (backgroundURL == null)
        throw new RuntimeException("Unable to load:  " + backgroundName);
    try{backgroundImage = ImageIO.read(backgroundURL);}catch(IOException ioe){}
    //load image

    String fileName = "explosion.png";
    URL url = getClass().getResource(fileName);
    if (url == null)
        throw new RuntimeException("Unable to load:  " + fileName);
    //image = new ImageIcon(url).getImage();
    try{image = ImageIO.read(url);}catch(IOException ioe){}
    System.out.println(image instanceof BufferedImage);
    setPreferredSize(new Dimension(1040,500));  //set size of drawing region

    //need for keyboard input
    setFocusable(true);  //indicates that WorldDisp can process key presses

    frame = new JFrame();
    frame.getContentPane().add(this);
    frame.pack();
    frame.setVisible(true);
}

public void paintComponent(Graphics g)
{

    super.paintComponent(g);
    if(backgroundImage != null)
        g.drawImage(backgroundImage,0,0,getWidth(), getHeight(), null);
    g.drawImage(image, imageX, imageY, this);  
}

public void run()
{ 
    while(true)
    {
    imageY+=1;
    repaint();
    try{Thread.sleep(100);}catch(Exception e){}
    }
}

}


Solution

  • So I took your code, added my own images and it runs fine for me.

    Having said that, there are some areas you can improve:

    • You're running the risk of either blocking the Event Dispatching Thread or introducing a thread race condition into your code with your run method. You should consider using a Swing Timer instead. See How to use Swing Timers for more details. This allows you to schedule regular callbacks which are called within the context of the EDT, making it safer to update the context of the UI
    • You should only ever create or modify the state of the UI from within the context of the EDT, Swing is not thread safe. See Initial Threads for more details. Swing has been known to have "issues" when the UI is not initialised within the EDT
    • Scaling an image is expensive, you should avoid doing so from within the paint methods, instead, scale the image and keep a reference to the result and use it when you need to paint it.
    • You should consider using the key bindings API over KeyListener, it will solve many of the issues associated with using KeyListener. See How to Use Key Bindings for more details.

    For example...

    Pony Up

    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    import javax.imageio.ImageIO;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.Timer;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class DisplayExample extends JComponent {
    
        private BufferedImage backgroundImage;
        private String backgroundName;
    
        private BufferedImage image;  //image to draw
        private int imageX;  //position of left edge of image
        private int imageY;  //position of top edge of image
    
        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    DisplayExample example = new DisplayExample();
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(example);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public DisplayExample() {
            imageX = 200;
            imageY = 200;
    
            try {
                backgroundImage = ImageIO.read(new File("..."));
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
            //load image
    
            try {
                image = ImageIO.read(new File("..."));
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
    
            //need for keyboard input
            //setFocusable(true);  //indicates that WorldDisp can process key presses
            // Use the key bindings API instead, causes less issues
            Timer timer = new Timer(100, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    imageY += 1;
                    repaint();
                }
            });
            timer.start();
        }
    
        @Override
        public Dimension getPreferredSize() {
            return backgroundImage == null ?  new Dimension(200, 200) : new Dimension(backgroundImage.getWidth(), backgroundImage.getHeight());
        }
    
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            if (backgroundImage != null) {
                // Scaling is expensive, don't do it here
                int x = (getWidth() - backgroundImage.getWidth()) / 2;
                int y = (getHeight() - backgroundImage.getHeight()) / 2;
                g2d.drawImage(backgroundImage, x, y, this);
            }
            g2d.drawImage(image, imageX, imageY, this);
            g2d.dispose();
        }
    }