New feature: Tap-to-focus (#100)
This commit is contained in:
@@ -21,6 +21,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.widget.LinearLayout
|
||||
import androidx.camera.core.CameraControl
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.FocusMeteringAction
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.ImageCapture
|
||||
import androidx.camera.core.ImageCaptureException
|
||||
@@ -69,6 +70,7 @@ import org.fairscan.imageprocessing.Quad
|
||||
import org.fairscan.imageprocessing.scaledTo
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Composable
|
||||
fun CameraPreview(
|
||||
@@ -283,6 +285,7 @@ class CameraCaptureController {
|
||||
var cameraControl: CameraControl? = null
|
||||
var imageCapture: ImageCapture? = null
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
var previewView: PreviewView? = null
|
||||
|
||||
fun shutdown() {
|
||||
executor.shutdown()
|
||||
@@ -302,6 +305,20 @@ class CameraCaptureController {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun tapToFocus(tapOffset: Offset) {
|
||||
val view = previewView ?: return
|
||||
val control = cameraControl ?: return
|
||||
|
||||
val factory = view.meteringPointFactory
|
||||
val point = factory.createPoint(tapOffset.x, tapOffset.y)
|
||||
|
||||
val action = FocusMeteringAction.Builder(point)
|
||||
.setAutoCancelDuration(5, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
control.startFocusAndMetering(action)
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface CameraBindState {
|
||||
|
||||
@@ -24,11 +24,13 @@ import androidx.camera.view.PreviewView
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -66,10 +68,13 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.boundsInWindow
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
@@ -156,7 +161,10 @@ fun CameraScreen(
|
||||
CameraPreview(
|
||||
onImageAnalyzed = onImageAnalyzed,
|
||||
captureController = captureController,
|
||||
onPreviewViewReady = { view -> previewView = view },
|
||||
onPreviewViewReady = { view ->
|
||||
previewView = view
|
||||
captureController.previewView = view
|
||||
},
|
||||
cameraPermission = cameraPermission,
|
||||
onError = { message, throwable -> cameraViewModel.logError(message, throwable) }
|
||||
)
|
||||
@@ -192,7 +200,8 @@ fun CameraScreen(
|
||||
isTorchEnabled = !isTorchEnabled
|
||||
captureController.cameraControl?.enableTorch(isTorchEnabled) },
|
||||
thumbnailCoords = thumbnailCoords,
|
||||
navigation = navigation
|
||||
navigation = navigation,
|
||||
captureController
|
||||
)
|
||||
}
|
||||
|
||||
@@ -207,7 +216,16 @@ private fun CameraScreenScaffold(
|
||||
onTorchSwitched: () -> Unit,
|
||||
thumbnailCoords: MutableState<Offset>,
|
||||
navigation: Navigation,
|
||||
captureController: CameraCaptureController,
|
||||
) {
|
||||
var focusPoint by remember { mutableStateOf<Offset?>(null) }
|
||||
LaunchedEffect(focusPoint) {
|
||||
if (focusPoint != null) {
|
||||
delay(1000)
|
||||
focusPoint = null
|
||||
}
|
||||
}
|
||||
|
||||
var tapCount by remember { mutableLongStateOf(0) }
|
||||
var lastTapTime by remember { mutableLongStateOf(0L) }
|
||||
val tapThreshold = 500L
|
||||
@@ -235,9 +253,16 @@ private fun CameraScreenScaffold(
|
||||
CameraPreviewBox(
|
||||
cameraPreview,
|
||||
cameraUiState,
|
||||
focusPoint,
|
||||
onCapture,
|
||||
onTorchSwitched,
|
||||
modifier.clickable(onClick = onPageCountClick)
|
||||
modifier.pointerInput(Unit) {
|
||||
detectTapGestures { offset ->
|
||||
focusPoint = offset
|
||||
captureController.tapToFocus(offset)
|
||||
onPageCountClick()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
if (cameraUiState.captureState is CaptureState.CapturePreview) {
|
||||
@@ -251,6 +276,7 @@ private fun CameraScreenScaffold(
|
||||
private fun CameraPreviewBox(
|
||||
cameraPreview: @Composable (() -> Unit),
|
||||
cameraUiState: CameraUiState,
|
||||
focusPoint: Offset?,
|
||||
onCapture: () -> Unit,
|
||||
onTorchSwitched: () -> Unit,
|
||||
modifier: Modifier,
|
||||
@@ -266,6 +292,7 @@ private fun CameraPreviewBox(
|
||||
if (cameraUiState.isDebugMode) {
|
||||
MessageBox(cameraUiState.liveAnalysisState.inferenceTime)
|
||||
}
|
||||
FocusOverlay(focusPoint)
|
||||
CaptureButton(
|
||||
onClick = onCapture,
|
||||
modifier = Modifier
|
||||
@@ -427,6 +454,23 @@ private fun CameraPreviewWithOverlay(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FocusOverlay(focusPoint: Offset?) {
|
||||
if (focusPoint == null) return
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
val size = 80f
|
||||
drawRect(
|
||||
color = Color.White,
|
||||
topLeft = Offset(
|
||||
focusPoint.x - size / 2,
|
||||
focusPoint.y - size / 2
|
||||
),
|
||||
size = Size(size, size),
|
||||
style = Stroke(width = 3f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MessageBox(inferenceTime: Long) {
|
||||
Text(
|
||||
@@ -519,7 +563,8 @@ private fun ScreenPreview(captureState: CaptureState, rotationDegrees: Float = 0
|
||||
onDebugModeSwitched = {},
|
||||
onTorchSwitched = {},
|
||||
thumbnailCoords = thumbnailCoords,
|
||||
navigation = dummyNavigation()
|
||||
navigation = dummyNavigation(),
|
||||
captureController = CameraCaptureController()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user