前回は、BitmapFactory.decodeStream(InputStream, Rect, BitmapFactory.Options) を利用し、メモリーの使用量を抑えて Bitmap を読み込んだ。画像を必要最小限の大きさで読み込むことで、メモリー使用量を抑えているわけだが、縮小サイズは2のべき乗に丸められてしまうため細かく指定できない。単純に ImageView に貼り付けるだけなら問題ないが、サムネイルを作成する場合など、幅と高さを正確に指定したい場合に困る。
幅と高さを正確に指定して画像を生成したい場合は、BitmapFactory.decodeStream(InputStream, Rect, BitmapFactory.Options) で読み込んだ画像を、Matrix を使って縮小すればよいだろう。また、デジカメで撮った JPEG 画像は、EXIF の Orientation 属性に画像の回転情報が含まれていることがある。画像の回転も Matrix で行えるので、同時に処理を行えばよさそうだ。
EXIF の Orientation 属性を読み込んで Matrix を生成
EXIF は、写真用のメタデータを含む画像ファイルフォーマットで、絞りやシャッター速度、GPS 情報などを含む。Wikipedia の説明が簡潔でわかりやすい。写真をプリントするのであれば、ホワイトバランスなどが重要になってくるかもしれないが、表示がメインの Android アプリの画像処理で必要になってくるのは Orientation 属性だ。規格書はExif Printのサイトにあるのだが、読んでもさっぱり分からない。Web でいろいろと調べてみると、JPEGのExifタグ情報のOrientaionの定義の早見表 – DQNEO起業日記という記事が見つかった。わかりやすい!と思って感動・・・と、Android の場合は ExifInterface の定数でだいたいわかるようになっていて少し拍子抜け。Java のコードにすると、以下のようになるだろうか。
ExifInterface exifInterface = new ExifInterface(file.getAbsolutePath()); Matrix matrix = new Matrix(); if(null == exifInterface) { return matrix; } int orientation = exifInterface.getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); switch (orientation) { case ExifInterface.ORIENTATION_UNDEFINED: break; case ExifInterface.ORIENTATION_NORMAL: break; case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: matrix.postScale(-1f, 1f); break; case ExifInterface.ORIENTATION_ROTATE_180: matrix.postRotate(180f); break; case ExifInterface.ORIENTATION_FLIP_VERTICAL: matrix.postScale(1f, -1f); break; case ExifInterface.ORIENTATION_ROTATE_90: matrix.postRotate(90f); break; case ExifInterface.ORIENTATION_TRANSVERSE: matrix.postRotate(-90f); matrix.postScale(1f, -1f); break; case ExifInterface.ORIENTATION_TRANSPOSE: matrix.postRotate(90f); matrix.postScale(1f, -1f); break; case ExifInterface.ORIENTATION_ROTATE_270: matrix.postRotate(-90f); break; } return matrix;
file は対象となる画像ファイルを引数で受け取っている。これをメソッドにして用意し、Matrix を戻すようにしておこう(実際には若干違ったコードになりましたが・・・)。
幅と高さを指定して、縮小した画像を生成
画像の縮小は簡単で、Matrix.postScale(float sx, float sy) メソッドを使うだけだ。EXIF の Orientation 情報と同時に処理するために、引数に BitmapFactory.decodeStream(InputStream, Rect, BitmapFactory.Options) で読み込んだ Bitmap、希望する高さ(desireHeight)と幅(desireWidth)、EXIF の Orientation 属性を読み込んで生成した Matrix を持ち、生成した Bitmap を戻すメソッドを用意してみた。内部のコードは、以下のような感じ。引数の Bitmap を再度利用することがないならば、内部で bitmap.recycle() を呼び出すようにしたほうがよさそうだ。
final int width = bitmap.getWidth(); final int height = bitmap.getHeight(); float scaleX = desireWidth / width; float scaleY = desireHeight / height; float scale = Math.min(scaleX, scaleY); matrix.postScale(scale, scale); return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
画像を指定した幅・高さで EXIF 情報を考慮して読み込むには
以下の順序で処理を行って、指定した幅・高さでサムネイルを生成することができた。
- BitmapFactory.decodeStream(InputStream, Rect, BitmapFactory.Options) で読み込む(前回参照)
- EXIF の Orientation 属性を読み込んで Matrix を生成
- 幅と高さを指定して、縮小した画像を生成
ピンバック: Android で Bitmap を安全に操作する(3) ~画像の編集・コピー~ | UB Lab.