Add radial mode, UI controls, front camera, update to API 35

- Add radial/elliptical blur mode with aspect ratio control
- Add UI sliders for blur intensity, falloff, and shape
- Add front camera support with flip button
- Update minimum SDK to API 35 (Android 15)
- Enable landscape orientation (fullSensor)
- Rename app to "Naiv Tilt Shift Camera"
- Set APK output name to naiv-tilt-shift
- Add project specification document

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-01-29 11:13:31 +01:00
commit d3ca23b71c
11 changed files with 679 additions and 94 deletions

View file

@ -1,20 +1,34 @@
package no.naiv.tiltshift.effect
/**
* Blur mode for tilt-shift effect.
*/
enum class BlurMode {
LINEAR, // Horizontal band of focus with blur above and below
RADIAL // Central focus point with blur radiating outward
}
/**
* Parameters controlling the tilt-shift blur effect.
*
* @param mode The blur mode (linear or radial)
* @param angle The rotation angle of the blur gradient in radians (0 = horizontal blur bands)
* @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)
* @param falloff Transition sharpness from focused to blurred (0.0 = sharp edge, 1.0 = gradual)
* @param aspectRatio Ellipse aspect ratio for radial mode (1.0 = circle, <1 = tall, >1 = wide)
*/
data class BlurParameters(
val mode: BlurMode = BlurMode.LINEAR,
val angle: Float = 0f,
val positionX: Float = 0.5f,
val positionY: Float = 0.5f,
val size: Float = 0.3f,
val blurAmount: Float = 0.8f
val blurAmount: Float = 0.8f,
val falloff: Float = 0.5f,
val aspectRatio: Float = 1.0f
) {
companion object {
val DEFAULT = BlurParameters()
@ -24,6 +38,10 @@ data class BlurParameters(
const val MAX_SIZE = 0.8f
const val MIN_BLUR = 0.0f
const val MAX_BLUR = 1.0f
const val MIN_FALLOFF = 0.1f
const val MAX_FALLOFF = 1.0f
const val MIN_ASPECT = 0.3f
const val MAX_ASPECT = 3.0f
}
/**
@ -56,4 +74,18 @@ data class BlurParameters(
fun withBlurAmount(amount: Float): BlurParameters {
return copy(blurAmount = amount.coerceIn(MIN_BLUR, MAX_BLUR))
}
/**
* Returns a copy with the falloff clamped to valid range.
*/
fun withFalloff(newFalloff: Float): BlurParameters {
return copy(falloff = newFalloff.coerceIn(MIN_FALLOFF, MAX_FALLOFF))
}
/**
* Returns a copy with the aspect ratio clamped to valid range.
*/
fun withAspectRatio(ratio: Float): BlurParameters {
return copy(aspectRatio = ratio.coerceIn(MIN_ASPECT, MAX_ASPECT))
}
}

View file

@ -23,11 +23,14 @@ class TiltShiftShader(private val context: Context) {
// Uniform locations
private var uTextureLocation: Int = 0
private var uModeLocation: Int = 0
private var uAngleLocation: Int = 0
private var uPositionXLocation: Int = 0
private var uPositionYLocation: Int = 0
private var uSizeLocation: Int = 0
private var uBlurAmountLocation: Int = 0
private var uFalloffLocation: Int = 0
private var uAspectRatioLocation: Int = 0
private var uResolutionLocation: Int = 0
/**
@ -61,11 +64,14 @@ class TiltShiftShader(private val context: Context) {
// Get uniform locations
uTextureLocation = GLES20.glGetUniformLocation(programId, "uTexture")
uModeLocation = GLES20.glGetUniformLocation(programId, "uMode")
uAngleLocation = GLES20.glGetUniformLocation(programId, "uAngle")
uPositionXLocation = GLES20.glGetUniformLocation(programId, "uPositionX")
uPositionYLocation = GLES20.glGetUniformLocation(programId, "uPositionY")
uSizeLocation = GLES20.glGetUniformLocation(programId, "uSize")
uBlurAmountLocation = GLES20.glGetUniformLocation(programId, "uBlurAmount")
uFalloffLocation = GLES20.glGetUniformLocation(programId, "uFalloff")
uAspectRatioLocation = GLES20.glGetUniformLocation(programId, "uAspectRatio")
uResolutionLocation = GLES20.glGetUniformLocation(programId, "uResolution")
// Clean up shaders (they're linked into program now)
@ -85,11 +91,14 @@ class TiltShiftShader(private val context: Context) {
GLES20.glUniform1i(uTextureLocation, 0)
// Set effect parameters
GLES20.glUniform1i(uModeLocation, if (params.mode == BlurMode.RADIAL) 1 else 0)
GLES20.glUniform1f(uAngleLocation, params.angle)
GLES20.glUniform1f(uPositionXLocation, params.positionX)
GLES20.glUniform1f(uPositionYLocation, params.positionY)
GLES20.glUniform1f(uSizeLocation, params.size)
GLES20.glUniform1f(uBlurAmountLocation, params.blurAmount)
GLES20.glUniform1f(uFalloffLocation, params.falloff)
GLES20.glUniform1f(uAspectRatioLocation, params.aspectRatio)
GLES20.glUniform2f(uResolutionLocation, width.toFloat(), height.toFloat())
}