// The floor the whole pipeline is budgeted against: the cheapest Android that // clears this app's minimum. What sets the minimum: minSdk 26 (below it // vision-camera's NativeBufferHelper throws "HardwareBuffers require minSdk 26 // or higher!" on every frame), a 32-bit armeabi-v7a build (the shipped APK // carries both ABIs), Camera2 with EXIF, and Skia's GL backend, which links // -lGLESv2 (see node_modules/@shopify/react-native-skia/android/CMakeLists.txt). // Every API 26 phone clears the GL bar, so the floor is the Android Go, 1 GB // class: Redmi Go (Snapdragon 425, Adreno 308, 5.8-7.2 GFLOPS / 5.3 GB/s, // 8 MP), Nokia 1 (MT6737M, Mali-T720, 5 MP) and Galaxy J2 Core (Exynos 7570, // 8 MP) — 4x Cortex-A53 at 1.1-1.4 GHz, 1 GB RAM, 8 GB storage, 480p-720p // screen. Nothing weaker can install. // // What that floor affords, at 4 bytes per RGBA8888 pixel: // 8 MP raster 32 MB -> the export's live surfaces alone = 96 MB // one full-frame pass 64 MB of traffic @ 5.3 GB/s ~ 12 ms // 4 slow A53 cores ~1/6 of a current flagship core // Hence, here: one resample at the door instead of a full-resolution surface // chain, and no 3-frame burst (six 8 MP decodes plus a CPU alignment on four // slow cores is seconds of shutter lag on the device class least able to afford // it). The live preview needs no cap — Viewfinder already forces the frame // stream to the 4:3 size nearest 1280x960. // // No state and no native import: the RAM reading comes from // photoMeta.devicePerf() and is passed in, so this module also runs under plain // node (see deviceFloor.check.ts). export const FLOOR_RAM_MB = 2048; export const FLOOR_EXPORT_PIXELS = 4_000_000; // ramMb 0 = the platform would not say. Unknown is never a floor device: the app // must not downgrade a phone it could not measure. export const isFloorDevice = (ramMb: number): boolean => ramMb > 0 && ramMb <= FLOOR_RAM_MB; // Most pixels the export may paint. 4 MP (2336x1752) still prints 10x15 cm at // 300 dpi and halves every surface behind it — the 12S Ultra's 12 MP capture is // untouched, since it is not a floor device. // ponytail: downscale rather than tile — tile the render the day a floor user // needs the full 8 MP file. export const maxExportPixels = (ramMb: number): number => isFloorDevice(ramMb) ? FLOOR_EXPORT_PIXELS : Infinity; // ponytail: one capability, all or nothing — add a 2-frame CPU-lite merge if a // floor device ever reports it can afford one. export const canBurst = (ramMb: number): boolean => !isFloorDevice(ramMb); // Scale a raster down to at most `cap` pixels, keeping its aspect. Whole pixels // only: the surface allocator and the EXIF stamp both read these back. export function floorRaster( w: number, h: number, cap: number ): { width: number; height: number } { if (!(w > 0 && h > 0) || !(w * h > cap)) return { width: w, height: h }; const s = Math.sqrt(cap / (w * h)); return { width: Math.max(1, Math.round(w * s)), height: Math.max(1, Math.round(h * s)) }; }