カメラでキャプチャした写真の向きを取得する (その3)
頼みの綱だった画像のExifタグ情報の"Orientation"属性だが、私の端末では使えないことが分かった。(他の端末、例えばXperia等では正しい値が返ってくると聴いたが、検証はしていない)
その後調べてみたが、Exifタグ情報以外に写真の向きを取得するには以下の方法があるようだ。
-
- コンテントプロバイダの列情報を使う
- 端末のセンサ情報を使う
前者はよく使われているが、写真を特定のURIで格納する必要がある。私はストレージに写真を永続的に保存したくない(一時的に保存する)ので、コンテントプロバイダは使えない。
後者はandroidに実装されているセンサーの中から傾きセンサの変化を使う方法だ。傾きセンサの出力はandroid framework中では角度の変化を通知するとしてイベントリスナとして実装されており、以下のように利用することができる。
OrientationEventListener orientationListener; this.orientationListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) { @Override public void onOrientationChanged(int orientation) { //orientationには回転角度が時計回り0〜360°でセットされる } }; this.orientationListener.enable();
ということでこれを使うのが良さそうだ。
ただし、私が欲しいのはのはポートレイト(上/下)かランドスケイプ(上/下)かの情報であり、逐一変わる角度ではない。このイベントは端末が少しでも向きを変えるとイベントが通知されてくるため、ある程度角度の範囲を集約しなくてはならない。
具体的には角度の変化が一定の範囲に入っているかを判定して、その範囲によって端末の角度を四方向に固定するロジック(フィルタ)が必要になる。感覚の問題だが例えば45°間隔で縦と横が切り替わる(私はこれがしっくりくる)ように実装すると以下のようになる。
int lastDegrees; this.orientationListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) { @Override public void onOrientationChanged(int orientation) { int degree = 0; if ( orientation > 0 && orientation <= 45) { degree = 0; } else if ( orientation > 45 && orientation <= 135) { degree = 90; } else if ( orientation > 135 && orientation <= 225) { degree = 180; } else if ( orientation > 225 && orientation <= 315) { degree = 270; } else { degree = 0; } if (lastDegrees != degree) { lastDegrees = degree; } } }; this.orientationListener.enable();
写真キャプチャ時にはlastDegreesには0, 90, 180, 270の4通りの値がセットされているはずなので、これを使って作った画像を回転してやれば良いだろう。
Bitmap b1 = BitmapFactory.decodeFile("写真を保存したパス"); Matrix m = new Matrix(); m.postRotate(lastDegrees); Bitmap b2 = Bitmap.createBitmap(b1, 0, 0, b1.getWidth(), b1.getHeight(), m, true);
Matrixクラスはビットマップに対して行列演算を実行することで回転、移動、拡大縮小等を施すことができる。OrientationEventListenerによって取得された現在の端末の角度を保存した写真の画像に対して適用している。
これでなんとか端末の角度を画像と同期できるようになったが、そもそもExifタグで正しく向きを取得できている端末もあると思われるので、実際の実装では
1.まずはExifIntefaceの"Orientation"属性を取得
↓
2.Orientationが0だった場合はOrientationEventListenerで取得した端末の角度を取得
の順で処理を行うようにすれば良いだろう。
また、OrientationEventListenerはセンサの値が逐一通知されてくるため、その値を使わない間はサイクル自体が無駄になる。ケースによってはOrientationEventListenerリスナのコンストラクタの第二パラメタ(現在はSENSOR_DELAY_UI)を変えて頻度を下げるか、使用していない時にはリスナをdisable()する必要もあるだろう。