diff --git a/app/src/main/java/no/naiv/tiltshift/camera/ImageCaptureHandler.kt b/app/src/main/java/no/naiv/tiltshift/camera/ImageCaptureHandler.kt index 704ceb6..c57dc7a 100644 --- a/app/src/main/java/no/naiv/tiltshift/camera/ImageCaptureHandler.kt +++ b/app/src/main/java/no/naiv/tiltshift/camera/ImageCaptureHandler.kt @@ -204,7 +204,8 @@ class ImageCaptureHandler( val mask = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) val pixels = IntArray(width * height) - val centerY = height * params.position + val centerX = width * params.positionX + val centerY = height * params.positionY val focusHalfHeight = height * params.size * 0.5f val transitionHeight = focusHalfHeight * 0.5f @@ -213,8 +214,8 @@ class ImageCaptureHandler( for (y in 0 until height) { for (x in 0 until width) { - // Rotate point around center - val dx = x - width / 2f + // Rotate point around focus center + val dx = x - centerX val dy = y - centerY val rotatedY = -dx * sinAngle + dy * cosAngle diff --git a/app/src/main/java/no/naiv/tiltshift/effect/BlurParameters.kt b/app/src/main/java/no/naiv/tiltshift/effect/BlurParameters.kt index dd6e26d..e145227 100644 --- a/app/src/main/java/no/naiv/tiltshift/effect/BlurParameters.kt +++ b/app/src/main/java/no/naiv/tiltshift/effect/BlurParameters.kt @@ -4,13 +4,15 @@ package no.naiv.tiltshift.effect * Parameters controlling the tilt-shift blur effect. * * @param angle The rotation angle of the blur gradient in radians (0 = horizontal blur bands) - * @param position The center position of the in-focus region (0.0 to 1.0, relative to screen) + * @param positionX The horizontal center of the in-focus region (0.0 to 1.0) + * @param positionY The vertical center of the in-focus region (0.0 to 1.0) * @param size The size of the in-focus region (0.0 to 1.0, as fraction of screen height) * @param blurAmount The intensity of the blur effect (0.0 to 1.0) */ data class BlurParameters( val angle: Float = 0f, - val position: Float = 0.5f, + val positionX: Float = 0.5f, + val positionY: Float = 0.5f, val size: Float = 0.3f, val blurAmount: Float = 0.8f ) { @@ -25,17 +27,20 @@ data class BlurParameters( } /** - * Returns a copy with the angle adjusted by the given delta. + * Returns a copy with the angle set to the given value. */ - fun withAngleDelta(delta: Float): BlurParameters { - return copy(angle = angle + delta) + fun withAngle(newAngle: Float): BlurParameters { + return copy(angle = newAngle) } /** * Returns a copy with the position clamped to valid range. */ - fun withPosition(newPosition: Float): BlurParameters { - return copy(position = newPosition.coerceIn(0f, 1f)) + fun withPosition(newX: Float, newY: Float): BlurParameters { + return copy( + positionX = newX.coerceIn(0f, 1f), + positionY = newY.coerceIn(0f, 1f) + ) } /** diff --git a/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftShader.kt b/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftShader.kt index a63d928..3ed138c 100644 --- a/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftShader.kt +++ b/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftShader.kt @@ -24,7 +24,8 @@ class TiltShiftShader(private val context: Context) { // Uniform locations private var uTextureLocation: Int = 0 private var uAngleLocation: Int = 0 - private var uPositionLocation: Int = 0 + private var uPositionXLocation: Int = 0 + private var uPositionYLocation: Int = 0 private var uSizeLocation: Int = 0 private var uBlurAmountLocation: Int = 0 private var uResolutionLocation: Int = 0 @@ -61,7 +62,8 @@ class TiltShiftShader(private val context: Context) { // Get uniform locations uTextureLocation = GLES20.glGetUniformLocation(programId, "uTexture") uAngleLocation = GLES20.glGetUniformLocation(programId, "uAngle") - uPositionLocation = GLES20.glGetUniformLocation(programId, "uPosition") + uPositionXLocation = GLES20.glGetUniformLocation(programId, "uPositionX") + uPositionYLocation = GLES20.glGetUniformLocation(programId, "uPositionY") uSizeLocation = GLES20.glGetUniformLocation(programId, "uSize") uBlurAmountLocation = GLES20.glGetUniformLocation(programId, "uBlurAmount") uResolutionLocation = GLES20.glGetUniformLocation(programId, "uResolution") @@ -84,7 +86,8 @@ class TiltShiftShader(private val context: Context) { // Set effect parameters GLES20.glUniform1f(uAngleLocation, params.angle) - GLES20.glUniform1f(uPositionLocation, params.position) + GLES20.glUniform1f(uPositionXLocation, params.positionX) + GLES20.glUniform1f(uPositionYLocation, params.positionY) GLES20.glUniform1f(uSizeLocation, params.size) GLES20.glUniform1f(uBlurAmountLocation, params.blurAmount) GLES20.glUniform2f(uResolutionLocation, width.toFloat(), height.toFloat()) diff --git a/app/src/main/java/no/naiv/tiltshift/ui/TiltShiftOverlay.kt b/app/src/main/java/no/naiv/tiltshift/ui/TiltShiftOverlay.kt index ef6406a..298b127 100644 --- a/app/src/main/java/no/naiv/tiltshift/ui/TiltShiftOverlay.kt +++ b/app/src/main/java/no/naiv/tiltshift/ui/TiltShiftOverlay.kt @@ -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()) diff --git a/app/src/main/res/raw/tiltshift_fragment.glsl b/app/src/main/res/raw/tiltshift_fragment.glsl index a051474..e5957f5 100644 --- a/app/src/main/res/raw/tiltshift_fragment.glsl +++ b/app/src/main/res/raw/tiltshift_fragment.glsl @@ -10,7 +10,8 @@ uniform samplerExternalOES uTexture; // Effect parameters uniform float uAngle; // Rotation angle in radians -uniform float uPosition; // Center position of focus (0-1) +uniform float uPositionX; // Horizontal center of focus (0-1) +uniform float uPositionY; // Vertical center of focus (0-1) uniform float uSize; // Size of in-focus region (0-1) uniform float uBlurAmount; // Maximum blur intensity (0-1) uniform vec2 uResolution; // Texture resolution for proper sampling @@ -19,15 +20,15 @@ varying vec2 vTexCoord; // Calculate signed distance from the focus line float focusDistance(vec2 uv) { - // Rotate coordinate system around center - vec2 center = vec2(0.5, uPosition); - vec2 rotated = uv - center; + // Center point of the focus region + vec2 center = vec2(uPositionX, uPositionY); + vec2 offset = uv - center; float cosA = cos(uAngle); float sinA = sin(uAngle); - // After rotation, measure vertical distance from center line - float rotatedY = -rotated.x * sinA + rotated.y * cosA; + // After rotation, measure perpendicular distance from center line + float rotatedY = -offset.x * sinA + offset.y * cosA; return abs(rotatedY); }