Halo! Selamat datang kembali di seri kami tentang CameraX dan Jetpack Compose. Di postingan sebelumnya, kita telah membahas dasar-dasar penyiapan pratinjau kamera dan menambahkan fungsi ketuk untuk memfokuskan.
🧱 Bagian 1: Membangun pratinjau kamera dasar menggunakan artefak camera-compose baru. Kita telah membahas penanganan izin dan integrasi dasar.
👆 Bagian 2: Menggunakan sistem gestur, grafis, dan coroutine Compose untuk menerapkan ketuk untuk memfokuskan visual.
🔦 Bagian 3 (postingan ini): Mempelajari cara melapisi elemen UI Compose di atas pratinjau kamera untuk pengalaman pengguna yang lebih kaya.
📂 Bagian 4: Menggunakan API adaptif dan framework animasi Compose untuk menganimasikan secara lancar ke dan dari mode tabletop di ponsel foldable.
Dalam postingan ini, kita akan mempelajari sesuatu yang lebih menarik secara visual — menerapkan efek sorotan di atas pratinjau kamera, menggunakan deteksi wajah sebagai dasar untuk efek tersebut. Mengapa? Saya tidak yakin. Namun, tampilannya keren 🙂. Dan yang lebih penting, hal ini menunjukkan cara kita dapat dengan mudah menerjemahkan koordinat sensor ke koordinat UI, sehingga kita dapat menggunakannya di Compose.
Mengaktifkan deteksi wajah
Pertama, mari kita ubah CameraPreviewViewModel untuk mengaktifkan deteksi wajah. Kita akan menggunakan Camera2Interop API, yang memungkinkan kita berinteraksi dengan Camera2 API yang mendasarinya dari CameraX. Hal ini memberi kita kesempatan untuk menggunakan fitur kamera yang tidak diekspos oleh CameraX secara langsung. Kita perlu melakukan perubahan berikut:
- Membuat StateFlow yang berisi batas wajah sebagai daftar
Rect. - Menetapkan opsi permintaan pengambilan
STATISTICS_FACE_DETECT_MODEke FULL, yang mengaktifkan deteksi wajah. - Menetapkan
CaptureCallbackuntuk mendapatkan informasi wajah dari hasil pengambilan.
class CameraPreviewViewModel : ViewModel() { ... private val _sensorFaceRects = MutableStateFlow(listOf<Rect>()) val sensorFaceRects: StateFlow<List<Rect>> = _sensorFaceRects.asStateFlow() private val cameraPreviewUseCase = Preview.Builder() .apply { Camera2Interop.Extender(this) .setCaptureRequestOption( CaptureRequest.STATISTICS_FACE_DETECT_MODE, CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL ) .setSessionCaptureCallback(object : CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted( session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult ) { super.onCaptureCompleted(session, request, result) result.get(CaptureResult.STATISTICS_FACES) ?.map { face -> face.bounds.toComposeRect() } ?.toList() ?.let { faces -> _sensorFaceRects.update { faces } } } }) } .build().apply { ... }
Dengan perubahan ini, model tampilan kita kini memancarkan daftar objek Rect yang mewakili kotak pembatas wajah yang terdeteksi dalam koordinat sensor.
Menerjemahkan koordinat sensor ke koordinat UI
Kotak pembatas wajah yang terdeteksi yang kita simpan di bagian terakhir menggunakan koordinat dalam sistem koordinat sensor. Untuk menggambar kotak pembatas di UI, kita perlu mengubah koordinat ini agar benar dalam sistem koordinat Compose. Kita perlu:
- Mengubah koordinat sensor menjadi koordinat buffer pratinjau
- Mengubah koordinat buffer pratinjau menjadi koordinat UI Compose
Transformasi ini dilakukan menggunakan matriks transformasi. Setiap transformasi memiliki matriksnya sendiri:
SurfaceRequestkita menyimpan instanceTransformationInfo, yang berisi matrikssensorToBufferTranform.CameraXViewfinderkita memilikiCoordinateTransformerterkait. Anda mungkin ingat bahwa kita telah menggunakan transformer ini di postingan blog sebelumnya untuk mengubah koordinat ketuk untuk memfokuskan.
Kita dapat membuat metode helper yang dapat melakukan transformasi untuk kita:
private fun List<Rect>.transformToUiCoords( transformationInfo: SurfaceRequest.TransformationInfo?, uiToBufferCoordinateTransformer: MutableCoordinateTransformer ): List<Rect> = this.map { sensorRect -> val bufferToUiTransformMatrix = Matrix().apply { setFrom(uiToBufferCoordinateTransformer.transformMatrix) invert() } val sensorToBufferTransformMatrix = Matrix().apply { transformationInfo?.let { setFrom(it.sensorToBufferTransform) } } val bufferRect = sensorToBufferTransformMatrix.map(sensorRect) val uiRect = bufferToUiTransformMatrix.map(bufferRect) uiRect }
- Kita melakukan iterasi melalui daftar wajah yang terdeteksi, dan untuk setiap wajah, kita menjalankan transformasi.
CoordinateTransformer.transformMatrixyang kita dapatkan dariCameraXViewfinderkita mengubah koordinat dari UI ke koordinat buffer secara default. Dalam kasus ini, kita ingin matriks berfungsi sebaliknya, mengubah koordinat buffer menjadi koordinat UI. Oleh karena itu, kita menggunakan metodeinvert()untuk membalikkan matriks.- Pertama, kita mengubah wajah dari koordinat sensor ke koordinat buffer menggunakan
sensorToBufferTransformMatrix, lalu mengubah koordinat buffer tersebut ke koordinat UI menggunakanbufferToUiTransformMatrix.
Menerapkan efek sorotan
Sekarang, mari kita perbarui composable CameraPreviewContent untuk menggambar efek sorotan. Kita akan menggunakan composable Canvas untuk menggambar mask gradien di atas pratinjau, sehingga wajah yang terdeteksi terlihat:
@Composable fun CameraPreviewContent( viewModel: CameraPreviewViewModel, modifier: Modifier = Modifier, lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current ) { val surfaceRequest by viewModel.surfaceRequest.collectAsStateWithLifecycle() val sensorFaceRects by viewModel.sensorFaceRects.collectAsStateWithLifecycle() val transformationInfo by produceState<SurfaceRequest.TransformationInfo?>(null, surfaceRequest) { try { surfaceRequest?.setTransformationInfoListener(Runnable::run) { transformationInfo -> value = transformationInfo } awaitCancellation() } finally { surfaceRequest?.clearTransformationInfoListener() } } val shouldSpotlightFaces by remember { derivedStateOf { sensorFaceRects.isNotEmpty() && transformationInfo != null} } val spotlightColor = Color(0xDDE60991) .. surfaceRequest?.let { request -> val coordinateTransformer = remember { MutableCoordinateTransformer() } CameraXViewfinder( surfaceRequest = request, coordinateTransformer = coordinateTransformer, modifier = .. ) AnimatedVisibility(shouldSpotlightFaces, enter = fadeIn(), exit = fadeOut()) { Canvas(Modifier.fillMaxSize()) { val uiFaceRects = sensorFaceRects.transformToUiCoords( transformationInfo = transformationInfo, uiToBufferCoordinateTransformer = coordinateTransformer ) // Fill the whole space with the color drawRect(spotlightColor) // Then extract each face and make it transparent uiFaceRects.forEach { faceRect -> drawRect( Brush.radialGradient( 0.4f to Color.Black, 1f to Color.Transparent, center = faceRect.center, radius = faceRect.minDimension * 2f, ), blendMode = BlendMode.DstOut ) } } } } }
Berikut cara kerjanya:
- Kita mengumpulkan daftar wajah dari model tampilan.
- Untuk memastikan kita tidak mengomposisi ulang seluruh layar setiap kali daftar wajah yang terdeteksi berubah, kita menggunakan
derivedStateOfuntuk melacak apakah ada wajah yang terdeteksi sama sekali. Kemudian, hal ini dapat digunakan denganAnimatedVisibilityuntuk menganimasikan overlay berwarna masuk dan keluar. surfaceRequestberisi informasi yang kita butuhkan untuk mengubah koordinat sensor menjadi koordinat buffer diSurfaceRequest.TransformationInfo. Kita menggunakan fungsiproduceStateuntuk menyiapkan pemroses dalam permintaan platform, dan menghapus pemroses ini saat composable keluar dari pohon komposisi.- Kita menggunakan
Canvasuntuk menggambar persegi panjang berwarna merah muda tembus cahaya yang menutupi seluruh layar. - Kita menunda pembacaan variabel
sensorFaceRectshingga kita berada di dalam blok gambarCanvas. Kemudian, kita mengubah koordinat menjadi koordinat UI. - Kita melakukan iterasi pada wajah yang terdeteksi, dan untuk setiap wajah, kita menggambar gradien radial yang akan membuat bagian dalam persegi panjang wajah menjadi transparan.
- Kita menggunakan
BlendMode.DstOutuntuk memastikan bahwa kita memotong gradien dari persegi panjang berwarna merah muda, sehingga membuat efek sorotan.
Catatan: Saat Anda mengubah kamera menjadi DEFAULT_FRONT_CAMERA Anda akan melihat bahwa sorotan dicerminkan. Masalah ini telah diketahui dan dilacak di Google Issue Tracker.
Hasil
Dengan kode ini, kita memiliki efek sorotan yang berfungsi penuh dan menyoroti wajah yang terdeteksi. Anda dapat menemukan cuplikan kode lengkap di sini.
Efek ini hanyalah permulaan — dengan menggunakan kemampuan Compose, Anda dapat membuat berbagai pengalaman kamera yang menakjubkan secara visual. Dengan kemampuan mengubah koordinat sensor dan buffer menjadi koordinat UI Compose dan kembali, kita dapat menggunakan semua fitur UI Compose dan mengintegrasikannya dengan lancar dengan sistem kamera yang mendasarinya. Dengan animasi, grafis UI canggih, pengelolaan status UI sederhana, dan kontrol gestur penuh, imajinasi Anda adalah batasnya.
Di postingan terakhir dalam seri ini, kita akan membahas cara menggunakan API adaptif dan framework animasi Compose untuk bertransisi dengan lancar antara UI kamera yang berbeda di perangkat foldable. Nantikan kabar terbarunya.
Cuplikan kode di blog ini memiliki lisensi berikut:
// Copyright 2024 Google LLC. SPDX-License-Identifier: Apache-2.0
Terima kasih banyak kepada Nick Butcher, Alex Vanyo, Trevor McGuire, Don Turner dan Lauren Ward atas tinjauan dan masukan yang diberikan. Dapat dilakukan berkat kerja keras Yasith Vidanaarachch.
-
PanduanDalam artikel ini, Anda akan mempelajari cara menggunakan waitUntil test API di Compose untuk menunggu kondisi tertentu terpenuhi.
Jose Alcérreca • 3 menit membaca -
PanduanJetpacker adalah aplikasi showcase teknis yang dibuat tim kami dari awal untuk Google I/O tahun ini (dibuat menggunakan Antigravity). Pada intinya, Jetpacker membantu pengguna merencanakan, menjelajahi, dan menikmati petualangan besar mereka berikutnya.
Jolanda Verhoef • 4 menit membaca -
PanduanSelamat datang kembali di seri postingan blog "Membangun aplikasi Android cerdas" tempat kita mengambil aplikasi Android dasar dan mengubahnya menjadi pengalaman yang dipersonalisasi, cerdas, dan agentic.
Thomas Ezan, Jolanda Verhoef, Caren Chang • 8 menit membaca
Dapatkan insight pengembangan Android terbaru yang dikirim ke kotak masuk Anda setiap minggu.