a85ebf0a81
- One free Play app (com.locphamtran.recipescamera) with an expo-iap PRO unlock, replacing the paid apk for the store channel - Android SEND/SEND_MULTIPLE share target feeds photos into the editor, on cold start and on resume - BACK button in the TopBar and press-and-hold PEEK replace the chip row - NOISE REDUCTION and SHARPENING accept -10: a negative SHARPENING softens the frame before the grain pass, a negative NOISE REDUCTION re-grains it, in the Skia engine, the live preview and the Kotlin export path alike
127 lines
5.5 KiB
TypeScript
127 lines
5.5 KiB
TypeScript
import { requireNativeModule } from 'expo-modules-core';
|
|
|
|
export interface DecodeEncodeTimings {
|
|
decodeMs: number;
|
|
encodeWriteMs: number;
|
|
totalMs: number;
|
|
}
|
|
|
|
export interface ColorTimings {
|
|
decodeMs: number;
|
|
toneMs?: number;
|
|
cinemaMs?: number;
|
|
colorMs: number;
|
|
clarityMs?: number;
|
|
grainMs?: number;
|
|
frameMs?: number;
|
|
sharpenMs?: number;
|
|
encodeWriteMs: number;
|
|
totalMs: number;
|
|
}
|
|
|
|
/** Rect (pixel coords) of the center-largest aspect crop, computed in JS. */
|
|
export interface CropRect {
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
/** ToneUniforms from toneShader.ts (0/0/0 = skip tone pass). */
|
|
export interface ToneUniforms {
|
|
dr: number;
|
|
hl: number;
|
|
sh: number;
|
|
}
|
|
|
|
/** Optional pipeline params passed as a map (native Coroutine arity cap = 8). */
|
|
export interface PhotoAdjust {
|
|
/** ToneUniforms from toneShader.ts; default 0/0/0 = skip tone pass. */
|
|
toneDr?: number;
|
|
toneHl?: number;
|
|
toneSh?: number;
|
|
/** 16 flat floats from cinemaShader.ts getCinemaUniforms(season).flat; null/omitted or flat[0]=0 = skip. */
|
|
cinema?: number[] | null;
|
|
/** ColorAdjustments.denoise -10..10; >0 blur, <0 re-grain (0 = skip). */
|
|
denoise?: number;
|
|
/** ColorAdjustments.clarity -10..10; >0 conv sharpen, <0 mist blur (0 = skip). */
|
|
clarity?: number;
|
|
/** ColorAdjustments.grain 0..10 (0 = skip). */
|
|
grain?: number;
|
|
/** JPEG quality 0..100; default 95. */
|
|
quality?: number;
|
|
/** Final screen-sharpen amount (exportEngine #9 screenSharpenImage); >0 applies. */
|
|
sharpen?: number;
|
|
/** Soften blur radius for a negative sharpening knob (exportEngine #4); >0 applies. */
|
|
soften?: number;
|
|
/** DPI stamped into the JPEG header after encode (patchJpegDpi port); 0 = skip. */
|
|
dpi?: number;
|
|
/** Composition aspect-ratio crop computed after decode (center-largest, engine #1b). */
|
|
aspect?: '4:3' | '3:2' | 'full';
|
|
/** Photo frame preset applied before encode, mirroring frameUtils.ts. Default 'none'. */
|
|
frameId?: 'none' | 'classic-white' | 'cinematic' | 'polaroid' | 'wallframe';
|
|
/** Base64 PNG art (wallframe only, 4000x3117). Decoded per call. */
|
|
frameArtB64?: string;
|
|
/** Base64 TTF body font (geo stamp only). Cached after first decode. */
|
|
fontB64?: string;
|
|
/** Base64 TTF emoji font (geo stamp only). Cached after first decode. */
|
|
iconB64?: string;
|
|
/** Optional GPS stamp {locality, timestamp}; null/omitted = skip. */
|
|
geo?: { locality: string; timestamp: number } | null;
|
|
/** Encode PNG instead of JPEG. Probe-only. */
|
|
png?: boolean;
|
|
/** Probe-only: sample these photo-space pixel coords after frame pass, before encode; logs via samplePixel. */
|
|
probeSamples?: Array<{ x: number; y: number }>;
|
|
/** Probe-only: log per-row stamp diff/amber/white metrics (see samplePixel) and return them in ColorTimings-adjacent logs. */
|
|
probeStampDiff?: boolean;
|
|
}
|
|
|
|
export interface RecipescamExportModule {
|
|
/** Decode JPEG/PNG at srcPath (plain absolute path), re-encode JPEG q and write dstPath. All native, background. Returns stage timings in ms. */
|
|
decodeEncodeAsync(srcPath: string, dstPath: string, quality: number): Promise<DecodeEncodeTimings>;
|
|
/**
|
|
* Native pipeline mirroring exportEngine.ts filter order — decode, optional
|
|
* center-largest crop, optional tone pass (TONE_SKSL port), optional cinema
|
|
* seasonal grade (CINEMA_SKSL port), row-major 4x5 color matrix (translate
|
|
* column in 0..1 Skia space), optional denoise blur / clarity conv / mist
|
|
* blur / grain Overlay, then JPEG-encode to dstPath. All native, off the
|
|
* JS/main threads. Stage order mirrors exportEngine: crop -> tone -> cinema
|
|
* -> matrix -> denoise/clarity -> grain -> frame (classic/cinematic/
|
|
* polaroid/wallframe) -> GPS stamp (geo, bottom of the composed output)
|
|
* -> encode. Timings gain frameMs when a frame/stamp pass runs.
|
|
*/
|
|
processPhotoAsync(srcPath: string, dstPath: string, matrix: number[], crop: CropRect | null, adjust?: PhotoAdjust): Promise<ColorTimings>;
|
|
/** P1 alias: matrix + crop, no tone/cinema/enhance passes. */
|
|
processColorAsync(srcPath: string, dstPath: string, matrix: number[], crop: CropRect | null, quality: number): Promise<ColorTimings>;
|
|
/**
|
|
* Insert an encoded image into DCIM/Camera (MediaStore RELATIVE_PATH) — where
|
|
* the stock camera writes. Returns the MediaStore _ID (expo Asset.id shape),
|
|
* or '' when the insert/copy fails, so JS falls back to MediaLibrary.
|
|
*/
|
|
saveToCameraRollAsync(srcPath: string, mimeType: string): Promise<string>;
|
|
/**
|
|
* Raw device identity the stock Xiaomi camera folds into EXIF 0x889e (the
|
|
* watermark blob HyperOS Gallery reads) but CameraX/HAL never supplies:
|
|
* codename, marketing name, manufacturer, model. Empty strings when absent.
|
|
*/
|
|
deviceInfo(): { device: string; marketName: string; manufacturer: string; model: string };
|
|
|
|
/**
|
|
* The image a system share handed this launch (AndroidManifest
|
|
* ACTION_SEND — see takeSharedImage in the Kotlin module): the sender's
|
|
* stream is copied into the cache and returned as an ordinary file.
|
|
* `path` is "" when this launch was not a share, or when the share was
|
|
* already taken — the intent is read and cleared exactly once.
|
|
*/
|
|
takeSharedImage(): Promise<{ path: string; assetId: string; type: string }>;
|
|
|
|
/** P0 probe helpers — removed before ship. */
|
|
ping(): Promise<string>;
|
|
materializeBenchAsset(assetName: string): Promise<string>;
|
|
materializeSolidAsset(r: number, g: number, b: number): Promise<string>;
|
|
materializeRampAsset(): Promise<string>;
|
|
materializeStepAsset(): Promise<string>;
|
|
}
|
|
|
|
export default requireNativeModule('RecipesCamExport') as RecipescamExportModule;
|