Wie füge ich ein Bild zu einem JPanel hinzu?

341

Ich habe ein JPanel, zu dem ich JPEG- und PNG-Bilder hinzufügen möchte , die ich im laufenden Betrieb generiere.

Alle Beispiele, die ich bisher in den Swing-Tutorials gesehen habe , insbesondere in den Swing-Beispielen, verwenden ImageIcons.

Ich generiere diese Bilder als Byte-Arrays und sie sind normalerweise größer als das in den Beispielen verwendete allgemeine Symbol (640 x 480).

  1. Gibt es ein (Leistungs- oder anderes) Problem bei der Verwendung der ImageIcon-Klasse zum Anzeigen eines Bildes dieser Größe in einem JPanel?
  2. Wie geht das normalerweise ?
  3. Wie füge ich einem JPanel ein Bild hinzu, ohne die ImageIcon-Klasse zu verwenden?

Bearbeiten : Eine genauere Prüfung der Tutorials und der API zeigt, dass Sie ein ImageIcon nicht direkt zu einem JPanel hinzufügen können. Stattdessen erzielen sie den gleichen Effekt, indem sie das Bild als Symbol für ein JLabel festlegen. Das fühlt sich einfach nicht richtig an ...

Leonel
quelle
1
Abhängig davon, wie Sie die Byte-Arrays generieren, ist es möglicherweise effizienter, a zu verwenden, MemoryImageSourceals sie in das JPEG- oder PNG-Format zu konvertieren und dann mit ImageIOden meisten Antworten zu lesen . Sie können eine Imageaus einem MemoryImageSourcemit Ihren Bilddaten erstellte Daten erhalten, indem Sie diese verwenden createImageund anzeigen, wie in einer der Antworten vorgeschlagen.
Alden
Überprüfen Sie meine Antwort stackoverflow.com/questions/43861991/…
tommybee

Antworten:

252

So mache ich das (mit ein paar Informationen zum Laden eines Bildes):

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
quelle
17
-1 für ungültig Implementierung von paintcomponent (@Dogmatixed wahrscheinlich das ist , warum Sie diese neu gezeichnet Probleme haben) - es muss seine gesamte Fläche garantee zu decken , wenn es lichtundurchlässig berichtet (das ist die Standardeinstellung), erreicht am einfachsten durch super.paintComponent Aufruf
Kleopatra
5
@kleopatra, danke, das habe ich nicht bemerkt ... laut javadoc: "Wenn Sie die Implementierung von super nicht aufrufen, müssen Sie die undurchsichtige Eigenschaft berücksichtigen, dh wenn diese Komponente undurchsichtig ist, müssen Sie die undurchsichtige vollständig ausfüllen Hintergrund in einer nicht undurchsichtigen Farbe. Wenn Sie die undurchsichtige Eigenschaft nicht berücksichtigen, werden Sie wahrscheinlich visuelle Artefakte sehen. " Ich werde die Antwort jetzt aktualisieren.
Brendan Cashman
8
Bitte respektieren Sie immer die Principle of Encapsulationüberschreibenden Methoden der paintComponent(...)protectedpublic
Superklasse
1
Dies ist nicht gut, es ändert nichts an der Größe des Bedienfelds für das Bild. Weitere Codes müssen später hinzugefügt werden, um dies zu beheben. Viel einfacher, die JLabel-Antwort zu verwenden.
Keilly
2
@ Jonathan: Es tut mir wirklich leid, was eine Quelle betrifft Principle of Encapsulation. Denken Sie beim Programmieren daran, dass das, was vor dem Rest des Codes verborgen bleiben sollte, auf diese Weise beibehalten werden muss, anstatt für alles im Code sichtbar zu sein. Es scheint, als wäre es eine solche Sache, die mit Erfahrung einhergeht, wie man jeden Tag lernt. Jedes Buch kann eine grundlegende Vorstellung davon geben, was Kapselung ist, aber es ist die Art und Weise, wie wir unseren Code implementieren, die darüber entscheidet, wie sehr wir uns an das Konzept halten.
Eis
520

Wenn Sie JPanels verwenden, arbeiten Sie wahrscheinlich mit Swing. Versuche dies:

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

Das Bild ist jetzt eine Swing-Komponente. Es unterliegt Layoutbedingungen wie jede andere Komponente.

Fred Haslam
quelle
16
Wie skaliere ich das Bild entsprechend der Größe des JLabel?
Coding_idiot
1
Schöner Code! Ich habe nicht viel Erfahrung mit Swing, aber ich kann es nicht zum Laufen bringen. Hat es jemand in jdk 1.6.0_16 versucht?
ATorras
3
@ATorras Ich weiß, dass Sie dies vor einiger Zeit gefragt haben, aber wenn andere Neulinge meine Probleme hatten, denken Sie daran, picLabel.setBounds ();
Kyle
Muss ich jedes Mal ein neues JLabel-Objekt erstellen, wenn ein Foto neu geladen wird?
0x6B6F77616C74
2
@coding_idiot new JLabel (neues ImageIcon (backgroundImage.getScaledInstance (Breite, Höhe, Image.SCALE_FAST)));
Davide
37

Fred Haslams Weg funktioniert gut. Ich hatte jedoch Probleme mit dem Dateipfad, da ich auf ein Bild in meinem Glas verweisen möchte. Dazu habe ich verwendet:

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

Da ich nur eine endliche Anzahl (ungefähr 10) Bilder habe, die ich mit dieser Methode laden muss, funktioniert es ziemlich gut. Es wird eine Datei abgerufen, ohne den richtigen relativen Dateipfad haben zu müssen.

Shawalli
quelle
1
Ich habe die erste Zeile durch ersetzt BufferedImage previewImage = ImageIO.read(new URL(imageURL));, um meine Bilder von einem Server zu erhalten.
Intcreator
Um Bilder aus dem Glas zu referenzieren
Nandha
33

Ich denke, es besteht keine Notwendigkeit, irgendetwas zu unterordnen. Verwenden Sie einfach ein Jlabel. Sie können ein Bild in ein Jlabel setzen. Ändern Sie also die Größe des Jlabels und füllen Sie es mit einem Bild. Es ist in Ordnung. So mache ich das.

CoderTim
quelle
20

Sie können das vollständige Rollen Ihrer eigenen Komponenten-Unterklasse vermeiden, indem Sie die JXImagePanel-Klasse aus den kostenlosen SwingX- Bibliotheken verwenden.

Herunterladen

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

quelle
8

Sie können JPanel in Unterklassen unterteilen - hier ist ein Auszug aus meinem ImagePanel, in dem ein Bild an einer von 5 Stellen platziert wird: oben / links, oben / rechts, Mitte / Mitte, unten / links oder unten / rechts:

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
quelle
8
  1. Es sollte kein Problem geben (außer allgemeinen Problemen, die bei sehr großen Bildern auftreten können).
  2. Wenn Sie über das Hinzufügen mehrerer Bilder zu einem einzelnen Panel sprechen, würde ich ImageIcons verwenden. Für ein einzelnes Bild würde ich darüber nachdenken, eine benutzerdefinierte Unterklasse zu erstellen JPanelund deren paintComponentMethode zum Zeichnen des Bildes zu überschreiben .
  3. (siehe 2)
Michael Myers
quelle
6

JPanelist fast immer die falsche Klasse zur Unterklasse. Warum würden Sie nicht Unterklasse JComponent?

Es gibt ein kleines Problem damit, ImageIcondass der Konstruktor das Lesen des Bildes blockiert. Kein wirkliches Problem beim Laden aus dem Anwendungsglas, aber möglicherweise, wenn Sie möglicherweise über eine Netzwerkverbindung lesen. Es gibt viele AWT-Ära Beispiele für die Verwendung MediaTracker, ImageObserverund Freunde, auch in den JDK - Demos.

Tom Hawtin - Tackline
quelle
6

Ich mache etwas sehr Ähnliches in einem privaten Projekt, an dem ich arbeite. Bisher habe ich Bilder bis zu 1024 x 1024 ohne Probleme (außer Speicher) generiert und kann sie sehr schnell und ohne Leistungsprobleme anzeigen.

Das Überschreiben der Malmethode der JPanel-Unterklasse ist übertrieben und erfordert mehr Arbeit als nötig.

Ich mache es so:

Class MapIcon implements Icon {...}

ODER

Class MapIcon extends ImageIcon {...}

Der Code, den Sie zum Generieren des Bildes verwenden, befindet sich in dieser Klasse. Ich benutze ein BufferedImage, um darauf zu zeichnen. Wenn das paintIcon () aufgerufen wird, verwende ich g.drawImvge (bufferedImage). Dies reduziert die Anzahl der Blinkvorgänge, die beim Generieren Ihrer Bilder ausgeführt werden, und Sie können sie einfädeln.

Als nächstes erweitere ich JLabel:

Class MapLabel extends Scrollable, MouseMotionListener {...}

Dies liegt daran, dass ich mein Bild in einen Bildlaufbereich einfügen möchte, dh einen Teil des Bildes anzeigen und den Benutzer nach Bedarf scrollen lassen möchte.

Dann verwende ich ein JScrollPane, um das MapLabel zu speichern, das nur das MapIcon enthält.

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

scrollPane.getViewport ().add (mapLabel);

Aber für Ihr Szenario (zeigen Sie jedes Mal das ganze Bild). Sie müssen das MapLabel zum obersten JPanel hinzufügen und sicherstellen, dass alle die volle Größe des Bildes haben (indem Sie GetPreferredSize () überschreiben).

Thomas Jones-Low
quelle
5

Erstellen Sie einen Quellordner in Ihrem Projektverzeichnis. In diesem Fall habe ich ihn Images genannt.

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

quelle
5

Sie können die Verwendung einer eigenen Components- und SwingX-Bibliothek und ImageIO-Klasse vermeiden :

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

Diese Antwort ist eine Ergänzung zu @ shawallis Antwort ...

Ich wollte auch auf ein Bild in meinem Glas verweisen, aber anstatt ein BufferedImage zu haben, habe ich einfach Folgendes getan:

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

Ich kann viele Antworten sehen, die die drei Fragen des OP nicht wirklich ansprechen.

1) Ein Wort zur Leistung: Byte-Arrays sind wahrscheinlich ineffizient, es sei denn, Sie können eine genaue Pixel-Byte-Reihenfolge verwenden, die der aktuellen Auflösung und Farbtiefe Ihres Displayadapters entspricht.

Um die beste Zeichenleistung zu erzielen, konvertieren Sie Ihr Bild einfach in ein BufferedImage, das mit einem Typ generiert wird, der Ihrer aktuellen Grafikkonfiguration entspricht. Siehe createCompatibleImage unter https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html

Diese Bilder werden nach mehrmaligem Zeichnen ohne Programmieraufwand automatisch im Speicher der Grafikkarte zwischengespeichert (dies ist in Swing seit Java 6 Standard). Daher dauert das eigentliche Zeichnen vernachlässigbar lange - wenn Sie das Bild nicht geändert haben .

Das Ändern des Bildes führt zu einer zusätzlichen Speicherübertragung zwischen Hauptspeicher und GPU-Speicher - was langsam ist. Vermeiden Sie das "Neuzeichnen" des Bildes in ein BufferedImage. Vermeiden Sie daher auf jeden Fall getPixel und setPixel.

Wenn Sie beispielsweise ein Spiel entwickeln, anstatt alle Spielakteure in ein BufferedImage und dann in ein JPanel zu zeichnen, ist es viel schneller, alle Akteure als kleinere BufferedImages zu laden und sie einzeln in Ihrem JPanel-Code unter zu zeichnen ihre richtige Position - auf diese Weise gibt es keine zusätzliche Datenübertragung zwischen dem Hauptspeicher und dem GPU-Speicher außer der anfänglichen Übertragung der Bilder zum Zwischenspeichern.

ImageIcon verwendet ein BufferedImage unter der Haube - aber im Grunde ist es der Schlüssel, ein BufferedImage mit dem richtigen Grafikmodus zuzuweisen , und es gibt keine Anstrengungen, dies richtig zu machen.

2) Der übliche Weg, dies zu tun, besteht darin, ein BufferedImage in einer überschriebenen paintComponent-Methode des JPanel zu zeichnen. Obwohl Java eine große Anzahl zusätzlicher Extras unterstützt, wie z. B. Pufferketten, die im GPU-Speicher zwischengespeicherte VolatileImages steuern, müssen diese nicht verwendet werden, da Java 6 eine einigermaßen gute Arbeit leistet, ohne all diese Details der GPU-Beschleunigung offenzulegen.

Beachten Sie, dass die GPU-Beschleunigung bei bestimmten Vorgängen möglicherweise nicht funktioniert, z. B. beim Strecken durchscheinender Bilder.

3) Nicht hinzufügen. Malen Sie es einfach wie oben erwähnt:

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

"Hinzufügen" ist sinnvoll, wenn das Bild Teil des Layouts ist. Wenn Sie dies als Hintergrund- oder Vordergrundbild benötigen, das das JPanel ausfüllt, zeichnen Sie einfach paintComponent ein. Wenn Sie es vorziehen, eine generische Swing-Komponente zu brauen, die Ihr Bild anzeigen kann, ist es dieselbe Geschichte (Sie können eine JComponent verwenden und ihre paintComponent-Methode überschreiben) - und diese dann Ihrem Layout der GUI-Komponenten hinzufügen .

4) So konvertieren Sie das Array in ein Pufferbild

Das Konvertieren Ihrer Byte-Arrays in PNG und das anschließende Laden ist sehr ressourcenintensiv. Eine bessere Möglichkeit besteht darin, Ihr vorhandenes Byte-Array in ein BufferedImage zu konvertieren.

Dafür: Nicht für Schleifen verwenden und Pixel kopieren. Das ist sehr sehr langsam. Stattdessen:

  • Lernen Sie die bevorzugte Bytestruktur des BufferedImage kennen (heutzutage ist es sicher, RGB oder RGBA anzunehmen, was 4 Bytes pro Pixel entspricht).
  • Lernen Sie die verwendete Scanlinie und die gescannte Größe (z. B. haben Sie möglicherweise ein Bild mit einer Breite von 142 Pixel - aber im wirklichen Leben wird dieses als 256 Pixel breites Byte-Array gespeichert, da es schneller verarbeitet werden kann und die nicht verwendeten Pixel von der GPU-Hardware maskiert werden )
  • Sobald Sie ein Array nach diesen Prinzipien erstellt haben, kann die Array-Methode setRGB des BufferedImage Ihr Array in das BufferedImage kopieren.
Gee Bee
quelle