Add 2D focus positioning, fix rotation tracking, sync preview with effect

2D positioning:
- Add positionX parameter to BlurParameters (was only Y before)
- Update shader with uPositionX and uPositionY uniforms
- Single-finger drag now moves focus center anywhere on screen
- Update gradient mask generation for capture

Rotation tracking:
- Remove dampening from rotation gesture (1:1 tracking)
- Rotate gesture now directly tracks finger movement
- Preview effect rotates in sync with overlay

Overlay and shader sync:
- Both now use same positionX, positionY, angle parameters
- Preview blur effect matches overlay visualization

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-01-28 15:55:17 +01:00
commit e53155e8ee
5 changed files with 53 additions and 41 deletions

View file

@ -33,17 +33,17 @@ import kotlin.math.sin
*/
private enum class GestureType {
NONE,
DRAG_POSITION, // Single finger drag to move focus position
DRAG_POSITION, // Single finger drag to move focus center
ROTATE, // Two-finger rotation
PINCH_SIZE, // Pinch near blur edges to resize
PINCH_ZOOM // Pinch in center to zoom camera
}
// Sensitivity factors for gesture controls (lower = less sensitive)
private const val POSITION_SENSITIVITY = 0.15f // Drag to move focus line
private const val ROTATION_SENSITIVITY = 0.2f // Two-finger rotation
private const val SIZE_SENSITIVITY = 0.25f // Pinch to resize blur zone
private const val ZOOM_SENSITIVITY = 0.4f // Pinch to zoom camera
// Rotation uses 1:1 tracking (no dampening) for natural feel
private const val POSITION_SENSITIVITY = 0.5f // Drag to move focus center
private const val SIZE_SENSITIVITY = 0.3f // Pinch to resize blur zone
private const val ZOOM_SENSITIVITY = 0.5f // Pinch to zoom camera
/**
* Overlay that shows tilt-shift effect controls and handles gestures.
@ -56,10 +56,10 @@ fun TiltShiftOverlay(
modifier: Modifier = Modifier
) {
var currentGesture by remember { mutableStateOf(GestureType.NONE) }
var initialZoom by remember { mutableFloatStateOf(1f) }
var initialAngle by remember { mutableFloatStateOf(0f) }
var initialSize by remember { mutableFloatStateOf(0.3f) }
var initialPosition by remember { mutableFloatStateOf(0.5f) }
var initialPositionX by remember { mutableFloatStateOf(0.5f) }
var initialPositionY by remember { mutableFloatStateOf(0.5f) }
Canvas(
modifier = modifier
@ -70,15 +70,15 @@ fun TiltShiftOverlay(
currentGesture = GestureType.NONE
var previousCentroid = firstDown.position
var previousPointerCount = 1
var accumulatedRotation = 0f
var accumulatedZoom = 1f
var accumulatedDragX = 0f
var accumulatedDragY = 0f
initialAngle = params.angle
initialSize = params.size
initialPosition = params.position
initialZoom = 1f
initialPositionX = params.positionX
initialPositionY = params.positionY
do {
val event = awaitPointerEvent()
@ -110,8 +110,8 @@ fun TiltShiftOverlay(
when (currentGesture) {
GestureType.ROTATE -> {
// Apply dampening to rotation
accumulatedRotation += rotation * ROTATION_SENSITIVITY
// 1:1 rotation tracking - no dampening
accumulatedRotation += rotation
val newAngle = initialAngle + accumulatedRotation
onParamsChange(params.copy(angle = newAngle))
}
@ -132,24 +132,25 @@ fun TiltShiftOverlay(
}
}
// Single finger
// Single finger - drag to move focus center (2D)
pointers.size == 1 -> {
if (currentGesture == GestureType.NONE) {
currentGesture = GestureType.DRAG_POSITION
}
if (currentGesture == GestureType.DRAG_POSITION) {
// Apply dampening to position drag
val deltaX = (centroid.x - previousCentroid.x) / size.width
val deltaY = (centroid.y - previousCentroid.y) / size.height
accumulatedDragX += deltaX * POSITION_SENSITIVITY
accumulatedDragY += deltaY * POSITION_SENSITIVITY
val newPosition = (initialPosition + accumulatedDragY).coerceIn(0f, 1f)
onParamsChange(params.copy(position = newPosition))
val newX = (initialPositionX + accumulatedDragX).coerceIn(0f, 1f)
val newY = (initialPositionY + accumulatedDragY).coerceIn(0f, 1f)
onParamsChange(params.copy(positionX = newX, positionY = newY))
}
}
}
previousCentroid = centroid
previousPointerCount = pointers.size
// Consume all pointer changes
pointers.forEach { it.consume() }
@ -172,12 +173,13 @@ private fun determineGestureType(
height: Float,
params: BlurParameters
): GestureType {
// Calculate distance from focus center line
val focusCenterY = height * params.position
// Calculate distance from focus center
val focusCenterX = width * params.positionX
val focusCenterY = height * params.positionY
val focusHalfHeight = height * params.size * 0.5f
// Rotate centroid to align with focus line
val dx = centroid.x - width / 2f
val dx = centroid.x - focusCenterX
val dy = centroid.y - focusCenterY
val rotatedY = -dx * sin(params.angle) + dy * cos(params.angle)
@ -207,8 +209,8 @@ private fun DrawScope.drawTiltShiftOverlay(params: BlurParameters) {
val width = size.width
val height = size.height
val centerX = width / 2f
val centerY = height * params.position
val centerX = width * params.positionX
val centerY = height * params.positionY
val focusHalfHeight = height * params.size * 0.5f
val angleDegrees = params.angle * (180f / PI.toFloat())