10 イメージOps APIの使用
この章では、JavaFXアプリケーション内でロー・ピクセルを読み書きできるようにするAPIである、イメージOpsについて説明します。
イメージからピクセルを読み取る方法、イメージにピクセルを書き込む方法およびスナップショットを作成する方法を学習します。
イメージOps APIの概要
イメージOps APIは、javafx.scene.imageパッケージの次のクラスおよびインタフェースで構成されています。
-
Image: グラフィック・イメージを表します。 このクラスによって、イメージからピクセルを直接読み取るためのPixelReaderが提供されます。 -
WritableImage:Imageのサブクラス。 このクラスによって、イメージにピクセルを直接書き込むためのPixelWriterが提供されます。WritableImageは、そこにピクセルを書き込むまでは、最初は空で(透過的に)作成されます。 -
PixelReader: ピクセルを含むイメージまたはその他の面からピクセル・データを取得するための方法を定義するインタフェース。 -
PixelWriter:WritableImageまたは書込み可能なピクセルを含むその他の面にピクセル・データを書き込むための方法を定義するインタフェース。 -
PixelFormat: 特定のフォーマットのピクセルについてデータのレイアウトを定義します。 -
WritablePixelFormat:PixelFormatのサブクラスであり、フル・カラーを格納できるピクセル・フォーマットを表します。 これは、任意のイメージからピクセル・データを書き込むための宛先フォーマットとして使用できます。
次の項では、コンパイルして実行できるコード例を使用して、このAPIについて説明します。
イメージからのピクセルの読取り
イメージを表示するJavaFXアプリケーションで(ImageViewとともに)使用されるjavafx.scene.image.Imageクラスについては、すでに理解している場合があります。 次の例では、JavaFXのロゴをoracle.comからロードしてJavaFX Sceneグラフに追加することによって、イメージを表示する方法を示します。
例10-1 イメージのロードおよび表示
package imageopstest;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class ImageOpsTest extends Application {
@Override
public void start(Stage primaryStage) {
// Create Image and ImageView objects
Image image = new Image("http://docs.oracle.com/javafx/"
+ "javafx/images/javafx-documentation.png");
ImageView imageView = new ImageView();
imageView.setImage(image);
// Display image on screen
StackPane root = new StackPane();
root.getChildren().add(imageView);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Image Read Test");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
このプログラムを実行すると、図10-1に示すイメージが生成されます。
ここで、Color情報をピクセルから直接読み取るように、このコードを変更します。 これを実行するには、getPixelReader()メソッドを呼び出し、返されたPixelReaderオブジェクトのgetColor(x,y)メソッドを使用して、指定した座標のピクセルの色を取得します。
例10-2 ピクセルからの色情報の読取り
package imageopstest;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.paint.Color;
public class ImageOpsTest extends Application {
@Override
public void start(Stage primaryStage) {
// Create Image and ImageView objects
Image image = new Image("http://docs.oracle.com/javafx/"
+ "javafx/images/javafx-documentation.png");
ImageView imageView = new ImageView();
imageView.setImage(image);
// Obtain PixelReader
PixelReader pixelReader = image.getPixelReader();
System.out.println("Image Width: "+image.getWidth());
System.out.println("Image Height: "+image.getHeight());
System.out.println("Pixel Format: "+pixelReader.getPixelFormat());
// Determine the color of each pixel in the image
for (int readY = 0; readY < image.getHeight(); readY++) {
for (int readX = 0; readX < image.getWidth(); readX++) {
Color color = pixelReader.getColor(readX, readY);
System.out.println("\nPixel color at coordinates ("
+ readX + "," + readY + ") "
+ color.toString());
System.out.println("R = " + color.getRed());
System.out.println("G = " + color.getGreen());
System.out.println("B = " + color.getBlue());
System.out.println("Opacity = " + color.getOpacity());
System.out.println("Saturation = " + color.getSaturation());
}
}
// Display image on screen
StackPane root = new StackPane();
root.getChildren().add(imageView);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Image Read Test");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
このバージョンでは、イメージのすべてのピクセルから色情報を取得するために、(getColorメソッドを呼び出す)ネストされたforループが使用されます。 ピクセルは、左上隅(0,0)から始まって、イメージを左から右へと1つずつ読み取られます。 Y座標は、行全体が読み取られた後にのみ増分されます。 各ピクセルに関する情報(色の値、不透明度、彩度の値など)が標準出力に出力され、読取り操作が正しく機能していることが示されます。
... // beginning of output omitted
Pixel color at coordinates (117,27) 0x95a7b4ff
R = 0.5843137502670288
G = 0.6549019813537598
B = 0.7058823704719543
Opacity = 1.0
Saturation = 0.17222220767979304
Pixel color at coordinates (118,27) 0x2d5169ff
R = 0.1764705926179886
G = 0.3176470696926117
B = 0.4117647111415863
Opacity = 1.0
Saturation = 0.5714285662587809
... // remainder of output omitted
各ピクセルの色を変更し、それを画面に書き込むことを試したい場合があります。 しかし、イメージ・オブジェクトは読取り専用です。この場合は、かわりにWritableImageのインスタンスが必要です。
イメージへのピクセルの書込み
ここで、各ピクセルが明るくなるようにこのデモを変更し、変更した結果をWritableImageオブジェクトに書き込みます。
例10-3 WritableImageへの書込み
package imageopstest;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.paint.Color;
import javafx.scene.image.WritableImage;
public class ImageOpsTest extends Application {
@Override
public void start(Stage primaryStage) {
// Create Image and ImageView objects
Image image = new Image("http://docs.oracle.com/javafx/"
+ "javafx/images/javafx-documentation.png");
ImageView imageView = new ImageView();
imageView.setImage(image);
// Obtain PixelReader
PixelReader pixelReader = image.getPixelReader();
System.out.println("Image Width: "+image.getWidth());
System.out.println("Image Height: "+image.getHeight());
System.out.println("Pixel Format: "+pixelReader.getPixelFormat());
// Create WritableImage
WritableImage wImage = new WritableImage(
(int)image.getWidth(),
(int)image.getHeight());
PixelWriter pixelWriter = wImage.getPixelWriter();
// Determine the color of each pixel in a specified row
for(int readY=0;readY<image.getHeight();readY++){
for(int readX=0; readX<image.getWidth();readX++){
Color color = pixelReader.getColor(readX,readY);
System.out.println("\nPixel color at coordinates ("+
readX+","+readY+") "
+color.toString());
System.out.println("R = "+color.getRed());
System.out.println("G = "+color.getGreen());
System.out.println("B = "+color.getBlue());
System.out.println("Opacity = "+color.getOpacity());
System.out.println("Saturation = "+color.getSaturation());
// Now write a brighter color to the PixelWriter.
color = color.brighter();
pixelWriter.setColor(readX,readY,color);
}
}
// Display image on screen
imageView.setImage(wImage);
StackPane root = new StackPane();
root.getChildren().add(imageView);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Image Write Test");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
このバージョンでは、JavaFXのロゴと同じ幅および高さに初期化されたWritableImageが作成されます。 (新しいイメージにピクセル・データを書き込むための) PixelWriterが取得された後、コードでは(現在のピクセルの色の陰影を明るくするために) brighter()メソッドが呼び出され、pixelWriter.setColor(readX,readY,Color)を呼び出すことで新しいイメージにデータが書き込まれます。
図10-2に、このプロセスの結果を示します。
バイト配列およびPixelFormatによるイメージの書込み
ここまでのデモでは、ピクセルの色を正常に取得して変更しましたが、APIで実行できることと比べて、コードは比較的単純でした(また、必ずしも最適ではありませんでした)。 例10-4では、ピクセル・データの格納方法を指定するためにPixelFormatを使用して、ピクセルを四角形に一度に書き込む新しいデモを作成します。 また、このバージョンでは、ImageViewではなくCanvasにイメージ・データを表示します。 (Canvasクラスの詳細は、「Canvas APIの使用」を参照してください。)
例10-4 キャンパスへの四角形の書込み
package imageopstest;
import java.nio.ByteBuffer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ImageOpsTest extends Application {
// Image Data
private static final int IMAGE_WIDTH = 10;
private static final int IMAGE_HEIGHT = 10;
private byte imageData[] =
new byte[IMAGE_WIDTH * IMAGE_HEIGHT * 3];
// Drawing Surface (Canvas)
private GraphicsContext gc;
private Canvas canvas;
private Group root;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("PixelWriter Test");
root = new Group();
canvas = new Canvas(200, 200);
canvas.setTranslateX(100);
canvas.setTranslateY(100);
gc = canvas.getGraphicsContext2D();
createImageData();
drawImageData();
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
private void createImageData() {
int i = 0;
for (int y = 0; y < IMAGE_HEIGHT; y++) {
int r = y * 255 / IMAGE_HEIGHT;
for (int x = 0; x < IMAGE_WIDTH; x++) {
int g = x * 255 / IMAGE_WIDTH;
imageData[i] = (byte) r;
imageData[i + 1] = (byte) g;
i += 3;
}
}
}
private void drawImageData() {
boolean on = true;
PixelWriter pixelWriter = gc.getPixelWriter();
PixelFormat<ByteBuffer> pixelFormat = PixelFormat.getByteRgbInstance();
for (int y = 50; y < 150; y += IMAGE_HEIGHT) {
for (int x = 50; x < 150; x += IMAGE_WIDTH) {
if (on) {
pixelWriter.setPixels(x, y, IMAGE_WIDTH,
IMAGE_HEIGHT, pixelFormat, imageData,
0, IMAGE_WIDTH * 3);
}
on = !on;
}
on = !on;
}
// Add drop shadow effect
gc.applyEffect(new DropShadow(20, 20, 20, Color.GRAY));
root.getChildren().add(canvas);
}
}
このデモでは、データは既存のイメージから読み取られず、新しいWritableImageオブジェクトが完全に最初から作成されます。 多色の10x10の四角形の行が複数描画され、その色のデータは各ピクセルのRGB値を表すバイトの配列に格納されます。
特に興味深いのは、privateメソッドcreateImageDataおよびdrawImageDataです。 createImageDataメソッドによって、10x10の各四角形に表示される色のRGB値が設定されます。
例10-5 ピクセルのRGB値の設定
...
private void createImageData() {
int i = 0;
for (int y = 0; y < IMAGE_HEIGHT; y++) {
System.out.println("y: "+y);
int r = y * 255 / IMAGE_HEIGHT;
for (int x = 0; x < IMAGE_WIDTH; x++) {
System.out.println("\tx: "+x);
int g = x * 255 / IMAGE_WIDTH;
imageData[i] = (byte) r;
imageData[i + 1] = (byte) g;
System.out.println("\t\tR: "+(byte)r);
System.out.println("\t\tG: "+(byte)g);
i += 3;
}
}
}
...
このメソッドによって、四角形の各ピクセルのRおよびGの値が設定されます(Bは常に0です)。 これらの値はimageDataバイト配列に格納され、バイト配列には合計で300の個別のバイトが保持されます。 (10x10の各四角形に100ピクセルあり、各ピクセルにR、GおよびBの値があるため、合計で300バイトになります)。
このデータが配置されると、drawImageDataメソッドによって各四角形のピクセルが画面にレンダリングされます。
例10-6 ピクセルのレンダリング
private void drawImageData() {
boolean on = true;
PixelWriter pixelWriter = gc.getPixelWriter();
PixelFormat<ByteBuffer> pixelFormat = PixelFormat.getByteRgbInstance();
for (int y = 50; y < 150; y += IMAGE_HEIGHT) {
for (int x = 50; x < 150; x += IMAGE_WIDTH) {
if (on) {
pixelWriter.setPixels(x, y, IMAGE_WIDTH,
IMAGE_HEIGHT, pixelFormat, imageData, 0, IMAGE_WIDTH * 3);
}
on = !on;
}
on = !on;
}
}
ここでは、PixelWriterがCanvasから取得され、新しいPixelFormatがインスタンス化されて、RGB値がバイト配列によって表されることが指定されます。 次に、このデータをPixelWriterのsetPixelsメソッドに渡すことによって、ピクセルが四角形全体に一度に書き込まれます。
スナップショットの作成
javafx.scene.Sceneクラスには、アプリケーションのシーンに現在表示されているすべてのもののWritableImageを返すスナップショット・メソッドもあります。 JavaのImageIOクラスと組み合せて使用すると、スナップショットをファイルシステムに保存できます。
例10-7 スナップショットの作成および保存
package imageopstest;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
public class ImageOpsTest extends Application {
// Image Data
private static final int IMAGE_WIDTH = 10;
private static final int IMAGE_HEIGHT = 10;
private byte imageData[] = new byte[IMAGE_WIDTH * IMAGE_HEIGHT * 3];
// Drawing Surface (Canvas)
private GraphicsContext gc;
private Canvas canvas;
private Group root;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("PixelWriter Test");
root = new Group();
canvas = new Canvas(200, 200);
canvas.setTranslateX(100);
canvas.setTranslateY(100);
gc = canvas.getGraphicsContext2D();
createImageData();
drawImageData();
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
//Take snapshot of the scene
WritableImage writableImage = scene.snapshot(null);
// Write snapshot to file system as a .png image
File outFile = new File("imageops-snapshot.png");
try {
ImageIO.write(SwingFXUtils.fromFXImage(writableImage, null),
"png", outFile);
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
private void createImageData() {
int i = 0;
for (int y = 0; y < IMAGE_HEIGHT; y++) {
System.out.println("y: " + y);
int r = y * 255 / IMAGE_HEIGHT;
for (int x = 0; x < IMAGE_WIDTH; x++) {
System.out.println("\tx: " + x);
int g = x * 255 / IMAGE_WIDTH;
imageData[i] = (byte) r;
imageData[i + 1] = (byte) g;
System.out.println("\t\tR: " + (byte) r);
System.out.println("\t\tG: " + (byte) g);
i += 3;
}
}
System.out.println("imageData.lengthdrawImageData: " + imageData.length);
}
private void drawImageData() {
boolean on = true;
PixelWriter pixelWriter = gc.getPixelWriter();
PixelFormat<ByteBuffer> pixelFormat = PixelFormat.getByteRgbInstance();
for (int y = 50; y < 150; y += IMAGE_HEIGHT) {
for (int x = 50; x < 150; x += IMAGE_WIDTH) {
if (on) {
pixelWriter.setPixels(x, y, IMAGE_WIDTH,
IMAGE_HEIGHT, pixelFormat,
imageData, 0, IMAGE_WIDTH * 3);
}
on = !on;
}
on = !on;
}
// Add drop shadow effect
gc.applyEffect(new DropShadow(20, 20, 20, Color.GRAY));
root.getChildren().add(canvas);
}
}
例10-8に示すように、注意する必要がある変更はstartメソッドに対する次の変更です。
例10-8 変更されたstartメソッド
...
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
//Take snapshot of the scene
WritableImage writableImage = scene.snapshot(null);
// Write snapshot to file system as a .png image
File outFile = new File("imageops-snapshot.png");
try {
ImageIO.write(SwingFXUtils.fromFXImage(writableImage, null),
"png", outFile);
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
...
ここからわかるように、scene.snapshot(null)を呼び出すと、新しいスナップショットが作成され、新しく構築されたWritableImageに割り当てられます。 次に、(ImageIOおよびSwingFXUtilsによって)このイメージはファイル・システムに.pngファイルとして書き込まれます。




