Как добавить изображение в JPanel?

У меня есть JPanel, к которому я хотел бы добавить изображения JPEG и PNG, которые я генерирую на лету.

Все примеры, которые я видел до сих пор в учебниках по Swing, особенно в примерах Swing, используют ImageIcon s.

Я генерирую эти изображения в виде байтовых массивов, и они обычно больше, чем общий значок, который они используют в примерах, в 640x480.

  1. Есть ли какая-либо (производительность или другая) проблема в использовании класса ImageIcon для отображения изображения такого размера в JPanel?
  2. Какой обычный способ сделать это?
  3. Как добавить изображение в JPanel без использования класса ImageIcon?

Изменить: более тщательное изучение учебников и API показывает, что вы не можете добавить ImageIcon непосредственно в JPanel. Вместо этого они достигают того же эффекта, устанавливая изображение в виде значка JLabel. Это просто не правильно...

14 ответов

Решение

Вот как я это делаю (немного больше информации о том, как загрузить изображение):

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            
    }

}

Если вы используете JPanels, то, вероятно, работаете с Swing. Попробуй это:

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

Изображение теперь является компонентом свинга. Это становится предметом условий макета, как и любой другой компонент.

Путь Фреда Хаслама работает отлично. У меня возникли проблемы с путем к файлу, так как я хочу сослаться на изображение в моем банке. Для этого я использовал:

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

Поскольку у меня есть только конечное число (около 10) изображений, которые мне нужно загрузить с помощью этого метода, он работает довольно хорошо. Он получает файл без правильного относительного пути к файлу.

Я думаю, что нет необходимости в подклассе чего-либо. Просто используйте Jlabel. Вы можете установить изображение в Jlabel. Итак, измените размер Jlabel, затем заполните его изображением. Все нормально. Это способ, которым я делаю.

Вы можете избежать полного развертывания своего собственного подкласса Component, используя класс JXImagePanel из бесплатных библиотек SwingX.

Скачать

JLabel imgLabel = new JLabel(new ImageIcon("path_to_image.png"));
  1. Не должно быть никаких проблем (кроме общих проблем, которые могут возникнуть у вас с очень большими изображениями).
  2. Если вы говорите о добавлении нескольких изображений на одну панель, я бы использовал ImageIcons. Для одного изображения я бы подумал о создании собственного подкласса JPanel и переопределив его paintComponent способ нарисовать изображение.
  3. (см. 2)

Вы можете создать подкласс JPanel - вот выдержка из моей ImagePanel, которая помещает изображение в любое из 5 мест: верхнее / левое, верхнее / правое, среднее / среднее, нижнее / левое или нижнее / правое:

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); }
    }

Я делаю нечто очень похожее в частном проекте, над которым я работаю. До сих пор я генерировал изображения размером до 1024x1024 без каких-либо проблем (кроме памяти) и могу отображать их очень быстро и без проблем с производительностью.

Переопределение метода рисования в подклассе JPanel является излишним и требует больше работы, чем нужно.

Я делаю это так:

Class MapIcon implements Icon {...}

ИЛИ ЖЕ

Class MapIcon extends ImageIcon {...}

Код, который вы используете для создания изображения, будет в этом классе. Я использую BufferedImage, чтобы рисовать тогда, когда вызывается paintIcon(), используйте g.drawImvge(bufferedImage); Это уменьшает количество перепрошивок, которые вы выполняете во время создания изображений, и вы можете их обработать.

Далее я расширяю JLabel:

Class MapLabel extends Scrollable, MouseMotionListener {...}

Это потому, что я хочу поместить свое изображение в область прокрутки, то есть отобразить часть изображения и заставить пользователя прокручивать его по мере необходимости.

Затем я использую JScrollPane для хранения MapLabel, который содержит только MapIcon.

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

scrollPane.getViewport ().add (mapLabel);

Но для вашего сценария (просто показывайте все изображение каждый раз). Вам необходимо добавить MapLabel в верхнюю JPanel и убедиться, что все их размеры соответствуют полному размеру изображения (переопределяя GetPreferredSize()).

Этот ответ является дополнением к ответу @shawalli...

Я тоже хотел сослаться на изображение в моем банке, но вместо BufferedImage я просто сделал это:

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

JPanel почти всегда неправильный класс для подкласса. Почему бы вам не подкласс JComponent?

Есть небольшая проблема с ImageIcon в этом конструктор блокирует чтение изображения. Не проблема при загрузке из jar приложения, но, возможно, если вы читаете по сетевому соединению. Существует множество примеров использования AWT-эры. MediaTracker, ImageObserver и друзья, даже в демоверсиях JDK.

Создайте исходную папку в каталоге вашего проекта, в данном случае я назвал ее 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();

Вы можете избежать использования собственного ComponentS и SwingX библиотека и ImageIO учебный класс:

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

Я вижу много ответов, на самом деле не обращаясь к трем вопросам ОП.

1) Слово о производительности: байтовые массивы, вероятно, неэффективны, если вы не можете использовать точный порядок байтов в пикселях, который соответствует вашим адаптерам дисплея, текущему разрешению и глубине цвета.

Чтобы добиться максимальной производительности при рисовании, просто преобразуйте изображение в BufferedImage, созданный с типом, соответствующим вашей текущей графической конфигурации. См. CreateCompatibleImage на https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html

Эти изображения будут автоматически кэшироваться в памяти карты дисплея после рисования несколько раз без каких-либо усилий по программированию (это стандартно в Swing начиная с Java 6), и, следовательно, фактическое рисование займет незначительное количество времени - если вы не изменили изображение,

Изменение изображения будет сопровождаться дополнительной передачей памяти между основной памятью и памятью GPU, что является медленным. Избегайте "перерисовки" изображения в BufferedImage, поэтому избегайте использования getPixel и setPixel во всех отношениях.

Например, если вы разрабатываете игру, вместо того, чтобы рисовать всех игровых акторов в BufferedImage, а затем в JPanel, гораздо быстрее загружать всех акторов как меньшие BufferedImages и рисовать их по одному в вашем коде JPanel в их правильное положение - таким образом, нет никакой дополнительной передачи данных между основной памятью и памятью GPU, кроме начальной передачи изображений для кэширования.

ImageIcon будет использовать BufferedImage под капотом - но в основном распределение BufferedImage с надлежащим графическим режимом является ключевым, и нет никаких усилий, чтобы сделать это правильно.

2) Обычный способ сделать это - нарисовать BufferedImage в переопределенном методе paintComponent в JPanel. Хотя Java поддерживает большое количество дополнительных полезных функций, таких как цепочки буферов, управляющие VolatileImages, кешируемыми в памяти графического процессора, нет необходимости использовать ни один из них, поскольку Java 6 работает достаточно хорошо, не раскрывая всех этих деталей ускорения графического процессора.

Обратите внимание, что ускорение графического процессора может не работать для определенных операций, таких как растяжение полупрозрачных изображений.

3) Не добавляйте. Просто нарисуйте, как указано выше:

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

"Добавление" имеет смысл, если изображение является частью макета. Если вам нужно это как фоновое или переднее изображение, заполняющее JPanel, просто нарисуйте paintComponent. Если вы предпочитаете создавать универсальный компонент Swing, который может отображать ваше изображение, то это та же история (вы можете использовать JComponent и переопределить его метод paintComponent) - и затем добавить его в свой макет компонентов GUI.

4) Как преобразовать массив в Bufferedimage

Преобразование ваших байтовых массивов в PNG, а затем их загрузка довольно ресурсоемки. Лучший способ - преобразовать существующий байтовый массив в BufferedImage.

Для этого: не используйте для циклов и копирования пикселей. Это очень, очень медленно. Вместо:

  • узнать предпочитаемую структуру байтов BufferedImage (в настоящее время можно предположить RGB или RGBA, что составляет 4 байта на пиксель)
  • научиться сканировать и сканировать размер при использовании (например, у вас может быть изображение шириной 142 пикселя - но в реальной жизни оно будет храниться в виде байтового массива шириной 256 пикселей, так как это быстрее обрабатывать и маскировать неиспользуемые пиксели с помощью оборудования графического процессора)
  • затем, если у вас есть сборка массива в соответствии с этими принципами, метод массива setRGB объекта BufferedImage может скопировать ваш массив в BufferedImage.
Другие вопросы по тегам