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
10
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
10
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#212121"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
</vector>
|
||||
49
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
49
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
|
||||
<!-- Camera body -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M30,40 L78,40 Q82,40 82,44 L82,72 Q82,76 78,76 L30,76 Q26,76 26,72 L26,44 Q26,40 30,40 Z"/>
|
||||
|
||||
<!-- Camera lens -->
|
||||
<path
|
||||
android:fillColor="#424242"
|
||||
android:pathData="M54,58 m-14,0 a14,14 0,1 1,28 0 a14,14 0,1 1,-28 0"/>
|
||||
|
||||
<path
|
||||
android:fillColor="#616161"
|
||||
android:pathData="M54,58 m-10,0 a10,10 0,1 1,20 0 a10,10 0,1 1,-20 0"/>
|
||||
|
||||
<!-- Lens reflection -->
|
||||
<path
|
||||
android:fillColor="#90CAF9"
|
||||
android:pathData="M48,52 Q52,48 58,52 Q54,56 48,52"/>
|
||||
|
||||
<!-- Flash -->
|
||||
<path
|
||||
android:fillColor="#FFB300"
|
||||
android:pathData="M70,44 L76,44 L76,50 L70,50 Z"/>
|
||||
|
||||
<!-- Tilt-shift blur indication (gradient bars) -->
|
||||
<path
|
||||
android:fillColor="#80FFFFFF"
|
||||
android:pathData="M26,32 L82,32 L82,36 L26,36 Z"/>
|
||||
|
||||
<path
|
||||
android:fillColor="#40FFFFFF"
|
||||
android:pathData="M26,28 L82,28 L82,30 L26,30 Z"/>
|
||||
|
||||
<path
|
||||
android:fillColor="#80FFFFFF"
|
||||
android:pathData="M26,80 L82,80 L82,84 L26,84 Z"/>
|
||||
|
||||
<path
|
||||
android:fillColor="#40FFFFFF"
|
||||
android:pathData="M26,86 L82,86 L82,88 L26,88 Z"/>
|
||||
|
||||
</vector>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
90
app/src/main/res/raw/tiltshift_fragment.glsl
Normal file
90
app/src/main/res/raw/tiltshift_fragment.glsl
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Fragment shader for tilt-shift effect
|
||||
// Applies gradient blur based on distance from focus line
|
||||
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
|
||||
precision mediump float;
|
||||
|
||||
// Camera texture (external texture for camera preview)
|
||||
uniform samplerExternalOES uTexture;
|
||||
|
||||
// Effect parameters
|
||||
uniform float uAngle; // Rotation angle in radians
|
||||
uniform float uPosition; // Center position 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
|
||||
|
||||
varying vec2 vTexCoord;
|
||||
|
||||
// Blur kernel size (must be odd)
|
||||
const int KERNEL_SIZE = 9;
|
||||
const float KERNEL_HALF = 4.0;
|
||||
|
||||
// Gaussian weights for 9-tap blur (sigma ~= 2.0)
|
||||
const float weights[9] = float[](
|
||||
0.0162, 0.0540, 0.1216, 0.1933, 0.2258,
|
||||
0.1933, 0.1216, 0.0540, 0.0162
|
||||
);
|
||||
|
||||
// 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;
|
||||
|
||||
float cosA = cos(uAngle);
|
||||
float sinA = sin(uAngle);
|
||||
|
||||
// After rotation, measure vertical distance from center line
|
||||
float rotatedY = -rotated.x * sinA + rotated.y * cosA;
|
||||
|
||||
return abs(rotatedY);
|
||||
}
|
||||
|
||||
// Calculate blur factor based on distance from focus
|
||||
float blurFactor(float dist) {
|
||||
// Smooth transition from in-focus to blurred
|
||||
float halfSize = uSize * 0.5;
|
||||
float transitionSize = halfSize * 0.5;
|
||||
|
||||
if (dist < halfSize) {
|
||||
return 0.0; // In focus region
|
||||
}
|
||||
|
||||
// Smooth falloff using smoothstep
|
||||
float normalizedDist = (dist - halfSize) / transitionSize;
|
||||
return smoothstep(0.0, 1.0, normalizedDist) * uBlurAmount;
|
||||
}
|
||||
|
||||
// Sample with Gaussian blur
|
||||
vec4 sampleBlurred(vec2 uv, float blur) {
|
||||
if (blur < 0.01) {
|
||||
return texture2D(uTexture, uv);
|
||||
}
|
||||
|
||||
vec4 color = vec4(0.0);
|
||||
vec2 texelSize = 1.0 / uResolution;
|
||||
|
||||
// Blur direction perpendicular to focus line
|
||||
float blurAngle = uAngle + 1.5707963; // +90 degrees
|
||||
vec2 blurDir = vec2(cos(blurAngle), sin(blurAngle));
|
||||
|
||||
// Scale blur radius by blur amount
|
||||
float radius = blur * 20.0;
|
||||
|
||||
for (int i = 0; i < KERNEL_SIZE; i++) {
|
||||
float offset = (float(i) - KERNEL_HALF);
|
||||
vec2 samplePos = uv + blurDir * texelSize * offset * radius;
|
||||
color += texture2D(uTexture, samplePos) * weights[i];
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float dist = focusDistance(vTexCoord);
|
||||
float blur = blurFactor(dist);
|
||||
|
||||
gl_FragColor = sampleBlurred(vTexCoord, blur);
|
||||
}
|
||||
12
app/src/main/res/raw/tiltshift_vertex.glsl
Normal file
12
app/src/main/res/raw/tiltshift_vertex.glsl
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Vertex shader for tilt-shift effect
|
||||
// Passes through position and calculates texture coordinates
|
||||
|
||||
attribute vec4 aPosition;
|
||||
attribute vec2 aTexCoord;
|
||||
|
||||
varying vec2 vTexCoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = aPosition;
|
||||
vTexCoord = aTexCoord;
|
||||
}
|
||||
6
app/src/main/res/values/colors.xml
Normal file
6
app/src/main/res/values/colors.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="tiltshift_accent">#FFFFB300</color>
|
||||
</resources>
|
||||
4
app/src/main/res/values/strings.xml
Normal file
4
app/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Tilt-Shift Camera</string>
|
||||
</resources>
|
||||
8
app/src/main/res/values/themes.xml
Normal file
8
app/src/main/res/values/themes.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.TiltShiftCamera" parent="android:Theme.Material.NoActionBar">
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue