2026-01-28 15:26:41 +01:00
|
|
|
package no.naiv.tiltshift.effect
|
|
|
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
|
import android.opengl.GLES11Ext
|
|
|
|
|
import android.opengl.GLES20
|
|
|
|
|
import no.naiv.tiltshift.R
|
|
|
|
|
import java.io.BufferedReader
|
|
|
|
|
import java.io.InputStreamReader
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
import kotlin.math.cos
|
|
|
|
|
import kotlin.math.sin
|
2026-01-28 15:26:41 +01:00
|
|
|
|
|
|
|
|
/**
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
* Manages OpenGL shader programs for the two-pass tilt-shift effect.
|
|
|
|
|
*
|
|
|
|
|
* Two programs:
|
|
|
|
|
* - **Passthrough**: copies camera texture (external OES) to an FBO, handling the
|
|
|
|
|
* coordinate transform via vertex/texcoord setup.
|
|
|
|
|
* - **Blur**: applies a directional Gaussian blur with tilt-shift mask.
|
|
|
|
|
* Used twice per frame (horizontal then vertical) via the [uBlurDirection] uniform.
|
2026-01-28 15:26:41 +01:00
|
|
|
*/
|
|
|
|
|
class TiltShiftShader(private val context: Context) {
|
|
|
|
|
|
2026-05-07 16:31:43 +02:00
|
|
|
private companion object {
|
|
|
|
|
val IDENTITY_MATRIX = floatArrayOf(
|
|
|
|
|
1f, 0f, 0f, 0f,
|
|
|
|
|
0f, 1f, 0f, 0f,
|
|
|
|
|
0f, 0f, 1f, 0f,
|
|
|
|
|
0f, 0f, 0f, 1f
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
// --- Passthrough program (camera → FBO) ---
|
|
|
|
|
|
|
|
|
|
private var passthroughProgramId: Int = 0
|
2026-01-28 15:26:41 +01:00
|
|
|
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
var passthroughPositionLoc: Int = 0
|
2026-01-28 15:26:41 +01:00
|
|
|
private set
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
var passthroughTexCoordLoc: Int = 0
|
2026-01-28 15:26:41 +01:00
|
|
|
private set
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
private var passthroughTextureLoc: Int = 0
|
2026-05-07 16:31:43 +02:00
|
|
|
private var passthroughTexMatrixLoc: Int = 0
|
|
|
|
|
private var passthroughMirrorLoc: Int = 0
|
2026-01-28 15:26:41 +01:00
|
|
|
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
// --- Blur program (FBO → FBO/screen) ---
|
|
|
|
|
|
|
|
|
|
private var blurProgramId: Int = 0
|
|
|
|
|
|
|
|
|
|
var blurPositionLoc: Int = 0
|
|
|
|
|
private set
|
|
|
|
|
var blurTexCoordLoc: Int = 0
|
|
|
|
|
private set
|
|
|
|
|
private var blurTextureLoc: Int = 0
|
2026-05-07 16:31:43 +02:00
|
|
|
private var blurTexMatrixLoc: Int = 0
|
|
|
|
|
private var blurMirrorLoc: Int = 0
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
private var blurModeLoc: Int = 0
|
|
|
|
|
private var blurPositionXLoc: Int = 0
|
|
|
|
|
private var blurPositionYLoc: Int = 0
|
|
|
|
|
private var blurSizeLoc: Int = 0
|
|
|
|
|
private var blurAmountLoc: Int = 0
|
|
|
|
|
private var blurFalloffLoc: Int = 0
|
|
|
|
|
private var blurAspectRatioLoc: Int = 0
|
|
|
|
|
private var blurResolutionLoc: Int = 0
|
|
|
|
|
private var blurCosAngleLoc: Int = 0
|
|
|
|
|
private var blurSinAngleLoc: Int = 0
|
|
|
|
|
private var blurDirectionLoc: Int = 0
|
2026-01-28 15:26:41 +01:00
|
|
|
|
|
|
|
|
/**
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
* Compiles and links both shader programs.
|
2026-01-28 15:26:41 +01:00
|
|
|
* Must be called from GL thread.
|
|
|
|
|
*/
|
|
|
|
|
fun initialize() {
|
|
|
|
|
val vertexSource = loadShaderSource(R.raw.tiltshift_vertex)
|
|
|
|
|
val vertexShader = compileShader(GLES20.GL_VERTEX_SHADER, vertexSource)
|
|
|
|
|
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
// Passthrough program
|
|
|
|
|
val passthroughFragSource = loadShaderSource(R.raw.tiltshift_passthrough_fragment)
|
|
|
|
|
val passthroughFragShader = compileShader(GLES20.GL_FRAGMENT_SHADER, passthroughFragSource)
|
|
|
|
|
passthroughProgramId = linkProgram(vertexShader, passthroughFragShader)
|
|
|
|
|
GLES20.glDeleteShader(passthroughFragShader)
|
|
|
|
|
|
|
|
|
|
passthroughPositionLoc = GLES20.glGetAttribLocation(passthroughProgramId, "aPosition")
|
|
|
|
|
passthroughTexCoordLoc = GLES20.glGetAttribLocation(passthroughProgramId, "aTexCoord")
|
|
|
|
|
passthroughTextureLoc = GLES20.glGetUniformLocation(passthroughProgramId, "uTexture")
|
2026-05-07 16:31:43 +02:00
|
|
|
passthroughTexMatrixLoc = GLES20.glGetUniformLocation(passthroughProgramId, "uTexMatrix")
|
|
|
|
|
passthroughMirrorLoc = GLES20.glGetUniformLocation(passthroughProgramId, "uMirrorX")
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
|
|
|
|
|
// Blur program
|
|
|
|
|
val blurFragSource = loadShaderSource(R.raw.tiltshift_fragment)
|
|
|
|
|
val blurFragShader = compileShader(GLES20.GL_FRAGMENT_SHADER, blurFragSource)
|
|
|
|
|
blurProgramId = linkProgram(vertexShader, blurFragShader)
|
|
|
|
|
GLES20.glDeleteShader(blurFragShader)
|
|
|
|
|
|
|
|
|
|
blurPositionLoc = GLES20.glGetAttribLocation(blurProgramId, "aPosition")
|
|
|
|
|
blurTexCoordLoc = GLES20.glGetAttribLocation(blurProgramId, "aTexCoord")
|
|
|
|
|
blurTextureLoc = GLES20.glGetUniformLocation(blurProgramId, "uTexture")
|
2026-05-07 16:31:43 +02:00
|
|
|
blurTexMatrixLoc = GLES20.glGetUniformLocation(blurProgramId, "uTexMatrix")
|
|
|
|
|
blurMirrorLoc = GLES20.glGetUniformLocation(blurProgramId, "uMirrorX")
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
blurModeLoc = GLES20.glGetUniformLocation(blurProgramId, "uMode")
|
|
|
|
|
blurPositionXLoc = GLES20.glGetUniformLocation(blurProgramId, "uPositionX")
|
|
|
|
|
blurPositionYLoc = GLES20.glGetUniformLocation(blurProgramId, "uPositionY")
|
|
|
|
|
blurSizeLoc = GLES20.glGetUniformLocation(blurProgramId, "uSize")
|
|
|
|
|
blurAmountLoc = GLES20.glGetUniformLocation(blurProgramId, "uBlurAmount")
|
|
|
|
|
blurFalloffLoc = GLES20.glGetUniformLocation(blurProgramId, "uFalloff")
|
|
|
|
|
blurAspectRatioLoc = GLES20.glGetUniformLocation(blurProgramId, "uAspectRatio")
|
|
|
|
|
blurResolutionLoc = GLES20.glGetUniformLocation(blurProgramId, "uResolution")
|
|
|
|
|
blurCosAngleLoc = GLES20.glGetUniformLocation(blurProgramId, "uCosAngle")
|
|
|
|
|
blurSinAngleLoc = GLES20.glGetUniformLocation(blurProgramId, "uSinAngle")
|
|
|
|
|
blurDirectionLoc = GLES20.glGetUniformLocation(blurProgramId, "uBlurDirection")
|
|
|
|
|
|
|
|
|
|
// Vertex shader is linked into both programs and can be freed
|
2026-01-28 15:26:41 +01:00
|
|
|
GLES20.glDeleteShader(vertexShader)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
* Activates the passthrough program and binds the camera texture.
|
2026-05-07 16:31:43 +02:00
|
|
|
*
|
|
|
|
|
* @param cameraTextureId The OES texture receiving camera frames.
|
|
|
|
|
* @param texMatrix 4x4 transform from SurfaceTexture.getTransformMatrix() —
|
|
|
|
|
* encodes sensor-to-display rotation and Y-flip.
|
|
|
|
|
* @param mirrorX true to horizontally mirror (front camera selfie view).
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
*/
|
2026-05-07 16:31:43 +02:00
|
|
|
fun usePassthrough(cameraTextureId: Int, texMatrix: FloatArray, mirrorX: Boolean) {
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
GLES20.glUseProgram(passthroughProgramId)
|
|
|
|
|
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
|
|
|
|
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTextureId)
|
|
|
|
|
GLES20.glUniform1i(passthroughTextureLoc, 0)
|
2026-05-07 16:31:43 +02:00
|
|
|
GLES20.glUniformMatrix4fv(passthroughTexMatrixLoc, 1, false, texMatrix, 0)
|
|
|
|
|
GLES20.glUniform1f(passthroughMirrorLoc, if (mirrorX) 1f else 0f)
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Activates the blur program and sets all uniforms for one blur pass.
|
|
|
|
|
*
|
|
|
|
|
* @param fboTextureId The FBO color attachment to sample from.
|
|
|
|
|
* @param params Current blur parameters.
|
|
|
|
|
* @param width Surface width in pixels.
|
|
|
|
|
* @param height Surface height in pixels.
|
|
|
|
|
* @param dirX Blur direction X component (1 for horizontal pass, 0 for vertical).
|
|
|
|
|
* @param dirY Blur direction Y component (0 for horizontal pass, 1 for vertical).
|
2026-01-28 15:26:41 +01:00
|
|
|
*/
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
fun useBlurPass(
|
|
|
|
|
fboTextureId: Int,
|
|
|
|
|
params: BlurParameters,
|
|
|
|
|
width: Int,
|
|
|
|
|
height: Int,
|
|
|
|
|
dirX: Float,
|
|
|
|
|
dirY: Float
|
|
|
|
|
) {
|
|
|
|
|
GLES20.glUseProgram(blurProgramId)
|
2026-01-28 15:26:41 +01:00
|
|
|
|
|
|
|
|
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fboTextureId)
|
|
|
|
|
GLES20.glUniform1i(blurTextureLoc, 0)
|
|
|
|
|
|
2026-05-07 16:31:43 +02:00
|
|
|
// FBO content is already in display orientation — pass identity matrix and no mirror.
|
|
|
|
|
GLES20.glUniformMatrix4fv(blurTexMatrixLoc, 1, false, IDENTITY_MATRIX, 0)
|
|
|
|
|
GLES20.glUniform1f(blurMirrorLoc, 0f)
|
|
|
|
|
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
GLES20.glUniform1i(blurModeLoc, if (params.mode == BlurMode.RADIAL) 1 else 0)
|
|
|
|
|
GLES20.glUniform1f(blurPositionXLoc, params.positionX)
|
|
|
|
|
GLES20.glUniform1f(blurPositionYLoc, params.positionY)
|
|
|
|
|
GLES20.glUniform1f(blurSizeLoc, params.size)
|
|
|
|
|
GLES20.glUniform1f(blurAmountLoc, params.blurAmount)
|
|
|
|
|
GLES20.glUniform1f(blurFalloffLoc, params.falloff)
|
|
|
|
|
GLES20.glUniform1f(blurAspectRatioLoc, params.aspectRatio)
|
|
|
|
|
GLES20.glUniform2f(blurResolutionLoc, width.toFloat(), height.toFloat())
|
|
|
|
|
|
|
|
|
|
// Raw screen-space angle (no camera rotation adjustment needed — FBO is already
|
|
|
|
|
// in screen orientation after the passthrough pass)
|
|
|
|
|
GLES20.glUniform1f(blurCosAngleLoc, cos(params.angle))
|
|
|
|
|
GLES20.glUniform1f(blurSinAngleLoc, sin(params.angle))
|
|
|
|
|
|
|
|
|
|
GLES20.glUniform2f(blurDirectionLoc, dirX, dirY)
|
2026-01-28 15:26:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
* Releases both shader programs.
|
2026-01-28 15:26:41 +01:00
|
|
|
*/
|
|
|
|
|
fun release() {
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
if (passthroughProgramId != 0) {
|
|
|
|
|
GLES20.glDeleteProgram(passthroughProgramId)
|
|
|
|
|
passthroughProgramId = 0
|
|
|
|
|
}
|
|
|
|
|
if (blurProgramId != 0) {
|
|
|
|
|
GLES20.glDeleteProgram(blurProgramId)
|
|
|
|
|
blurProgramId = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun linkProgram(vertexShader: Int, fragmentShader: Int): Int {
|
|
|
|
|
val programId = GLES20.glCreateProgram()
|
|
|
|
|
GLES20.glAttachShader(programId, vertexShader)
|
|
|
|
|
GLES20.glAttachShader(programId, fragmentShader)
|
|
|
|
|
GLES20.glLinkProgram(programId)
|
|
|
|
|
|
|
|
|
|
val linkStatus = IntArray(1)
|
|
|
|
|
GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, 0)
|
|
|
|
|
if (linkStatus[0] == 0) {
|
|
|
|
|
val error = GLES20.glGetProgramInfoLog(programId)
|
2026-01-28 15:26:41 +01:00
|
|
|
GLES20.glDeleteProgram(programId)
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
throw RuntimeException("Shader program link failed: $error")
|
2026-01-28 15:26:41 +01:00
|
|
|
}
|
Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.
Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
aspect correction on X axis (height-normalized), uBlurDirection
uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
(stale zoomRatio base prevented delta accumulation)
Bump version to 1.1.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
|
|
|
|
|
|
|
|
return programId
|
2026-01-28 15:26:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun loadShaderSource(resourceId: Int): String {
|
|
|
|
|
val inputStream = context.resources.openRawResource(resourceId)
|
|
|
|
|
val reader = BufferedReader(InputStreamReader(inputStream))
|
|
|
|
|
return reader.use { it.readText() }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun compileShader(type: Int, source: String): Int {
|
|
|
|
|
val shader = GLES20.glCreateShader(type)
|
|
|
|
|
GLES20.glShaderSource(shader, source)
|
|
|
|
|
GLES20.glCompileShader(shader)
|
|
|
|
|
|
|
|
|
|
val compileStatus = IntArray(1)
|
|
|
|
|
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0)
|
|
|
|
|
if (compileStatus[0] == 0) {
|
|
|
|
|
val error = GLES20.glGetShaderInfoLog(shader)
|
|
|
|
|
GLES20.glDeleteShader(shader)
|
|
|
|
|
val shaderType = if (type == GLES20.GL_VERTEX_SHADER) "vertex" else "fragment"
|
|
|
|
|
throw RuntimeException("$shaderType shader compilation failed: $error")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shader
|
|
|
|
|
}
|
|
|
|
|
}
|