FX - Image をバックグラウンドでロードする
デフォルトは同期的にロードされる
Image
のインスタンスは、普通に生成すると画像のロードが同期的に行われる(ロードが終了するまで処理は返ってこない)。
画像が小さい場合は特に問題はないが、大きいサイズの画像を表示する場合は処理がしばらく停止してしまうので、操作感が損なわれる恐れがある。
バックグラウンドでロードする
Image
インスタンスでの画像のロードをバックグラウンドで行うには、コンストラクタ引数の backgroundLoading
に true
を指定する。
backgroundLoading
を指定できるコンストラクタは、次の2つがある。
backgroundLoading
に true
を指定してインスタンスを生成すると、画像は非同期でロードされる。
これにより、処理は止まることなく次に進むことができるようになる。
ロードの進捗を表示する
ただ単純にロードをバックグラウンドにしただけだと、読み込みが完了するまで画像が表示されない状態が続く。
サイズが大きくロードに時間がかかる場合、画像が何も表示されないというのはユーザを不安にさせてしまうかもしれない。
そこで、ロードがどれくらい進んでいるか、進捗をプログレスバーなどで表示すると良いかもしれない。
Image
クラスは、ロードの進捗を知ることができる progress プロパティを提供している。
これを利用すれば、わりと簡単にロードの進捗を表示できる。
実装
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane style="-fx-padding: 10px;" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.javafx.MainController">
<center>
<VBox alignment="CENTER" minHeight="0.0" minWidth="0.0" BorderPane.alignment="CENTER">
<children>
<ProgressBar fx:id="progressBar" maxWidth="1.7976931348623157E308" progress="0.0" />
<ImageView fx:id="imageView" fitHeight="200.0" fitWidth="300.0" pickOnBounds="true" preserveRatio="true" />
</children>
</VBox>
</center>
</BorderPane>
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ProgressBar;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private ProgressBar progressBar;
@FXML
private ImageView imageView;
public void initStage(Stage stage) {
stage.setWidth(500);
stage.setHeight(400);
}
@Override
public void initialize(URL location, ResourceBundle resources) {
progressBar.managedProperty().bind(progressBar.visibleProperty());
progressBar.visibleProperty().bind(progressBar.progressProperty().lessThan(1));
imageView.managedProperty().bind(imageView.visibleProperty());
imageView.visibleProperty().bind(progressBar.progressProperty().isEqualTo(1));
Image image = new Image(Paths.get("./image/shirakawago.jpg").toUri().toString(), true);
progressBar.progressProperty().bind(image.progressProperty());
imageView.setImage(image);
}
}
実行結果
ちょっと早くてわかりづらいが、画像をロードしている間はプログレスバーが表示され、ロードが完了すると画像が表示されている。
説明
- 画像表示のための
ImageView
のとなりにProgressBar
を追加している
progressBar.managedProperty().bind(progressBar.visibleProperty());
progressBar.visibleProperty().bind(progressBar.progressProperty().lessThan(1));
imageView.managedProperty().bind(imageView.visibleProperty());
imageView.visibleProperty().bind(progressBar.progressProperty().isEqualTo(1));
- ロード中はプログレスバーのみを表示しロードが完了したら画像だけを表示するため、それぞれの
visible
プロパティを制御している -
visible
プロパティの制御は、プログレスバーのprogress
プロパティの値にバインドすることで実現している- プログレスバーは、
progress
が1
より小さいときだけ表示 - 画像は、
progress
が1
のときだけ表示、としている
- プログレスバーは、
Image image = new Image(Paths.get("./image/shirakawago.jpg").toUri().toString(), true);
progressBar.progressProperty().bind(image.progressProperty());
-
Image
を生成するときのコンストラクタ引数で、backgroundLoading
にtrue
を指定 -
Image
のprogress
プロパティをプログレスバーのprogress
プロパティにバインドしている
あらかじめロードしておく
メリット
バックグラウンドでのロードは、現在表示しようとしている画像だけでなく、次に表示する予定の画像をあらかじめロードしておくといった手段でも利用できる。
この場合、 Image
インスタンスだけを裏で生成しておき、画像を切り替えるときに ImageView
の setImage()
で Image
インスタンスを差し替えるように実装する。
@FXML
private ImageView imageView;
private Image nextImage;
public void initialize(URL location, ResourceBundle resources) {
// バックグラウンドで次の画像をロードしておく
nextImage = new Image("next-image.jpg", true);
Image initialImage = new Image("initial-image.jpg", true);
imageView.setImage(initialImage);
}
@FXML
public void nextPage() {
// ロードしておいた次の画像をセットする
imageView.setImage(nextImage);
}
重い画像でも、あらかじめロードしておくことで素早く表示が実現できるようになり、操作感の向上につながるかもしれない。
メモリ使用量
事前にロードをしておくことで表示速度は向上するかもしれないが、その代わりメモリの消費は増えることに注意しなければならない。
特に、画像はディスク上でのサイズとメモリ上にロードしたときのサイズには大きな差があることに気を付ける必要がある。
ディスク上では、例えば JPEG 画像の場合は大幅に圧縮されたサイズになっているのに対して、 Image
でメモリ上にロードした場合は非圧縮状態になっている。
実際にどれくらい違うが調べてみる。
こちらがディスク上でのサイズで、およそ 6.5 MB ある。
この画像を Image
でロードしてから、ヒープダンプを取って Image
がロードした画像がどれくらいメモリを使用しているか調べてみる。
ヒープダンプの調査には Eclipse Memory Analyzer を使用した。
Image
の中の platformImage
がそれっぽいので、その中を見に行く。
com.sum.prism.Image
がそれっぽいので、その中を見に行く。
width
, height
に画像のピクセルサイズが格納されている。
さらに、 pixelBuffer
が画像データをバイト配列で格納しているっぽいので、その中を見に行く。
ビンゴっぽい。
capacity
が 36636672
となっている(約 35 MB)。
調べてみると hb
というのが byte
の配列になっていて、画像データが全て格納されているっぽい。
この 36,636,672
というサイズは、ちょうど画像の「縦×横×3」のサイズに一致している。
(4,288 * 2,848 = 12,212,224
, 12,212,224 * 3 = 36,636,672
)
つまり、全ピクセルの RGB (3byte) 分のデータが全てロードされているということなのだろう。
ディスク上でのサイズが約 6.5 MB なのに対して、メモリ上にロードしたときは 35 MB とかなり大きくなっている。
元画像の圧縮形式や、アルファチャンネル(透明度)の有無によってはディスク上とメモリ上でのサイズの差は色々変わると思う。
サイズを小さくしてロードする
Image
は画像のサイズを指定してロードができるようになっている。
これを利用すれば、オリジナルよりも小さいサイズで画像をロードできるので、メモリ消費量を抑えることができる。
ただし、小さいサイズでロードすることになるので、元画像と同じ品質では表示できないことに注意。
まとめ
バックグラウンドでのロードは表示速度の向上につながるかもしれないが、事前ロードによるキャッシュ利用をする場合はメモリの消費量にも注意したほうが良さげ。
Author And Source
この問題について(FX - Image をバックグラウンドでロードする), 我々は、より多くの情報をここで見つけました https://qiita.com/opengl-8080/items/f7b4080824186b3f3a53著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .