androidandroid-cameraandroid-drawableandroid-camerax

Blur CameraX preview


I am using CameraX library to build the camera preview for a layout and I want to blur the preview. I search a lot of methods but most of them are outdated or too complicated.

Now I am trying to refer to the blur method in CameraX: How to set background of PreviewView?, but my camera preview still do not blur.

I tried capturing the camera preview as texture view and convert it to bitmap, then use renderscript to blur it. However, my log keep showing "Camera preview bitmap is null".

MainActivity.kt

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_welcome)

        cameraPreview = findViewById(R.id.cameraPreview);
        cameraBtn = findViewById(R.id.cameraButton);
        blurOverlayImageView = findViewById(R.id.blurOverlay);

        // Request camera permission and initialize CameraX

            if (allPermissionsGranted()) {
                startCamera()
            } else {
                ActivityCompat.requestPermissions(
                    this, arrayOf(Manifest.permission.CAMERA), CAMERA_PERM_CODE
                )
            }


        cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture?.addListener({
            val cameraProvider = cameraProviderFuture?.get()
            bindPreview(cameraProvider)
        }, ContextCompat.getMainExecutor(this))

        cameraBtn.setOnClickListener {
            val intent = Intent(this, CameraActivity::class.java)
            startActivity(intent)

        }
    }

    private fun bindPreview(cameraProvider: ProcessCameraProvider?) {
        // Configure the preview use case
        val preview = Preview.Builder().build()
        val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()

        // Connect the preview to the PreviewView
        preview.setSurfaceProvider(cameraPreview.surfaceProvider)

        // Unbind any previous use cases before rebinding
        try {
            cameraProvider?.unbindAll()

            // Bind the camera use cases to the cameraProvider
            val camera = cameraProvider?.bindToLifecycle(this, cameraSelector, preview)
        } catch (exc: Exception) {
            // Handle exceptions here
        }
    }


    private fun allPermissionsGranted() = ActivityCompat.checkSelfPermission(
        this, Manifest.permission.CAMERA
    ) == PackageManager.PERMISSION_GRANTED

    private fun startCamera() {
        val cameraProvider = cameraProviderFuture?.get()
        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

        // Initialize the preview animator for continuous blur
        initializePreviewAnimator()

        val preview = Preview.Builder()
            .build()
            .also { it.setSurfaceProvider(cameraPreview.surfaceProvider) }

        try {
            cameraProvider?.unbindAll()
            val camera = cameraProvider?.bindToLifecycle(
                this, cameraSelector, preview
            )
        } catch (exc: Exception) {
            // Handle exceptions here
        }
    }


    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<String>, grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == CAMERA_PERM_CODE) {
            if (allPermissionsGranted()) {
                startCamera()
            } else {
                Toast.makeText(this, "Camera Permission is Required to Use the camera.", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun blurRenderScript(smallBitmap: Bitmap, radius: Int): Bitmap? {
        val defaultBitmapScale = 0.1f
        val width = (smallBitmap.width * defaultBitmapScale).roundToInt()
        val height = (smallBitmap.height * defaultBitmapScale).roundToInt()
        val inputBitmap = Bitmap.createScaledBitmap(smallBitmap, width, height, false)
        val outputBitmap = Bitmap.createBitmap(inputBitmap)
        val renderScript = RenderScript.create(this)
        val theIntrinsic = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
        val tmpIn = Allocation.createFromBitmap(renderScript, inputBitmap)
        val tmpOut = Allocation.createFromBitmap(renderScript, outputBitmap)
        theIntrinsic.setRadius(radius.toFloat())
        theIntrinsic.setInput(tmpIn)
        theIntrinsic.forEach(tmpOut)
        tmpOut.copyTo(outputBitmap)
        return outputBitmap
    }

    private fun initializePreviewAnimator() {
        // Initializing the preview animator with values to blur
        previewAnimator = ValueAnimator.ofInt(6, 12, 18, 24, 25)
        // Set the animation to repeat infinitely
        previewAnimator.repeatCount = ValueAnimator.INFINITE
        previewAnimator.repeatMode = ValueAnimator.RESTART

        // Setting animation duration for each frame (adjust as needed)
        //previewAnimator.duration = 100 // Set the duration for each frame (in milliseconds)

        // Adding listener for every value update of the animation
        previewAnimator.addUpdateListener { animator ->
            // Get the current blur level from the animator
            val blurLevel = animator.animatedValue as Int
            // Capture the camera preview frame
            val cameraBitmap = captureCameraPreview()
            if (cameraBitmap != null) {
                // Blurring the captured frame using the blurRenderScript function
                val blurredBitmap = blurRenderScript(cameraBitmap, blurLevel)
                // Set the blurred bitmap as the ImageView's image
                blurOverlayImageView.setImageBitmap(blurredBitmap)
            } else {
                Log.e(TAG, "Camera preview bitmap is null")
            }
        }

        // Start the previewAnimator when you start the camera
        previewAnimator.start()
    }

    private fun captureCameraPreview(): Bitmap? {
        val view = cameraPreview.getChildAt(0) as? TextureView
        return view?.bitmap
    }

Solution

  • Converting the PRIV output of preview to Bitmap and using RenderScript to blur is highly inefficient. The recommended way of applying effects with CameraX is the CameraEffect API. You will need to write OpenGL code for that. You can check out the code sample here.

    In that sample code, the OpenGlRenderer file contains the OpenGL code. When you copy this code, update the fragment shader to apply a blur effect. An example of a blur shader can be found in this answer. Or, you can just ask ChatGPT to update the sample code with blur effect.