0

I'm trying to make a fullscreen image viewer for my messenger app.

However, I can only see the image only after minimizing and restoring the window again. I don't understand why it is happening, because when I use the debugger I minimize the app window, and after I restore it back the image is in place. Also I tried setting some printlns to the console, and from what I see the overridden paintComponent() method is not even called after the window is shown.

Here is my code:

import java.awt.event.KeyEvent;
import java.io.IOException;
import java.net.MalformedURLException;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyListener;
import java.awt.image.ImageObserver;
import java.net.URL;
import javax.imageio.ImageIO;

public class Overlay extends JFrame {

    public Overlay() {
        super();
        this.setUndecorated(true);
        getRootPane().setOpaque(true);
        panel = new JPanel();
        panel.setBackground(new Color(12,12,12));
        setLayout(new CardLayout());
        add(panel);
        panel.setBorder(BorderFactory.createEmptyBorder(90, 90, 90, 90));

        this.addKeyListener(new KeyListener() {

            @Override
            public void keyTyped(KeyEvent e) {}

            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    setVisible(false);
                    dispose();
                }
            }

            @Override
            public void keyReleased(KeyEvent e) {}
        });
    }

    public class ImageViewer extends JPanel {

        public ImageViewer(String src) {
            this.src = src;
            setOpaque(false);
            try {
                img = ImageIO.read(new URL(src));
                int imgHeight = img.getHeight(observer);
                if (imgHeight > 0) {
                    w = (int) (h * ((double) img.getWidth(null) / imgHeight));
                }
            } catch (IOException ex) {}
        }

        public ImageViewer(String src, int width, int height) {
            setOpaque(false);
            setPreferredSize(new Dimension(width, height));
            setMinimumSize(new Dimension(width, height));
            setMaximumSize(new Dimension(width, height));
            w = width;
            h = height;
            this.src = src;
            try {
                img = ImageIO.read(new URL(src));
                int imgHeight = img.getHeight(observer);
                if (imgHeight > 0) {
                    w = (int) (h * ((double) img.getWidth(null) / imgHeight));
                }
            } catch (IOException ex) {}
        }

        ImageObserver observer = new ImageObserver() {
            @Override
            public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
                if (height == 0) return false;
                w = (int) (h * ((double) width / height));
                if (getParent() != null) getParent().repaint();
                return true;
            }
        };

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (img == null) return;
            Graphics2D g2d = (Graphics2D) g;
            img.getHeight(observer);
            int x = (int) (getWidth() - w) / 2;
            int y = (int) (getHeight() - h) / 2;
            System.out.println(x + ", " + y);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            g2d.drawImage(img, x, y, w, h, null);
        }

        private int w;
        private int h;

        private Image img;
        private String src = null;
    }

    public void showContent() {
        try {
            GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this);
            setVisible(true);
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    int width = (int) (getBounds().getWidth() * 0.7 + 0.4);
                    int height = (int) (width / 16 * 9 + 0.4);
                    System.err.println(width + "x" + height);
                    ImageViewer viewer = new ImageViewer("http://popov654.pp.ru/copybox/image.jpg", width, height);
                    panel.setLayout(new BorderLayout());
                    panel.add(viewer);
                    try {
                        Thread.sleep(100);
                        viewer.repaint();
                    } catch (InterruptedException ex) {}
                }
            });
        } catch (Exception error) {}
    }

    public static void main(String[] args) {
        new Overlay().showContent();
    }

    JPanel panel;
}
Alex Popov
  • 65
  • 7
  • Without the repaint calls in ImageObserver it is also not working, of course – Alex Popov Dec 25 '22 at 09:48
  • Also it does not work with a local image file for some reason – Alex Popov Dec 25 '22 at 10:13
  • Don't use empty catch blocks. How will you ever know when you have an Exception? – camickr Dec 25 '22 at 15:41
  • There are no exceptions there, I checked the code with the debugger several times. – Alex Popov Dec 25 '22 at 22:37
  • When you add/remove components from a visible frame you need to invoke revalidate() and repaint() on the panel. This will invoke the layout manager and give the component a size/location. Or, a simpler solution is to add all the components to the frame BEFORE making the frame visible. Also, I don't think you need 1) Thread.sleep or 2) the ImageObserver. Check out: https://stackoverflow.com/a/6296381/131872 which is able to get the width/height of an image loaded over the internet without the above code. – camickr Dec 26 '22 at 03:06
  • Thanks for your help! I managed to solve the problem by adding `panel.doLayout()` at the end of my code. Also I removed `sleep()`, because it does not help at all anyway. Is my solution correct, or is it still bad? – Alex Popov Dec 29 '22 at 05:47
  • Yes, I also tried adding them before, and I usually do it this way, but this time I needed to read user's screen dimensions to do the overlay layout. I guess there is another way to get them, but using getBounds() was quick and dirty way :) – Alex Popov Dec 29 '22 at 05:49
  • "When you add/remove components from a visible frame you need to invoke revalidate() and repaint() on the panel" - can you post it as an answer, please? I think it will be useful for someone else here. – Alex Popov Dec 30 '22 at 03:59

1 Answers1

0

The solution is to replace this block of code

try {
    Thread.sleep(100);
    viewer.repaint();
} catch (InterruptedException ex) {}

with this one:

panel.doLayout();

Or, which is much better in terms of "show up" speed, just move the code to the constructor:

public Overlay() {
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    int width = (int) (dim.width * 0.7 + 0.4);
    int height = (int) (imgWidth / 16 * 9 + 0.4);
    //System.err.println(width + "x" + height);
    ImageViewer viewer = new ImageViewer("http://popov654.pp.ru/copybox/image.jpg", width, height);
    panel.setLayout(new BorderLayout());
    panel.add(viewer);
    ...
}
Alex Popov
  • 65
  • 7
  • My suggestion was to invoke revalidate() and repaint(). If you read the API for the doLayout() method it will state that "most programs should not invoke this method directly". You should read the [Swing tutorial](https://docs.oracle.com/javase/tutorial/uiswing/TOC.html) for Swing basics. It contains lots of working examples to help you better structure your code. For example: 1) don't extend JFrame 2) add components to the GUI BEFORE the frame is made visble. 3) Create the components on the Event Dispatch Thread (EDT). 4) Use Key Bindings, not a KeyListener. – camickr Dec 30 '22 at 05:21