0

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?

dust2
  • 3
  • 1
  • `contentPane.setLayout(null);` Java GUIs have to work on different OS', screen size, screen resolution etc. As such, they are not conducive to pixel perfect layout. Instead use layout managers, or [combinations of them](http://stackoverflow.com/a/5630271/418556) along with layout padding and borders for [white space](http://stackoverflow.com/a/17874718/418556). – Andrew Thompson Dec 07 '14 at 03:36

1 Answers1

1

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.

Xyene
  • 2,304
  • 20
  • 36
  • Your paintComponent method breaks the painting chain. – Hovercraft Full Of Eels Dec 07 '14 at 02:26
  • @HovercraftFullOfEels I've updated my answer to include a super call to paintComponent. My (perhaps unfounded) logic for not including it was that a video panel will likely never have children, and clearing the canvas yourself (with fillRect) is quite a bit faster than delegating to super.paintComponent. – Xyene Dec 07 '14 at 02:34
  • @Nox `paintComponent` paints the background, but failing to clear the `Graphics` context could lead to other paint artifacts showing up, which you don't really want. *"and clearing the canvas yourself (with fillRect) is quite a bit faster than delegating to super.paintComponent"* - Do you have metrics for that, because essentially that's all `super.paintComponent` does, by default – MadProgrammer Dec 07 '14 at 02:42
  • @MadProgrammer Fair enough. I should've been more clear on what I meant by "clearing the canvas yourself". `super.paintComponent` eventually calls `fillRect(0, 0, getWidth(), getHeight())`, which does more work than is necessary: since we know the frame will always be written, we only need to repaint on the sides of image, and not the image itself. Last time I was in the same position as the OP, performing selective background painting sped up the video playback significantly when the window was maximized on a large-ish screen. – Xyene Dec 07 '14 at 03:07
  • *"However, this will likely be too slow,.."* On what evidence do you base that statement? – Andrew Thompson Dec 07 '14 at 03:38
  • @AndrewThompson `ImageIcon.setImage` has some overhead caused by its backing `MediaTracker`, which performs some `MediaEntry` instantiations and collection modifications. In the same vein, using an `ImageIcon` prevents the optimization of only clearing the image borders. Not that this should matter much, but since the OP is already performing a highly intensive decoding task, reducing the constant overhead even a bit should help. – Xyene Dec 07 '14 at 03:59