Visualizar una Imagen en JavaFX

Son dos clases: Image, que es la imagen en memoria, e ImageView, que es el nodo que la dibuja. Separarlas permite mostrar la misma imagen en varios sitios sin cargarla dos veces.

JavaFX ya no viene con el JDK. Hasta Java 10 estaba dentro; desde Java 11 se distribuye aparte, como OpenJFX. Si copias un ejemplo de hace años y te dice package javafx.application does not exist, no es que esté mal: es que faltan los módulos. Con Maven o Gradle se añaden como dependencia; a mano, se bajan y se pasan en el module path.

El ejemplo completo

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class VerImagen extends Application {

    @Override
    public void start(Stage ventana) {
        Image imagen = new Image("file:duke.png");

        ImageView vista = new ImageView(imagen);
        vista.setFitHeight(220);
        vista.setPreserveRatio(true);
        vista.setSmooth(true);

        Label pie = new Label((int) imagen.getWidth() + " x " + (int) imagen.getHeight() + " px");

        VBox raiz = new VBox(10, vista, pie);
        raiz.setAlignment(Pos.CENTER);
        raiz.setPadding(new Insets(16));

        ventana.setTitle("Ver imagen");
        ventana.setScene(new Scene(raiz, 300, 320));
        ventana.show();
    }

    public static void main(String[] args) { launch(args); }
}
Ventana JavaFX mostrando el logotipo de Duke escalado y, debajo, el texto 111 x 196 px
El resultado: la imagen escalada a 220 píxeles de alto y, debajo, su tamaño real.

De dónde se puede cargar

new Image("file:duke.png");                                  // ruta relativa en el disco
new Image("file:/home/ana/fotos/duke.png");                  // ruta absoluta
new Image("https://decodigo.com/logo.png");                  // por red
new Image(VerImagen.class.getResourceAsStream("/duke.png")); // dentro del jar
La forma de dentro del jar es la que hay que usar en una aplicación de verdad. Una ruta de archivo funciona mientras desarrollas y deja de funcionar en cuanto empaquetas, porque la imagen pasa a estar dentro del .jar y ya no hay tal archivo. Si getResourceAsStream devuelve null, la ruta está mal: tiene que empezar por / y colgar de src/main/resources.

Escalar sin deformar

vista.setFitHeight(220);
vista.setPreserveRatio(true);
vista.setSmooth(true);

Las tres juntas. Si pones setFitWidth y setFitHeight sin preserveRatio, la imagen se estira. Con preserveRatio, el valor que pongas es un máximo y el otro se calcula. Y setSmooth usa un algoritmo mejor al reducir: cuesta un poco más y se nota bastante.

Escalar en el ImageView no ahorra memoria: la imagen sigue cargada a su tamaño original. Para eso se escala al cargar:

Image miniatura = new Image("file:foto-grande.jpg", 200, 0, true, true);
//                                    ancho ↑  alto ↑  ratio ↑  suave ↑

Con el alto a 0 y el ratio a true, calcula el alto solo. Para una galería de miniaturas, la diferencia entre cargar a 200 píxeles o cargar a 4000 es de varios cientos de megabytes.

Que no se congele la ventana

Image imagen = new Image("https://ejemplo.com/foto-grande.jpg", true);   // en segundo plano
vista.setImage(imagen);

imagen.progressProperty().addListener((o, v, n) -> barra.setProgress(n.doubleValue()));
imagen.errorProperty().addListener((o, v, hayError) -> {
    if (hayError) etiqueta.setText("no se pudo cargar");
});

Ese true del final es backgroundLoading: la llamada vuelve en el acto y la imagen se va pintando cuando llega. Sin él, una imagen por red bloquea la interfaz hasta que termine la descarga.

Con backgroundLoading, getWidth() devuelve 0 mientras la imagen no haya llegado, y una carga fallida no lanza ninguna excepción: hay que mirar isError() o suscribirse a errorProperty.
Para…Usa
Mostrarlanew ImageView(imagen)
Que quepa sin deformarsesetFitHeight + setPreserveRatio(true)
Que no ocupe memoria de máspasar el tamaño al constructor de Image
Que no bloqueeel cuarto parámetro backgroundLoading a true
Empaquetarla en el jargetResourceAsStream("/imagen.png")
Recortarlavista.setViewport(new Rectangle2D(x, y, ancho, alto))

Más información en inglés: Display an image in JavaFX.

Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.