Pandiyan Mani
4 min readNov 15, 2021

--

How to implement camera Capture and save Image using Jetpack compose

For this we need to implement camerax in our app level gradle file

def camerax_version = "1.0.2"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-view:1.0.0-alpha29"

Followed by our permission implementation

implementation "com.google.accompanist:accompanist-permissions:0.19.0"

regarding permission you can check my previous post on

Now lets start with getting permission

val cameraPermissionState = rememberPermissionState(
permission = Manifest.permission.CAMERA)

Button(
onClick = {
cameraPermissionState.launchPermissionRequest()
}
) {
Text(text = "Permission")
}

here permissionstate will be used if you want to trigger single permission for mutliple permission you can use rememberMultiplePermissionsState and you can listOf all your permission inside. Currently i am using singlee permission that is CAMERA so i passed my camera permission inside rememberPermissionState on Button onclick i triggered the permission.Another thing is add prmission on manifest file

<uses-permission android:name="android.permission.CAMERA"/>

So we are done with permission part lets see how we start camera.First i am going to use Box scope for designing what is Box Scope its similar to FrameLayout in XML we can place over that layout iniside that i am creating Androidview ny using AndroidView takes a viewblock it s provided with context so we dont need to use Context

AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { ctx ->
val previewView = PreviewView(ctx)
cameraProviderFuture.addListener({
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.apply {
setAnalyzer(executor, FaceAnalyzer())
}
imageCapture = ImageCapture.Builder()
.setTargetRotation(previewView.display.rotation)
.build()

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

cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
imageCapture,
preview
)
}, executor)
preview = Preview.Builder().build().also {
it
.setSurfaceProvider(previewView.surfaceProvider)
}
previewView
}
)

In the above you can see code with Camerax.

Final Code

class MainActivity : ComponentActivity() {
@ExperimentalPermissionsApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CameraTheme {
Surface(color = MaterialTheme.colors.background) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(10.dp))

val cameraPermissionState = rememberPermissionState(
permission = Manifest.permission.CAMERA)

Button(
onClick = {
cameraPermissionState.launchPermissionRequest()
}
) {
Text(text = "Permission")
}

Spacer(modifier = Modifier.height(30.dp))

CamerOpen(getDirectory())
}
}
}
}
}

//Store the capture image
private fun getDirectory(): File {
val mediaDir = externalMediaDirs.firstOrNull()?.let {
File(it, resources.getString(R.string.app_name)).apply { mkdirs() }
}
return if (mediaDir != null && mediaDir.exists())
mediaDir else filesDir
}
}

CamerOpen Composable

package com.jetpack.camera

import android.annotation.SuppressLint
import android.content.Context
import android.net.Uri
import android.widget.Toast
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.jetpack.camera.ui.theme.Purple500
import java.io.File
import java.text.SimpleDateFormat
import java.util.*

@Composable
fun CamerOpen(directory: File) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current

SimpleCameraPreview(
modifier = Modifier.fillMaxSize(),
context = context,
lifecycleOwner = lifecycleOwner,
outputDirectory = directory,
onMediaCaptured = { url -> }
)
}

@Composable
fun SimpleCameraPreview(
modifier: Modifier = Modifier,
context: Context,
lifecycleOwner: LifecycleOwner,
outputDirectory: File,
onMediaCaptured: (Uri?) -> Unit
) {
val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
var imageCapture: ImageCapture? by remember { mutableStateOf(null) }
var preview by remember { mutableStateOf<Preview?>(null) }
val camera: Camera? = null
var lensFacing by remember { mutableStateOf(CameraSelector.LENS_FACING_BACK) }
var flashEnabled by remember { mutableStateOf(false) }
var flashRes by remember { mutableStateOf(R.drawable.ic_outline_flashlight_on) }
val executor = ContextCompat.getMainExecutor(context)
var cameraSelector: CameraSelector?
val cameraProvider = cameraProviderFuture.get()

Box {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { ctx ->
val previewView = PreviewView(ctx)
cameraProviderFuture.addListener({
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.apply {
setAnalyzer(executor, FaceAnalyzer())
}
imageCapture = ImageCapture.Builder()
.setTargetRotation(previewView.display.rotation)
.build()

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

cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
imageCapture,
preview
)
}, executor)
preview = Preview.Builder().build().also {
it
.setSurfaceProvider(previewView.surfaceProvider)
}
previewView
}
)

Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(15.dp)
.align(Alignment.TopStart)
) {
IconButton(
onClick = {
Toast.makeText(context, "Back Clicked", Toast.LENGTH_SHORT).show()
}
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "back arrow",
tint = MaterialTheme.colors.surface
)
}
}

Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(15.dp)
.clip(RoundedCornerShape(15.dp))
.background(Purple500, RoundedCornerShape(15.dp))
.padding(8.dp)
.align(Alignment.BottomCenter)
) {
IconButton(
onClick = {
camera?.let {
if (it.cameraInfo.hasFlashUnit()) {
flashEnabled = !flashEnabled
flashRes = if (flashEnabled) R.drawable.ic_outline_flashlight_off else
R.drawable.ic_outline_flashlight_on
it.cameraControl.enableTorch(flashEnabled)
}
}
}
) {
Icon(
painter = painterResource(id = flashRes),
contentDescription = "",
modifier = Modifier.size(35.dp),
tint = MaterialTheme.colors.surface
)
}

Button(
onClick = {
val imgCapture = imageCapture ?: return@Button
val photoFile = File(
outputDirectory,
SimpleDateFormat("yyyyMMDD-HHmmss", Locale.US)
.format(System.currentTimeMillis()) + ".jpg"
)
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
imgCapture.takePicture(
outputOptions,
executor,
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
onMediaCaptured(Uri.fromFile(photoFile))
}

override fun onError(exception: ImageCaptureException) {
Toast.makeText(context, "Something went wrong", Toast.LENGTH_SHORT).show()
}
}
)
},
modifier = Modifier
.size(70.dp)
.background(Purple500, CircleShape)
.shadow(4.dp, CircleShape)
.clip(CircleShape)
.border(5.dp, Color.LightGray, CircleShape),
colors = ButtonDefaults.buttonColors(backgroundColor = Purple500),
) {

}

IconButton(
onClick = {
lensFacing = if (lensFacing == CameraSelector.LENS_FACING_BACK) CameraSelector.LENS_FACING_FRONT
else CameraSelector.LENS_FACING_BACK

cameraSelector = CameraSelector.Builder()
.requireLensFacing(lensFacing)
.build()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector as CameraSelector,
imageCapture,
preview
)
}
) {
Icon(
painter = painterResource(id = R.drawable.ic_outline_rotate),
contentDescription = "",
modifier = Modifier.size(35.dp),
tint = MaterialTheme.colors.surface
)
}
}
}
}

private class FaceAnalyzer(): ImageAnalysis.Analyzer {
@SuppressLint("UnsafeOptInUsageError")
override fun analyze(image: ImageProxy) {
val imagePic = image.image
imagePic?.close()
}
}

--

--