fix: lỗi áp dụng recipe lên live view của camera
This commit is contained in:
+43
-39
@@ -1,45 +1,26 @@
|
||||
// Tanner Helland's Kelvin to RGB approximation
|
||||
// Kelvin range: 1000K to 40000K (we use 2500K to 10000K)
|
||||
// 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).
|
||||
// When WB is set LOW (e.g. 2500K), the camera compensates with COOL tones (blue).
|
||||
export function kelvinToRGB(kelvin: number): { r: number; g: number; b: number } {
|
||||
const temp = Math.max(1000, Math.min(40000, kelvin)) / 100;
|
||||
let r = 0;
|
||||
let g = 0;
|
||||
let b = 0;
|
||||
const k = Math.min(10000, Math.max(2500, kelvin));
|
||||
let r = 1.0, g = 1.0, b = 1.0;
|
||||
|
||||
// Red
|
||||
if (temp <= 66) {
|
||||
r = 255;
|
||||
if (k < 5500) {
|
||||
// 2500K → 5500K: Warm light → need blue/cyan compensation
|
||||
const factor = (5500 - k) / 3000;
|
||||
r = 1.0 - factor * 0.45;
|
||||
g = 1.0 - factor * 0.15;
|
||||
b = 1.0 + factor * 0.50;
|
||||
} else {
|
||||
r = temp - 60;
|
||||
r = 329.698727446 * Math.pow(r, -0.1332047592);
|
||||
r = Math.max(0, Math.min(255, r));
|
||||
// 5500K → 10000K: Cool light → need warm/amber compensation
|
||||
const factor = (k - 5500) / 4500;
|
||||
r = 1.0 + factor * 0.55;
|
||||
g = 1.0 + factor * 0.20;
|
||||
b = 1.0 - factor * 0.40;
|
||||
}
|
||||
|
||||
// Green
|
||||
if (temp <= 66) {
|
||||
g = temp;
|
||||
g = 99.4708025861 * Math.log(g) - 161.1195681661;
|
||||
g = Math.max(0, Math.min(255, g));
|
||||
} else {
|
||||
g = temp - 60;
|
||||
g = 288.1221695283 * Math.pow(g, -0.0755148492);
|
||||
g = Math.max(0, Math.min(255, g));
|
||||
}
|
||||
|
||||
// Blue
|
||||
if (temp >= 66) {
|
||||
b = 255;
|
||||
} else {
|
||||
if (temp <= 19) {
|
||||
b = 0;
|
||||
} else {
|
||||
b = temp - 10;
|
||||
b = 138.5177312231 * Math.log(b) - 305.0447927307;
|
||||
b = Math.max(0, Math.min(255, b));
|
||||
}
|
||||
}
|
||||
|
||||
return { r: r / 255, g: g / 255, b: b / 255 };
|
||||
return { r, g, b };
|
||||
}
|
||||
|
||||
// Generate a 4x5 ColorMatrix (array of 20 floats) based on base style and adjustments
|
||||
@@ -85,7 +66,7 @@ export function getSkiaColorMatrix(
|
||||
0, 0, 0, 1, 0,
|
||||
];
|
||||
} else if (baseFilter === 'monochrome') {
|
||||
// Classic black and white filter (luminance weights)
|
||||
// Acros Monochrome: ITU-R BT.709 luma — pure grayscale, zero color leakage
|
||||
const r = 0.2126;
|
||||
const g = 0.7152;
|
||||
const b = 0.0722;
|
||||
@@ -128,7 +109,10 @@ export function getSkiaColorMatrix(
|
||||
matrix = multiplyMatrices(chromeMat, matrix);
|
||||
}
|
||||
|
||||
// 5. White Balance (Kelvin Temperature and Tint)
|
||||
// 5. White Balance (Kelvin Temperature and Tint) — skip for monochrome to prevent color leakage
|
||||
if (baseFilter === 'monochrome') {
|
||||
return matrix;
|
||||
}
|
||||
const rgbTemp = kelvinToRGB(temperature);
|
||||
// Tint adjustment: -10 is Green (+G), +10 is Magenta (+R, +B)
|
||||
const tintR = tint > 0 ? (tint / 10) * 0.08 : 0;
|
||||
@@ -159,6 +143,26 @@ export function getSkiaColorMatrix(
|
||||
return 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 } {
|
||||
const apply = (r: number, g: number, b: number, a: number) => {
|
||||
const row = (offset: number) =>
|
||||
matrix[offset + 0] * r + matrix[offset + 1] * g + matrix[offset + 2] * b + matrix[offset + 3] * a + matrix[offset + 4];
|
||||
return { r: row(0), g: row(5), b: row(10), a: row(15) };
|
||||
};
|
||||
|
||||
// Apply matrix to neutral white to get the filter's color cast
|
||||
const white = apply(1, 1, 1, 1);
|
||||
// Clamp and compute perceived intensity to determine opacity
|
||||
const clamp = (v: number) => Math.max(0, Math.min(1, v));
|
||||
const intensity = (white.r + white.g + white.b) / 3;
|
||||
// Opacity is higher when the filter deviates from neutral
|
||||
const deviation = Math.abs(white.r - intensity) + Math.abs(white.g - intensity) + Math.abs(white.b - intensity);
|
||||
const opacity = Math.min(0.35, deviation * 2.5);
|
||||
return { r: clamp(white.r), g: clamp(white.g), b: clamp(white.b), a: opacity };
|
||||
}
|
||||
|
||||
// 4x5 Matrix multiplication utility: A * B
|
||||
function multiplyMatrices(a: number[], b: number[]): number[] {
|
||||
const result = new Array(20).fill(0);
|
||||
|
||||
@@ -50,6 +50,18 @@ export async function getCurrentGPS(): Promise<GPSInfo | null> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function reverseGeocode(latitude: number, longitude: number): Promise<string | null> {
|
||||
try {
|
||||
const geo = await Location.reverseGeocodeAsync({ latitude, longitude });
|
||||
if (geo && geo.length > 0) {
|
||||
return (geo[0].subregion || geo[0].city || geo[0].region || '').toUpperCase() || null;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Reverse geocode failed:', e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function formatCoordinate(val: number, type: 'lat' | 'lon'): string {
|
||||
const absolute = Math.abs(val);
|
||||
const degrees = Math.floor(absolute);
|
||||
|
||||
Reference in New Issue
Block a user