2

As part of learning JavaFX, I would like to see a minimal example of how to read & write JPEG images to a database while displaying them in a JavaFX app.

This Question may be similar to this other Question, insert an image to a database with javafx, but hopefully more focused.

I have seen bits of code here and there for processing images, but I have not been able to convert an image from a OpenJFX Image object to something that can be stored in a field in a SQL database such as H2 Database Engine via JDBC.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154

1 Answers1

2

Example app

While I am no expert on JavaFX/OpenJFX, nor am I an expert on image encoding, I was able to cobble together this crude but effective little app to demonstrate:

  1. Loading an image from a local file.
  2. Displaying that image in an ImageView field in JavaFX.
  3. Writing that image content to a field in an H2 database.
  4. Showing a list of IDs tracking each saved image. (IDs are UUIDs.)
  5. Retrieving a selected image from database, and displaying in JavaFX.

screenshot of example app showing images displayed while reading and writing to database

I found various chunks of code for encoding/decoding image data. I do not know what chunks may be old-fashioned or outmoded. All I know is that I got them to work.

Some used classes from the AWT and Swing frameworks bundled with Java. But be aware that this example JavaFX app is modularized, so you must add elements to your module-info.java file to access those classes.

In this example, the H2 database is configured to be an in-memory database. This means the stored data is not written to storage. When your app ends, the database and all its rows disappear, poof .

This app was written in Java 19 with OpenJFX 19.

I starting this app by creating a project using the JavaFX template built into the New Project dialog box of the IntelliJ IDE.

The code for encoding the image data is found in the two transform… methods.

The transformImageIntoInputStream method takes a JavaFX image object, uses Swing & AWT to create an AWT buffered image (not to be confused with JavaFX image). That buffered image is then converted to an array of byte, from which we get an InputStream. The input stream can be passed to the JDBC method PreparedStatement :: setBlob to store a BLOB in the database.

    @SuppressWarnings ( "UnnecessaryLocalVariable" )
    private InputStream transformImageIntoInputStream ( final Image image )
    {
        BufferedImage awtBufferedImage = SwingFXUtils.fromFXImage( image , null );
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try
        {
            ImageIO.write( awtBufferedImage , "jpg" , outputStream );
            byte[] res = outputStream.toByteArray();
            InputStream inputStream = new ByteArrayInputStream( res );
            return inputStream;
        }
        catch ( IOException e ) { e.printStackTrace(); }
        throw new IllegalStateException( "Should not reach this point in method `transformImageIntoInputStream`." );
    }

To retrieve from the database, we call ResultSet :: getBlob to produce a java.sql.Blob object. We use javax.imageio.ImageIO to read a binary stream obtained from that Blob object we retrieved. That produces another AWT buffered image (again, not to be confused with a JavaFX image). We then make use of Swing again to produce a JavaFX image from that AWT image.

    private Image transformBlobIntoImage ( final Blob blob )
    {
        try
        {
            BufferedImage awtBufferedImage = ImageIO.read( blob.getBinaryStream() );
            Image image = SwingFXUtils.toFXImage( awtBufferedImage , null );
            return image;
        }
        catch ( IOException e ) { throw new RuntimeException( e ); }
        catch ( SQLException e ) { throw new RuntimeException( e ); }
    }

As noted above, I am no expert on image handling. This sure seems a roundabout way to go to get image data encoded/decoded. And I am surprised to have found only approaches using AWT and Swing rather than straight JavaFX. If anyone knows better approaches, please do post!

Source Code

Here is the entire Java code for the app, in a single file.

/*
 By Basil Bourque.  http://www.Basil.work/

 This code is a response to this Question on Stack Overflow:
 > insert an image to a database with javafx
 https://stackoverflow.com/q/75632865/642706

 This code was cobbled together quickly, as a starting point
 for further learning. Not meant for production use.
 Caveat: I an not an expert in JavaFX nor in image encoding.
 Use at your own risk.

 This demo app opens a window with five items, running left to right:
 • A button to load JPEG files from your local storage.
 • An `ImageView` field to show the image you loaded. Defaults to the Java mascot "Duke", loaded over the internet.
 • A button to save the loaded image to an in-memory database using the H2 Database Engine (written in pure Java).
 • A list of the UUIDs that each identify an image stored in the database.
 • Another `ImageView` field to show the image retrieved from the database per the UUID selected in the list.
*/
package work.basil.example.fximage;

import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

import javax.imageio.ImageIO;
import javax.sql.DataSource;
import java.awt.image.BufferedImage;
import java.io.*;
import java.sql.*;
import java.util.Objects;
import java.util.UUID;

@SuppressWarnings ( "SqlNoDataSourceInspection" )
public class App extends Application
{
    @Override
    public void start ( final Stage stage )
    {
        // Widgets  ---------------------------------------
        Button loadFromDesktopButton = new Button( "Open JPEG file…" );

        String url = "https://www.oracle.com/a/ocom/img/rc24-duke-java-mascot.jpg";  // Arbitrary example image.
        Image duke = new Image( url );
        ImageView desktopImageView = new ImageView();
        desktopImageView.setImage( duke );

        desktopImageView.setFitWidth( 300 );
        desktopImageView.setPreserveRatio( true );
        desktopImageView.setSmooth( true );
        desktopImageView.setCache( true );

        Button storeInDatabaseButton = new Button( "Store in database" );

        ListView < UUID > listView = new ListView <>();
        listView.setPrefWidth( 300 );
        ObservableList < UUID > ids = FXCollections.observableArrayList();
        listView.setItems( ids );

        ImageView databaseImageView = new ImageView();
        databaseImageView.setFitWidth( 300 );
        databaseImageView.setPreserveRatio( true );
        databaseImageView.setSmooth( true );
        databaseImageView.setCache( true );

        // Behavior  --------------------------------------
        DataSource dataSource = this.configureDataSource();
        this.configureDatabase( dataSource );

        loadFromDesktopButton.setOnAction(
                ( ActionEvent event ) -> this.loadImageFromLocalStorage( stage , desktopImageView )
        );
        storeInDatabaseButton.setOnAction(
                ( ActionEvent event ) -> {
                    Image image = desktopImageView.getImage();
                    UUID id = this.storeImageInDatabase( image , dataSource );
                    ids.add( id );  // Add a list item for the image we just stored in database.
                    listView.getSelectionModel().select( id ); // Select the newly added list item.
                }
        );

        listView.getSelectionModel().selectedItemProperty().addListener(
                ( ObservableValue < ? extends UUID > observableValue , UUID oldValue , UUID newValue ) -> {
                    Image image = this.fetchImageFromDatabase( newValue , dataSource );
                    databaseImageView.setImage( image );
                }
        );

        // Layout  --------------------------------------
        stage.setTitle( "Image To Database" );
        HBox root = new HBox();
        root.setPadding( new Insets( 15 , 12 , 15 , 12 ) );
        root.setSpacing( 10 );
        root.setStyle( "-fx-background-color: Grey;" );

        // Arrange  --------------------------------------
        root.getChildren().add( loadFromDesktopButton );
        root.getChildren().add( desktopImageView );
        root.getChildren().add( storeInDatabaseButton );
        root.getChildren().add( listView );
        root.getChildren().add( databaseImageView );

        Scene scene = new Scene( root , 1_250 , 300 );
        stage.setScene( scene );
        stage.show();
    }


    // Subroutines  --------------------------------------
    private void loadImageFromLocalStorage ( final Stage stage , final ImageView imageView )
    {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle( "Open JPEG file" );
        File file = fileChooser.showOpenDialog( stage );
        if ( file != null )
        {
            System.out.println( file );
            Image image = new Image( file.toURI().toString() );
            imageView.setImage( image );
        }
    }

    private DataSource configureDataSource ( )
    {
        org.h2.jdbcx.JdbcDataSource ds = new org.h2.jdbcx.JdbcDataSource();  // Implementation of `DataSource` bundled with H2. You may choose to use some other implementation.
        ds.setURL( "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;" );
        ds.setUser( "scott" );
        ds.setPassword( "tiger" );
        ds.setDescription( "An example database for storing an image." );
        return ds;
    }

    private void configureDatabase ( final DataSource dataSource )
    {
        // Create table.
        String sql =
                """
                CREATE TABLE IF NOT EXISTS image_
                (
                    id_ uuid NOT NULL ,
                    CONSTRAINT image_pkey_ PRIMARY KEY ( id_ ) ,
                    row_created_ TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP() ,
                    content_ BLOB
                )
                ;
                """;
        try (
                Connection conn = dataSource.getConnection() ;
                Statement stmt = conn.createStatement()
        )
        {
            System.out.println( "INFO - Running `configureDatabase` method." );
            stmt.executeUpdate( sql );
        }
        catch ( SQLException e ) { e.printStackTrace(); }
    }


    private UUID storeImageInDatabase ( final Image image , final DataSource dataSource )
    {
        InputStream inputStream = this.transformImageIntoInputStream( Objects.requireNonNull( image ) );

        // Write to database
        String sql =
                """
                INSERT INTO image_ ( id_ , content_ )
                VALUES ( ? , ? )
                ;
                """;
        try
                (
                        Connection conn = dataSource.getConnection() ;
                        PreparedStatement preparedStatement = conn.prepareStatement( sql )
                )
        {
            UUID id = UUID.randomUUID(); // In real work, we would retrieve the value generated by the database rather the app.
            preparedStatement.setObject( 1 , id );
            preparedStatement.setBlob( 2 , inputStream );
            int rowCount = preparedStatement.executeUpdate();
            if ( rowCount != 1 ) { throw new IllegalStateException( "SQL insert failed to affect any rows." ); }
            return id;
        }
        catch ( SQLException e ) { e.printStackTrace(); }
        throw new IllegalStateException( "Should never have reached this point in `storeImageInDatabase` method. " );
    }

    private Image fetchImageFromDatabase ( final UUID id , final DataSource dataSource )
    {
        byte[] bytes = null;
        String sql =
                """
                SELECT *
                FROM image_
                WHERE id_ = ?
                ;
                """;
        try
                (
                        Connection conn = dataSource.getConnection() ;
                        PreparedStatement preparedStatement = conn.prepareStatement( sql )
                )
        {
            System.out.println( "DEBUG id = " + id );
            preparedStatement.setObject( 1 , id );
            try (
                    ResultSet resultSet = preparedStatement.executeQuery() ;
            )
            {
                if ( resultSet.next() )
                {
                    System.out.println( "DEBUG resultSet = " + resultSet );
                    Blob blob = resultSet.getBlob( "content_" );
                    System.out.println( "DEBUG blob = " + blob );
                    Image image = this.transformBlobIntoImage( blob );
                    return image;
                }
                else { throw new IllegalStateException( "Failed to find any rows for id: " + id ); }
            }
        }
        catch ( SQLException e ) { e.printStackTrace(); }
        throw new IllegalStateException( "Should never have reached this point in `storeImageInDatabase` method. " );
    }


    @SuppressWarnings ( "UnnecessaryLocalVariable" )
    private InputStream transformImageIntoInputStream ( final Image image )
    {
        BufferedImage awtBufferedImage = SwingFXUtils.fromFXImage( image , null );
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try
        {
            ImageIO.write( awtBufferedImage , "jpg" , outputStream );
            byte[] res = outputStream.toByteArray();
            InputStream inputStream = new ByteArrayInputStream( res );
            return inputStream;
        }
        catch ( IOException e ) { e.printStackTrace(); }
        throw new IllegalStateException( "Should not reach this point in method `transformImageIntoInputStream`." );
    }

    private Image transformBlobIntoImage ( final Blob blob )
    {
        try
        {
            BufferedImage awtBufferedImage = ImageIO.read( blob.getBinaryStream() );
            Image image = SwingFXUtils.toFXImage( awtBufferedImage , null );
            return image;
        }
        catch ( IOException e ) { throw new RuntimeException( e ); }
        catch ( SQLException e ) { throw new RuntimeException( e ); }
    }

    public static void main ( final String[] args )
    {
        launch();
    }
}

Here is the module-info.java file.

module work.basil.example.fximage {
    requires javafx.controls;  // JavaFX/OpenJFX API.

    requires java.desktop;     // Needed for classes in AWT that convert image to octets.
    requires javafx.swing;     // Needed for `javafx.embed.swing.SwingFXUtils` class to convert image to octets.

    requires java.sql;         // JDBC API.
    requires java.naming;      // Java Naming and Directory Interface (JNDI) API. Needed for `DataSource` in JDBC.
    requires com.h2database;   // H2 Database Engine.

    exports work.basil.example.fximage;
}

And my Apache Maven POM file.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns = "http://maven.apache.org/POM/4.0.0"
         xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>work.basil.example</groupId>
    <artifactId>FxImageToDb</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>FxImage</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-controls -->
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>19.0.2.1</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-swing -->
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-swing</artifactId>
            <version>19.0.2.1</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.9.2</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.1.214</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
                <configuration>
                    <source>19</source>
                    <target>19</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.8</version>
                <executions>
                    <execution>
                        <!-- Default configuration for running with: mvn clean javafx:run -->
                        <id>default-cli</id>
                        <configuration>
                            <mainClass>work.basil.example.fximage/work.basil.example.fximage.App</mainClass>
                            <launcher>app</launcher>
                            <jlinkZipName>app</jlinkZipName>
                            <jlinkImageName>app</jlinkImageName>
                            <noManPages>true</noManPages>
                            <stripDebug>true</stripDebug>
                            <noHeaderFiles>true</noHeaderFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Resources

Convert Blob to JPG and update blob

Java BLOB to image file

Auto-generate lambda expression as argument in IntelliJ

JavaFX select item in ListView

Using JavaFX UI Controls — 11 List View

Using JavaFX UI Controls — 26 File Chooser

How can I show an image using the ImageView component in javafx and fxml?

H2 Database Engine documentation for BLOB type and for discussion of the Large Object types.

AWT & Swing classes: SwingFXUtils

How to convert a JavaFX ImageView to InputStream

Saving an image in MySQL from Java

How to save an image from HTML to a DATABASE using JAVA

Storing Image in Data Base Using Java in Binary Format

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • One thing to note is that `ImageIO.write( awtBufferedImage , "jpg" , outputStream );` is a lossy conversion. So each time you save the image, it loses some fidelity. For most apps, where you just save the image once, it won't be an issue. But for an image editing app, where you might load and save the same image with small modifications multiple times, you might want to use a lossless format, e.g. png. – jewelsea Mar 07 '23 at 00:04
  • JavaFX does have it's own [imageio](https://github.com/openjdk/jfx/tree/86b854dc367fb32743810716da5583f7d59208f8/modules/javafx.graphics/src/main/java/com/sun/javafx/iio) implementation, parts of which (jpeg loading) are [native code](https://github.com/openjdk/jfx/tree/master/modules/javafx.graphics/src/main/native-iio/libjpeg), but the interfaces only support loading not writing and the only public interface is the Image class (everything else is private implementation), – jewelsea Mar 07 '23 at 00:19
  • So the `javax.imageio.ImageIO` solution you used is the only realistic solution I know of. Unless you just save the image byte buffer retrived from a PixelReader or you encode those bytes using some other third party image library. – jewelsea Mar 07 '23 at 00:20