画像の書き込み
画像生成
例題1 グラデーション画像
次のプログラムを作成して実行してみましょう.
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;;
public class Gradiation {
void run() throws IOException {
// 横幅 255,高さ 255 の画像を生成する.
BufferedImage image = new BufferedImage(255, 255, BufferedImage.TYPE_INT_RGB);
for(Integer x = 0; x < 255; x++) {
for(Integer y = 0; y < 255; y++) {
image.setRGB(x, y, x);
}
}
ImageIO.write(image, "png", new File("dest.png"));
}
public static void main(String[] args) throws IOException {
Gradiation g = new Gradiation();
g.run();
}
}実行結果は dest.png というファイルに png フォーマットで出力されます.
具体的な内容は上の折り畳みを開いて確認してください.
Java で画像を扱うには,BufferedImage という型を用います.
この画像は,上記の10行目のように,BufferedImage の実体を作成しています.
この結果,横幅255,高さ255の画像が生成されます.
また,この画像の各画素(1つのピクセル)は int 型で表され,その値は RGB(Red, Green, Blue)を表すことを示しています.
13 行目の image.setRGB で指定されたピクセルの値を設定しています.
setRGB の引数は前から順に x座標,y座標,ピクセルの値を表しています.
13 行目を image.setRGB(i, j, j) や image.setRGB(i, j, i << 8 | j) のように変更して結果がどのように変わるか確認してみましょう.
画像の書き出し
runメソッドの最後の行(16行目)では,画像ファイルを dest.png というファイルに PNG フォーマットで出力しています.
ImageIO(イメージアイオーと呼ぶ) は画像の入出力を扱うクラスです.
第2引数の "png" を "jpeg" や "gif" などに変えると指定されたフォーマットで出力されるようになります.
以上で画像の生成から出力まで行えるようになりましたが,入出力エラーに対応する必要があります.
それが,8行目,17行目の runメソッド,mainメソッドの定義の後ろについている throws IOException というものです.
もし,この throws IOException がなければコンパイラは次のメッセージを表示し,コンパイルに失敗します.
Gradiation.java:15: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
ImageIO.write(image, "png", new File("dest.png"));
^
エラー1個サポートされている画像フォーマット
ImageIOのAPIドキュメント に記載されている通り,以下のフォーマットの読み書きがサポートされています.
- BMP
- GIF
- JPEG
- PNG
- TIFF
- WBMP
例外機構(Exception Architecture)
例外(Exception)は,近年のプログラミング言語で採用されている実行時エラーの通知機構です.
従来のプログラミング言語,例えば,C 言語の場合,fopenで開くファイルが見つからなかった場合,
返り値をNULL にすることでエラーを通知していました. この場合,プログラマが責任を持って,
返り値の値を確認して,正常か異常かを判断しなければいけませんでした.
一方の例外機構は,異常処理を行うための別の処理経路を作るものです. もし,プログラムの実行途中で何らかの異常が発生した時,それまでに行なっていた処理を中断し, 別の処理を行うようにする機構です.
graph LR;
A[ファイルを開く] --> B{存在確認}
B -->|存在する| C[正常処理]
B -->|存在しない| D[異常処理]
C 言語のようなエラー処理は上記のフローチャートのように,
プログラマ自身がfopenの返り値を元に分岐処理によって,正常処理,異常処理を振り分ける必要があります.
graph LR; A[ファイルを開く] A --> C[正常処理] A -->|存在しない| D[異常処理]
対して,例外機構がサポートされている言語の場合,プログラマが異常,正常の分岐を明示的に書く必要はありません. 異常が起こった場合の処理の経路が決まっており,その経路に処理を書いておくことが異常処理を行うことになります. 正常処理の場合は,それまでの処理の続きにそのまま処理を書いていきます. これにより正常処理の見通しがよくなります.
そして,例外が投げられれば,何らかの異常が発生したと判断できるようになります. 逆に,例外が投げられなければ,データが変であろうが,正常な処理であると判断できます (もちろん,開発途中で変な場合はバグの可能性はありますが).
プログラマが明示的に投げる例外も存在しますが,多くの場合,システムが異常を検知し例外を投げます. 例えば,ファイルが見つからない場合,スリープ中に割り込みが入った場合などです. そのような例外が発生した時に,どのような対応をするのかをあらかじめ決めておく必要があります.
次のプログラムが,例外処理のイメージです.
void exceptionalMethod() throws Exception {
// 例外が発生する可能性のある処理
someMethodCall();
// 例外が起こらなかった場合の処理
process();
}someMethod の実行中に何らかの例外が起こった場合,まずsomeMethodの呼び出し元であるexceptionalMethodにどのように処理するかが問い合わされます.
そして,exceptionalMethodは例外は処理せず,呼び出し元に処理を任せるよう設定している(メソッドにthrows Exceptionと宣言しているため)ため,exceptionalMethod の呼び出し元に通知されます.
このthrows節は後ほど説明します.
検査例外と非検査例外
Java の例外は,検査例外と非検査例外の2つに分類できます. 違いは次に挙げる通りです.
検査例外
例外が発生した時の処理をプログラム中に明示的に書いておかなければコンパイルエラーになる例外. 完全に防ぐことが不可能な例外(実行時の状態やユーザの操作によって発生しうる例外).
例えば,実行時エラーは,プログラムでどれほど厳密にチェックしたとしても,完全に回避できません.
- 代表的な検査例外
IOException- 入出力時にエラーが発生した時.
InterruptedException- 割り込みが発生した時に発生する例外.
非検査例外
一方,非検査例外はプログラム中で十分にチェックすることで避けることが可能です. そのため,例外が発生した時の処理は書かなくてもコンパイルが通るようになっています.
- 代表的な非検査例外
NullPointerException- 初期化されていない変数に対する処理を行なった場合に発生する例外.
ArrayIndexOutOfBoundsException- 配列の範囲を超えてアクセスしようとした時に発生する例外.
NumberFormatException- 数値の変換に失敗した時に投げられる例外.
非検査例外は,事前にチェックすることで,例外の発生を抑えられます. 逆に言えば,実行時に非検査例外が投げられた場合は,事前のチェックが不十分であるとも言えます. 例えば,配列の範囲を超えてアクセスすることは,事前に配列の範囲を超えないようにプログラム中で確認することで避けられます.
例外の責任転嫁
さて,例外が発生した時の対処法をプログラム中に書いておく必要があります. 取れる対処法は2つです.
- 例外が投げられたら,その場で例外に対応する.
- 例外が発生する可能性のあるメソッドを呼び出している元に対応を任せる.
どちらがふさわしいかは場合により異なります. ここでは,呼び出し元に対応を任せましょう. そのために,呼び出し元に責任を丸投げするようにプログラム中に明示しておきましょう.
こうすることで,例外が発生した時はそのメソッドの呼び出し元に責任を転嫁し,正常処理のみに集中して処理を書くことができます.
本講義では,例外に対応する処理については省略します.
詳細を知りたい場合は,try-catch について調べてみてください.
IOException の責任転嫁
さて,コンパイルエラーで,IOExceptionが投げられる可能性があると述べられています.
この例外は,ファイル書き込みに何らかの問題があったときに発生する例外です.
ここでは,呼び出し元に責任を転嫁しましょう.
以下のような対応になります.
ImageIO.writeが例外を発生させた時,ImageIO.writeの呼び出し元であるrunに対応が任されます. これが例外が投げられた,ということです.- そこで,
runの呼び出し元であるmainに対応を任せましょう. - さらに,
mainも呼び出し元に対応を任せましょう. - すると,実行環境が対応されなかった例外をスタックトレースという形で出力し,プログラムが終了するようになります.
throws 節
呼び出し元に対応を任せるには,メソッドのシグネチャに throws節を追加します.
throws 節は以下のように指定します.
以下のように書くことで,例外クラスが発生した時,methodNameの呼び出し元に責任を転嫁することができるようになります.
public class ClassName{
void methodName() throws 例外クラス {
// 検査例外が発生する可能性のある処理
}
}なお,複数の例外が発生する可能性のある場合,throws節に例外クラスの名前をコンマ区切りで指定できます.
例題2. 画像の読み込み/書き込み
ImageIOは画像の書き込みだけではなく,readメソッドで読み込みも可能です.
以下のようなプログラムで画像のフォーマット変換が可能になります.
args[0]から画像を読み出し,出力先(args[1])の拡張子からフォーマットを取り出し,画像を出力します.
import java.io.IOException;
import java.io.File;
import java.util.Objects;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
public class ToJpegConverter {
void run(String[] args) throws IOException {
// 画像ファイルを読み込む.
BufferedImage image = ImageIO.read(new File(args[0]));
String destName = findDestName(args[1])
ImageIO.write(image, "jpg", new File(destName)); // 画像を書き出す.
}
String findDestName(String fileName) {
// ファイル名から最後のドット(.)の位置を取得する.
Integer index = fileName.lastIndexOf(".");
// 取得した位置から後ろの文字列を取得する.拡張子に相当する.
String extension = fileName.substring(index + 1).toLowerCase();
// 拡張子が jpg,もしくは jpeg ならそのまま返す.
if(Objects.equals(extension, "jpg") || Objects.equals(extension, "jpeg"))
return fileName;
// そうでなければ拡張子の前の部分を取り出して,".jpg" を追加して返す.
return fileName.substring(0, index) + ".jpg";
}
public static void main(String[] args) throws IOException {
ToJpegConverter tjc = new ToJpegConverter();
tjc.run(args);
}
}実行結果
$ ls
Gradiation.class Gradiation.java ToJpegConverter.class ToJpegConverter.java dest.png
$ file dest.png # dest.png のファイルフォーマットを調べる.
dest.png: PNG image data, 255 x 255, 8-bit/color RGB, non-interlaced
$ java ToJpegConverter dest.png dest.jpg # フォーマットを変換し,jpegファイルを出力する.
$ file dest.jpg # 出力された dest.jpg のフォーマットを調べる.
dest.jpg: JPEG image data, JFIF standard 1.02, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 255x255, components 3
$ java ToJpegConverter dest.png dest2.png # 拡張子が何であっても,jpegファイルを出力する.
$ file dest2.jpg # 出力された dest.jpg のフォーマットを調べる.
dest2.jpg: JPEG image data, JFIF standard 1.02, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 255x255, components 3