Camera X Android Java 実装 (TextureView)

### ■ UI
【01】
<img width="200" alt="Camera X img_01 toshiyuki natsume" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478380/a70da9eb-a8b0-c6f7-3707-c01f5dd8b90f.jpeg">
【02】
<img width="200" alt="Camera X img_02 toshiyuki natsume" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478380/2172d1cb-551f-db64-2209-c92b461d4543.jpeg">
【03】
<img width="200" alt="Camera X img_03 toshiyuki natsume" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478380/087dbad5-e79a-6b5d-7922-5f6a4f2c1e29.jpeg">


### ■ ソースコード

#### ●MainActivity.java
```java MainActivity.java

package com.example.camera_x_001;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.CameraX;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageAnalysisConfig;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureConfig;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
import androidx.camera.core.PreviewConfig;

import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;

import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.media.Image;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.common.util.concurrent.ListenableFuture;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

import java.io.File;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class MainActivity extends AppCompatActivity {

    // 定数
    private final int REQUEST_CODE_PERMISSIONS = 101;


    // UI
    private TextureView textureView;
    private Button captureButton, opne_button, capture_button;

    private TextView get_with, get_height, get_gyouretu;
    private String str_get_with, str_get_height;

    // === ピンチアウト用
    private ScaleGestureDetector scaleGestureDetector;
    private float scaleFactor = 0.05f;

    private int test_count = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();

        textureView = findViewById(R.id.texture_view);

     //   scaleGestureDetector = new ScaleGestureDetector(this, new ScaleListener());

        /**
         *   === カメラ起動 ===
         */
        opne_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // カメラ　起動
                startCamera();
            }
        });

    } // === END onCreate

    /**
     *   === カメラ　画面タッチ　イベント ===
     */

    /*
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        scaleGestureDetector.onTouchEvent(event);
        return true;
    }

     */

    /**
     *   === カメラ　画面タッチ　イベント ===
     */

    /*
    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            scaleFactor *= detector.getScaleFactor();

            // 最小・最大スケールを制限する場合は、適宜設定
            scaleFactor = Math.max(0.01f, Math.min(scaleFactor, 2.0f));

            // TextureViewのサイズを更新する
            int width = (int) (textureView.getWidth() * scaleFactor);
            int height = (int) (textureView.getHeight() * scaleFactor);
            textureView.setLayoutParams(new FrameLayout.LayoutParams(width, height));

            return true;
        }
    }

*/

        /**
         * ========= カメラの開始 =========
         */
        private void startCamera() {
            // プレビューの表示
            PreviewConfig pConfig = new PreviewConfig.Builder().build();
            Preview preview = new Preview(pConfig);
            preview.setOnPreviewOutputUpdateListener(

                    output -> {
                        // SurfaceTextureの更新
                        ViewGroup parent = (ViewGroup)this.textureView.getParent();
                        parent.removeView(this.textureView);
                        parent.addView(this.textureView, 0);

                        // SurfaceTextureをTextureViewに指定
                        this.textureView.setSurfaceTexture(output.getSurfaceTexture());

                        // TextureViewのサイズの調整
                        int w = output.getTextureSize().getWidth();
                        int h = output.getTextureSize().getHeight();
                        int degree = output.getRotationDegrees();
                        if (degree == 90 || degree == 270) {
                            w = output.getTextureSize().getHeight();
                            h = output.getTextureSize().getWidth();
                        }
                        h = h * textureView.getWidth() / w;
                        w = textureView.getWidth();
                        ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(w,h);
                        params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
                        params.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
                        params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
                        params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
                        textureView.setLayoutParams(params);;

                    });


                    /**
                     * ========= 画像の解析 =========
                     */
                    ImageAnalysisConfig config = new ImageAnalysisConfig.Builder()
                            .setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
                            .build();
                    ImageAnalysis imageAnalysis = new ImageAnalysis(config);
                    imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(),
                            new ImageAnalysis.Analyzer() {
                                @Override
                                public void analyze(ImageProxy image, int rotationDegrees) {

                                    // === 横　、縦　取得
                                    str_get_with = String.valueOf(image.getWidth() * test_count);
                                    str_get_height = String.valueOf(image.getHeight() * test_count);

                                    // === 表示行列 取得
                                    Matrix transformMatrix = new Matrix();
                                    String Tmp_Matrix = String.valueOf(textureView.getTransform(transformMatrix));

                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            get_with.setText(str_get_with); // 横
                                            get_height.setText(str_get_height); // 縦

                                            get_gyouretu.setText(Tmp_Matrix); // 表示行列
                                        }
                                    });

                                    android.util.Log.d("出力:::analyze:::",+image.getWidth()+"x"+image.getHeight());
                                    android.util.Log.d("出力:::analyze:::",Tmp_Matrix);

                                    test_count++;


                                }
                            });


                    /**
                     * ========= 画像のキャプチャ =========
                     */
                    ImageCaptureConfig cConfig = new ImageCaptureConfig.Builder()
                            .setTargetRotation(getWindowManager().getDefaultDisplay().getRotation())
                            .build();
                    ImageCapture imageCapture = new ImageCapture(cConfig);

                    // ボタンのイベントリスナー
                    captureButton.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            // 画像のキャプチャ
                            File file = new File(getFilesDir(), "captured.jpg");
                            imageCapture.takePicture(file, Executors.newSingleThreadExecutor(),
                                    new ImageCapture.OnImageSavedListener() {
                                        // 成功時に呼ばれる
                                        @Override
                                        public void onImageSaved(File file) {
                                            android.util.Log.d("debug","success フォト保存OKOKOK");
                                        }

                                        // エラー時に呼ばれる
                                        @Override
                                        public void onError(
                                                ImageCapture.ImageCaptureError imageCaptureError,
                                                String message, Throwable cause) {
                                            android.util.Log.d("debug","error");
                                        }
                                    });
                        }
                    });


            // カメラのライフサイクルのバインド
           // CameraX.bindToLifecycle(this, imageAnalysis,preview);
            CameraX.bindToLifecycle(this, imageCapture, imageAnalysis, preview);
        }


        private void init() {

            captureButton = findViewById(R.id.capture_button); // 画面キャプチャ
            opne_button = findViewById(R.id.opne_button); // カメラ起動

            get_with = findViewById(R.id.get_with); // 横
            get_height = findViewById(R.id.get_height); // 縦

            get_gyouretu = findViewById(R.id.get_gyouretu); // 表示行列

        }


}

```
#### ●activity_main.xml
```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">

    <TextView
        android:id="@+id/get_with"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12dp"
        android:textColor="#000"
        android:text="横："
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        />

    <TextView
        android:id="@+id/get_height"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12dp"
        android:textColor="#000"
        android:text="縦："
        app:layout_constraintTop_toBottomOf="@+id/get_with"
        app:layout_constraintLeft_toLeftOf="parent"
        />

    <TextView
        android:id="@+id/get_gyouretu"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12dp"
        android:textColor="#000"
        android:text="表示行列："
        app:layout_constraintTop_toBottomOf="@+id/get_height"
        app:layout_constraintLeft_toLeftOf="parent"
        />

    <TextureView
        android:id="@+id/texture_view"
        android:layout_width="540px"
        android:layout_height="540px"
        app:layout_constraintTop_toBottomOf="@+id/get_gyouretu"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />


    <Button
        android:id="@+id/opne_button"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="カメラ起動"
        android:layout_marginTop="22dp"
        android:layout_marginBottom="22dp"
        app:layout_constraintTop_toBottomOf="@+id/texture_view"
        app:layout_constraintRight_toLeftOf="@+id/capture_button"
        app:layout_constraintLeft_toLeftOf="parent"
        />

    <Button
        android:id="@+id/capture_button"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="撮影"
        android:layout_marginTop="22dp"
        android:layout_marginBottom="22dp"
        app:layout_constraintTop_toBottomOf="@+id/texture_view"
        app:layout_constraintLeft_toRightOf="@+id/opne_button"
        app:layout_constraintRight_toRightOf="parent"
       />

</androidx.constraintlayout.widget.ConstraintLayout>

```
#### ●AndroidManifest.xml
``` xml AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- パーミッション　追加 -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-feature android:name="android.hardware.camera" android:required="false"/>
    <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.Camera_x_001"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

```

#### ●build.gradle（Module:app）

```grandle

plugins {
    id 'com.android.application'
}

android {
    namespace 'com.example.camera_x_001'
    compileSdk 33

    defaultConfig {
        applicationId "com.example.camera_x_001"
        minSdk 25
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.9.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation project(path: ':sdk')
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

    // === モジュール　追加 ===
   //  def camerax_version = "1.0.0-beta03"
    def camerax_version = '1.0.0-alpha06'
    implementation "androidx.camera:camera-camera2:$camerax_version"
    implementation "androidx.camera:camera-core:${camerax_version}"




}

```