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
}
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.