feat: stamp 300dpi metadata on camera-captured exports

Skia JPEG re-encode drops original header (72dpi camera default). New
src/utils/jpegDpi.ts rewrites JFIF APP0 density + EXIF X/YResolution
(endian-aware) or inserts minimal JFIF when neither exists; applied via
processAndExportPhoto options.dpi in the camera-capture branch only.
Library-mode exports untouched. Pixels never change - metadata only.
This commit is contained in:
2026-09-07 16:31:24 +07:00
parent 96f412e269
commit aa6a74f229
3 changed files with 178 additions and 3 deletions
+15 -2
View File
@@ -6,12 +6,18 @@ import { Recipe, GPSInfo, FrameId } from '../types';
import { getSkiaColorMatrix } from './colorUtils';
import { drawFrameOnCanvas } from './frameUtils';
import { formatCoordinate } from './locationUtils';
import { base64ToBytes, bytesToBase64, patchJpegDpi } from './jpegDpi';
export interface ExportResult {
uri: string;
savedToLibrary: boolean;
}
export interface ExportOptions {
// DPI to stamp into the JPEG header (metadata only — pixel size never changes).
dpi?: number;
}
// Fixed filename: every export overwrites the previous one, so temp files
// never accumulate in the cache directory.
const EXPORT_FILE = `${FileSystem.cacheDirectory}camrecipe_pro_last_export.jpg`;
@@ -21,7 +27,8 @@ export async function processAndExportPhoto(
recipe: Recipe,
frameId: FrameId,
useGeotag: boolean,
gpsInfo: GPSInfo | null
gpsInfo: GPSInfo | null,
options?: ExportOptions
): Promise<ExportResult | null> {
try {
// 1. Read source image file into Skia
@@ -181,13 +188,19 @@ export async function processAndExportPhoto(
// 9. Snapshot & Encode to JPEG (base64 string, no SkData round-trip)
const resultImage = surface.makeImageSnapshot();
const base64 = resultImage.encodeToBase64(ImageFormat.JPEG, 95);
let base64 = resultImage.encodeToBase64(ImageFormat.JPEG, 95);
if (!base64) {
console.error('Failed to encode image to JPEG');
return null;
}
// 9b. Patch DPI metadata (JFIF density / EXIF resolution) when requested
if (options?.dpi && options.dpi > 0) {
const patched = patchJpegDpi(base64ToBytes(base64), options.dpi);
base64 = bytesToBase64(patched);
}
// 10. Write binary bytes to temporary local file (overwrites previous export)
await FileSystem.writeAsStringAsync(EXPORT_FILE, base64, {
encoding: FileSystem.EncodingType.Base64,