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 menempatkan elemen UI Compose di atas pratinjau kamera untuk pengalaman pengguna yang lebih kaya.
📂 Bagian 4: Menggunakan API adaptif dan framework animasi Compose untuk menganimasikan transisi ke dan dari mode di atas meja dengan lancar di ponsel perangkat lipat.
Dalam postingan ini, kita akan membahas sesuatu yang lebih menarik secara visual, yaitu menerapkan efek sorotan di atas pratinjau kamera, menggunakan deteksi wajah sebagai dasar untuk efek tersebut. Mengapa, katamu? 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 langsung oleh CameraX. Kita perlu melakukan perubahan berikut:
- Buat StateFlow yang berisi batas wajah sebagai daftar
Rect. - Tetapkan opsi permintaan pengambilan foto
STATISTICS_FACE_DETECT_MODEke FULL, yang memungkinkan deteksi wajah. - Tetapkan
CaptureCallbackuntuk mendapatkan informasi wajah dari hasil pengambilan gambar.
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 merepresentasikan 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 harus:
- Ubah 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.CameraXViewfinderkami memilikiCoordinateTransformerterkait. Anda mungkin ingat bahwa kita telah menggunakan transformer ini dalam postingan blog sebelumnya untuk mengubah koordinat ketuk untuk fokus.
Kita dapat membuat metode bantuan 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 bekerja dengan cara lain, yaitu 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 merekomposisi seluruh layar setiap kali daftar wajah yang terdeteksi berubah, kita menggunakan
derivedStateOfuntuk melacak apakah ada wajah yang terdeteksi. Kemudian, hal ini dapat digunakan denganAnimatedVisibilityuntuk menganimasikan masuk dan keluarnya overlay berwarna. surfaceRequestberisi informasi yang kita butuhkan untuk mengubah koordinat sensor menjadi koordinat buffer diSurfaceRequest.TransformationInfo. Kita menggunakan fungsiproduceStateuntuk menyiapkan pemroses dalam permintaan permukaan, dan menghapus pemroses ini saat composable keluar dari hierarki komposisi.- Kita menggunakan
Canvasuntuk menggambar persegi panjang merah muda transparan 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 transparan.
- Kita menggunakan
BlendMode.DstOutuntuk memastikan bahwa kita memotong gradien dari persegi panjang merah muda, sehingga menciptakan efek sorotan.
Catatan: Saat Anda mengubah kamera ke DEFAULT_FRONT_CAMERA, Anda akan melihat bahwa sorotan dicerminkan. Ini adalah masalah umum, yang dilacak di Pelacak Masalah Google.
Hasil
Dengan kode ini, kita memiliki efek sorotan yang berfungsi penuh yang menyoroti wajah yang terdeteksi. Anda dapat menemukan cuplikan kode lengkap di sini.
Efek ini hanyalah permulaan — dengan menggunakan kecanggihan Compose, Anda dapat menciptakan berbagai pengalaman kamera yang memukau secara visual. Dengan dapat mengubah koordinat sensor dan buffer menjadi koordinat UI Compose dan sebaliknya, kita dapat memanfaatkan semua fitur UI Compose dan mengintegrasikannya secara lancar dengan sistem kamera yang mendasarinya. Dengan animasi, grafis UI tingkat lanjut, pengelolaan status UI sederhana, dan kontrol gestur penuh, imajinasi Anda adalah batasnya.
Di postingan terakhir dalam seri ini, kita akan mempelajari cara menggunakan API adaptif dan framework animasi Compose untuk melakukan transisi yang lancar antara berbagai UI kamera di perangkat foldable. Nantikan kabar terbarunya.
Cuplikan kode dalam 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 peninjauan dan masukan yang diberikan. Dihadirkan berkat kerja keras Yasith Vidanaarachch.
Lanjutkan membaca
-
Petunjuk
Dalam artikel ini, Anda akan mempelajari cara menggunakan waitUntil test API di Compose untuk menunggu hingga kondisi tertentu terpenuhi.
Jose Alcérreca • Waktu baca 3 menit
-
Petunjuk
Sebagai pengakuan bahwa pengurasan baterai yang berlebihan menjadi perhatian utama bagi pengguna Android, Google telah mengambil langkah-langkah signifikan untuk membantu developer membangun aplikasi yang lebih hemat daya.
Alice Yuan • Waktu baca: 8 menit
-
Petunjuk
Kami ingin memberi Anda contoh fitur yang didukung AI menggunakan model di perangkat dan Cloud, serta menginspirasi Anda untuk menciptakan pengalaman yang menyenangkan bagi pengguna Anda.
Thomas Ezan, Ivy Knight • Waktu baca: 2 menit
Terus dapatkan informasi
Dapatkan insight pengembangan Android terbaru yang dikirim ke kotak masuk Anda setiap minggu.