diff --git a/package-lock.json b/package-lock.json index 5e3e539a..974a7490 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "expo-build-properties": "~1.0.10", "expo-camera": "~17.0.10", "expo-file-system": "~19.0.23", + "expo-image-manipulator": "~14.0.8", "expo-linear-gradient": "~15.0.8", "expo-media-library": "~18.2.1", "expo-status-bar": "~3.0.9", @@ -4670,6 +4671,27 @@ "react-native": "*" } }, + "node_modules/expo-image-loader": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-6.0.0.tgz", + "integrity": "sha512-nKs/xnOGw6ACb4g26xceBD57FKLFkSwEUTDXEDF3Gtcu3MqF3ZIYd3YM+sSb1/z9AKV1dYT7rMSGVNgsveXLIQ==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-image-manipulator": { + "version": "14.0.8", + "resolved": "https://registry.npmjs.org/expo-image-manipulator/-/expo-image-manipulator-14.0.8.tgz", + "integrity": "sha512-sXsXjm7rIxLWZe0j2A41J/Ph53PpFJRdyzJ3EQ/qetxLUvS2m3K1sP5xy37px43qCf0l79N/i6XgFgenFV36/Q==", + "license": "MIT", + "dependencies": { + "expo-image-loader": "~6.0.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-keep-awake": { "version": "15.0.8", "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-15.0.8.tgz", diff --git a/package.json b/package.json index 0f61b4d1..6b76bb2d 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "expo-build-properties": "~1.0.10", "expo-camera": "~17.0.10", "expo-file-system": "~19.0.23", + "expo-image-manipulator": "~14.0.8", "expo-linear-gradient": "~15.0.8", "expo-media-library": "~18.2.1", "expo-status-bar": "~3.0.9", diff --git a/src/screens/CameraScreen.tsx b/src/screens/CameraScreen.tsx index c8653af1..15a3e1f7 100644 --- a/src/screens/CameraScreen.tsx +++ b/src/screens/CameraScreen.tsx @@ -11,6 +11,7 @@ import { } from 'react-native'; import { CameraView, useCameraPermissions } from 'expo-camera'; import { File, Paths } from 'expo-file-system'; +import * as ImageManipulator from 'expo-image-manipulator'; import { LinearGradient } from 'expo-linear-gradient'; import Animated, { useSharedValue, @@ -210,6 +211,8 @@ export default function CameraScreen({ triggerFlash(); let capturedUri: string; + let rawWidth: number = 0; + let rawHeight: number = 0; if (Platform.OS === 'android' && isUsbConnected && usbCameraRef.current) { // USB Camera Capture @@ -225,11 +228,84 @@ export default function CameraScreen({ skipProcessing: false, }); capturedUri = photo.uri; + rawWidth = photo.width; + rawHeight = photo.height; } else { throw new Error('Kamera-Referenz ist nicht verfügbar.'); } } + // -- DOWNSCALE & CROP TO EXACTLY 3:2 -- + try { + console.log('Manipulating image to 3:2 ratio and downscaling to prevent OOM...'); + let w = rawWidth; + let h = rawHeight; + + // If USB camera didn't provide width/height, read them + if (!w || !h) { + const info = await ImageManipulator.manipulateAsync(capturedUri, []); + w = info.width; + h = info.height; + } + + const actions: ImageManipulator.Action[] = []; + const isLandscape = w >= h; + const targetRatio = 3 / 2; + const currentRatio = isLandscape ? w / h : h / w; + + let cropWidth = w; + let cropHeight = h; + let originX = 0; + let originY = 0; + + // Tolerance for floating point inaccuracies + if (Math.abs(currentRatio - targetRatio) > 0.05) { + if (isLandscape) { + if (currentRatio > targetRatio) { + cropWidth = h * targetRatio; + originX = (w - cropWidth) / 2; + } else { + cropHeight = w / targetRatio; + originY = (h - cropHeight) / 2; + } + } else { + if (currentRatio > targetRatio) { + cropHeight = w * targetRatio; + originY = (h - cropHeight) / 2; + } else { + cropWidth = h / targetRatio; + originX = (w - cropWidth) / 2; + } + } + actions.push({ + crop: { + originX: Math.round(originX), + originY: Math.round(originY), + width: Math.round(cropWidth), + height: Math.round(cropHeight) + } + }); + } + + // Resize to a maximum of 1800px width (prevents memory crash) + const MAX_DIM = 1800; + if (isLandscape) { + actions.push({ resize: { width: MAX_DIM } }); + } else { + actions.push({ resize: { height: MAX_DIM } }); + } + + const manipResult = await ImageManipulator.manipulateAsync( + capturedUri, + actions, + { compress: 0.9, format: ImageManipulator.SaveFormat.JPEG } + ); + capturedUri = manipResult.uri; + console.log('Successfully manipulated and cropped image'); + } catch (manipError) { + console.error('Failed to manipulate image, proceeding with original:', manipError); + } + // Auto-save individual photo to gallery try { await saveToGallery(capturedUri);