###問題
CameraXを使用してカメラアプリを作っているのですが、takePictureでキャプチャした写真が90°回転してしまいます。
毎回回転してしまうわけではなく、横画面時(TextureView.display.rotationが1,3の時)は回転せず、ちゃんとキャプチャできています。
しかし、縦画面時(TextureView.display.rotationが0,2)のときは下図のように横長で90°回転した写真をキャプチャしてしまいます。
ImageCapture.setTargetRotationで回転を指定しても何も変わりませんでした。
Googleのこのサンプルを参考にしながらアプリを作成したのですが、Googleのサンプルのキャプチャ後の写真は正しい回転をしています。サンプルをほぼなぞるようにして書いたので、何が違い、何が原因で回転してしまうのか全く分かりません。
何かわかる方がいましたらご回答お願いします。
###写真
縦画面時に撮影した写真
90°回転してしまっています。
###コード
Kotlin
1package caios.android.camera_x_sample 2 3 4import android.content.ContentValues 5import android.content.Context 6import android.content.res.Configuration 7import android.hardware.camera2.CameraManager 8import android.hardware.display.DisplayManager 9import androidx.camera.core.ImageCapture.Metadata 10import android.os.Bundle 11import android.os.Environment 12import android.os.Message 13import android.provider.MediaStore 14import android.util.DisplayMetrics 15import androidx.fragment.app.Fragment 16import android.view.LayoutInflater 17import android.view.TextureView 18import android.view.View 19import android.view.ViewGroup 20import android.widget.Button 21import android.widget.Toast 22import androidx.appcompat.app.AppCompatActivity 23import androidx.camera.core.* 24import androidx.constraintlayout.widget.ConstraintLayout 25import androidx.core.app.ActivityCompat 26import androidx.core.content.ContextCompat 27import java.io.File 28import java.util.concurrent.Executor 29import kotlin.math.abs 30import kotlin.math.max 31import kotlin.math.min 32 33class CameraFragment : Fragment(), View.OnClickListener { 34 35 private lateinit var callerContext: Context 36 private lateinit var callerActivity: AppCompatActivity 37 38 private lateinit var rootLayout: ConstraintLayout 39 private lateinit var textureView: TextureView 40 41 private lateinit var cameramanager: CameraManager 42 private lateinit var displayManager: DisplayManager 43 44 private lateinit var preview: Preview 45 private lateinit var imageCapture: ImageCapture 46 47 private lateinit var mainExecutor: Executor 48 49 private var cameraFacing = CameraX.LensFacing.BACK 50 private var displayId = 0 51 52 private val displayListener = object : DisplayManager.DisplayListener{ 53 override fun onDisplayAdded(id: Int) = Unit 54 override fun onDisplayRemoved(id: Int) = Unit 55 override fun onDisplayChanged(id: Int) = view?.let {view -> 56 if(this@CameraFragment.displayId == id){ 57 preview.setTargetRotation(view.display.rotation) 58 imageCapture.setTargetRotation(view.display.rotation) 59 } 60 } ?:Unit 61 } 62 63 override fun onAttach(context: Context) { 64 super.onAttach(context) 65 callerContext = context 66 callerActivity = context as AppCompatActivity 67 } 68 69 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 70 return inflater.inflate(R.layout.fragment_camera, container, false) 71 } 72 73 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 74 super.onViewCreated(view, savedInstanceState) 75 76 rootLayout = view as ConstraintLayout 77 textureView = rootLayout.findViewById(R.id.FC_TextureView) 78 79 rootLayout.systemUiVisibility = FLAGS_FULLSCREEN 80 81 cameramanager = callerContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager 82 83 displayManager = callerActivity.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager 84 displayManager.registerDisplayListener(displayListener, null) 85 86 mainExecutor = ContextCompat.getMainExecutor(requireContext()) 87 88 textureView.post { 89 displayId = textureView.display.displayId 90 updateUI() 91 startCameraX() 92 } 93 } 94 95 override fun onResume() { 96 super.onResume() 97 if (::rootLayout.isInitialized) rootLayout.systemUiVisibility = FLAGS_FULLSCREEN 98 displayManager.registerDisplayListener(displayListener, null) 99 } 100 101 override fun onPause() { 102 super.onPause() 103 displayManager.unregisterDisplayListener(displayListener) 104 } 105 106 override fun onConfigurationChanged(newConfig: Configuration) { 107 super.onConfigurationChanged(newConfig) 108 updateUI() 109 } 110 111 override fun onClick(view: View?) { 112 when(view?.id){ 113 R.id.CU_Capture -> takePicture() 114 } 115 } 116 117 private fun startCameraX(){ 118 val matrix = DisplayMetrics().also { textureView.display.getRealMetrics(it) } 119 val screenAspectRatio = aspectRatio(matrix.widthPixels, matrix.heightPixels) 120 121 val previewConfig = PreviewConfig.Builder().apply { 122 setLensFacing(cameraFacing) 123 setTargetAspectRatio(screenAspectRatio) 124 setTargetRotation(textureView.display.rotation) 125 }.build() 126 127 val imageCaptureConfig = ImageCaptureConfig.Builder().apply { 128 setLensFacing(cameraFacing) 129 setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY) 130 setFlashMode(FlashMode.AUTO) 131 setTargetAspectRatio(screenAspectRatio) 132 setTargetRotation(textureView.display.rotation) 133 }.build() 134 135 preview = AutoFitPreviewBuilder.build(previewConfig, textureView) 136 imageCapture = ImageCapture(imageCaptureConfig) 137 138 CameraX.bindToLifecycle(viewLifecycleOwner, preview, imageCapture) 139 } 140 141 private fun updateUI(){ 142 rootLayout.findViewById<ConstraintLayout>(R.id.CU_RootView)?.let { 143 rootLayout.removeView(it) 144 } 145 146 val controls = View.inflate(requireContext(), R.layout.camera_ui, rootLayout) 147 148 controls.findViewById<Button>(R.id.CU_Capture).setOnClickListener(this) 149 } 150 151 private fun takePicture(){ 152 imageCapture.let {imageCapture -> 153 val fileName = System.currentTimeMillis().toString() + ".JPG" 154 val imageFile = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), fileName) 155 val metaData = Metadata().apply { 156 isReversedHorizontal = (cameraFacing == CameraX.LensFacing.FRONT) 157 } 158 val imageSavedListener = object : ImageCapture.OnImageSavedListener { 159 override fun onImageSaved(file: File) { 160 toast("Capture Success\n${file.absolutePath}") 161 reloadPictures(file) 162 } 163 164 override fun onError(imageCaptureError: ImageCapture.ImageCaptureError, message: String, cause: Throwable?) = toast("Capture Error\n$message") 165 } 166 167 imageCapture.takePicture(imageFile, metaData, mainExecutor, imageSavedListener) 168 } 169 } 170 171 private fun reloadPictures(file: File){ 172 val contentValue = ContentValues().apply { 173 put(MediaStore.Images.Media.MIME_TYPE, "image/JPG") 174 put("_data", file.absolutePath) 175 } 176 177 callerContext.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValue) 178 } 179 180 private fun aspectRatio(width: Int, height: Int): AspectRatio { 181 val previewRatio = max(width, height).toDouble() / min(width, height) 182 183 if (abs(previewRatio - ASPECT_RATIO_4_3) <= abs(previewRatio - ASPECT_RATIO_16_9)) { 184 return AspectRatio.RATIO_4_3 185 } 186 return AspectRatio.RATIO_16_9 187 } 188 189 private fun toast(message: String) = Toast.makeText(callerContext, message, Toast.LENGTH_SHORT).show() 190 191 companion object{ 192 const val ASPECT_RATIO_4_3 = 4.0 / 3.0 193 const val ASPECT_RATIO_16_9 = 16.0 / 9.0 194 const val MAX_PREVIEW_WIDTH = 1920 195 const val MAX_PREVIEW_HEIGHT = 1080 196 const val FLAGS_FULLSCREEN = View.SYSTEM_UI_FLAG_LOW_PROFILE or 197 View.SYSTEM_UI_FLAG_FULLSCREEN or 198 View.SYSTEM_UI_FLAG_LAYOUT_STABLE or 199 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or 200 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or 201 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 202 } 203}
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/01/10 03:59