Comment ajouter une image à un JPanel?

341

J'ai un JPanel auquel j'aimerais ajouter des images JPEG et PNG que je génère à la volée.

Tous les exemples que j'ai vus jusqu'à présent dans les didacticiels Swing , spécialement dans les exemples Swing, utilisent ImageIcons.

Je génère ces images sous forme de tableaux d'octets, et elles sont généralement plus grandes que l'icône commune qu'elles utilisent dans les exemples, à 640x480.

  1. Existe-t-il un problème (de performance ou autre) dans l'utilisation de la classe ImageIcon pour afficher une image de cette taille dans un JPanel?
  2. Quelle est la manière habituelle de procéder?
  3. Comment ajouter une image à un JPanel sans utiliser la classe ImageIcon?

Edit : Un examen plus attentif des didacticiels et de l'API montre que vous ne pouvez pas ajouter un ImageIcon directement à un JPanel. Au lieu de cela, ils obtiennent le même effet en définissant l'image comme une icône d'un JLabel. Cela ne me semble pas juste ...

Leonel
la source
1
Selon la façon dont vous générez les tableaux d'octets, il peut être plus efficace d'utiliser un MemoryImageSourceque de les convertir au format JPEG ou PNG, puis de les lire ImageIOcomme le suggèrent la plupart des réponses. Vous pouvez obtenir un à Imagepartir d'un MemoryImageSourceconstruit avec vos données d'image en utilisant createImageet afficher comme suggéré dans l'une des réponses.
Alden
Vérifiez ma réponse stackoverflow.com/questions/43861991/…
tommybee

Réponses:

252

Voici comment je le fais (avec un peu plus d'informations sur la façon de charger une image):

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
la source
17
-1 pour une implémentation non valide de paintComponent (@Dogmatixed est probablement la raison pour laquelle vous rencontrez ces problèmes de redessinage) - il doit garantir de couvrir toute sa zone s'il signale qu'il est opaque (ce qui est la valeur par défaut), le plus simple étant obtenu en appelant super.paintComponent
kleopatra
5
@kleopatra, Merci, je ne m'étais pas rendu compte que ... selon le javadoc: "De plus, si vous n'invoquez pas l'implémentation de super, vous devez honorer la propriété opaque, c'est-à-dire si ce composant est opaque, vous devez remplir complètement le l'arrière-plan dans une couleur non opaque. Si vous n'honorez pas la propriété opaque, vous verrez probablement des artefacts visuels. " Je vais mettre à jour la réponse maintenant.
Brendan Cashman
8
Veuillez toujours respecter les Principle of Encapsulationméthodes de paintComponent(...)protectedpublic
substitution
1
Ce n'est pas bon, cela ne fait rien pour redimensionner le panneau à l'image. Plus de code devra être ajouté plus tard. Beaucoup plus simple d'utiliser la réponse JLabel.
Keilly
2
@Jonathan: Je suis vraiment désolé, concernant une source pour Principle of Encapsulation. N'oubliez pas que pendant la programmation, ce qui doit être caché du reste du code doit être conservé de cette façon, au lieu d'être visible par tout dans le code. On dirait que c'est une chose qui vient avec l'expérience, comme on apprend chaque jour. N'importe quel livre peut donner une idée de base de ce qu'est l'encapsulation, mais c'est la façon dont nous mettons en œuvre notre code qui décide de notre adhésion au concept.
nIcE cOw
520

Si vous utilisez JPanels, vous travaillez probablement avec Swing. Essaye ça:

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

L'image est maintenant un composant swing. Il devient soumis à des conditions de mise en page comme tout autre composant.

Fred Haslam
la source
16
comment redimensionner l'image en fonction de la taille du JLabel?
coding_idiot
1
Beau code! Je n'ai pas beaucoup d'expérience avec Swing mais je ne peux pas le faire fonctionner. Quelqu'un l'a-t-il essayé dans jdk 1.6.0_16?
ATorras
3
@ATorras Je sais que vous avez posé cette question il y a quelque temps, mais si d'autres débutants ont eu mes problèmes, n'oubliez pas de picLabel.setBounds ();
kyle
Dois-je créer un nouvel objet JLabel à chaque fois qu'une photo est rechargée?
0x6B6F77616C74
2
@coding_idiot new JLabel (new ImageIcon (backgroundImage.getScaledInstance (width, height, Image.SCALE_FAST)));
Davide
37

La manière de Fred Haslam fonctionne bien. J'ai cependant eu des problèmes avec le chemin du fichier, car je veux référencer une image dans mon pot. Pour ce faire, j'ai utilisé:

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

Étant donné que je n'ai qu'un nombre fini (environ 10) d'images que je dois charger à l'aide de cette méthode, cela fonctionne très bien. Il obtient le fichier sans avoir à avoir le chemin de fichier relatif correct.

shawalli
la source
1
J'ai substitué la première ligne par BufferedImage previewImage = ImageIO.read(new URL(imageURL));pour récupérer mes images depuis un serveur.
intcreator
Pour référencer des images du pot, essayez ce stackoverflow.com/a/44844922/3211801
Nandha
33

Je pense qu'il n'est pas nécessaire de sous-classer quoi que ce soit. Utilisez simplement un Jlabel. Vous pouvez définir une image dans un Jlabel. Donc, redimensionnez le Jlabel puis remplissez-le avec une image. C'est bon. C'est ma façon de faire.

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

la source
8

Vous pouvez sous-classer JPanel - voici un extrait de mon ImagePanel, qui met une image dans l'un des 5 emplacements, haut / gauche, haut / droite, milieu / milieu, bas / gauche ou bas / droite:

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
la source
8
  1. Il ne devrait pas y avoir de problème (à part les problèmes généraux que vous pourriez avoir avec de très grandes images).
  2. Si vous parlez d'ajouter plusieurs images à un seul panneau, j'utiliserais le par ImageIcon. Pour une seule image, je penserais à créer une sous-classe personnalisée JPanelet à remplacer sa paintComponentméthode pour dessiner l'image.
  3. (voir 2)
Michael Myers
la source
6

JPanelest presque toujours la mauvaise classe à sous-classer. Pourquoi ne sous JComponent- classeriez-vous pas ?

Il y a un léger problème avec le fait ImageIconque le constructeur bloque la lecture de l'image. Pas vraiment un problème lors du chargement à partir du pot d'application, mais peut-être si vous lisez potentiellement sur une connexion réseau. Il existe de nombreux exemples d'utilisation de l'ère AWT MediaTracker, ImageObserveret des amis, même dans les démos JDK.

Tom Hawtin - sellerie
la source
6

Je fais quelque chose de très similaire dans un projet privé sur lequel je travaille. Jusqu'à présent, j'ai généré des images jusqu'à 1024x1024 sans aucun problème (sauf la mémoire) et je peux les afficher très rapidement et sans aucun problème de performances.

Remplacer la méthode de peinture de la sous-classe JPanel est exagéré et nécessite plus de travail que vous n'en avez besoin.

Ma façon de procéder est:

Class MapIcon implements Icon {...}

OU

Class MapIcon extends ImageIcon {...}

Le code que vous utilisez pour générer l'image sera dans cette classe. J'utilise un BufferedImage pour dessiner puis lorsque le paintIcon () est appelé, utilisez g.drawImvge (bufferedImage); Cela réduit la quantité de clignotement effectuée pendant que vous générez vos images et vous pouvez les enfiler.

J'étends ensuite JLabel:

Class MapLabel extends Scrollable, MouseMotionListener {...}

C'est parce que je veux mettre mon image dans un volet de défilement, c'est-à-dire afficher une partie de l'image et faire défiler l'utilisateur au besoin.

Alors j'utilise un JScrollPane pour contenir le MapLabel, qui ne contient que le MapIcon.

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

scrollPane.getViewport ().add (mapLabel);

Mais pour votre scénario (montrez l'image entière à chaque fois). Vous devez ajouter le MapLabel au JPanel supérieur et assurez-vous de les redimensionner tous à la taille complète de l'image (en remplaçant GetPreferredSize ()).

Thomas Jones-Low
la source
5

Créez un dossier source dans votre répertoire de projet, dans ce cas je l'ai appelé Images.

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

la source
5

Vous pouvez éviter d'utiliser sa propre Componentbibliothèque et ImageIOclasse s et SwingX :

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

Cette réponse est un complément à la réponse de @ shawalli ...

Je voulais aussi référencer une image dans mon pot, mais au lieu d'avoir une BufferedImage, j'ai simplement fait ceci:

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

Je peux voir beaucoup de réponses, ne répondant pas vraiment aux trois questions du PO.

1) Un mot sur les performances: les tableaux d'octets sont probablement inefficaces, sauf si vous pouvez utiliser un ordre d'octets de pixels exact qui correspond à la résolution actuelle et à la profondeur de couleur de vos cartes graphiques.

Pour obtenir les meilleures performances de dessin, il vous suffit de convertir votre image en une BufferedImage qui est générée avec un type correspondant à votre configuration graphique actuelle. Voir createCompatibleImage sur https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html

Ces images seront automatiquement mises en cache sur la mémoire de la carte graphique après avoir dessiné plusieurs fois sans aucun effort de programmation (ceci est standard dans Swing depuis Java 6), et donc le dessin réel prendra un temps négligeable - si vous n'avez pas changé l'image .

La modification de l'image entraînera un transfert de mémoire supplémentaire entre la mémoire principale et la mémoire GPU - ce qui est lent. Évitez de "redessiner" l'image en une BufferedImage, évitez donc de faire getPixel et setPixel par tous les moyens.

Par exemple, si vous développez un jeu, au lieu de dessiner tous les acteurs du jeu sur une BufferedImage puis sur un JPanel, il est beaucoup plus rapide de charger tous les acteurs sous forme de BufferedImages plus petits et de les dessiner un par un dans votre code JPanel à leur position correcte - de cette façon, il n'y a pas de transfert de données supplémentaires entre la mémoire principale et la mémoire GPU, à l'exception du transfert initial des images pour la mise en cache.

ImageIcon utilisera une BufferedImage sous le capot - mais en gros, allouer une BufferedImage avec le bon mode graphique est la clé, et il n'y a aucun effort pour le faire correctement.

2) La manière habituelle de procéder est de dessiner une BufferedImage dans une méthode paintComponent surchargée de JPanel. Bien que Java prenne en charge une grande quantité de fonctionnalités supplémentaires telles que les chaînes de tampons contrôlant les images volatiles mises en cache dans la mémoire du GPU, il n'est pas nécessaire d'utiliser ces éléments depuis Java 6 qui fait un travail raisonnablement bon sans exposer tous ces détails de l'accélération du GPU.

Notez que l'accélération GPU peut ne pas fonctionner pour certaines opérations, telles que l'étirement d'images translucides.

3) N'ajoutez pas. Il suffit de le peindre comme mentionné ci-dessus:

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

"Ajouter" est logique si l'image fait partie de la mise en page. Si vous en avez besoin en tant qu'image d'arrière-plan ou de premier plan remplissant le JPanel, dessinez simplement paintComponent. Si vous préférez brasser un composant Swing générique qui peut montrer votre image, il est alors la même histoire (vous pouvez utiliser un JComponent et remplacer la méthode de paintComponent) - puis ajoutez ce à votre disposition des composants GUI.

4) Comment convertir le tableau en une image tampon

La conversion de vos tableaux d'octets en PNG, puis leur chargement nécessitent beaucoup de ressources. Une meilleure façon est de convertir votre tableau d'octets existant en BufferedImage.

Pour cela: ne pas utiliser pour les boucles et copier les pixels. C'est très très lent. Au lieu:

  • apprendre la structure d'octets préférée de la BufferedImage (de nos jours, il est sûr de supposer RVB ou RGBA, qui est de 4 octets par pixel)
  • apprendre la ligne de numérisation et la taille de numérisation en cours d'utilisation (par exemple, vous pourriez avoir une image de 142 pixels de large - mais dans la vraie vie qui sera stockée sous forme de tableau d'octets de 256 pixels de large car il est plus rapide de traiter cela et de masquer les pixels inutilisés par le matériel GPU )
  • puis, une fois que vous avez construit un tableau selon ces principes, la méthode du tableau setRGB du BufferedImage peut copier votre tableau sur le BufferedImage.
Gee Bee
la source