Bypass expo-image-manipulator entirely and rely on React Native resizeMethod to prevent Android OOM
This commit is contained in:
@@ -235,90 +235,19 @@ export default function CameraScreen({
|
|||||||
console.log('Capturing from built-in camera fallback...');
|
console.log('Capturing from built-in camera fallback...');
|
||||||
if (expoCameraRef.current) {
|
if (expoCameraRef.current) {
|
||||||
const photo = await expoCameraRef.current.takePictureAsync({
|
const photo = await expoCameraRef.current.takePictureAsync({
|
||||||
quality: 0.7, // Lower quality slightly to reduce file size and OOM risk
|
quality: 0.7, // Lower quality slightly to reduce file size
|
||||||
skipProcessing: true, // Crucial for Android tablets: prevents internal OOM that corrupts the image file
|
skipProcessing: false, // Ensure correct orientation
|
||||||
});
|
});
|
||||||
capturedUri = photo.uri;
|
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 {
|
} else {
|
||||||
throw new Error('Kamera-Referenz ist nicht verfügbar.');
|
throw new Error('Kamera-Referenz ist nicht verfügbar.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- DOWNSCALE & CROP TO EXACTLY 3:2 --
|
// Instead of manipulating the image and risking an OOM or BitmapFactory crash,
|
||||||
try {
|
// we directly use the captured photo and rely on React Native's Image component
|
||||||
console.log('Manipulating image to 3:2 ratio and downscaling to prevent OOM...');
|
// to safely downsample it during rendering via resizeMethod="resize".
|
||||||
// ALWAYS read true dimensions from ImageManipulator.
|
console.log('Photo captured successfully, proceeding to PreviewScreen.');
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-save individual photo to gallery
|
// Auto-save individual photo to gallery
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ export default function PreviewScreen({
|
|||||||
<View style={[styles.collageCanvas, styles.stripCanvas]}>
|
<View style={[styles.collageCanvas, styles.stripCanvas]}>
|
||||||
<View style={styles.stripContent}>
|
<View style={styles.stripContent}>
|
||||||
{photoUris.slice(-3).map((uri, idx) => (
|
{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>
|
||||||
<View style={styles.collageFooter}>
|
<View style={styles.collageFooter}>
|
||||||
@@ -323,7 +323,7 @@ export default function PreviewScreen({
|
|||||||
<View style={[styles.collageCanvas, styles.gridCanvas]}>
|
<View style={[styles.collageCanvas, styles.gridCanvas]}>
|
||||||
<View style={styles.gridContainer}>
|
<View style={styles.gridContainer}>
|
||||||
{photoUris.slice(-4).map((uri, idx) => (
|
{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>
|
||||||
<View style={styles.collageFooter}>
|
<View style={styles.collageFooter}>
|
||||||
@@ -336,7 +336,7 @@ export default function PreviewScreen({
|
|||||||
<View style={[styles.collageCanvas, styles.duoCanvas]}>
|
<View style={[styles.collageCanvas, styles.duoCanvas]}>
|
||||||
<View style={styles.duoContainer}>
|
<View style={styles.duoContainer}>
|
||||||
{photoUris.slice(-2).map((uri, idx) => (
|
{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>
|
||||||
<View style={styles.collageFooter}>
|
<View style={styles.collageFooter}>
|
||||||
@@ -347,7 +347,12 @@ export default function PreviewScreen({
|
|||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<View style={styles.singleCanvas}>
|
<View style={styles.singleCanvas}>
|
||||||
<Image source={{ uri: currentPhoto }} style={styles.singleImage} />
|
<Image
|
||||||
|
source={{ uri: currentPhoto }}
|
||||||
|
style={styles.singleImage}
|
||||||
|
resizeMode="cover"
|
||||||
|
resizeMethod="resize"
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user