fix(camera): matrix-based zoom for pinch/double-tap, tap-to-focus AE/AF lock, library letterbox smear fix, export raw JPEG bytes q95, resume isActive fixes, dark theme polish

This commit is contained in:
2026-09-08 10:25:34 +07:00
parent 515f408c67
commit 8eca31ce58
25 changed files with 1450 additions and 822 deletions
+47 -11
View File
@@ -1,3 +1,7 @@
import { ColorAdjustments } from '../types';
export type BaseFilter = 'classic-neg' | 'provia' | 'velvia' | 'monochrome' | 'none';
// Kelvin to RGB compensation for White Balance.
// In photography, when WB is set HIGH (e.g. 10000K), the camera understands
// the scene light is COLD and compensates with WARM tones (red/orange).
@@ -23,16 +27,21 @@ export function kelvinToRGB(kelvin: number): { r: number; g: number; b: number }
return { r, g, b };
}
// Generate a 4x5 ColorMatrix (array of 20 floats) based on base style and adjustments
export function getSkiaColorMatrix(
baseFilter: 'classic-neg' | 'provia' | 'velvia' | 'monochrome' | 'none',
exposure: number, // -10 to +10
contrast: number, // -10 to +10
saturation: number, // -10 to +10
temperature: number, // 2500 to 10000
tint: number, // -10 to +10
colorChrome: 'none' | 'weak' | 'strong'
): number[] {
// Generate a 4x5 ColorMatrix (array of 20 floats) based on base style and
// adjustments. Tone-domain knobs (dynamicRange/highlight/shadow) and the
// exposure-compensation EV gain are intentionally NOT part of the matrix —
// dynamicRange/highlight/shadow run as a tone shader (see toneShader.ts),
// and EV is applied either as hardware AE bias (camera) or by the caller via
// applyExposureGain (library), never doubled.
export function getSkiaColorMatrix(baseFilter: BaseFilter, adj: ColorAdjustments): number[] {
const exposure = adj.exposure;
const contrast = adj.contrast;
const saturation = adj.saturation;
const temperature = adj.temperature;
const tint = adj.tint;
const colorChrome = adj.colorChrome || 'none';
const colorChromeBlue = adj.colorChromeBlue || 'none';
// 1. Start with Identity Matrix
let matrix = [
1, 0, 0, 0, 0,
@@ -110,6 +119,18 @@ export function getSkiaColorMatrix(
matrix = multiplyMatrices(chromeMat, matrix);
}
// 4b. Color Chrome Effect Blue — deep-blue richness boost (blue channel only)
if (baseFilter !== 'monochrome' && colorChromeBlue !== 'none') {
const factor = colorChromeBlue === 'strong' ? 0.35 : 0.15;
const blueMat = [
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1 + factor, 0, 0,
0, 0, 0, 1, 0,
];
matrix = multiplyMatrices(blueMat, matrix);
}
// 5. White Balance (Kelvin Temperature and Tint) — skip for monochrome to prevent color leakage
if (baseFilter !== 'monochrome') {
const rgbTemp = kelvinToRGB(temperature);
@@ -146,6 +167,21 @@ export function getSkiaColorMatrix(
return matrix;
}
// Apply photographic EV compensation (stops) as a linear gain: 2^EV on every
// RGB channel (offsets scale too, matching true exposure). Only used for
// stills that were NOT exposed with a hardware AE bias (library images).
export function applyExposureGain(matrix: number[], evStops: number): number[] {
if (!evStops) return matrix;
const s = Math.pow(2, evStops);
const gainMat = [
s, 0, 0, 0, 0,
0, s, 0, 0, 0,
0, 0, s, 0, 0,
0, 0, 0, 1, 0,
];
return multiplyMatrices(gainMat, matrix);
}
// Compute a tint color + opacity from the matrix for camera overlay preview
// Applies the matrix to white (1,1,1,1) to see what color cast the filter creates
export function computeMatrixTint(matrix: number[]): { r: number; g: number; b: number; a: number } {
@@ -177,7 +213,7 @@ function multiplyMatrices(a: number[], b: number[]): number[] {
a[r * 5 + 2] * b[2 * 5 + c] +
a[r * 5 + 3] * b[3 * 5 + c];
}
// Handle the 5th column translation translation
// Handle the 5th column translation
result[r * 5 + 4] =
a[r * 5 + 0] * b[0 * 5 + 4] +
a[r * 5 + 1] * b[1 * 5 + 4] +