user3186472
user3186472

Reputation:

How to properly refresh image in JFrame?

This is a problem that disturbs me for few hours now and I'm not able to find a solution by myself...

I've found similar topics all around the net, but I couldn't find exact same problem with well explained and as simple as possible solution. I've also looked at EDT and SwingWorker API docs, but it was far too complicated for me :(

So, let's get to the point. I have a simple JFrame with JLabel inside, that consist of my image:

private static class MyJLabel extends JLabel {
    private ImageIcon img = null;

    public MyJLabel(ImageIcon img) {
        super();
        this.img = img;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(img.getImage(), 0, 0, getWidth(), getHeight(), this);
    }
}

private static class MyJFrame extends JFrame implements Runnable {
    private BufferedImage img = null;
    private MyJLabel label = null;

    public MyJFrame(BufferedImage image, String title) {
        super(title);
        img = image;
    }

    @Override
    public void run() {
        Dimension dims = new Dimension(img.getWidth(), img.getHeight());
        dims = new Dimension(dims.width / 2, dims.height / 2);

        label = new MyJLabel(new ImageIcon(img));
        label.setPreferredSize(dims);

        addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                label.repaint();
            }
        });
        setLayout(new BorderLayout());
        getContentPane().add(BorderLayout.CENTER, label);
        setLocation(200, 200);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        pack();
        setVisible(true);
    }

    public void changeImage(BufferedImage image) {
        img = image;
        if (label != null) {
            label.setIcon(new ImageIcon(img));
            label.repaint();
        }
    }
}

It's invoked by this piece of code:

buffer = receiveImage(in); // download image

MyJFrame f = null;
javax.swing.SwingUtilities.invokeLater(f = new MyJFrame(buffer, "RDP"));

int x = 0;
while (x <= 15) {
    txt.println("next"); // notify server that we are ready

    while (true) { // wait for server
        if (reader.readLine().equals("ready")) break;
    }

    buffer = receiveImage(in); // download image

    // do some magic here and refresh image somehow :(
    f.changeImage(buffer); // does not work!

    x++;
}

Unfortunately, my approach with changeImage method does not work - nothing happens (GUI starts but never gets updated).

I'd appreciate little help with this. Simple, working example with proper explanation would be appreciated the most ;)

Greetings!

Upvotes: 0

Views: 4104

Answers (2)

user3186472
user3186472

Reputation:

This is what I came up with:

private static class MyJPanel extends JPanel {
    private Image img = null;

    public MyJPanel() {}

    public void setImage(Image value) {
        if (img != value) {
            Image old = img;
            this.img = value;
            firePropertyChange("image", old, img);
            revalidate();
            repaint();
        }
    }

    public Image getImage() {
        return img;
    }

    @Override
    public Dimension getPreferredSize() {
        return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(this), img.getHeight(this));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (img != null) {
            Graphics2D g2d = (Graphics2D) g.create();

            int width = getWidth();
            int height = getHeight();
            double scaleFactor = getScaleFactorToFit(new Dimension(img.getWidth(this), img.getHeight(this)), getSize());
            int x = (int) ((width - (img.getWidth(this) * scaleFactor)) / 2);
            int y = (int) ((height - (img.getHeight(this) * scaleFactor)) / 2);

            AffineTransform at = new AffineTransform();
            at.translate(x, y);
            at.scale(scaleFactor, scaleFactor);
            g2d.setTransform(at);
            g2d.drawImage(img, 0, 0, this);
            g2d.dispose();
        }
    }

    public double getScaleFactor(int iMasterSize, int iTargetSize) {
        return (double) iTargetSize / (double) iMasterSize;
    }

    public double getScaleFactorToFit(Dimension original, Dimension toFit) {
        double dScale = 1d;
        if (original != null && toFit != null) {
            double dScaleWidth = getScaleFactor(original.width, toFit.width);
            double dScaleHeight = getScaleFactor(original.height, toFit.height);
            dScale = Math.min(dScaleHeight, dScaleWidth);
        }
        return dScale;
    }
}

private static class MyJFrame extends JFrame implements Runnable {
    private BufferedImage img = null;
    private MyJPanel panel = null;

    public MyJFrame(BufferedImage image, String title) {
        super(title);
        img = image;
    }

    @Override
    public void run() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {}

        panel = new MyJPanel();
        panel.setImage(img);

        setLayout(new BorderLayout());
        add(BorderLayout.CENTER, panel);
        setLocation(200, 200);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        pack();
        setVisible(true);
    }

    public void changeImage(BufferedImage image) {
        if ((panel != null) && (panel.getImage() != image)) panel.setImage(image);
    }
}

It's pretty much straightforward copy-paste from example that @MadProgrammer provided.

The only thing left is EDT usage, which is rather magic for me. I'm still invoking this code using dirty way:

MyJFrame mjf = null;
javax.swing.SwingUtilities.invokeLater(mjf = new MyJFrame(buffer, "RDP"));
...
mjf.changeImage(buffer);

My question is: how do I use changeImage method with EDT?

Upvotes: 0

MadProgrammer
MadProgrammer

Reputation: 347184

Personally, I would either resize it before applying it to the label or use a JPanel to perform the painting. JLabel has to much functionality dragging around with it.

Case in point, the problem you're having is you're actually using setIcon to set the image, but using paintComponent to paint another (the initial) image over the top of it

Your custom label takes a ImageIcon as a inital parameter and paints it as such...

private static class MyJLabel extends JLabel {
    private ImageIcon img = null;

    public MyJLabel(ImageIcon img) {
        super();
        this.img = img;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(img.getImage(), 0, 0, getWidth(), getHeight(), this);
    }
}

You initialise it as such...

label = new MyJLabel(new ImageIcon(img));

It should be noted that if you used the Icon support of JLabel, this...

label.setPreferredSize(dims);

Would be irrelevant as the JLabel would use the icon size to determine it's preferred size...but any way...

Then you update the icon using this..

img = image;
if (label != null) {
    label.setIcon(new ImageIcon(img));
    label.repaint();
}

It should be pointed out, that based on your example, this is actually been called outside of the EDT, which is dangerous and could lead to a dirty paint

But setIcon never changes the value of img within MyLabel, so when your paintComponent method is called, you are actually painting over the icon you have supplied in the update...

// Paint the new Icon
super.paintComponent(g);
// Paint the old/initial image...
g.drawImage(img.getImage(), 0, 0, getWidth(), getHeight(), this);

Updated

Personally, what I would do is create a custom component, using something like a JPanel and scale the original image based on the current size of the panel, for example...

Now, normally, when performing image scaling I prefer to use a divide and conqure approach as demonstrated in Java: maintaining aspect ratio of JPanel background image, but for this example, I've simply used and AffineTransform for simplicity sake

SmallLarge

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class ScalableImageExample {

    public static void main(String[] args) {
        new ScalableImageExample();
    }

    public ScalableImageExample() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                try {
                    ResizableImagePane pane = new ResizableImagePane();
                    pane.setImage(...);

                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(pane);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException exp) {
                    exp.printStackTrace();
                }
            }
        });
    }

    public class ResizableImagePane extends JPanel {

        private Image img;

        public ResizableImagePane() {
        }

        public void setImage(Image value) {
            if (img != value) {
                Image old = img;
                this.img = value;
                firePropertyChange("image", old, img);
                revalidate();
                repaint();
            }
        }

        public Image getImage() {
            return img;
        }

        @Override
        public Dimension getPreferredSize() {
            return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(this), img.getHeight(this));
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (img != null) {
                Graphics2D g2d = (Graphics2D) g.create();

                int width = getWidth();
                int height = getHeight();

                double scaleFactor = getScaleFactorToFit(new Dimension(img.getWidth(this), img.getHeight(this)), getSize());

                int x = (int)((width - (img.getWidth(this) * scaleFactor)) / 2);
                int y = (int)((height - (img.getHeight(this) * scaleFactor)) / 2);

                AffineTransform at = new AffineTransform();
                at.translate(x, y);
                at.scale(scaleFactor, scaleFactor);
                g2d.setTransform(at);
                g2d.drawImage(img, 0, 0, this);
                g2d.dispose();
            }
        }

        public double getScaleFactor(int iMasterSize, int iTargetSize) {

            return (double) iTargetSize / (double) iMasterSize;

        }

        public double getScaleFactorToFit(Dimension original, Dimension toFit) {

            double dScale = 1d;

            if (original != null && toFit != null) {

                double dScaleWidth = getScaleFactor(original.width, toFit.width);
                double dScaleHeight = getScaleFactor(original.height, toFit.height);

                dScale = Math.min(dScaleHeight, dScaleWidth);

            }

            return dScale;

        }

    }

}

Upvotes: 2

Related Questions