Author: itfitness links: www.jianshu.com/p/f084082cc…

Contents of this article:

preface

Before, I used Camera to implement a custom Camera filter (Android custom Camera filter), but it ran a little bit slow. This time, I used Camerax to achieve the same effect and found it very smooth. I would like to record it here, and also hope to help students who need it.

Implementation effect

Implementation steps

1. Introduce dependency libraries

The dependency libraries I’ve introduced here are CameraX, GPUImage(filter library), Utilcodex (a useful utility class)

CameraX Core Library Using camerA2 implementation "Androidx. Camera: Camera2:1.0.1 CameraX View class implementation Lifecycle Library implementation "Androidx. camera: CameraX: Lifecycle :1.0.1 "Androidx. Camera: the camera - view: 1.0.0 - alpha27" implementation "jp. Co. Cyberagent. Android. Gpuimage: gpuimage - library: the 1.4.1 ' Implementation 'com. Blankj: utilcodex: 1.30.6'Copy the code
2. Introduce the libyuv

I’m using this example here (github.com/theeasiestw…

3. Write CameraX preview code

The layout code is as follows

<? The XML version = "1.0" encoding = "utf-8"? > <FrameLayout 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"> <androidx.camera.view.PreviewView android:id="@+id/viewFinder" android:layout_width="0dp" android:layout_height="0dp" />  </FrameLayout>Copy the code

The code to start the camera preview in the Activity is as follows, which is basically the official case code provided by Google

class MainActivity : AppCompatActivity() {
    private lateinit var cameraExecutor: ExecutorService
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

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

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

    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 startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener(Runnable {
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
            val preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(viewFinder.surfaceProvider)
                }
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
            try {
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview)
            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }
        }, ContextCompat.getMainExecutor(this))
    }

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

    companion object {
        private const val TAG = "CameraXBasic"
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)

    }
}
Copy the code

This is where the camera preview comes in

4. Add camera data callback

In order to increase the filter effect, we must operate on the camera data. Here, we obtain the modifiable data by obtaining the camera data callback

Val imageAnalyzer = ImageAnalysis.builder () // Set the ratio of callback data to 16:9.settarGetAspectratio (aspectratio.ratio_16_9).build() .also { it.setAnalyzer(cameraExecutor,this@MainActivity) }Copy the code

Here we also need to bind

We also need to implement the ImageAnalysis.Analyzer interface in our Activity, where the data is retrieved from the interface’s callback method, as shown below, where ImageProxy contains the image data

override fun analyze(image: ImageProxy) {

}
Copy the code
5. Process the callback data

We process the image and add the filter in the camera data callback method, but before that we also need to create the GPUImage object and set the filter type

private var bitmap:Bitmap? = null private var gpuImage:GPUImage? Private fun initFilter() {GPUImage = GPUImage(this) GPUImage! .setFilter(GPUImageSketchFilter()) } @SuppressLint("UnsafeOptInUsageError") override fun analyze(image: Var yuvFrame = yuvutils.convertToi420 (image.image!!) {// Convert Android YUV data to libYuv data. YuvFrame = yuvutils.rotate (yuvFrame = yuvutils.rotate (yuvFrame, Bitmap = bitmap.createBitmap (yuvFrame. Width, yuvFrame. Height, yuvFrame. Bitmap.config. ARGB_8888) // Convert the image to Argb format and fill it into Bitmap yuvutils.yuv420toarGB (yuvFrame, Bitmap!!) // Add filter to image with GpuImage bitmap = GpuImage!! . GetBitmapWithFilterApplied (bitmap) / / because this is not the UI thread needs to be updated in the UI thread UI img. Post {img. SetImageBitmap (bitmap) / / close ImageProxy, Image.close ()}}Copy the code
6. Take pictures

So here we have a button to take a picture

<? The XML version = "1.0" encoding = "utf-8"? > <FrameLayout 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"> <androidx.camera.view.PreviewView android:id="@+id/viewFinder" android:layout_width="match_parent" android:layout_height="match_parent" /> <ImageView android:id="@+id/img" android:scaleType="centerCrop" android:layout_width="match_parent" android:layout_height="match_parent"/> <Button android:id="@+id/bt_takepicture" Android: layout_gravity = "center_horizontal | bottom" android: layout_marginBottom = "100 dp" android: text = "photos" android:layout_width="70dp" android:layout_height="70dp"/> </FrameLayout>Copy the code

Then we add the logic to the Activity to take a photo, which turns a Bitmap into an image and saves it to an SD card, using the Utilcodex tool that we introduced earlier. IsTakePhoto turns true when we click the button and then saves the image in the camera callback

bt_takepicture.setOnClickListener {
            isTakePhoto = true
        }
Copy the code

And we add variable controls that don’t handle callback data when taking photos

@SuppressLint("UnsafeOptInUsageError") override fun analyze(image: ImageProxy) { if(! IsTakePhoto){// Convert Android YUV data to libYuv data var yuvFrame = yuvutils.converttoi420 (image.image!!) YuvFrame = yuvutils.rotate (yuvFrame = yuvutils.rotate (yuvFrame, Bitmap = bitmap.createBitmap (yuvFrame. Width, yuvFrame. Height, yuvFrame. Bitmap.config. ARGB_8888) // Convert the image to Argb format and fill it into Bitmap yuvutils.yuv420toarGB (yuvFrame, Bitmap!!) // Add filter to image with GpuImage bitmap = GpuImage!! . GetBitmapWithFilterApplied (bitmap) / / because this is not the UI thread needs to be updated in the UI thread UI img. Post {img. SetImageBitmap (bitmap) if (isTakePhoto) { // Close ImageProxy, Image.close ()}}else{image.close()}} / private fun takePhoto() {Thread{val filePath = File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),"${System.currentTimeMillis()}save.png") ImageUtils. Save (bitmap, filePath. AbsolutePath, Bitmap.Com pressFormat. PNG) ToastUtils. ShowShort (takes "success") isTakePhoto = false  }.start() }Copy the code

Results the following

The saved images are in the following directory

The saved images are shown below

Only continuous learning progress, can not be eliminated by The Times. Pay attention to me, share knowledge every day!