Search code examples
javaswinguser-interfacebufferedimagexuggle

Accessing BufferedImages to update JFrame components


Basically, I am extracting images from a video in realtime as BufferedImages and displaying them within a JFrame after processing. Unfortunately I am very bad with swing, so while the images show up within the JFrame as intended, it spawns a new JFrame for every new image (24 per second).

I send the BufferedImages to the GUI with:

UserInterface.main(currentFrame);

Where they are received by my GUI class, which is essentially A JLabel containing the current image inside a JPanel inside a JFrame:

public class UserInterface extends JFrame {
private JPanel contentPane;

public static void main(BufferedImage inputImage) throws IOException 
{   
    UserInterface frame = new UserInterface(inputImage);
    frame.setVisible(true);
}

public UserInterface(BufferedImage inputImage) throws IOException 
{       
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);    
    setBounds(50, 50, 1024, 768);
    contentPane = new JPanel();
    contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
    setContentPane(contentPane);
    contentPane.setLayout(null);

    JLabel imageLabel = new JLabel(new ImageIcon(inputImage));
    add(imageLabel);
    imageLabel.setBounds(11, 60, 480, 360);
    contentPane.add(imageLabel);        
}

Can anyone advise on how to break this up to make just 1 JFrame appear, in which I can display all the images dynamically?


Solution

  • You should refactor imageLabel to be a class member of UserInterface, and update its backing ImageIcon every frame. However, this might too slow, in which case you'll want to create your own panel class that overrides paintComponent(Graphics) to draw the frame image. Finally, you should manually trigger repaints for that panel by calling paintImmediately(getBounds()). Something along the following lines should work:

    public class VideoPanel extends JPanel {
        private BufferedImage frame;
    
        public VideoPanel() {
    
        }
    
        public void setFrame(BufferedImage image) {
            this.frame = image;
            paintImmediately(getBounds());
        }
    
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            if(frame == null) return;
            int width = frame.getWidth();
            int height = frame.getHeight();
            Dimension boundary = getSize();
            // Scale image dimensions with aspect ratio to fit inside the panel
            int bwidth;
            int bheight = ((bwidth = boundary.width) * height) / width;
            if (bheight > boundary.height) {
                bwidth = ((bheight = boundary.height) * width) / height;
            }
            // Center it in the space given
            int x = Math.abs(boundary.width - bwidth) / 2;
            int y = Math.abs(boundary.height - bheight) / 2;
            g.drawImage(frame, x, y, bwidth, bheight, null);
        }
    }
    

    You'll also likely want to clear the background of the panel before you draw on it, to prevent rendering artifacts.