본문 바로가기

Android/개인 기록

[Compose] Custom CameraView 만들기

이미지 업로드 관련 서버가 자꾸 맛이 가는 걸 언제 어디서나 확인할 수 있도록 하기 위해 서비스 중인 앱에다가 PC 없이도 로그를 볼 수 있는 스크린을 만들고 있다.
현재 서비스 중인 앱의 카메라 기능은 전부 XML으로 커스텀뷰를 만들어서 사용 중인데, 요즘 Compose를 공부 중이라 그런지 이번 기능은 괜히 컴포즈로 만들어 보고 싶다는 생각이 들었다. 그래서 한번 만들어 봤다.

 

일단 사진을 촬영해서 서버에 전송해야하는데.. 진짜 딱 프리뷰-촬영만 만들면 되는데, 갑자기 난독증이 도진건지 설명이 어려운건지 Compose Camera 관련 자료들이 이해하기 넘모 어려웠다.

그래서 여차저차 간단하게 딱 프리뷰-촬영 기능만을 구현한 코드를 기록해 두려고 한다.

 

[❗️주의❗️]
진짜 나만 쓴다는 생각으로 작성한 코드라 가독성이나 null safe 안전성은 크게 고려하지 않고 작성했다. 때문에 실제 서비스에서 그대로 사용하면 문제가 발생할 있다.
문제 발생 공유해주시면 같이 고민해서 해결해 보도록합시다.

 


코드

@Composable
fun CameraComponent(
    modifier: Modifier = Modifier,
    cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA,
    takeAction: Boolean,
    receiveImageUrl: (String?) -> Unit
) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val imageCapture = remember { ImageCapture.Builder().build() }
    val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
    val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
    val preview: Preview = Preview.Builder().build()
    val scope = lifecycleOwner.lifecycle.coroutineScope

    // 카메라 바인딩
    // cameraProvicer를 통해 ProcessCameraProvider를 비동기적으로 가져와 카메라의 생명주기를 현재 카메라 컴포넌트 생명주기와 바인딩
    // preview, imageCapture를 lifecycleOwner에 바인딩하여 카메라 미리보기와 이미지 캡쳐를 구현
    // 카메라 세션 준비가 완료되면 프리뷰를 화면에 표시
    LaunchedEffect(cameraProviderFuture) {
        cameraProviderFuture.addListener({
            try {
                cameraProvider.unbindAll()

                cameraProvider.bindToLifecycle(
                    lifecycleOwner,
                    cameraSelector,
                    preview,
                    imageCapture
                )
            } catch (exc: Exception) {
                Timber.e("Camera binding failed")
            }
        }, ContextCompat.getMainExecutor(context))
    }

    // takeAction 상태가 true로 변경될 때, imageCapture의 takePicture 메서드를 호출하여 이미지 캡쳐
    // 촬영 성공 시, onCaptureSuccess 콜백에서 이미지를 비트맵으로 변환하고, 사용자 지정 작업 수행
    // 실패 시, onError 콜백에서 실패 이유를 로그로 출력하고, 사용자 지정 작업 수행
    LaunchedEffect(takeAction) {
        if (takeAction) {
            imageCapture.takePicture(
                ContextCompat.getMainExecutor(context),
                object : ImageCapture.OnImageCapturedCallback() {
                    override fun onCaptureSuccess(image: ImageProxy) {
                        val bitmap = imageProxyToBitmap(image)
                        image.close()
                        scope.launch(Dispatchers.IO) {
                            val url = getImageUrl(bitmap, imageName, imageType)
                            receiveImageUrl(url)
                        }
                    }

                    override fun onError(exception: ImageCaptureException) {
                        Timber.e("Image capture failed ${exception.message}")
                        receiveImageUrl(null)
                    }
                }
            )
        }
    }

    // PrwviewView로 카메라 미리보기를 구현
    AndroidView(
        modifier = modifier.clipToBounds(),
        factory = { ctx ->
            PreviewView(ctx).apply {
                implementationMode = PreviewView.ImplementationMode.COMPATIBLE
            }
        },
        update = { previewView ->
            preview.setSurfaceProvider(previewView.surfaceProvider)
        }
    )

 

카메라 비율을 일정하게 고정시키고 싶으면

clipToBounds()를 지우면 된다.

 

본인은 지정된 크기에 맞게 그려지게 하고싶었는데, 프리뷰 비율이 자꾸 고정되서 이상하게 그려져서 오히려 문제였다.

갓GPT도 계속 헛소리만 해주고 답답해서 정신 나갈뻔했다..

한참 고민하다가 Image 프레임에 맞춰 자를 , modifier clipToBounds() 해주던게 생각나서 해봤는데 원하는 모양새로 조정되었다.(굿)

 

clipToBound() 적용 전 -> 후

 

 

코드 한줄만 올려두고 글을 마무리하는 것은 너무 도움이 안되는 것 같아서 간단하게 구현한 카메라 샘플 앱 링크를 첨부한다.

https://github.com/parade621/Compose_Basic_Camera

 

GitHub - parade621/Compose_Basic_Camera: Jetpack Compose로 만든 기본 카메라

Jetpack Compose로 만든 기본 카메라. Contribute to parade621/Compose_Basic_Camera development by creating an account on GitHub.

github.com