Jak dodać obraz do JPanel?


341

Mam JPanel, do którego chciałbym dodać obrazy JPEG i PNG, które generuję na bieżąco.

Wszystkie przykłady, które widziałem do tej pory w Swing Tutorials , szczególnie w przykładach Swing, używają ImageIcons.

Generuję te obrazy jako tablice bajtów i zwykle są one większe niż zwykła ikona, której używają w przykładach, w rozdzielczości 640x480.

  1. Czy jest jakiś (wydajność lub inny) problem z użyciem klasy ImageIcon do wyświetlenia obrazu o takim rozmiarze w JPanel?
  2. Jak to zwykle robi?
  3. Jak dodać obraz do JPanel bez użycia klasy ImageIcon?

Edycja : Dokładniejsze sprawdzenie samouczków i interfejsu API pokazuje, że nie można dodać ImageIcon bezpośrednio do JPanel. Zamiast tego osiągają ten sam efekt, ustawiając obraz jako ikonę JLabel. To po prostu nie wydaje się właściwe ...


1
W zależności od tego, w jaki sposób generujesz tablice bajtów, bardziej efektywne może być użycie MemoryImageSourceniż konwertowanie ich do formatu JPEG lub PNG, a następnie czytanie, ImageIOjak sugeruje większość odpowiedzi. Można dostać Imagez punktu A MemoryImageSourcezbudowane z danych obrazu za pomocą createImage, a wyświetlacz jak zasugerował w jednym z odpowiedziami.
Alden

Odpowiedzi:


252

Oto jak to zrobić (z odrobiną więcej informacji na temat ładowania obrazu):

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 za niepoprawną implementację paintComponent (@Dogmatixed najprawdopodobniej dlatego masz problemy z przerysowaniem) - musi zagwarantować pokrycie całego obszaru, jeśli zgłasza, że ​​jest nieprzejrzysty (co jest domyślnym), najłatwiej to osiągnąć wywołując super.paintComponent
kleopatra,

5
@kleopatra, Dzięki, nie zdawałem sobie sprawy, że ... zgodnie z javadoc: „Ponadto, jeśli nie wywołujesz implementacji super, musisz uszanować nieprzejrzystą właściwość, to znaczy jeśli ten komponent jest nieprzejrzysty, musisz całkowicie wypełnić tło w nieprzezroczystym kolorze. Jeśli nie uszanujesz nieprzezroczystej właściwości, prawdopodobnie zobaczysz artefakty wizualne. ” Zaktualizuję odpowiedź teraz.
Brendan Cashman

8
Należy zawsze przestrzegać Principle of Encapsulationmetod podczas Nadrzędnym Super klasy, dostęp Specyfikator tego paintComponent(...)sposobu jest protectedi nie public:-)
Nicea krOwa

1
To nie jest dobre, nie ma nic wspólnego z dopasowaniem panelu do obrazu. Więcej kodu trzeba będzie później dodać, aby to zrobić. Znacznie prostsze jest użycie odpowiedzi JLabel.
Keilly,

2
@Jathanathan: Przykro mi, jeśli chodzi o jakieś źródło Principle of Encapsulation. Pamiętaj tylko, że podczas programowania to, co powinno być ukryte przed resztą kodu, musi być utrzymane w ten sposób, zamiast być widoczne dla wszystkich w kodzie. Wydaje się, że jest to jedna z takich rzeczy, które pojawiają się wraz z doświadczeniem, ponieważ uczy się każdego dnia. Każda książka może dać podstawowe wyobrażenie o tym, czym jest enkapsulacja, ale to w jaki sposób wdrażamy nasz kod, decyduje o tym, jak bardzo trzymamy się tej koncepcji.
nICe cOw

520

Jeśli używasz JPanels, prawdopodobnie pracujesz z Swing. Spróbuj tego:

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

Obraz jest teraz elementem składowym. Staje się przedmiotem warunków układu, jak każdy inny komponent.


16
jak skalować obraz zgodnie z rozmiarem JLabel?
coding_idiot

1
Niezły kod! Nie mam dużego doświadczenia ze Swingiem, ale nie mogę go uruchomić. Czy ktoś próbował tego w jdk 1.6.0_16?
ATorras

3
@ATorras Wiem, że pytałeś o to jakiś czas temu, ale jeśli jakikolwiek inny początkujący miał moje problemy, pamiętaj o picLabel.setBounds ();
kyle

Czy muszę tworzyć nowy obiekt JLabel za każdym razem, gdy zdjęcie zostanie ponownie załadowane?
0x6B6F77616C74,

2
@coding_idiot new JLabel (nowy ImageIcon (backgroundImage.getScaledInstance (szerokość, wysokość, Image.SCALE_FAST)));
Davide

37

Sposób Freda Haslama działa dobrze. Miałem jednak problem z ścieżką pliku, ponieważ chcę odwoływać się do obrazu w moim słoiku. Aby to zrobić, użyłem:

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

Ponieważ mam tylko skończoną liczbę (około 10) obrazów, które muszę załadować za pomocą tej metody, działa całkiem dobrze. Pobiera plik bez konieczności posiadania poprawnej względnej ścieżki pliku.


1
Pierwszą linię zastąpiłem, BufferedImage previewImage = ImageIO.read(new URL(imageURL));aby pobrać moje zdjęcia z serwera.
intcreator,

Aby odsyłać zdjęcia ze słoika, wypróbuj ten stackoverflow.com/a/44844922/3211801
Nandha

33

Myślę, że nie trzeba niczego podklasować. Po prostu użyj Jlabela. Możesz ustawić obraz w Jlabel. Zmień rozmiar Jlabela, a następnie wypełnij go obrazem. W porządku. Tak właśnie robię.



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

8

Możesz podklasować JPanel - oto fragment z mojego ImagePanel, który umieszcza obraz w jednym z 5 miejsc: u góry / po lewej, u góry / po prawej, w środku / w środku, u dołu / po lewej lub u dołu / po prawej:

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. Nie powinno być żadnych problemów (poza ogólnymi problemami, które możesz mieć z bardzo dużymi obrazami).
  2. Jeśli mówisz o dodawaniu wielu obrazów do jednego panelu, użyłbym ImageIcons. W przypadku pojedynczego obrazu pomyślałbym o stworzeniu niestandardowej podklasy JPaneli zastąpieniu jej paintComponentmetody rysowania obrazu.
  3. (patrz 2)

6

JPanelprawie zawsze jest złą klasą do podklasy. Dlaczego nie podklasujesz JComponent?

Istnieje niewielki problem ImageIconpolegający na tym, że konstruktor blokuje czytanie obrazu. Naprawdę nie jest to problem podczas ładowania ze słoika aplikacji, ale może, jeśli potencjalnie czytasz przez połączenie sieciowe. Jest mnóstwo przykładów AWT-era użyciu MediaTracker, ImageObserveri przyjaciół, nawet w demo JDK.


6

Robię coś bardzo podobnego w prywatnym projekcie, nad którym pracuję. Do tej pory wygenerowałem obrazy do 1024x1024 bez żadnych problemów (oprócz pamięci) i mogę je wyświetlać bardzo szybko i bez problemów z wydajnością.

Przesłonięcie metody malowania w podklasie JPanel jest przesadą i wymaga więcej pracy niż trzeba.

Tak to robię:

Class MapIcon implements Icon {...}

LUB

Class MapIcon extends ImageIcon {...}

Kod użyty do wygenerowania obrazu będzie w tej klasie. Korzystam z BufferedImage, aby rysować, kiedy wywoływana jest funkcja paintIcon (), używam g.drawImvge (bufferedImage); Zmniejsza to ilość flashowania wykonanego podczas generowania obrazów i można go wątkować.

Następnie rozszerzam JLabel:

Class MapLabel extends Scrollable, MouseMotionListener {...}

Wynika to z tego, że chcę umieścić obraz w okienku przewijania, tzn. Wyświetlić część obrazu i pozwolić użytkownikowi przewijać w razie potrzeby.

Więc używam JScrollPane do przechowywania MapLabel, który zawiera tylko MapIcon.

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

scrollPane.getViewport ().add (mapLabel);

Ale dla twojego scenariusza (po prostu pokazuj cały obraz za każdym razem). Musisz dodać MapLabel do najwyższego JPanel i upewnić się, że wszystkie one mają rozmiar do pełnego rozmiaru obrazu (przez przesłonięcie GetPreferredSize ()).


5

Utwórz folder źródłowy w katalogu projektu, w tym przypadku nazwałem go Obrazy.

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

Możesz uniknąć korzystania z własnej Componentbiblioteki i ImageIOklasy SwingX oraz klasy:

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

4

Ta odpowiedź jest uzupełnieniem odpowiedzi @ shawalli ...

Chciałem również odwołać się do obrazu w moim słoiku, ale zamiast mieć buforowany obraz, po prostu zrobiłem to:

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

1

Widzę wiele odpowiedzi, w rzeczywistości nie odnosząc się do trzech pytań PO.

1) Słowo o wydajności: tablice bajtów są prawdopodobnie niewystarczające, chyba że można użyć dokładnej kolejności bajtów pikseli, która pasuje do bieżącej rozdzielczości i głębi kolorów kart graficznych.

Aby osiągnąć najlepszą wydajność rysowania, wystarczy przekonwertować obraz na buforowany obraz, który jest generowany z typem odpowiadającym bieżącej konfiguracji grafiki. Zobacz createCompatibleImage na https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html

Te obrazy będą automatycznie buforowane w pamięci karty graficznej po rysowaniu kilka razy bez żadnego wysiłku programistycznego (jest to standard w Swing od Java 6), a zatem faktyczny rysunek zajmie znikomą ilość czasu - jeśli nie zmieniłeś obrazu .

Zmiana obrazu nastąpi z dodatkowym transferem pamięci między pamięcią główną a pamięcią GPU - co jest wolne. Dlatego unikaj „przerysowywania” obrazu do BufferedImage, dlatego unikaj wykonywania getPixel i setPixel za wszelką cenę.

Na przykład, jeśli tworzysz grę, zamiast przyciągać wszystkich aktorów do BufferedImage, a następnie do JPanel, ładowanie wszystkich aktorów jako mniejsze BufferedImages jest znacznie szybsze i rysowanie ich jeden po drugim w kodzie JPanel na stronie ich właściwe położenie - w ten sposób nie zachodzi dodatkowy transfer danych między pamięcią główną a pamięcią GPU, z wyjątkiem początkowego transferu obrazów do buforowania.

ImageIcon użyje BufferedImage pod maską - ale w zasadzie kluczem jest przydzielenie BufferedImage z odpowiednim trybem graficznym i nie ma wysiłku, aby zrobić to dobrze.

2) Zwykłym sposobem na zrobienie tego jest narysowanie BufferedImage w przesłoniętej metodzie paintComponent JPanel. Chociaż Java obsługuje wiele dodatkowych dodatków, takich jak łańcuchy buforów kontrolujące VolatileImages buforowane w pamięci GPU, nie ma potrzeby korzystania z żadnego z nich, ponieważ Java 6 wykonuje dość dobrą pracę bez ujawniania wszystkich tych szczegółów przyspieszenia GPU.

Pamiętaj, że przyspieszenie GPU może nie działać w przypadku niektórych operacji, takich jak rozciąganie półprzezroczystych obrazów.

3) Nie dodawaj. Po prostu pomaluj jak wspomniano powyżej:

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

„Dodawanie” ma sens, jeśli obraz jest częścią układu. Jeśli potrzebujesz tego jako obrazu tła lub pierwszego planu wypełniającego JPanel, po prostu narysuj paintComponent. Jeśli wolisz zaparzyć ogólny komponent Swing, który może wyświetlać twój obraz, to jest to ta sama historia (możesz użyć JComponent i zastąpić jego metodę paintComponent) - a następnie dodać to do układu komponentów GUI.

4) Jak przekonwertować tablicę na obraz buforowany

Konwersja tablic bajtów do formatu PNG, a następnie ich załadowanie wymaga dużych zasobów. Lepszym sposobem jest konwersja istniejącej tablicy bajtów na BufferedImage.

W tym celu: nie należy używać do pętli i kopiowania pikseli. To jest bardzo, bardzo wolne. Zamiast:

  • poznaj preferowaną strukturę bajtów BufferedImage (obecnie bezpiecznie jest założyć RGB lub RGBA, czyli 4 bajty na piksel)
  • poznaj używaną linię skanowania i skanuj (np. możesz mieć obraz o szerokości 142 pikseli - ale w rzeczywistości będzie on przechowywany jako tablica bajtów o szerokości 256 pikseli, ponieważ szybsze przetwarzanie i maskowanie nieużywanych pikseli przez sprzęt GPU )
  • następnie po zbudowaniu tablicy zgodnie z tymi zasadami metoda setRGB z tablicy BufferedImage może skopiować tablicę do BufferedImage.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.