¿Cómo agregar una imagen a un JPanel?

341

Tengo un JPanel al que me gustaría agregar imágenes JPEG y PNG que genero sobre la marcha.

Todos los ejemplos que he visto hasta ahora en los Tutoriales de Swing , especialmente en los ejemplos de Swing, usan ImageIcons.

Estoy generando estas imágenes como conjuntos de bytes, y generalmente son más grandes que el icono común que usan en los ejemplos, a 640x480.

  1. ¿Hay algún problema (de rendimiento u otro) al usar la clase ImageIcon para mostrar una imagen de ese tamaño en un JPanel?
  2. ¿Cuál es la forma habitual de hacerlo?
  3. ¿Cómo agregar una imagen a un JPanel sin usar la clase ImageIcon?

Editar : un examen más cuidadoso de los tutoriales y la API muestra que no puede agregar un ImageIcon directamente a un JPanel. En cambio, logran el mismo efecto al configurar la imagen como un icono de un JLabel. Esto simplemente no se siente bien ...

Leonel
fuente
1
Dependiendo de cómo esté generando los conjuntos de bytes, puede ser más eficiente usar a MemoryImageSourceque convertirlos a formato JPEG o PNG y luego leer con la ImageIOmayoría de las respuestas. Puede obtener una Imagede una MemoryImageSourceconstruida con sus datos de imagen mediante el uso createImagey mostrar como se sugiere en una de las respuestas.
Alden
Comprueba mi respuesta stackoverflow.com/questions/43861991/…
tommybee

Respuestas:

252

Así es como lo hago (con un poco más de información sobre cómo cargar una imagen):

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
fuente
17
-1 para la implementación no válida de paintComponent (@Dogmatixed probablemente sea por eso que tiene esos problemas de redibujado): debe garantizar que cubra su área completa si informa que es opaco (que es el valor predeterminado), lo más fácil se logra llamando a super.paintComponent
kleopatra
55
@kleopatra, gracias, no me di cuenta de eso ... según el javadoc: "Además, si no invoca la implementación de Super, debe respetar la propiedad opaca, es decir, si este componente es opaco, debe completar por completo el fondo en un color no opaco. Si no respeta la propiedad opaca, es probable que vea artefactos visuales ". Actualizaré la respuesta ahora.
Brendan Cashman
8
Siempre respete los Principle of Encapsulationmétodos de anulación de la Superclase, el Especificador de acceso del paintComponent(...)método es protectedy no public:-)
nIcE cOw
1
Esto no es bueno, no hace nada para cambiar el tamaño del panel a la imagen. Tendremos que agregar más código más adelante, lidie con esto. Mucho más simple de usar la respuesta JLabel.
Keilly
2
@ Jonathan: Realmente lo siento, con respecto a alguna fuente para Principle of Encapsulation. Solo recuerde que durante la programación, lo que debe ocultarse del resto del código debe mantenerse de esa manera, en lugar de ser visible para todo el código. Parece que es una cosa que viene con la experiencia, ya que uno aprende cada día. Cualquier libro puede dar una idea básica de qué es la encapsulación, pero es cómo implementamos nuestro código lo que decide cuánto nos adherimos al concepto.
VACA NICE
520

Si está utilizando JPanels, probablemente esté trabajando con Swing. Prueba esto:

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

La imagen ahora es un componente de swing. Está sujeto a condiciones de diseño como cualquier otro componente.

Fred Haslam
fuente
16
¿Cómo escalar la imagen según el tamaño de JLabel?
coding_idiot
1
Buen código! No tengo mucha experiencia con Swing pero no puedo hacer que funcione. ¿Alguien lo intentó en jdk 1.6.0_16?
ATorras
3
@ATorras Sé que lo preguntaste hace un tiempo, pero si algún otro novato tuvo mis problemas, recuerda picLabel.setBounds ();
kyle
¿Tengo que crear un nuevo objeto JLabel cada vez que se recarga una foto?
0x6B6F77616C74
2
@coding_idiot new JLabel (nueva ImageIcon (backgroundImage.getScaledInstance (ancho, alto, Image.SCALE_FAST)));
Davide
37

El camino de Fred Haslam funciona bien. Sin embargo, tuve problemas con la ruta de archivo, ya que quiero hacer referencia a una imagen dentro de mi jar. Para hacer esto, usé:

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

Como solo tengo un número finito (aproximadamente 10) de imágenes que necesito cargar con este método, funciona bastante bien. Obtiene el archivo sin tener que tener la ruta de archivo relativa correcta.

shawalli
fuente
1
Sustituí la primera línea con BufferedImage previewImage = ImageIO.read(new URL(imageURL));para obtener mis imágenes de un servidor.
intcreator
Para referir imágenes de jar, pruebe este stackoverflow.com/a/44844922/3211801
Nandha
33

Creo que no hay necesidad de subclase de nada. Solo usa un Jlabel. Puede configurar una imagen en una Jlabel. Entonces, cambie el tamaño de Jlabel y luego llénelo con una imagen. Está bien. Así es como lo hago.

CoderTim
fuente
20

Puede evitar rodar su propia subclase de componentes por completo utilizando la clase JXImagePanel de las bibliotecas gratuitas de SwingX .

Descargar

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

fuente
8

Puede subclasificar JPanel: aquí hay un extracto de mi ImagePanel, que coloca una imagen en cualquiera de las 5 ubicaciones, arriba / izquierda, arriba / derecha, medio / medio, abajo / izquierda o abajo / derecha:

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
fuente
8
  1. No debería haber ningún problema (aparte de cualquier problema general que pueda tener con imágenes muy grandes).
  2. Si está hablando de agregar varias imágenes a un solo panel, usaría ImageIcons. Para una sola imagen, pensaría en hacer una subclase personalizada JPanely anular su paintComponentmétodo para dibujar la imagen.
  3. (ver 2)
Michael Myers
fuente
6

JPanelcasi siempre es la clase incorrecta para subclase. ¿Por qué no subclase JComponent?

Hay un pequeño problema con ImageIconque el constructor bloquea la lectura de la imagen. No es realmente un problema al cargar desde el archivo jar de la aplicación, pero tal vez si estás leyendo potencialmente a través de una conexión de red. Hay un montón de ejemplos de AWT de la era de la utilización MediaTracker, ImageObservery amigos, incluso en las demostraciones de JDK.

Tom Hawtin - tackline
fuente
6

Estoy haciendo algo muy similar en un proyecto privado en el que estoy trabajando. Hasta ahora he generado imágenes de hasta 1024x1024 sin ningún problema (excepto la memoria) y puedo mostrarlas muy rápidamente y sin ningún problema de rendimiento.

Anular el método de pintura de la subclase JPanel es excesivo y requiere más trabajo del que necesita hacer.

La forma en que lo hago es:

Class MapIcon implements Icon {...}

O

Class MapIcon extends ImageIcon {...}

El código que use para generar la imagen estará en esta clase. Utilizo una BufferedImage para dibujar cuando se llama a paintIcon (), uso g.drawImvge (bufferedImage); Esto reduce la cantidad de flasheo realizado mientras genera sus imágenes y puede enhebrarlo.

A continuación extiendo JLabel:

Class MapLabel extends Scrollable, MouseMotionListener {...}

Esto se debe a que quiero poner mi imagen en un panel de desplazamiento, es decir, mostrar parte de la imagen y hacer que el usuario se desplace según sea necesario.

Entonces uso un JScrollPane para contener MapLabel, que contiene solo MapIcon.

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

scrollPane.getViewport ().add (mapLabel);

Pero para su escenario (solo muestre la imagen completa cada vez). Debe agregar MapLabel al JPanel superior y asegurarse de ajustarlos a todo el tamaño de la imagen (anulando GetPreferredSize ()).

Thomas Jones-Low
fuente
5

Cree una carpeta de origen en el directorio de su proyecto, en este caso lo llamé Imágenes.

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

fuente
5

Puede evitar usar su propia Componentbiblioteca y ImageIOclase de SwingX :

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

Esta respuesta es un complemento de la respuesta de @ shawalli ...

También quería hacer referencia a una imagen dentro de mi jar, pero en lugar de tener una BufferedImage, simplemente hice esto:

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

Puedo ver muchas respuestas, sin abordar realmente las tres preguntas del PO.

1) Una palabra sobre el rendimiento: las matrices de bytes son probablemente poco eficientes a menos que pueda usar un orden de bytes de píxeles exacto que coincida con la resolución actual y la profundidad de color de sus adaptadores de pantalla.

Para lograr el mejor rendimiento de dibujo, simplemente convierta su imagen en una Imagen Buffered que se genera con un tipo correspondiente a su configuración gráfica actual. Consulte createCompatibleImage en https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html

Estas imágenes se almacenarán en caché automáticamente en la memoria de la tarjeta de visualización después de dibujar varias veces sin ningún esfuerzo de programación (esto es estándar en Swing desde Java 6) y, por lo tanto, el dibujo real tomará una cantidad de tiempo insignificante, si no cambió la imagen .

La alteración de la imagen vendrá con una transferencia de memoria adicional entre la memoria principal y la memoria de la GPU, que es lenta. Evite "volver a dibujar" la imagen en una BufferedImage, por lo tanto, evite hacer getPixel y setPixel por todos los medios.

Por ejemplo, si está desarrollando un juego, en lugar de atraer a todos los actores del juego a una BufferedImage y luego a un JPanel, es mucho más rápido cargar a todos los actores como BufferedImages más pequeños y dibujarlos uno por uno en su código JPanel en su posición correcta: de esta manera no hay transferencia de datos adicional entre la memoria principal y la memoria de la GPU, excepto la transferencia inicial de las imágenes para el almacenamiento en caché.

ImageIcon usará una BufferedImage debajo del capó, pero básicamente la asignación de una BufferedImage con el modo gráfico adecuado es la clave, y no hay ningún esfuerzo para hacerlo correctamente.

2) La forma habitual de hacer esto es dibujar una BufferedImage en un método anulado de paintComponent del JPanel. Aunque Java admite una buena cantidad de beneficios adicionales, como cadenas de búfer que controlan VolatileImages en caché en la memoria de la GPU, no hay necesidad de usar ninguno de estos, ya que Java 6 hace un trabajo razonablemente bueno sin exponer todos estos detalles de la aceleración de la GPU.

Tenga en cuenta que la aceleración de GPU puede no funcionar para ciertas operaciones, como estirar imágenes translúcidas.

3) No agregar. Solo píntalo como se mencionó anteriormente:

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

"Agregar" tiene sentido si la imagen es parte del diseño. Si necesita esto como una imagen de fondo o primer plano que llena el JPanel, simplemente dibuje paintComponent. Si prefiere preparar un componente Swing genérico que pueda mostrar su imagen, entonces es la misma historia (puede usar un JComponent y anular su método paintComponent), y luego agregar esto a su diseño de componentes GUI.

4) Cómo convertir la matriz a una imagen almacenada en búfer

La conversión de sus conjuntos de bytes a PNG y luego su carga requieren muchos recursos. Una mejor manera es convertir su matriz de bytes existente a una imagen almacenada.

Para eso: no lo use para bucles ni copie píxeles. Eso es muy muy lento. En lugar:

  • aprenda la estructura de bytes preferida de BufferedImage (hoy en día es seguro asumir RGB o RGBA, que es de 4 bytes por píxel)
  • aprenda la línea de escaneo y el tamaño de escaneo en uso (por ejemplo, puede tener una imagen de 142 píxeles de ancho, pero en la vida real se almacenará como una matriz de bytes de 256 píxeles de ancho, ya que es más rápido procesar eso y enmascarar los píxeles no utilizados por el hardware de la GPU )
  • luego, una vez que haya creado una matriz de acuerdo con estos principios, el método de matriz setRGB de BufferedImage puede copiar su matriz a BufferedImage.
Gee Bee
fuente