【Android】CameraX習作（ビットマップを取得する）

## はじめに
ケアプラン作成は、いまだにエクセル使用している施設ケアマネジャーです。
画像中に人物が一人であるときに、その人物の服の色を判別できるところまでできたので、今回はカメラで映像を取得します。

## 課題
カメラ映像からビットマップを取得したい。ただし、保存はしない。

## 方法
Camerax を用いて、Bitmap を取得する。

## 結果
カメラからビットマップを取得できた。

## 今後の課題
Camerax の画像解析ユースケースを用いて、一人歩きの利用者を発見すること。

## コード等

## build.gradle.kts(.app) 

```build.gradle.kts(.app) 

android {
...
    buildFeatures {
        viewBinding = true
    }
}

dependencies {
    ...
    val camerax_version = "1.5.0-alpha03"
    implementation("androidx.camera:camera-core:${camerax_version}")
    implementation("androidx.camera:camera-camera2:${camerax_version}")
    implementation("androidx.camera:camera-lifecycle:${camerax_version}")
    ...
}
```
## AndroidManifest.xml
```AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-feature android:name="android.hardware.camera.any" />
    <uses-permission android:name="android.permission.CAMERA"/>

    <application
    ...
```

## activity_main.xml
```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_marginStart="40dp"
        android:layout_marginTop="44dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="128dp"
        android:layout_marginTop="88dp"
        android:text="Button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView" />

</androidx.constraintlayout.widget.ConstraintLayout>

```
## MainActivity.kt
```MainActivity.kt
package yourpackagename

import android.Manifest
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Matrix
import android.os.Build
import android.os.Bundle
import android.util.Size
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888
import androidx.camera.core.ImageProxy
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.core.resolutionselector.ResolutionStrategy
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resumeWithException

import com.android.example.yourappname.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var viewBinding: ActivityMainBinding
    private var cameraProvider: ProcessCameraProvider? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Request camera permissions
        if (allPermissionsGranted()) {
           // startCamera()
        } else {
            ActivityCompat.requestPermissions(
                this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
        }

        viewBinding.button.setOnClickListener{ doJudge() }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<String>, grantResults:
        IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                // startCamera()
            } else {
                Toast.makeText(this,
                    "Permissions not granted by the user.",
                    Toast.LENGTH_SHORT).show()
                finish()
            }
        }
    }

    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(
            baseContext, it) == PackageManager.PERMISSION_GRANTED
    }

    override fun onDestroy() {
        super.onDestroy()
        cameraExecutor.shutdown()
    }

    companion object {
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS =
            mutableListOf(
                Manifest.permission.CAMERA,
            ).apply {
                if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
                    // add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                }
            }.toTypedArray()
    }

    private val cameraExecutor by lazy {
        Executors.newSingleThreadExecutor()
    }

    private val imageAnalyzer by lazy {
        ImageAnalysis.Builder()
            //.setOutputImageFormat(OUTPUT_IMAGE_FORMAT_YUV_420_888)//https://developer.android.com/reference/kotlin/androidx/camera/core/ImageAnalysis#OUTPUT_IMAGE_FORMAT_RGBA_8888()
            .setOutputImageFormat(OUTPUT_IMAGE_FORMAT_RGBA_8888)//https://developer.android.com/reference/kotlin/androidx/camera/core/ImageAnalysis#OUTPUT_IMAGE_FORMAT_RGBA_8888()
            //.setTargetResolution(Size(640, 480))//'setTargetResolution(Size): ImageAnalysis.Builder' is deprecated. Deprecated in Java
            .setResolutionSelector(
                ResolutionSelector.Builder()
                    .setResolutionStrategy(ResolutionStrategy(Size(640, 480), ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER))
                    .build()
            )
            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build()
    }

    private suspend fun setAnalyzer(executor: Executor): ImageProxy {
        return suspendCancellableCoroutine { continuation ->
            try {
                if (continuation.isActive) {
                    imageAnalyzer.setAnalyzer(executor) { image ->
                        continuation.resume(image)
                    }
                }
            } catch (exception: Exception) {
                if (continuation.isActive) {
                    continuation.resumeWithException(exception)
                }
            }
        }
    }

    private fun doJudge() {
        var resultImage: ImageProxy? = null
        lifecycleScope.launch {
            try {
                val cameraProviderFuture = ProcessCameraProvider.getInstance(this@MainActivity)
                cameraProvider = cameraProviderFuture.get()

                val cameraSelector = CameraSelector.Builder()
                    .requireLensFacing(CameraSelector.LENS_FACING_FRONT)
                    .build()

                cameraProvider?.bindToLifecycle(this@MainActivity, cameraSelector, imageAnalyzer)

                // ここで取れた resultImage の中に画像データが入っている
                resultImage = setAnalyzer(cameraExecutor)

                val bitmap = imageProxyToBitmap(resultImage)
                val rotation = resultImage!!.imageInfo.rotationDegrees
                val rotateBitmap = rotateImage(bitmap, rotation)//上下を整える
                val mirroredBitmap = mirrorImageBitmap(rotateBitmap)//鏡像にする

                viewBinding.imageView.setImageBitmap(mirroredBitmap)

                resultImage!!.close()
                resultImage = null
                cameraProvider?.unbindAll()
                cameraProvider = null
            } catch (e: Exception) {
                //
            } finally {
                cameraProvider?.unbindAll()
                cameraProvider = null
                resultImage?.close()
            }
        }
    }

    private fun rotateImage(bitmap: Bitmap, degree: Int): Bitmap {
        val matrix = Matrix()
        matrix.postRotate(degree.toFloat())
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
    }

    private fun mirrorImageBitmap(bitmap: Bitmap) : Bitmap{
        val matrix = Matrix()
        matrix.preScale(-1f, 1f)
        return  Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
    }

    private fun imageProxyToBitmap(image: ImageProxy?): Bitmap {
        val planes: Array<ImageProxy.PlaneProxy> = image!!.planes
        val buffer = planes[0].buffer
        val bitmap = Bitmap.createBitmap(image.width, image.height, Bitmap.Config.ARGB_8888)
        bitmap.copyPixelsFromBuffer(buffer)
        return bitmap
    }
}
```
## 参考
https://developer.android.com/codelabs/camerax-getting-started?hl=ja#0

https://developer.android.com/media/camera/camerax?hl=ja

https://qiita.com/Gazyu/items/ae9e42bee7a38ddb631d

https://qiita.com/takasshii/items/d40a31e66a08c2c5ae25#%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE