英文:
Example JavaFX app to read and write JPEG images to a database
问题
作为学习JavaFX的一部分,我想看到如何将JPEG图像读取并写入数据库,并在JavaFX应用程序中显示它们的最简示例。
这个问题可能类似于另一个问题,在JavaFX中将图像插入到数据库中,但希望更加专注。
我已经在各个地方看到了一些处理图像的代码片段,但我还没有能够将来自OpenJFX的Image
对象转换为可以存储在SQL数据库字段中的内容,比如H2数据库引擎通过JDBC。
英文:
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.
答案1
得分: 2
示例应用程序
尽管我不是JavaFX/OpenJFX的专家,也不是图像编码的专家,但我能够组合出这个粗糙但有效的小应用程序来演示以下内容:
- 从本地文件加载图像。
- 在JavaFX中的
ImageView
领域中显示该图像。 - 将该图像内容写入H2数据库中的字段。
- 显示跟踪每个保存图像的ID的列表(ID为UUID)。
- 从数据库中检索所选图像,并在JavaFX中显示。
我找到了各种编码/解码图像数据的代码片段。我不知道哪些代码可能已经过时。我只知道我让它们工作了。
一些代码使用了Java捆绑的AWT和Swing框架的类。但请注意,此示例的JavaFX应用程序是模块化的,因此您必须在module-info.java
文件中添加元素才能访问这些类。
在此示例中,H2数据库配置为内存数据库。这意味着存储的数据不写入存储。当您的应用程序结束时,数据库及其所有行都会消失,消失了 💨。
此应用程序是使用Java 19和OpenJFX 19编写的。
我通过使用IntelliJ IDE的新项目对话框中内置的JavaFX模板创建了此应用程序。
用于编码图像数据的代码位于两个transform…
方法中。
transformImageIntoInputStream
方法接受一个JavaFX图像对象,使用Swing和AWT创建一个AWT缓冲图像(不要与JavaFX图像混淆)。然后,将该缓冲图像转换为byte
数组,从中获取InputStream
。可以将输入流传递给JDBC方法PreparedStatement :: setBlob
以将BLOB存储在数据库中。
@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( "不应该在方法`transformImageIntoInputStream`中到达这一点。" );
}
要从数据库中检索图像,我们调用ResultSet :: getBlob
来生成一个java.sql.Blob
对象。我们使用javax.imageio.ImageIO
来读取从我们检索到的Blob
对象获得的二进制流。这将产生另一个AWT缓冲图像(再次不要与JavaFX图像混淆)。然后,我们再次使用Swing来从AWT图像生成JavaFX图像。
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 a RuntimeException( e ); }
catch ( SQLException e ) { throw a RuntimeException( e ); }
}
正如前面提到的,我不是图像处理的专家。这似乎是获取图像数据编码/解码的一种迂回方法。我很惊讶只找到了使用AWT和Swing而不是直接使用JavaFX的方法。如果有人知道更好的方法,请发表意见!
源代码
以下是应用程序的完整Java代码,位于一个单独的文件中。
/*
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 );
<details>
<summary>英文:</summary>
# 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`][1] field in JavaFX.
3. Writing that image content to a field in an [H2][2] database.
4. Showing a list of IDs tracking each saved image. (IDs are [UUID][3]s.)
5. Retrieving a selected image from database, and displaying in JavaFX.
[![screenshot of example app showing images displayed while reading and writing to database][4]][4]
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][5] and [Swing][6] frameworks bundled with Java. But be aware that this example JavaFX app is [modularized][7], 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][8]. 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][9] [IDE][10].
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`][11] to store a [BLOB][12] in the database.
```java
@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
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论