Androidアプリ開発でcamera2 apiを使用し静止画撮影機能を実装しました。
googleのサンプルであるgooglesamples / android-Camera2Basicを参考にしました。
その中問題が1点出てきました。
端末によって画像が90℃回転して保存されるという問題です。
今回はその問題の正体と解決方法についてまとめます。
問題の正体
端末によって問題が発生するので端末依存の問題だと思っていましたが、
調べたところAndroidのバージョン依存でした。
下記はgoogleのサンプル中のコード(Camera2BasicFragment ::captureStillPicture)ですが
カメラで静止画をキャプチャーするときは
val captureBuilder = cameraDevice?.createCaptureRequest( CameraDevice.TEMPLATE_STILL_CAPTURE)?.apply { addTarget(imageReader?.surface) // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) // We have to take that into account and rotate JPEG properly. // For devices with orientation of 90, we return our mapping from ORIENTATIONS. // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. set(c, (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360) // Use the same AE and AF modes as the preview. set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) }?.also { setAutoFlash(it) }
のようにCaptureRequest.JPEG_ORIENTATIONに回転角をセットします。
カメラの角度は横向きが基本になっているものが多く、
ここで現在の端末の回転角を適用してあげることで回転が補正されるという作りになっています。
一方でAndroidのバージョンが10以上ではこの
set(CaptureRequest.JPEG_ORIENTATION, (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360)
が効かない。
そのためカメラのデフォルトの角度で保存されてしまうという状態でした。
解決策
カメラに対して回転させて保存する指示が出せないので、
カメラが保存してきたものを回転させてしまおうという考えに至りました。
googleのサンプルでは画像保存用のクラスとしてImageSaverというクラスを実装しています。(下記)
internal class ImageSaver( /** * The JPEG image */ private val image: Image, /** * The file we save the image into. */ private val file: File ) : Runnable { override fun run() { val buffer = image.planes[0].buffer val bytes = ByteArray(buffer.remaining()) buffer.get(bytes) var output: FileOutputStream? = null try { output = FileOutputStream(file).apply { write(bytes) } } catch (e: IOException) { Log.e(TAG, e.toString()) } finally { image.close() output?.let { try { it.close() } catch (e: IOException) { Log.e(TAG, e.toString()) } } } } companion object { /** * Tag for the [Log]. */ private val TAG = "ImageSaver" } }
見たままですが保存する際にはこのクラスでImageがFileに書き込まれます。
このクラスにrotationを引き回して回転させるように変更します。(下記)
internal class ImageSaver internal constructor( private val mImage: Image, private val mFile: File, private val imageRotation : Int ) : Runnable { override fun run() { val matrix = Matrix() matrix.postRotate(imageRotation.toFloat()) val buffer = mImage.planes[0].buffer val bytes = ByteArray(buffer.remaining()) buffer[bytes] val bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, null); //回転したBitmapを生成 val rotatedBmp = Bitmap.createBitmap( bmp, 0, 0, bmp.width, bmp.height, matrix, false ) var output: FileOutputStream? = null try { output = FileOutputStream(mFile) rotatedBmp.compress(Bitmap.CompressFormat.JPEG, 100, output) } catch (e: IOException) { e.printStackTrace() } finally { mImage.close() if (null != output) { try { output.close() } catch (e: IOException) { e.printStackTrace() } } } } }
上記のimageRotation : Intは先ほどのcaptureBuilderにパラメータをセットしているところの値を使用します。
これを適用することで画像の回転を戻すことができます。
問題点が1つ、キャプチャーしてから回転させているためどうしても保存処理が少し遅くなってしまいますので注意してください。
参考
https://medium.com/@kenodoggy/solving-image-rotation-on-android-using-camera2-api-7b3ed3518ab6