Java - JFrame ou JPanel com Imagem de Fundo

Frequentemente surge alguém com uma dúvida de como colocar uma imagem de fundo em uma aplicação desktop feita em Swing.

Pois bem, o Swing não tem nenhum componente pronto pra isso, mas a implementação é bastante simples de se fazer, basta especializar um JPanel e sobrescrever o método paintComponent para desenhar a imagem desejada.

Portanto, criei a classe ImagePanel, disponível logo abaixo:

Classe ImagePanel:

import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.LayoutManager;
import java.awt.MediaTracker;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

public class ImagePanel extends JPanel {

    private static final Component OBSERVER = new Component() {

        private static final long serialVersionUID = 1;
    };

    private static final long serialVersionUID = 1;

    public static BufferedImage bufferize(Image img) {
        if (img instanceof BufferedImage) {
            return (BufferedImage) img;
        }
        try {
            MediaTracker tracker = new MediaTracker(OBSERVER);
            tracker.addImage(img, 0);
            tracker.waitForID(0);
            tracker.removeImage(img, 0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        BufferedImage buffered = new BufferedImage(img.getWidth(OBSERVER), img.getHeight(OBSERVER), BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = buffered.createGraphics();
        graphics.drawImage(img, 0, 0, null);
        graphics.dispose();
        return buffered;
    }

    private boolean stretched = true;

    private BufferedImage image;

    public ImagePanel() {
        super();
    }

    public ImagePanel(boolean isDoubleBuffered) {
        super(isDoubleBuffered);
    }

    public ImagePanel(LayoutManager layout) {
        super(layout);
    }

    public ImagePanel(LayoutManager layout, boolean isDoubleBuffered) {
        super(layout, isDoubleBuffered);
    }

    public BufferedImage getImage() {
        return image;
    }

    public boolean isSideBySide() {
        return !stretched;
    }

    public boolean isStretched() {
        return stretched;
    }

    public void setImage(BufferedImage image) {
        this.image = image;
    }

    public void setImage(Image image) {
        setImage(bufferize(image));
    }

    public void setSideBySide(boolean sideBySide) {
        stretched = !sideBySide;
    }

    public void setStretched(boolean stretch) {
        this.stretched = stretch;
    }

    @Override
    protected void paintComponent(Graphics g) {
        if (image == null) {
            super.paintComponent(g);
            return;
        }
        int w = getWidth();
        int h = getHeight();
        if (stretched) {
            g.drawImage(image, 0, 0, w, h, this);
            return;
        }
        int iw = image.getWidth();
        int ih = image.getHeight();
        int colunas = w / iw;
        int linhas = h / ih;
        if (colunas * iw < w) {
            colunas++;
        }
        if (linhas * ih < h) {
            linhas++;
        }
        int offsetX = 0;
        for (int i = 0; i < linhas; i++) {
            int y = i * ih;
            if (y > h) {
                break;
            }
            for (int j = 0; j < colunas; j++) {
                int x = j * iw + offsetX;
                if (j == 0 && x > 0) {
                    g.drawImage(image, -(iw - x), y, iw, ih, this);
                }
                if (j == colunas - 1 && x < w) {
                    g.drawImage(image, x + iw, y, iw, ih, this);
                }
                if (x > w) {
                    break;
                }
                if (x < -iw) {
                    break;
                }
                g.drawImage(image, x, y, iw, ih, this);
            }
        }
    }
}

Exemplo de uso.

Suponhamos que eu queira renderizar a seguinte imagem no fundo de uma tela Swing:

por-do-sol.jpg

Tudo o que eu preciso fazer, é instanciar um ImagePanel e definir a sua propriedade image, conforme o trecho de código abaixo:

InputStream arquivo = getClass().getResourceAsStream("/por-do-sol.jpg");
BufferedImage imagem = ImageIO.read(arquivo);
ImagePanel imagePanel = new ImagePanel(new GridBagLayout());
imagePanel.setImage(imagem);

Abaixo está o fonte completo de um programa hipotético que não faz nada, mas possui uma imagem de fundo:

Classe ExemploImagePanel:

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.InputStream;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.UIManager;

@SuppressWarnings("serial")
public class ExemploImagePanel extends JFrame {

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            ExemploImagePanel telinha = new ExemploImagePanel();
            telinha.setLocationRelativeTo(null);
            telinha.setVisible(true);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    private JTextField textFieldLogin;
    private JPasswordField textFieldSenha;

    public ExemploImagePanel() throws Exception {
        super("Exemplo com ImagePanel");

        InputStream arquivo = getClass().getResourceAsStream("/por-do-sol.jpg");
        BufferedImage imagem = ImageIO.read(arquivo);
        ImagePanel imagePanel = new ImagePanel(new GridBagLayout());
        imagePanel.setImage(imagem);

        GridBagConstraints constraints = new GridBagConstraints();
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.anchor = GridBagConstraints.CENTER;
        constraints.gridx = 0;
        constraints.gridy = 0;
        imagePanel.add(criarPanelLogin(), constraints);

        constraints = new GridBagConstraints();
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.anchor = GridBagConstraints.CENTER;
        constraints.gridx = 0;
        constraints.gridy = 1;
        imagePanel.add(criarPanelSenha(), constraints);

        constraints = new GridBagConstraints();
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.anchor = GridBagConstraints.CENTER;
        constraints.gridx = 0;
        constraints.gridy = 2;
        imagePanel.add(criarPanelBotoes(), constraints);

        setContentPane(imagePanel);
        setMinimumSize(new Dimension(480, 320));
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                sair();
            }
        });
    }

    private JPanel criarPanelBotoes() {
        JButton buttonLogin = new JButton("Login");
        buttonLogin.setPreferredSize(new Dimension(100, 30));
        buttonLogin.addActionListener(event -> login());

        JButton buttonSair = new JButton("Sair");
        buttonSair.setPreferredSize(new Dimension(100, 30));
        buttonSair.addActionListener(event -> sair());

        JPanel panelBotoes = new JPanel(new FlowLayout(FlowLayout.CENTER, 15, 10));
        panelBotoes.setOpaque(false);
        panelBotoes.add(buttonLogin);
        panelBotoes.add(buttonSair);
        panelBotoes.setPreferredSize(new Dimension(300, 40));
        return panelBotoes;
    }

    private JPanel criarPanelLogin() {
        JLabel labelLogin = new JLabel("Login:");
        labelLogin.setOpaque(false);
        textFieldLogin = new JTextField();

        JPanel panelLogin = new JPanel(new BorderLayout());
        panelLogin.setOpaque(false);
        panelLogin.add(labelLogin, BorderLayout.NORTH);
        panelLogin.add(textFieldLogin, BorderLayout.CENTER);
        panelLogin.setPreferredSize(new Dimension(300, 40));
        return panelLogin;
    }

    private JPanel criarPanelSenha() {
        JLabel labelSenha = new JLabel("Senha:");
        labelSenha.setOpaque(false);
        textFieldSenha = new JPasswordField();

        JPanel panelSenha = new JPanel(new BorderLayout());
        panelSenha.setOpaque(false);
        panelSenha.add(labelSenha, BorderLayout.NORTH);
        panelSenha.add(textFieldSenha, BorderLayout.CENTER);
        panelSenha.setPreferredSize(new Dimension(300, 40));
        return panelSenha;
    }

    private void login() {
        Component parentComponent = this;
        String user = textFieldLogin.getText();
        String pass = new String(textFieldSenha.getPassword());

        Object message = "Prezado(a) \"" + user + "\"\n\n" //
                + "Você digitou a senha \"" + pass + "\"\n\n"//
                + "Mas o login ainda não foi implementado!";
        String title = "Atenção";
        int messageType = JOptionPane.WARNING_MESSAGE;
        JOptionPane.showMessageDialog(parentComponent, message, title, messageType);
    }

    private void sair() {
        Component parentComponent = this;
        String message = "Deseja realmente sair?";
        String title = "Confirmação";
        int optionType = JOptionPane.YES_NO_OPTION;
        int messageType = JOptionPane.QUESTION_MESSAGE;
        int option = JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType);
        if (option == JOptionPane.YES_OPTION) {
            System.exit(0);
        }
    }
}

Ao executar o programa acima, a seguinte tela será apresentada:

ExemploImagePanel executando no Windows 10
Plano de fundo "por-do-sol.jpg" esticado
telinha-esticada

Por padrão o ImagePanel renderiza a imagem de fundo esticando ela para ocupar todo o espaço disponível (propriedade stretched), porém é possível configurá-lo para renderizar as imagens lado-a-lado, setando a propriedade sideBySide.

Suponhamos que agora eu queira a seguinte imagem como plano de fundo lado-a-lado:

camuflagem.jpg
camuflagem

Basta alterar a inicialização do ImagePanel conforme abaixo e executar novamente o ExemploImagePanel:

InputStream arquivo = getClass().getResourceAsStream("/camuflagem.jpg");
BufferedImage imagem = ImageIO.read(arquivo);
ImagePanel imagePanel = new ImagePanel(new GridBagLayout());
imagePanel.setImage(imagem);
imagePanel.setSideBySide(true); // renderizar a imagem lado a lado

Agora o aspecto do programa em execução será este:

ExemploImagePanel executando no Windows 10
Plano de fundo "camuflagem.jpg" lado-a-lado
telinha-lado-a-lado

É isso galera, espero que este fonte tenha alguma utilidade pra vocês.
:slight_smile:

9 curtidas