Como adicionar uma imagem a um JPanel?


341

Eu tenho um JPanel ao qual gostaria de adicionar imagens JPEG e PNG que eu gere em tempo real.

Todos os exemplos que eu vi até agora nos tutoriais do Swing , especialmente nos exemplos do Swing, usam ImageIcons.

Estou gerando essas imagens como matrizes de bytes e geralmente são maiores que o ícone comum usado nos exemplos, em 640x480.

  1. Existe algum problema (desempenho ou outro) no uso da classe ImageIcon para exibir uma imagem desse tamanho em um JPanel?
  2. Qual é a maneira usual de fazer isso?
  3. Como adicionar uma imagem a um JPanel sem usar a classe ImageIcon?

Editar : um exame mais cuidadoso dos tutoriais e da API mostra que você não pode adicionar um ImageIcon diretamente a um JPanel. Em vez disso, eles atingem o mesmo efeito configurando a imagem como um ícone de um JLabel. Isso simplesmente não parece certo ...


11
Dependendo de como você está gerando as matrizes de bytes, pode ser mais eficiente usar a MemoryImageSourcedo que convertê-las para o formato JPEG ou PNG e ler com a ImageIOmaioria das respostas. Você pode obter um Imagede MemoryImageSourceseus dados de imagem usando createImagee exibir conforme sugerido em uma das respostas.
Alden

Respostas:


252

Aqui está como eu faço isso (com um pouco mais de informações sobre como carregar uma imagem):

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JPanel;

public class ImagePanel extends JPanel{

    private BufferedImage image;

    public ImagePanel() {
       try {                
          image = ImageIO.read(new File("image name and path"));
       } catch (IOException ex) {
            // handle exception...
       }
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(image, 0, 0, this); // see javadoc for more info on the parameters            
    }

}

17
-1 para implementação inválida do paintComponent (@Dogmatixed provavelmente é por isso que você está tendo esses problemas de redesenho) - ele deve garantir a cobertura de toda a sua área se relatar ser opaco (que é o padrão), mais fácil conseguido chamando super.paintComponent
kleopatra

5
@kleopatra, Obrigado, eu não percebi isso ... de acordo com o javadoc: "Além disso, se você não invocar a implementação do super, você deve honrar a propriedade opaca, ou seja, se esse componente for opaco, preencha completamente o fundo em uma cor não opaca. Se você não respeitar a propriedade opaca, provavelmente verá artefatos visuais ". Vou atualizar a resposta agora.
Brendan Cashman

8
Por favor, respeite sempre os Principle of Encapsulationmétodos enquanto primordial da classe Super, o especificador de acesso do paintComponent(...)método é protectede não public:-)
Vaca agradável

11
Isso não é bom, não faz nada para dimensionar o painel na imagem. Mais código terá que ser adicionado mais tarde para lidar com isso. Muito mais simples de usar a resposta JLabel.
217 Keilly

2
@ Jonathan: Lamento muito, sobre alguma fonte para Principle of Encapsulation. Lembre-se de que durante a programação, o que deve estar oculto no restante do código, precisa ser mantido dessa maneira, em vez de ser visível para tudo no código. Parece que é algo que vem com a experiência, como se aprende a cada dia. Qualquer livro pode dar uma idéia básica do que é encapsulamento, mas é como implementamos nosso código que decide o quanto aderimos ao conceito.
nIcE cow

520

Se você estiver usando o JPanels, provavelmente está trabalhando com o Swing. Tente o seguinte:

BufferedImage myPicture = ImageIO.read(new File("path-to-file"));
JLabel picLabel = new JLabel(new ImageIcon(myPicture));
add(picLabel);

A imagem agora é um componente de oscilação. Torna-se sujeito a condições de layout como qualquer outro componente.


16
como dimensionar a imagem de acordo com o tamanho do JLabel?
Codigo_idiot

11
Código legal! Não tenho muita experiência com o Swing, mas não consigo fazê-lo funcionar. Alguém tentou isso no jdk 1.6.0_16?
ATorras 14/01

3
@ATorras Eu sei que você pediu isso há um tempo, mas se algum outro iniciante teve meus problemas, lembre-se de picLabel.setBounds ();
Kyle

Preciso criar um novo objeto JLabel toda vez que uma foto é recarregada?
0x6B6F77616C74

2
@coding_idiot new JLabel (novo ImageIcon (backgroundImage.getScaledInstance (largura, altura, Image.SCALE_FAST)));
Davide

37

O jeito de Fred Haslam funciona bem. No entanto, tive problemas com o caminho do arquivo, pois quero fazer referência a uma imagem dentro do meu jar. Para fazer isso, eu usei:

BufferedImage wPic = ImageIO.read(this.getClass().getResource("snow.png"));
JLabel wIcon = new JLabel(new ImageIcon(wPic));

Como só tenho um número finito (cerca de 10) de imagens que preciso carregar usando esse método, ele funciona muito bem. Ele obtém o arquivo sem ter que ter o caminho de arquivo relativo correto.


11
Substituí a primeira linha por BufferedImage previewImage = ImageIO.read(new URL(imageURL));para obter minhas imagens de um servidor.
Intcreator

Para se referir imagens de jar, tente este stackoverflow.com/a/44844922/3211801
Nandha

33

Eu acho que não há necessidade de subclasse de nada. Basta usar um Jlabel. Você pode definir uma imagem em um Jlabel. Então, redimensione o Jlabel e preencha-o com uma imagem. Está certo. É assim que eu faço.



11
JLabel imgLabel = new JLabel(new ImageIcon("path_to_image.png"));

8

Você pode subclassificar o JPanel - aqui está um extrato do meu ImagePanel, que coloca uma imagem em qualquer um dos 5 locais: superior / esquerdo, superior / direito, meio / meio, inferior / esquerdo ou inferior / direito:

protected void paintComponent(Graphics gc) {
    super.paintComponent(gc);

    Dimension                           cs=getSize();                           // component size

    gc=gc.create();
    gc.clipRect(insets.left,insets.top,(cs.width-insets.left-insets.right),(cs.height-insets.top-insets.bottom));
    if(mmImage!=null) { gc.drawImage(mmImage,(((cs.width-mmSize.width)/2)       +mmHrzShift),(((cs.height-mmSize.height)/2)        +mmVrtShift),null); }
    if(tlImage!=null) { gc.drawImage(tlImage,(insets.left                       +tlHrzShift),(insets.top                           +tlVrtShift),null); }
    if(trImage!=null) { gc.drawImage(trImage,(cs.width-insets.right-trSize.width+trHrzShift),(insets.top                           +trVrtShift),null); }
    if(blImage!=null) { gc.drawImage(blImage,(insets.left                       +blHrzShift),(cs.height-insets.bottom-blSize.height+blVrtShift),null); }
    if(brImage!=null) { gc.drawImage(brImage,(cs.width-insets.right-brSize.width+brHrzShift),(cs.height-insets.bottom-brSize.height+brVrtShift),null); }
    }

8
  1. Não deve haver nenhum problema (exceto problemas gerais que você possa ter com imagens muito grandes).
  2. Se você está falando sobre adicionar várias imagens a um único painel, eu usaria ImageIcons. Para uma única imagem, eu pensaria em criar uma subclasse personalizada JPanele substituir seu paintComponentmétodo para desenhar a imagem.
  3. (ver 2)

6

JPanelquase sempre é a classe errada para subclasse. Por que você não subclasse JComponent?

Há um pequeno problema com o ImageIconfato de o construtor bloquear a leitura da imagem. Não é realmente um problema ao carregar a partir do jar do aplicativo, mas talvez se você estiver lendo potencialmente em uma conexão de rede. Há uma abundância de exemplos AWT-era de usar MediaTracker, ImageObservere amigos, mesmo nos demos JDK.


6

Estou fazendo algo muito semelhante em um projeto particular em que estou trabalhando. Até agora, eu gerei imagens de até 1024x1024 sem problemas (exceto memória) e posso exibi-las muito rapidamente e sem problemas de desempenho.

A substituição do método de pintura da subclasse JPanel é um exagero e requer mais trabalho do que você precisa.

O jeito que eu faço é:

Class MapIcon implements Icon {...}

OU

Class MapIcon extends ImageIcon {...}

O código que você usa para gerar a imagem estará nesta classe. Eu uso um BufferedImage para chamar então quando o paintIcon () é chamado, use g.drawImvge (bufferedImage); Isso reduz a quantidade de flashes efetuados enquanto você gera suas imagens, e você pode encadear.

Em seguida, estendo o JLabel:

Class MapLabel extends Scrollable, MouseMotionListener {...}

Isso ocorre porque eu quero colocar minha imagem em um painel de rolagem, ou seja, exibir parte da imagem e fazer com que o usuário role conforme necessário.

Então, eu uso um JScrollPane para armazenar o MapLabel, que contém apenas o MapIcon.

MapIcon map = new MapIcon (); 
MapLabel mapLabel = new MapLabel (map);
JScrollPane scrollPane = new JScrollPane();

scrollPane.getViewport ().add (mapLabel);

Mas para o seu cenário (apenas mostre toda a imagem). Você precisa adicionar o MapLabel ao JPanel superior e dimensioná-los todos para o tamanho total da imagem (substituindo GetPreferredSize ()).


5

Crie uma pasta de origem no diretório do seu projeto, neste caso eu chamei de Imagens.

JFrame snakeFrame = new JFrame();
snakeFrame.setBounds(100, 200, 800, 800);
snakeFrame.setVisible(true);
snakeFrame.add(new JLabel(new ImageIcon("Images/Snake.png")));
snakeFrame.pack();

5

Você pode evitar usar a própria Componentbiblioteca e ImageIOclasse SwingX :

File f = new File("hello.jpg");
JLabel imgLabel = new JLabel(new ImageIcon(file.getName()));

4

Esta resposta é um complemento para a resposta de @ shawalli ...

Eu também queria fazer referência a uma imagem dentro do meu jar, mas em vez de ter uma BufferedImage, fiz isso simplesmente:

 JPanel jPanel = new JPanel();      
 jPanel.add(new JLabel(new ImageIcon(getClass().getClassLoader().getResource("resource/images/polygon.jpg"))));

1

Eu posso ver muitas respostas, não abordando realmente as três perguntas do OP.

1) Uma palavra sobre desempenho: é provável que as matrizes de bytes sejam ineficientes, a menos que você possa usar uma ordem exata de bytes de pixel que corresponda aos seus adaptadores de vídeo na resolução e profundidade de cores atuais.

Para obter o melhor desempenho de desenho, basta converter sua imagem em uma BufferedImage que é gerada com um tipo correspondente à sua configuração gráfica atual. Consulte createCompatibleImage em https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html

Essas imagens serão armazenadas em cache automaticamente na memória da placa de vídeo depois de desenhar algumas vezes sem nenhum esforço de programação (isso é padrão no Swing desde Java 6) e, portanto, o desenho real levará um tempo insignificante - se você não alterar a imagem .

A alteração da imagem vem com uma transferência de memória adicional entre a memória principal e a memória da GPU - que é lenta. Evite "redesenhar" a imagem em um BufferedImage, portanto, evite getPixel e setPixel de todas as formas.

Por exemplo, se você estiver desenvolvendo um jogo, em vez de desenhar todos os atores do jogo para uma BufferedImage e depois para um JPanel, é muito mais rápido carregar todos os atores como BufferedImages menores e desenhá-los um a um no seu código JPanel em sua posição correta - dessa forma, não há transferência de dados adicional entre a memória principal e a memória da GPU, exceto a transferência inicial das imagens para armazenamento em cache.

O ImageIcon usará uma BufferedImage sob o capô - mas basicamente a alocação de uma BufferedImage com o modo gráfico adequado é a chave, e não há esforço para fazer isso corretamente.

2) A maneira usual de fazer isso é desenhar uma BufferedImage em um método paintComponent substituído do JPanel. Embora o Java suporte uma boa quantidade de itens adicionais, como cadeias de buffer que controlam o VolatileImages armazenadas em cache na memória da GPU, não há necessidade de usá-las, pois o Java 6 faz um trabalho razoavelmente bom sem expor todos esses detalhes da aceleração da GPU.

Observe que a aceleração da GPU pode não funcionar em determinadas operações, como esticar imagens translúcidas.

3) Não adicione. Basta pintá-lo como mencionado acima:

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.drawImage(image, 0, 0, this); 
}

"Adicionar" faz sentido se a imagem fizer parte do layout. Se você precisar disso como uma imagem de plano de fundo ou primeiro plano preenchendo o JPanel, basta desenhar o paintComponent. Se você preferir criar um componente Swing genérico que possa mostrar sua imagem, é a mesma história (você pode usar um JComponent e substituir seu método paintComponent) - e depois adicioná- lo ao seu layout de componentes da GUI.

4) Como converter a matriz em uma imagem em buffer

Converter suas matrizes de bytes em PNG e carregá-las consome bastante recursos. Uma maneira melhor é converter sua matriz de bytes existente em uma BufferedImage.

Para isso: não use para loops e copie pixels. Isso é muito, muito lento. Em vez de:

  • aprenda a estrutura de bytes preferida da BufferedImage (atualmente é seguro assumir RGB ou RGBA, que é de 4 bytes por pixel)
  • aprenda a linha de varredura e o tamanho da digitalização em uso (por exemplo, você pode ter uma imagem de 142 pixels de largura - mas na vida real será armazenada como uma matriz de bytes de 256 pixels de largura, pois é mais rápido processar isso e mascarar os remixes não utilizados pelo hardware da GPU )
  • depois que você criar uma matriz de acordo com esses princípios, o método de matriz setRGB da BufferedImage poderá copiar sua matriz para a BufferedImage.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.