feat(exif): stamp RC_<ts> filenames + EXIF on capture and export
This commit is contained in:
@@ -9,6 +9,8 @@ import { TONE_SKSL, getToneUniforms, toneIsActive, toneUniformArray } from './to
|
||||
import { CINEMA_SKSL, getCinemaUniforms, cinemaIsActive } from './cinemaShader';
|
||||
import { drawFrameOnCanvas, polaroidLayout, POLAROID_CARD, POLAROID_WIN_W, wallframeLayout, WALLFRAME_W, WALLFRAME_H, WALLFRAME_LAND_W, WALLFRAME_LAND_H } from './frameUtils';
|
||||
import { patchJpegDpi } from './jpegDpi';
|
||||
import { writeJpegExif } from './exifWrite';
|
||||
import { nextPhotoPath, readPhotoBytes } from './photoMeta';
|
||||
import { applyPhotoRotation } from './skiaImage';
|
||||
|
||||
export interface ExportResult {
|
||||
@@ -76,6 +78,13 @@ export interface ExportOptions {
|
||||
// see exactly the orientation the preview showed.
|
||||
photoRotation?: 0 | 90 | 180 | 270;
|
||||
photoStraighten?: number;
|
||||
// This export came from a photo loaded out of the library (not a live
|
||||
// capture): the file is named RC_<ts>_exported.jpg and the loaded photo's
|
||||
// own EXIF lives on in the output.
|
||||
exported?: boolean;
|
||||
// Shutter instant of a live capture (ms). Absent = keep the loaded photo's
|
||||
// own DateTimeOriginal.
|
||||
capturedAt?: number | null;
|
||||
}
|
||||
|
||||
// Lightroom-style "Screen" output sharpen: subtle unsharp mask. A 3x3
|
||||
@@ -111,12 +120,6 @@ function screenSharpenImage(image: SkImage, amount = 0.5): SkImage {
|
||||
return sharpened;
|
||||
}
|
||||
|
||||
// Unique filename per export. A fixed path made every export share one URI, and
|
||||
// RN's <Image> cache (keyed by URI) then kept showing the previous photo in the
|
||||
// thumbnail after a new capture overwrote the file. A fresh URI per export busts
|
||||
// that cache.
|
||||
const nextExportFile = () => `${FileSystem.cacheDirectory}camrecipe_pro_export_${Date.now()}.jpg`;
|
||||
|
||||
export async function processAndExportPhoto(
|
||||
sourceUri: string,
|
||||
recipe: Recipe,
|
||||
@@ -706,10 +709,27 @@ export async function processAndExportPhoto(
|
||||
bytes = patchJpegDpi(bytes, options.dpi);
|
||||
}
|
||||
|
||||
// 9c. EXIF. Skia's re-encode dropped the loaded photo's metadata block, so
|
||||
// rebuild it here: the source photo donates every tag this app does not
|
||||
// own (camera, lens, exposure) and the fields it is responsible for are
|
||||
// stamped on top — software + version, capture date, GPS, pixel size.
|
||||
bytes = writeJpegExif(
|
||||
bytes,
|
||||
{
|
||||
dateTime: options?.capturedAt ? new Date(options.capturedAt) : null,
|
||||
gps: useGeotag && gpsInfo ? { latitude: gpsInfo.latitude, longitude: gpsInfo.longitude } : null,
|
||||
width: resultImage.width(),
|
||||
height: resultImage.height(),
|
||||
dpi: options?.dpi ?? null,
|
||||
},
|
||||
await readPhotoBytes(sourceUri)
|
||||
);
|
||||
|
||||
// 10. Write binary bytes to a unique temporary local file. The unique name
|
||||
// is load-bearing: a fixed URI would be served from RN's <Image> cache and
|
||||
// the thumbnail/preview would keep showing the previous export.
|
||||
const exportFile = nextExportFile();
|
||||
// the thumbnail/preview would keep showing the previous export (and the
|
||||
// gallery shows this basename).
|
||||
const exportFile = nextPhotoPath(options?.exported === true);
|
||||
try {
|
||||
new File(exportFile).write(bytes);
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user