Fix concurrency, lifecycle, performance, and config issues from audit
Concurrency & bitmap lifecycle: - Defer bitmap recycling by one cycle so Compose finishes drawing before native memory is freed (preview bitmaps, thumbnails) - Make galleryPreviewSource @Volatile for cross-thread visibility - Join preview job before recycling source bitmap in cancelGalleryPreview() to prevent use-after-free during CPU blur loop - Add @Volatile to TiltShiftRenderer.currentTexCoords (UI/GL thread race) - Fix error dismiss race with cancellable Job tracking Lifecycle & resource management: - Release GL resources via glSurfaceView.queueEvent (must run on GL thread) - Pause GLSurfaceView when entering gallery preview mode - Shut down captureExecutor in CameraManager.release() (thread leak) - Use WeakReference for lifecycleOwnerRef to avoid Activity GC delay - Fix thumbnail bitmap leak on coroutine cancellation (add to finally) - Guarantee imageProxy.close() in finally block Performance: - Compute gradient mask at 1/4 resolution with bilinear upscale (~93% less per-pixel trig work, ~75% less mask memory) - Precompute cos/sin on CPU, pass as uCosAngle/uSinAngle uniforms (eliminates per-fragment transcendental calls in GLSL) - Unroll 9-tap Gaussian blur kernel (avoids integer-branched weight lookup that de-optimizes on mobile GPUs) - Add 80ms debounce to preview recomputation during slider drags Silent failure fixes: - Check bitmap.compress() return value; report error on failure - Log all loadBitmapFromUri null paths (stream, dimensions, decode) - Surface preview computation errors and ActivityNotFoundException to user - Return boolean from writeExifToUri, log at ERROR level - Wrap gallery preview downscale in try-catch (OOM protection) Config: - Add ACCESS_MEDIA_LOCATION permission (GPS EXIF on Android 10+) - Accept coarse-only location grant for geotags - Remove dead adjustResize (no effect with edge-to-edge) - Set windowBackground to black (eliminates white flash on cold start) - Add values-night theme for dark mode - Remove overly broad ProGuard keeps (CameraX/GMS ship consumer rules) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
12051b2a83
commit
11a79076bc
13 changed files with 292 additions and 159 deletions
|
|
@ -20,6 +20,10 @@ uniform float uFalloff; // Transition sharpness (0-1, higher = more grad
|
|||
uniform float uAspectRatio; // Ellipse aspect ratio for radial mode
|
||||
uniform vec2 uResolution; // Texture resolution for proper sampling
|
||||
|
||||
// Precomputed trig for the adjusted angle (avoids per-fragment cos/sin calls)
|
||||
uniform float uCosAngle;
|
||||
uniform float uSinAngle;
|
||||
|
||||
varying vec2 vTexCoord;
|
||||
|
||||
// Calculate signed distance from the focus region for LINEAR mode
|
||||
|
|
@ -37,25 +41,11 @@ float linearFocusDistance(vec2 uv) {
|
|||
vec2 offset = uv - center;
|
||||
|
||||
// Correct for screen aspect ratio to make coordinate space square
|
||||
// After transform: offset.x = screen Y direction, offset.y = screen X direction
|
||||
// Scale offset.y to match the scale of offset.x (height units)
|
||||
float screenAspect = uResolution.x / uResolution.y;
|
||||
offset.y *= screenAspect;
|
||||
|
||||
// Adjust angle to compensate for the coordinate transformation
|
||||
// Back camera: +90° for the 90° CW rotation
|
||||
// Front camera: -90° (negated due to X flip mirror effect)
|
||||
float adjustedAngle;
|
||||
if (uIsFrontCamera == 1) {
|
||||
adjustedAngle = -uAngle - 1.5707963;
|
||||
} else {
|
||||
adjustedAngle = uAngle + 1.5707963;
|
||||
}
|
||||
float cosA = cos(adjustedAngle);
|
||||
float sinA = sin(adjustedAngle);
|
||||
|
||||
// After rotation, measure perpendicular distance from center line
|
||||
float rotatedY = -offset.x * sinA + offset.y * cosA;
|
||||
// Use precomputed cos/sin for the adjusted angle
|
||||
float rotatedY = -offset.x * uSinAngle + offset.y * uCosAngle;
|
||||
|
||||
return abs(rotatedY);
|
||||
}
|
||||
|
|
@ -63,7 +53,6 @@ float linearFocusDistance(vec2 uv) {
|
|||
// Calculate signed distance from the focus region for RADIAL mode
|
||||
float radialFocusDistance(vec2 uv) {
|
||||
// Center point of the focus region
|
||||
// Transform from screen coordinates to texture coordinates
|
||||
vec2 center;
|
||||
if (uIsFrontCamera == 1) {
|
||||
center = vec2(1.0 - uPositionY, 1.0 - uPositionX);
|
||||
|
|
@ -72,24 +61,14 @@ float radialFocusDistance(vec2 uv) {
|
|||
}
|
||||
vec2 offset = uv - center;
|
||||
|
||||
// Correct for screen aspect ratio to make coordinate space square
|
||||
// After transform: offset.x = screen Y direction, offset.y = screen X direction
|
||||
// Scale offset.y to match the scale of offset.x (height units)
|
||||
// Correct for screen aspect ratio
|
||||
float screenAspect = uResolution.x / uResolution.y;
|
||||
offset.y *= screenAspect;
|
||||
|
||||
// Apply rotation with angle adjustment for coordinate transformation
|
||||
float adjustedAngle;
|
||||
if (uIsFrontCamera == 1) {
|
||||
adjustedAngle = -uAngle - 1.5707963;
|
||||
} else {
|
||||
adjustedAngle = uAngle + 1.5707963;
|
||||
}
|
||||
float cosA = cos(adjustedAngle);
|
||||
float sinA = sin(adjustedAngle);
|
||||
// Use precomputed cos/sin for rotation
|
||||
vec2 rotated = vec2(
|
||||
offset.x * cosA - offset.y * sinA,
|
||||
offset.x * sinA + offset.y * cosA
|
||||
offset.x * uCosAngle - offset.y * uSinAngle,
|
||||
offset.x * uSinAngle + offset.y * uCosAngle
|
||||
);
|
||||
|
||||
// Apply ellipse aspect ratio
|
||||
|
|
@ -114,26 +93,12 @@ float blurFactor(float dist) {
|
|||
return smoothstep(0.0, 1.0, normalizedDist) * uBlurAmount;
|
||||
}
|
||||
|
||||
// Get Gaussian weight for blur kernel (9-tap, sigma ~= 2.0)
|
||||
float getWeight(int i) {
|
||||
if (i == 0) return 0.0162;
|
||||
if (i == 1) return 0.0540;
|
||||
if (i == 2) return 0.1216;
|
||||
if (i == 3) return 0.1933;
|
||||
if (i == 4) return 0.2258;
|
||||
if (i == 5) return 0.1933;
|
||||
if (i == 6) return 0.1216;
|
||||
if (i == 7) return 0.0540;
|
||||
return 0.0162; // i == 8
|
||||
}
|
||||
|
||||
// Sample with Gaussian blur
|
||||
// Sample with Gaussian blur (9-tap, sigma ~= 2.0, unrolled for GLSL ES 1.00 compatibility)
|
||||
vec4 sampleBlurred(vec2 uv, float blur) {
|
||||
if (blur < 0.01) {
|
||||
return texture2D(uTexture, uv);
|
||||
}
|
||||
|
||||
vec4 color = vec4(0.0);
|
||||
vec2 texelSize = 1.0 / uResolution;
|
||||
|
||||
// For radial mode, blur in radial direction from center
|
||||
|
|
@ -141,7 +106,6 @@ vec4 sampleBlurred(vec2 uv, float blur) {
|
|||
vec2 blurDir;
|
||||
if (uMode == 1) {
|
||||
// Radial: blur away from center
|
||||
// Transform from screen coordinates to texture coordinates
|
||||
vec2 center;
|
||||
if (uIsFrontCamera == 1) {
|
||||
center = vec2(1.0 - uPositionY, 1.0 - uPositionX);
|
||||
|
|
@ -156,26 +120,25 @@ vec4 sampleBlurred(vec2 uv, float blur) {
|
|||
blurDir = vec2(1.0, 0.0);
|
||||
}
|
||||
} else {
|
||||
// Linear: blur perpendicular to focus line
|
||||
// Adjust angle for coordinate transformation
|
||||
float blurAngle;
|
||||
if (uIsFrontCamera == 1) {
|
||||
blurAngle = -uAngle - 1.5707963;
|
||||
} else {
|
||||
blurAngle = uAngle + 1.5707963;
|
||||
}
|
||||
blurDir = vec2(cos(blurAngle), sin(blurAngle));
|
||||
// Linear: blur perpendicular to focus line using precomputed trig
|
||||
blurDir = vec2(uCosAngle, uSinAngle);
|
||||
}
|
||||
|
||||
// Scale blur radius by blur amount
|
||||
float radius = blur * 20.0;
|
||||
vec2 step = blurDir * texelSize * radius;
|
||||
|
||||
// 9-tap Gaussian blur
|
||||
for (int i = 0; i < 9; i++) {
|
||||
float offset = float(i) - 4.0;
|
||||
vec2 samplePos = uv + blurDir * texelSize * offset * radius;
|
||||
color += texture2D(uTexture, samplePos) * getWeight(i);
|
||||
}
|
||||
// Unrolled 9-tap Gaussian blur (avoids integer-branched weight lookup)
|
||||
vec4 color = vec4(0.0);
|
||||
color += texture2D(uTexture, uv + step * -4.0) * 0.0162;
|
||||
color += texture2D(uTexture, uv + step * -3.0) * 0.0540;
|
||||
color += texture2D(uTexture, uv + step * -2.0) * 0.1216;
|
||||
color += texture2D(uTexture, uv + step * -1.0) * 0.1933;
|
||||
color += texture2D(uTexture, uv) * 0.2258;
|
||||
color += texture2D(uTexture, uv + step * 1.0) * 0.1933;
|
||||
color += texture2D(uTexture, uv + step * 2.0) * 0.1216;
|
||||
color += texture2D(uTexture, uv + step * 3.0) * 0.0540;
|
||||
color += texture2D(uTexture, uv + step * 4.0) * 0.0162;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue