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 ...

Leonel
fonte
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
Verifique minha resposta stackoverflow.com/questions/43861991/…
tommybee 9/17/17 /

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            
    }

}
Brendan Cashman
fonte
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.

Fred Haslam
fonte
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.

shawalli
fonte
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.

CoderTim
fonte
20

Você pode evitar a rolagem de sua própria subclasse Component completamente usando a classe JXImagePanel das bibliotecas gratuitas SwingX .

Baixar

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

fonte
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); }
    }
Lawrence Dol
fonte
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)
Michael Myers
fonte
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.

Tom Hawtin - linha de orientação
fonte
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 ()).

Thomas Jones-Low
fonte
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();

fonte
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()));
Muskovets
fonte
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"))));
Filipe Brito
fonte
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.
Gee Bee
fonte