Bypass expo-image-manipulator entirely and rely on React Native resizeMethod to prevent Android OOM
This commit is contained in:
@@ -235,91 +235,20 @@ export default function CameraScreen({
|
||||
console.log('Capturing from built-in camera fallback...');
|
||||
if (expoCameraRef.current) {
|
||||
const photo = await expoCameraRef.current.takePictureAsync({
|
||||
quality: 0.7, // Lower quality slightly to reduce file size and OOM risk
|
||||
skipProcessing: true, // Crucial for Android tablets: prevents internal OOM that corrupts the image file
|
||||
quality: 0.7, // Lower quality slightly to reduce file size
|
||||
skipProcessing: false, // Ensure correct orientation
|
||||
});
|
||||
capturedUri = photo.uri;
|
||||
rawWidth = photo.width;
|
||||
rawHeight = photo.height;
|
||||
|
||||
// Wait 500ms to ensure the OS has completely flushed the file to disk.
|
||||
// This prevents "Loading bitmap failed" when ImageManipulator reads it too fast.
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
} 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...');
|
||||
// ALWAYS read true dimensions from ImageManipulator.
|
||||
// expo-camera rawWidth/rawHeight ignores EXIF rotation, causing crop bounds to exceed image bounds!
|
||||
const info = await ImageManipulator.manipulateAsync(capturedUri, []);
|
||||
let w = info.width;
|
||||
let 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.max(0, Math.floor(originX)),
|
||||
originY: Math.max(0, Math.floor(originY)),
|
||||
width: Math.min(w - Math.max(0, Math.floor(originX)), Math.floor(cropWidth)),
|
||||
height: Math.min(h - Math.max(0, Math.floor(originY)), Math.floor(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: 'jpeg' as any }
|
||||
);
|
||||
capturedUri = manipResult.uri;
|
||||
console.log('Successfully manipulated and cropped image');
|
||||
} catch (manipError) {
|
||||
console.error('Failed to manipulate image, preventing OOM crash!', manipError);
|
||||
alert('Bildverarbeitungs-Fehler: ' + (manipError.message || manipError.toString()));
|
||||
onCancel();
|
||||
return; // Abort here so we don't send a 16MB raw image to PreviewScreen
|
||||
}
|
||||
|
||||
// Instead of manipulating the image and risking an OOM or BitmapFactory crash,
|
||||
// we directly use the captured photo and rely on React Native's Image component
|
||||
// to safely downsample it during rendering via resizeMethod="resize".
|
||||
console.log('Photo captured successfully, proceeding to PreviewScreen.');
|
||||
|
||||
// Auto-save individual photo to gallery
|
||||
try {
|
||||
await saveToGallery(capturedUri);
|
||||
|
||||
@@ -309,7 +309,7 @@ export default function PreviewScreen({
|
||||
<View style={[styles.collageCanvas, styles.stripCanvas]}>
|
||||
<View style={styles.stripContent}>
|
||||
{photoUris.slice(-3).map((uri, idx) => (
|
||||
<Image key={idx} source={{ uri }} style={styles.stripImage} />
|
||||
<Image key={idx} source={{ uri }} style={styles.stripImage} resizeMethod="resize" />
|
||||
))}
|
||||
</View>
|
||||
<View style={styles.collageFooter}>
|
||||
@@ -323,7 +323,7 @@ export default function PreviewScreen({
|
||||
<View style={[styles.collageCanvas, styles.gridCanvas]}>
|
||||
<View style={styles.gridContainer}>
|
||||
{photoUris.slice(-4).map((uri, idx) => (
|
||||
<Image key={idx} source={{ uri }} style={styles.gridImage} />
|
||||
<Image key={idx} source={{ uri }} style={styles.gridImage} resizeMethod="resize" />
|
||||
))}
|
||||
</View>
|
||||
<View style={styles.collageFooter}>
|
||||
@@ -336,7 +336,7 @@ export default function PreviewScreen({
|
||||
<View style={[styles.collageCanvas, styles.duoCanvas]}>
|
||||
<View style={styles.duoContainer}>
|
||||
{photoUris.slice(-2).map((uri, idx) => (
|
||||
<Image key={idx} source={{ uri }} style={styles.duoImage} />
|
||||
<Image key={idx} source={{ uri }} style={styles.duoImage} resizeMethod="resize" />
|
||||
))}
|
||||
</View>
|
||||
<View style={styles.collageFooter}>
|
||||
@@ -347,7 +347,12 @@ export default function PreviewScreen({
|
||||
default:
|
||||
return (
|
||||
<View style={styles.singleCanvas}>
|
||||
<Image source={{ uri: currentPhoto }} style={styles.singleImage} />
|
||||
<Image
|
||||
source={{ uri: currentPhoto }}
|
||||
style={styles.singleImage}
|
||||
resizeMode="cover"
|
||||
resizeMethod="resize"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user