Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package com.simprints.face.capture.models
import android.graphics.Bitmap
import com.simprints.core.tools.time.Timestamp
import com.simprints.face.infra.basebiosdk.detection.Face
import com.simprints.face.infra.basebiosdk.detection.SpoofCheckResult
import com.simprints.infra.images.model.SecuredImageRef
import java.util.UUID

internal data class FaceDetection(
val original: Bitmap,
val bitmap: Bitmap,
val face: Face?,
val status: Status,
Expand All @@ -15,6 +17,7 @@ internal data class FaceDetection(
var isFallback: Boolean = false,
var id: String = UUID.randomUUID().toString(),
var detectionEndTime: Timestamp,
var spoofCheckResult: SpoofCheckResult? = null,
) {
enum class Status {
VALID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ internal class CropToTargetOverlayAnalyzer(
private val previewRect: RectF,
private val overlayWidth: Int,
private val overlayHeight: Int,
private val onImageCropped: (Bitmap) -> Unit,
private val onImageCropped: (original: Bitmap, cropped: Bitmap) -> Unit,
) : ImageAnalysis.Analyzer {
override fun analyze(image: ImageProxy) {
val croppedBitmap = image.use {
val (originalBitmap, croppedBitmap) = image.use {
if (previewRect.isEmpty) return

// Adjust overlay size to be fit-center with the image size
Expand All @@ -37,15 +37,16 @@ internal class CropToTargetOverlayAnalyzer(
val cropTop = offsetY + (previewRect.top * scale).toInt()
val cropHeight = (previewRect.height() * scale).toInt()

Bitmap.createBitmap(
it.toBitmap(),
val imageBitmap = it.toBitmap()
imageBitmap to Bitmap.createBitmap(
imageBitmap,
cropLeft,
cropTop,
cropWidth,
cropHeight,
)
Comment thread
luhmirin-s marked this conversation as resolved.
}
onImageCropped(croppedBitmap)
onImageCropped(originalBitmap, croppedBitmap)
}

private fun getSmallerRatio(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.camera.lifecycle.awaitInstance
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
Expand Down Expand Up @@ -78,6 +79,9 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
private val defaultCaptureProgressColor: Int
get() = ContextCompat.getColor(requireContext(), IDR.color.simprints_blue_grey_light)

private val validationProgressColor: Int
get() = ContextCompat.getColor(requireContext(), IDR.color.simprints_orange)

private val launchPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestPermission(),
) { granted ->
Expand Down Expand Up @@ -181,7 +185,7 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
previewRect = RectF(binding.captureOverlay.circleRect), // create a new instance to avoid threading issues
overlayWidth = binding.captureOverlay.width,
overlayHeight = binding.captureOverlay.height,
onImageCropped = ::analyze,
onImageCropped = { original, cropped -> analyze(original, cropped) },
)

imageAnalyzer.setAnalyzer(cameraExecutor, cropAnalyzer)
Expand Down Expand Up @@ -255,6 +259,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
}

private fun bindViewModel() {
vm.progressBarValue.observe(viewLifecycleOwner) {
binding.captureProgress.value = it
}

vm.currentDetection.observe(viewLifecycleOwner) {
renderCurrentDetection(it)
}
Expand All @@ -263,6 +271,8 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
when (it) {
LiveFeedbackFragmentViewModel.CapturingState.NOT_STARTED -> renderCaptureNotStarted()
LiveFeedbackFragmentViewModel.CapturingState.CAPTURING -> renderCapturing()
LiveFeedbackFragmentViewModel.CapturingState.VALIDATING -> renderValidating()
LiveFeedbackFragmentViewModel.CapturingState.VALIDATION_FAILED -> renderResetAfterValidation()
LiveFeedbackFragmentViewModel.CapturingState.FINISHED -> {
mainVm.captureFinished(vm.sortedQualifyingCaptures)
findNavController().navigateSafely(
Expand All @@ -274,9 +284,12 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
}
}

private fun analyze(image: Bitmap) {
private fun analyze(
original: Bitmap,
cropped: Bitmap,
) {
try {
vm.process(croppedBitmap = image)
vm.process(originalBitmap = original, croppedBitmap = cropped)
} catch (t: Throwable) {
Simber.e("Image analysis crashed", t, tag = FACE_CAPTURE)
// Image analysis is running in bg thread
Expand All @@ -287,6 +300,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
}

private fun renderCurrentDetection(faceDetection: FaceDetection) {
if (vm.isAutoCapture && vm.capturingState.value != LiveFeedbackFragmentViewModel.CapturingState.CAPTURING) {
return
}

when (faceDetection.status) {
FaceDetection.Status.NOFACE -> renderNoFace()
FaceDetection.Status.OFFYAW -> renderFaceNotStraight()
Expand All @@ -304,23 +321,25 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
if (vm.isAutoCapture) {
captureFeedbackBtn.setText(IDR.string.face_capture_start_capture)
captureFeedbackBtn.isChecked = true
captureFeedbackBtn.isClickable = true
} else {
captureFeedbackBtn.setText(IDR.string.face_capture_title_previewing)
}

captureOverlay.drawSemiTransparentTarget()
captureFeedbackTxtExplanation.setTextColor(ContextCompat.getColor(requireContext(), IDR.color.simprints_text_white))

captureFeedbackBtn.isVisible = true
captureFeedbackPermissionButton.isGone = true
setManualCaptureButtonClickable(false)
captureFeedbackTxtExplanation.isGone = true
}
}

private fun renderCapturing() {
binding.apply {
captureOverlay.drawWhiteTarget()
captureFeedbackTxtExplanation.setTextColor(
ContextCompat.getColor(requireContext(), IDR.color.simprints_blue_grey),
)
captureFeedbackTxtExplanation.setTextColor(ContextCompat.getColor(requireContext(), IDR.color.simprints_blue_grey))
captureFeedbackTxtExplanation.isVisible = true

captureProgress.isVisible = true
captureFeedbackBtn.setText(IDR.string.face_capture_prep_begin_button_capturing)
Expand All @@ -330,6 +349,32 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
}
}

private fun renderValidating() {
binding.apply {
captureOverlay.drawWhiteTarget()

captureProgress.progressColor = validationProgressColor
captureProgress.isVisible = true

captureFeedbackBtn.setText(IDR.string.face_capture_title_validating)
captureFeedbackBtn.setCheckedWithLeftDrawable(false)
setManualCaptureButtonClickable(false)

captureFeedbackTxtExplanation.isVisible = false
}
}

private fun renderResetAfterValidation() {
binding.apply {
captureProgress.isInvisible = true
captureFeedbackBtn.setText(IDR.string.face_capture_title_validating_failed)

captureFeedbackTxtExplanation.setTextColor(ContextCompat.getColor(requireContext(), IDR.color.simprints_blue_grey))
captureFeedbackTxtExplanation.setText(IDR.string.face_capture_error_validating_failed)
captureFeedbackTxtExplanation.isVisible = true
}
}

private fun renderValidFace() {
binding.apply {
if (vm.isAutoCapture) {
Expand Down Expand Up @@ -361,7 +406,7 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
true,
ContextCompat.getDrawable(requireContext(), R.drawable.ic_checked_white_18dp),
)
renderProgressBar(false)
captureProgress.progressColor = validCaptureProgressColor
}
}

Expand All @@ -374,7 +419,7 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)

captureFeedbackBtn.setCheckedWithLeftDrawable(false)
setManualCaptureButtonClickable(false)
renderProgressBar(false)
captureProgress.progressColor = defaultCaptureProgressColor
}
}

Expand All @@ -387,7 +432,7 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)

captureFeedbackBtn.setCheckedWithLeftDrawable(false)
setManualCaptureButtonClickable(false)
renderProgressBar(false)
captureProgress.progressColor = defaultCaptureProgressColor
}
}

Expand All @@ -400,7 +445,7 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)

captureFeedbackBtn.setCheckedWithLeftDrawable(false)
setManualCaptureButtonClickable(false)
renderProgressBar(false)
captureProgress.progressColor = defaultCaptureProgressColor
}
}

Expand All @@ -413,7 +458,7 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)

captureFeedbackBtn.setCheckedWithLeftDrawable(false)
setManualCaptureButtonClickable(false)
renderProgressBar(false)
captureProgress.progressColor = defaultCaptureProgressColor
}
}

Expand All @@ -426,15 +471,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)

captureFeedbackBtn.setCheckedWithLeftDrawable(false)
setManualCaptureButtonClickable(false)
renderProgressBar(false)
captureProgress.progressColor = defaultCaptureProgressColor
}
}

private fun FragmentLiveFeedbackBinding.renderProgressBar(validCapture: Boolean) {
captureProgress.progressColor = if (validCapture) validCaptureProgressColor else defaultCaptureProgressColor
captureProgress.value = vm.getNormalizedProgress()
}

private fun FragmentLiveFeedbackBinding.setManualCaptureButtonClickable(clickable: Boolean) {
if (!vm.isAutoCapture) {
captureFeedbackBtn.isClickable = clickable
Expand Down
Loading