Initial implementation of Tilt-Shift Camera Android app
A dedicated camera app for tilt-shift photography with: - Real-time OpenGL ES 2.0 shader-based blur preview - Touch gesture controls (drag, rotate, pinch) for adjusting effect - CameraX integration for camera preview and high-res capture - EXIF metadata with GPS location support - MediaStore integration for saving to gallery - Jetpack Compose UI with haptic feedback Tech stack: Kotlin, CameraX, OpenGL ES 2.0, Jetpack Compose Min SDK: 26 (Android 8.0), Target SDK: 35 (Android 15) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
07e10ac9c3
38 changed files with 3489 additions and 0 deletions
159
app/src/main/java/no/naiv/tiltshift/effect/TiltShiftRenderer.kt
Normal file
159
app/src/main/java/no/naiv/tiltshift/effect/TiltShiftRenderer.kt
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
package no.naiv.tiltshift.effect
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.SurfaceTexture
|
||||
import android.opengl.GLES11Ext
|
||||
import android.opengl.GLES20
|
||||
import android.opengl.GLSurfaceView
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.FloatBuffer
|
||||
import javax.microedition.khronos.egl.EGLConfig
|
||||
import javax.microedition.khronos.opengles.GL10
|
||||
|
||||
/**
|
||||
* OpenGL renderer for applying tilt-shift effect to camera preview.
|
||||
*
|
||||
* This renderer receives camera frames via SurfaceTexture and applies
|
||||
* the tilt-shift blur effect using GLSL shaders.
|
||||
*/
|
||||
class TiltShiftRenderer(
|
||||
private val context: Context,
|
||||
private val onSurfaceTextureAvailable: (SurfaceTexture) -> Unit
|
||||
) : GLSurfaceView.Renderer {
|
||||
|
||||
private lateinit var shader: TiltShiftShader
|
||||
private var surfaceTexture: SurfaceTexture? = null
|
||||
private var cameraTextureId: Int = 0
|
||||
|
||||
private lateinit var vertexBuffer: FloatBuffer
|
||||
private lateinit var texCoordBuffer: FloatBuffer
|
||||
|
||||
private var surfaceWidth: Int = 0
|
||||
private var surfaceHeight: Int = 0
|
||||
|
||||
// Current effect parameters (updated from UI thread)
|
||||
@Volatile
|
||||
var blurParameters: BlurParameters = BlurParameters.DEFAULT
|
||||
|
||||
// Quad vertices (full screen)
|
||||
private val vertices = floatArrayOf(
|
||||
-1f, -1f, // Bottom left
|
||||
1f, -1f, // Bottom right
|
||||
-1f, 1f, // Top left
|
||||
1f, 1f // Top right
|
||||
)
|
||||
|
||||
// Texture coordinates (flip Y for camera)
|
||||
private val texCoords = floatArrayOf(
|
||||
0f, 1f, // Bottom left
|
||||
1f, 1f, // Bottom right
|
||||
0f, 0f, // Top left
|
||||
1f, 0f // Top right
|
||||
)
|
||||
|
||||
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
|
||||
GLES20.glClearColor(0f, 0f, 0f, 1f)
|
||||
|
||||
// Initialize shader
|
||||
shader = TiltShiftShader(context)
|
||||
shader.initialize()
|
||||
|
||||
// Create vertex buffer
|
||||
vertexBuffer = ByteBuffer.allocateDirect(vertices.size * 4)
|
||||
.order(ByteOrder.nativeOrder())
|
||||
.asFloatBuffer()
|
||||
.put(vertices)
|
||||
vertexBuffer.position(0)
|
||||
|
||||
// Create texture coordinate buffer
|
||||
texCoordBuffer = ByteBuffer.allocateDirect(texCoords.size * 4)
|
||||
.order(ByteOrder.nativeOrder())
|
||||
.asFloatBuffer()
|
||||
.put(texCoords)
|
||||
texCoordBuffer.position(0)
|
||||
|
||||
// Create camera texture
|
||||
val textures = IntArray(1)
|
||||
GLES20.glGenTextures(1, textures, 0)
|
||||
cameraTextureId = textures[0]
|
||||
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTextureId)
|
||||
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR)
|
||||
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR)
|
||||
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
|
||||
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
|
||||
|
||||
// Create SurfaceTexture for camera frames
|
||||
surfaceTexture = SurfaceTexture(cameraTextureId).also {
|
||||
onSurfaceTextureAvailable(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
|
||||
GLES20.glViewport(0, 0, width, height)
|
||||
surfaceWidth = width
|
||||
surfaceHeight = height
|
||||
}
|
||||
|
||||
override fun onDrawFrame(gl: GL10?) {
|
||||
// Update texture with latest camera frame
|
||||
surfaceTexture?.updateTexImage()
|
||||
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
|
||||
// Use shader and set parameters
|
||||
shader.use(cameraTextureId, blurParameters, surfaceWidth, surfaceHeight)
|
||||
|
||||
// Set vertex positions
|
||||
GLES20.glEnableVertexAttribArray(shader.aPositionLocation)
|
||||
GLES20.glVertexAttribPointer(
|
||||
shader.aPositionLocation,
|
||||
2,
|
||||
GLES20.GL_FLOAT,
|
||||
false,
|
||||
0,
|
||||
vertexBuffer
|
||||
)
|
||||
|
||||
// Set texture coordinates
|
||||
GLES20.glEnableVertexAttribArray(shader.aTexCoordLocation)
|
||||
GLES20.glVertexAttribPointer(
|
||||
shader.aTexCoordLocation,
|
||||
2,
|
||||
GLES20.GL_FLOAT,
|
||||
false,
|
||||
0,
|
||||
texCoordBuffer
|
||||
)
|
||||
|
||||
// Draw quad
|
||||
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
|
||||
|
||||
// Cleanup
|
||||
GLES20.glDisableVertexAttribArray(shader.aPositionLocation)
|
||||
GLES20.glDisableVertexAttribArray(shader.aTexCoordLocation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates blur parameters. Thread-safe.
|
||||
*/
|
||||
fun updateParameters(params: BlurParameters) {
|
||||
blurParameters = params
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases OpenGL resources.
|
||||
* Must be called from GL thread.
|
||||
*/
|
||||
fun release() {
|
||||
shader.release()
|
||||
surfaceTexture?.release()
|
||||
surfaceTexture = null
|
||||
|
||||
if (cameraTextureId != 0) {
|
||||
GLES20.glDeleteTextures(1, intArrayOf(cameraTextureId), 0)
|
||||
cameraTextureId = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue