import * as FileSystem from 'expo-file-system'; import * as ImageManipulator from 'expo-image-manipulator'; const FOOD_IMAGES_DIR = `${FileSystem.documentDirectory}food-images/`; async function ensureImageDirectory(): Promise { const info = await FileSystem.getInfoAsync(FOOD_IMAGES_DIR); if (!info.exists) { await FileSystem.makeDirectoryAsync(FOOD_IMAGES_DIR, { intermediates: true }); } } export async function persistImage(tempUri: string, id: string): Promise { await ensureImageDirectory(); const ext = tempUri.split('.').pop() || 'jpg'; const permanentPath = `${FOOD_IMAGES_DIR}${id}.${ext}`; await FileSystem.copyAsync({ from: tempUri, to: permanentPath }); return permanentPath; } export async function resizeAndEncodeImage(uri: string): Promise { const result = await ImageManipulator.manipulateAsync(uri, [{ resize: { width: 1024 } }], { compress: 0.7, format: ImageManipulator.SaveFormat.JPEG, base64: true, }); if (!result.base64) { throw new Error('Failed to generate base64 from image'); } return result.base64; } export type CropRegion = { x: number; y: number; width: number; height: number; }; export async function cropImage(uri: string, region: CropRegion): Promise { const result = await ImageManipulator.manipulateAsync( uri, [ { crop: { originX: region.x, originY: region.y, width: region.width, height: region.height }, }, ], { compress: 0.9, format: ImageManipulator.SaveFormat.JPEG } ); return result.uri; } export type NormalizedBoundingBox = { x: number; y: number; width: number; height: number; }; export async function cropBase64WithBoundingBox( base64: string, boundingBox: NormalizedBoundingBox ): Promise { const tempUri = `${FileSystem.cacheDirectory}temp_crop_${Date.now()}.jpg`; await FileSystem.writeAsStringAsync(tempUri, base64, { encoding: FileSystem.EncodingType.Base64, }); const info = await ImageManipulator.manipulateAsync(tempUri, [], {}); const imgWidth = info.width; const imgHeight = info.height; const originX = Math.round(boundingBox.x * imgWidth); const originY = Math.round(boundingBox.y * imgHeight); const cropWidth = Math.round(boundingBox.width * imgWidth); const cropHeight = Math.round(boundingBox.height * imgHeight); const result = await ImageManipulator.manipulateAsync( tempUri, [{ crop: { originX, originY, width: cropWidth, height: cropHeight } }], { compress: 0.8, format: ImageManipulator.SaveFormat.JPEG, base64: true } ); await FileSystem.deleteAsync(tempUri, { idempotent: true }); if (!result.base64) { throw new Error('Failed to crop image'); } return result.base64; }