2026-01-28 15:26:41 +01:00
|
|
|
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
|
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 android.util.Log
|
2026-05-11 16:12:29 +02:00
|
|
|
import android.view.Surface
|
2026-01-28 15:26:41 +01:00
|
|
|
import java.nio.ByteBuffer
|
|
|
|
|
import java.nio.ByteOrder
|
|
|
|
|
import java.nio.FloatBuffer
|
|
|
|
|
import javax.microedition.khronos.egl.EGLConfig
|
|
|
|
|
import javax.microedition.khronos.opengles.GL10
|
|
|
|
|
|
|
|
|
|
/**
|
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
|
|
|
* OpenGL renderer for applying tilt-shift effect to camera preview
|
|
|
|
|
* using a two-pass separable Gaussian blur.
|
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
|
|
|
* Rendering pipeline (3 draw calls per frame):
|
|
|
|
|
* 1. **Passthrough**: camera texture → FBO-A (handles coordinate transform via vertex/texcoord)
|
|
|
|
|
* 2. **Horizontal blur**: FBO-A → FBO-B (13-tap Gaussian, tilt-shift mask)
|
|
|
|
|
* 3. **Vertical blur**: FBO-B → screen (13-tap Gaussian, tilt-shift mask)
|
|
|
|
|
*
|
|
|
|
|
* The passthrough decouples the camera's rotated coordinate system from the blur
|
|
|
|
|
* passes, which work entirely in screen space.
|
2026-01-28 15:26:41 +01:00
|
|
|
*/
|
|
|
|
|
class TiltShiftRenderer(
|
|
|
|
|
private val context: Context,
|
2026-03-05 13:58:17 +01:00
|
|
|
private val onSurfaceTextureAvailable: (SurfaceTexture) -> Unit,
|
|
|
|
|
private val onFrameAvailable: () -> Unit
|
2026-01-28 15:26:41 +01:00
|
|
|
) : GLSurfaceView.Renderer {
|
|
|
|
|
|
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
|
|
|
companion object {
|
|
|
|
|
private const val TAG = "TiltShiftRenderer"
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 15:26:41 +01:00
|
|
|
private lateinit var shader: TiltShiftShader
|
|
|
|
|
private var surfaceTexture: SurfaceTexture? = null
|
|
|
|
|
private var cameraTextureId: Int = 0
|
|
|
|
|
|
2026-05-11 15:57:32 +02:00
|
|
|
// Camera quad: crop-to-fill vertices + rotated texcoords (pass 1 only)
|
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 lateinit var cameraVertexBuffer: FloatBuffer
|
2026-05-11 15:57:32 +02:00
|
|
|
private lateinit var cameraTexCoordBuffer: FloatBuffer
|
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
|
|
|
|
|
|
|
|
// Fullscreen quad for blur passes (no crop, standard texcoords)
|
|
|
|
|
private lateinit var fullscreenVertexBuffer: FloatBuffer
|
|
|
|
|
private lateinit var fullscreenTexCoordBuffer: FloatBuffer
|
2026-01-28 15:26:41 +01:00
|
|
|
|
|
|
|
|
private var surfaceWidth: Int = 0
|
|
|
|
|
private var surfaceHeight: 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
|
|
|
// FBO resources: one framebuffer, two color textures for ping-pong
|
|
|
|
|
private var fboId: Int = 0
|
|
|
|
|
private var fboTexA: Int = 0
|
|
|
|
|
private var fboTexB: Int = 0
|
|
|
|
|
|
2026-01-28 15:26:41 +01:00
|
|
|
// Current effect parameters (updated from UI thread)
|
|
|
|
|
@Volatile
|
|
|
|
|
var blurParameters: BlurParameters = BlurParameters.DEFAULT
|
|
|
|
|
|
2026-01-29 17:03:26 +01:00
|
|
|
@Volatile
|
|
|
|
|
private var isFrontCamera: Boolean = false
|
|
|
|
|
|
2026-03-05 13:58:17 +01:00
|
|
|
// Camera resolution for aspect ratio correction (set from UI thread)
|
|
|
|
|
@Volatile
|
|
|
|
|
private var cameraWidth: Int = 0
|
|
|
|
|
@Volatile
|
|
|
|
|
private var cameraHeight: Int = 0
|
2026-05-11 15:57:32 +02:00
|
|
|
@Volatile
|
|
|
|
|
private var vertexBufferDirty: Boolean = false
|
|
|
|
|
|
2026-05-11 16:12:29 +02:00
|
|
|
// Texture coordinates for the back camera, indexed by Surface.ROTATION_*.
|
|
|
|
|
// The base orientation (index 0) applies the 90° CCW rotation that maps
|
|
|
|
|
// the landscape sensor frame to a portrait display. Indices 1/2/3 layer
|
|
|
|
|
// additional CCW rotations on top so the activity's rotation is
|
|
|
|
|
// compensated and world-up stays at clip-space-top.
|
|
|
|
|
private val texCoordsBackByRotation = arrayOf(
|
|
|
|
|
floatArrayOf(1f, 1f, 1f, 0f, 0f, 1f, 0f, 0f), // ROTATION_0
|
|
|
|
|
floatArrayOf(1f, 0f, 0f, 0f, 1f, 1f, 0f, 1f), // ROTATION_90
|
|
|
|
|
floatArrayOf(0f, 0f, 0f, 1f, 1f, 0f, 1f, 1f), // ROTATION_180
|
|
|
|
|
floatArrayOf(0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f) // ROTATION_270
|
2026-05-11 15:57:32 +02:00
|
|
|
)
|
|
|
|
|
|
2026-05-11 16:12:29 +02:00
|
|
|
// Front camera variants: same as back, but horizontally mirrored
|
|
|
|
|
// for the natural selfie view.
|
|
|
|
|
private val texCoordsFrontByRotation = arrayOf(
|
|
|
|
|
floatArrayOf(0f, 1f, 0f, 0f, 1f, 1f, 1f, 0f), // ROTATION_0
|
|
|
|
|
floatArrayOf(0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f), // ROTATION_90
|
|
|
|
|
floatArrayOf(1f, 0f, 1f, 1f, 0f, 0f, 0f, 1f), // ROTATION_180
|
|
|
|
|
floatArrayOf(1f, 1f, 0f, 1f, 1f, 0f, 0f, 0f) // ROTATION_270
|
2026-05-11 15:57:32 +02:00
|
|
|
)
|
2026-01-29 17:03:26 +01:00
|
|
|
|
2026-03-05 13:44:12 +01:00
|
|
|
@Volatile
|
2026-05-11 16:12:29 +02:00
|
|
|
private var displayRotation: Int = Surface.ROTATION_0
|
|
|
|
|
|
|
|
|
|
@Volatile
|
|
|
|
|
private var currentTexCoords = texCoordsBackByRotation[Surface.ROTATION_0]
|
2026-01-29 17:03:26 +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
|
|
|
@Volatile
|
2026-05-11 15:57:32 +02:00
|
|
|
private var updateTexCoordBuffer = false
|
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-01-28 15:26:41 +01:00
|
|
|
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
|
|
|
|
|
GLES20.glClearColor(0f, 0f, 0f, 1f)
|
|
|
|
|
|
|
|
|
|
shader = TiltShiftShader(context)
|
|
|
|
|
shader.initialize()
|
|
|
|
|
|
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
|
|
|
// Camera quad vertex buffer (crop-to-fill, recomputed when resolution is known)
|
|
|
|
|
cameraVertexBuffer = allocateFloatBuffer(8)
|
|
|
|
|
cameraVertexBuffer.put(floatArrayOf(-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f))
|
|
|
|
|
cameraVertexBuffer.position(0)
|
2026-01-28 15:26:41 +01:00
|
|
|
|
2026-05-11 15:57:32 +02:00
|
|
|
// Camera texcoord buffer (rotated for portrait)
|
|
|
|
|
cameraTexCoordBuffer = allocateFloatBuffer(8)
|
|
|
|
|
cameraTexCoordBuffer.put(currentTexCoords)
|
|
|
|
|
cameraTexCoordBuffer.position(0)
|
|
|
|
|
|
|
|
|
|
// Fullscreen quad for blur passes (standard coords)
|
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
|
|
|
fullscreenVertexBuffer = allocateFloatBuffer(8)
|
|
|
|
|
fullscreenVertexBuffer.put(floatArrayOf(-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f))
|
|
|
|
|
fullscreenVertexBuffer.position(0)
|
|
|
|
|
|
|
|
|
|
fullscreenTexCoordBuffer = allocateFloatBuffer(8)
|
|
|
|
|
fullscreenTexCoordBuffer.put(floatArrayOf(0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f))
|
|
|
|
|
fullscreenTexCoordBuffer.position(0)
|
2026-01-28 15:26:41 +01:00
|
|
|
|
|
|
|
|
// 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 {
|
2026-03-05 13:58:17 +01:00
|
|
|
it.setOnFrameAvailableListener { onFrameAvailable() }
|
2026-01-28 15:26:41 +01:00
|
|
|
onSurfaceTextureAvailable(it)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
|
|
|
|
|
GLES20.glViewport(0, 0, width, height)
|
|
|
|
|
surfaceWidth = width
|
|
|
|
|
surfaceHeight = height
|
2026-03-05 13:58:17 +01:00
|
|
|
vertexBufferDirty = true
|
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
|
|
|
recreateFBOs(width, height)
|
2026-01-28 15:26:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onDrawFrame(gl: GL10?) {
|
2026-05-11 15:57:32 +02:00
|
|
|
surfaceTexture?.updateTexImage()
|
2026-01-28 15:26:41 +01:00
|
|
|
|
2026-03-05 13:58:17 +01:00
|
|
|
if (vertexBufferDirty) {
|
|
|
|
|
recomputeVertices()
|
|
|
|
|
vertexBufferDirty = false
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-11 15:57:32 +02:00
|
|
|
if (updateTexCoordBuffer) {
|
|
|
|
|
cameraTexCoordBuffer.clear()
|
|
|
|
|
cameraTexCoordBuffer.put(currentTexCoords)
|
|
|
|
|
cameraTexCoordBuffer.position(0)
|
|
|
|
|
updateTexCoordBuffer = false
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
val params = blurParameters
|
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
|
|
|
// --- Pass 1: Camera → FBO-A (passthrough with crop-to-fill) ---
|
|
|
|
|
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId)
|
|
|
|
|
GLES20.glFramebufferTexture2D(
|
|
|
|
|
GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
|
|
|
|
|
GLES20.GL_TEXTURE_2D, fboTexA, 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
|
|
|
GLES20.glViewport(0, 0, surfaceWidth, surfaceHeight)
|
|
|
|
|
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
2026-05-11 15:57:32 +02:00
|
|
|
shader.usePassthrough(cameraTextureId)
|
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
|
|
|
drawQuad(
|
|
|
|
|
shader.passthroughPositionLoc, shader.passthroughTexCoordLoc,
|
2026-05-11 15:57:32 +02:00
|
|
|
cameraVertexBuffer, cameraTexCoordBuffer
|
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
|
|
|
// --- Pass 2: FBO-A → FBO-B (horizontal blur) ---
|
|
|
|
|
GLES20.glFramebufferTexture2D(
|
|
|
|
|
GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
|
|
|
|
|
GLES20.GL_TEXTURE_2D, fboTexB, 0
|
|
|
|
|
)
|
|
|
|
|
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
|
|
|
|
shader.useBlurPass(fboTexA, params, surfaceWidth, surfaceHeight, 1f, 0f)
|
|
|
|
|
drawQuad(
|
|
|
|
|
shader.blurPositionLoc, shader.blurTexCoordLoc,
|
|
|
|
|
fullscreenVertexBuffer, fullscreenTexCoordBuffer
|
|
|
|
|
)
|
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
|
|
|
// --- Pass 3: FBO-B → screen (vertical blur) ---
|
|
|
|
|
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0)
|
|
|
|
|
GLES20.glViewport(0, 0, surfaceWidth, surfaceHeight)
|
|
|
|
|
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
|
|
|
|
shader.useBlurPass(fboTexB, params, surfaceWidth, surfaceHeight, 0f, 1f)
|
|
|
|
|
drawQuad(
|
|
|
|
|
shader.blurPositionLoc, shader.blurTexCoordLoc,
|
|
|
|
|
fullscreenVertexBuffer, fullscreenTexCoordBuffer
|
|
|
|
|
)
|
2026-01-28 15:26:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun updateParameters(params: BlurParameters) {
|
|
|
|
|
blurParameters = params
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 17:03:26 +01:00
|
|
|
fun setFrontCamera(front: Boolean) {
|
2026-05-11 15:57:32 +02:00
|
|
|
if (isFrontCamera != front) {
|
|
|
|
|
isFrontCamera = front
|
2026-05-11 16:12:29 +02:00
|
|
|
refreshTexCoords()
|
2026-05-11 15:57:32 +02:00
|
|
|
}
|
2026-01-29 17:03:26 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-05 13:58:17 +01:00
|
|
|
fun setCameraResolution(width: Int, height: Int) {
|
|
|
|
|
if (cameraWidth != width || cameraHeight != height) {
|
|
|
|
|
cameraWidth = width
|
|
|
|
|
cameraHeight = height
|
|
|
|
|
vertexBufferDirty = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-11 16:12:29 +02:00
|
|
|
/**
|
|
|
|
|
* Updates the active display rotation. The texture-coordinate buffer is
|
|
|
|
|
* rebuilt so the camera image stays world-aligned as the activity rotates
|
|
|
|
|
* with the device under screenOrientation="fullSensor", and the
|
|
|
|
|
* crop-to-fill math picks the correct effective aspect ratio.
|
|
|
|
|
*/
|
|
|
|
|
fun setDisplayRotation(rotation: Int) {
|
|
|
|
|
if (displayRotation != rotation) {
|
|
|
|
|
displayRotation = rotation
|
|
|
|
|
refreshTexCoords()
|
|
|
|
|
vertexBufferDirty = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun refreshTexCoords() {
|
|
|
|
|
val table = if (isFrontCamera) texCoordsFrontByRotation else texCoordsBackByRotation
|
|
|
|
|
val idx = displayRotation.coerceIn(0, table.size - 1)
|
|
|
|
|
currentTexCoords = table[idx]
|
|
|
|
|
updateTexCoordBuffer = true
|
|
|
|
|
}
|
|
|
|
|
|
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 release() {
|
|
|
|
|
shader.release()
|
|
|
|
|
surfaceTexture?.release()
|
|
|
|
|
surfaceTexture = null
|
|
|
|
|
|
|
|
|
|
if (cameraTextureId != 0) {
|
|
|
|
|
GLES20.glDeleteTextures(1, intArrayOf(cameraTextureId), 0)
|
|
|
|
|
cameraTextureId = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deleteFBOs()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Private helpers ---
|
|
|
|
|
|
|
|
|
|
private fun drawQuad(
|
|
|
|
|
positionLoc: Int,
|
|
|
|
|
texCoordLoc: Int,
|
|
|
|
|
vertices: FloatBuffer,
|
|
|
|
|
texCoords: FloatBuffer
|
|
|
|
|
) {
|
|
|
|
|
GLES20.glEnableVertexAttribArray(positionLoc)
|
|
|
|
|
GLES20.glVertexAttribPointer(positionLoc, 2, GLES20.GL_FLOAT, false, 0, vertices)
|
|
|
|
|
|
|
|
|
|
GLES20.glEnableVertexAttribArray(texCoordLoc)
|
|
|
|
|
GLES20.glVertexAttribPointer(texCoordLoc, 2, GLES20.GL_FLOAT, false, 0, texCoords)
|
|
|
|
|
|
|
|
|
|
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
|
|
|
|
|
|
|
|
|
|
GLES20.glDisableVertexAttribArray(positionLoc)
|
|
|
|
|
GLES20.glDisableVertexAttribArray(texCoordLoc)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 13:58:17 +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
|
|
|
* Recomputes camera vertex positions to achieve crop-to-fill.
|
2026-03-05 13:58:17 +01:00
|
|
|
*
|
2026-05-11 16:12:29 +02:00
|
|
|
* The camera sensor is landscape; after the orientation-dependent texcoord
|
|
|
|
|
* rotation, the effective dimensions seen on screen are either swapped
|
|
|
|
|
* (portrait orientations) or kept (landscape orientations). We scale the
|
|
|
|
|
* vertex quad so the camera frame fills the surface without stretching —
|
|
|
|
|
* the GPU clips the overflow.
|
2026-03-05 13:58:17 +01:00
|
|
|
*/
|
|
|
|
|
private fun recomputeVertices() {
|
|
|
|
|
var scaleX = 1f
|
|
|
|
|
var scaleY = 1f
|
|
|
|
|
|
|
|
|
|
if (cameraWidth > 0 && cameraHeight > 0 && surfaceWidth > 0 && surfaceHeight > 0) {
|
2026-05-11 16:12:29 +02:00
|
|
|
val isPortrait = displayRotation == Surface.ROTATION_0 ||
|
|
|
|
|
displayRotation == Surface.ROTATION_180
|
|
|
|
|
val effectiveW = if (isPortrait) cameraHeight else cameraWidth
|
|
|
|
|
val effectiveH = if (isPortrait) cameraWidth else cameraHeight
|
|
|
|
|
val cameraRatio = effectiveW.toFloat() / effectiveH
|
2026-03-05 13:58:17 +01:00
|
|
|
val screenRatio = surfaceWidth.toFloat() / surfaceHeight
|
|
|
|
|
|
|
|
|
|
if (cameraRatio > screenRatio) {
|
|
|
|
|
scaleX = cameraRatio / screenRatio
|
|
|
|
|
} else {
|
|
|
|
|
scaleY = screenRatio / cameraRatio
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
cameraVertexBuffer.clear()
|
|
|
|
|
cameraVertexBuffer.put(floatArrayOf(
|
2026-03-05 13:58:17 +01:00
|
|
|
-scaleX, -scaleY,
|
|
|
|
|
scaleX, -scaleY,
|
|
|
|
|
-scaleX, scaleY,
|
|
|
|
|
scaleX, scaleY
|
|
|
|
|
))
|
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
|
|
|
cameraVertexBuffer.position(0)
|
2026-03-05 13:58:17 +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
|
|
|
private fun recreateFBOs(width: Int, height: Int) {
|
|
|
|
|
deleteFBOs()
|
|
|
|
|
|
|
|
|
|
// Create two color textures for ping-pong
|
|
|
|
|
val texIds = IntArray(2)
|
|
|
|
|
GLES20.glGenTextures(2, texIds, 0)
|
|
|
|
|
fboTexA = texIds[0]
|
|
|
|
|
fboTexB = texIds[1]
|
|
|
|
|
|
|
|
|
|
for (texId in texIds) {
|
|
|
|
|
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId)
|
|
|
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR)
|
|
|
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR)
|
|
|
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
|
|
|
|
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
|
|
|
|
|
GLES20.glTexImage2D(
|
|
|
|
|
GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA,
|
|
|
|
|
width, height, 0,
|
|
|
|
|
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null
|
|
|
|
|
)
|
|
|
|
|
}
|
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
|
|
|
// Create single FBO (we swap the attached texture for ping-pong)
|
|
|
|
|
val fbos = IntArray(1)
|
|
|
|
|
GLES20.glGenFramebuffers(1, fbos, 0)
|
|
|
|
|
fboId = fbos[0]
|
|
|
|
|
|
|
|
|
|
// Verify with texture A
|
|
|
|
|
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId)
|
|
|
|
|
GLES20.glFramebufferTexture2D(
|
|
|
|
|
GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
|
|
|
|
|
GLES20.GL_TEXTURE_2D, fboTexA, 0
|
|
|
|
|
)
|
|
|
|
|
val status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER)
|
|
|
|
|
if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
|
|
|
|
|
Log.e(TAG, "FBO incomplete: $status")
|
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
|
|
|
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun deleteFBOs() {
|
|
|
|
|
if (fboId != 0) {
|
|
|
|
|
GLES20.glDeleteFramebuffers(1, intArrayOf(fboId), 0)
|
|
|
|
|
fboId = 0
|
|
|
|
|
}
|
|
|
|
|
if (fboTexA != 0 || fboTexB != 0) {
|
|
|
|
|
GLES20.glDeleteTextures(2, intArrayOf(fboTexA, fboTexB), 0)
|
|
|
|
|
fboTexA = 0
|
|
|
|
|
fboTexB = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun allocateFloatBuffer(floatCount: Int): FloatBuffer {
|
|
|
|
|
return ByteBuffer.allocateDirect(floatCount * 4)
|
|
|
|
|
.order(ByteOrder.nativeOrder())
|
|
|
|
|
.asFloatBuffer()
|
2026-01-28 15:26:41 +01:00
|
|
|
}
|
|
|
|
|
}
|