react-native-expo-cropper 1.2.38 → 1.2.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CustomCamera.js +66 -26
- package/dist/ImageCropper.js +132 -214
- package/dist/ImageCropperStyles.js +31 -5
- package/package.json +1 -1
- package/src/CustomCamera.js +70 -20
- package/src/ImageCropper.js +142 -221
- package/src/ImageCropperStyles.js +27 -4
|
@@ -17,6 +17,8 @@ var styles = _reactNative.StyleSheet.create({
|
|
|
17
17
|
container: {
|
|
18
18
|
flex: 1,
|
|
19
19
|
alignItems: 'center',
|
|
20
|
+
justifyContent: 'flex-start',
|
|
21
|
+
// ✅ Start from top, allow content to flow down
|
|
20
22
|
backgroundColor: DEEP_BLACK
|
|
21
23
|
},
|
|
22
24
|
buttonContainer: {
|
|
@@ -32,6 +34,20 @@ var styles = _reactNative.StyleSheet.create({
|
|
|
32
34
|
zIndex: 10,
|
|
33
35
|
gap: 10
|
|
34
36
|
},
|
|
37
|
+
buttonContainerBelow: {
|
|
38
|
+
// ✅ Buttons positioned BELOW image, not overlapping, above Android navigation bar
|
|
39
|
+
position: 'absolute',
|
|
40
|
+
bottom: 0,
|
|
41
|
+
left: 0,
|
|
42
|
+
right: 0,
|
|
43
|
+
flexDirection: 'row',
|
|
44
|
+
justifyContent: 'center',
|
|
45
|
+
alignItems: 'center',
|
|
46
|
+
paddingHorizontal: 10,
|
|
47
|
+
paddingTop: 16,
|
|
48
|
+
gap: 8,
|
|
49
|
+
backgroundColor: DEEP_BLACK // Fond noir pour séparer visuellement
|
|
50
|
+
},
|
|
35
51
|
iconButton: {
|
|
36
52
|
backgroundColor: PRIMARY_GREEN,
|
|
37
53
|
width: 60,
|
|
@@ -41,16 +57,26 @@ var styles = _reactNative.StyleSheet.create({
|
|
|
41
57
|
justifyContent: 'center',
|
|
42
58
|
marginRight: 5
|
|
43
59
|
},
|
|
60
|
+
rotationButton: {
|
|
61
|
+
// Cercle, même couleur que Reset/Confirm (#549433), même hauteur que les autres boutons
|
|
62
|
+
backgroundColor: '#549433',
|
|
63
|
+
width: 56,
|
|
64
|
+
height: 48,
|
|
65
|
+
// Même hauteur approximative que les boutons rectangulaires (padding 10 + texte)
|
|
66
|
+
borderRadius: 28,
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
justifyContent: 'center'
|
|
69
|
+
},
|
|
44
70
|
button: {
|
|
45
71
|
flex: 1,
|
|
46
|
-
|
|
47
|
-
|
|
72
|
+
minHeight: 48,
|
|
73
|
+
// Même hauteur que le bouton de rotation
|
|
74
|
+
paddingVertical: 12,
|
|
75
|
+
paddingHorizontal: 10,
|
|
48
76
|
alignItems: "center",
|
|
49
77
|
justifyContent: "center",
|
|
50
78
|
backgroundColor: "#549433",
|
|
51
|
-
borderRadius: 5
|
|
52
|
-
marginBottom: 20,
|
|
53
|
-
marginRight: 5
|
|
79
|
+
borderRadius: 5
|
|
54
80
|
},
|
|
55
81
|
buttonText: {
|
|
56
82
|
color: 'white',
|
package/package.json
CHANGED
package/src/CustomCamera.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
SafeAreaView,
|
|
9
9
|
Dimensions,
|
|
10
10
|
Image,
|
|
11
|
+
Platform,
|
|
11
12
|
} from 'react-native';
|
|
12
13
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
13
14
|
import { Camera, CameraView } from 'expo-camera';
|
|
@@ -51,6 +52,9 @@ export default function CustomCamera({ onPhotoCaptured, onFrameCalculated }) {
|
|
|
51
52
|
// ✅ CRITICAL FIX: Calculate green frame coordinates relative to camera preview
|
|
52
53
|
// The green frame should be calculated on the wrapper (as it's visually drawn there)
|
|
53
54
|
// But we store it with wrapper dimensions so ImageCropper can map it correctly
|
|
55
|
+
//
|
|
56
|
+
// NOTE: Camera capture aspect ratio (typically 4:3) may differ from wrapper aspect ratio (9:16)
|
|
57
|
+
// This is handled in ImageCropper by using "cover" mode to match preview content
|
|
54
58
|
const calculateGreenFrameCoordinates = () => {
|
|
55
59
|
const wrapperWidth = cameraWrapperLayout.width;
|
|
56
60
|
const wrapperHeight = cameraWrapperLayout.height;
|
|
@@ -104,45 +108,85 @@ export default function CustomCamera({ onPhotoCaptured, onFrameCalculated }) {
|
|
|
104
108
|
// Wait a bit before taking picture (works on iOS)
|
|
105
109
|
await waitForRender(2);
|
|
106
110
|
|
|
107
|
-
// ✅
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
// ✅ OPTIMIZED: Capture with maximum quality and native camera ratio
|
|
112
|
+
// Platform-specific optimizations for best quality
|
|
113
|
+
const captureOptions = {
|
|
114
|
+
// Maximum quality (0-1, 1 = best quality, no compression)
|
|
115
|
+
quality: 1,
|
|
116
|
+
|
|
117
|
+
// Disable shutter sound for better UX
|
|
110
118
|
shutterSound: false,
|
|
111
|
-
|
|
112
|
-
//
|
|
119
|
+
|
|
120
|
+
// ✅ CRITICAL: skipProcessing preserves original resolution and avoids interpolation
|
|
121
|
+
// This ensures pixel-perfect quality and prevents premature resizing
|
|
113
122
|
skipProcessing: true,
|
|
114
|
-
|
|
115
|
-
//
|
|
123
|
+
|
|
124
|
+
// Include EXIF metadata (orientation, camera settings, etc.)
|
|
116
125
|
exif: true,
|
|
126
|
+
|
|
127
|
+
// ✅ Platform-specific optimizations
|
|
128
|
+
...(Platform.OS === 'ios' && {
|
|
129
|
+
// iOS: Use native capture format (typically 4:3 or 16:9 depending on device)
|
|
130
|
+
// No additional processing to preserve quality
|
|
131
|
+
}),
|
|
132
|
+
...(Platform.OS === 'android' && {
|
|
133
|
+
// Android: Ensure maximum resolution capture
|
|
134
|
+
// skipProcessing already handles this, but we can add Android-specific options if needed
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
console.log("📸 Capturing photo with maximum quality settings:", {
|
|
139
|
+
platform: Platform.OS,
|
|
140
|
+
options: captureOptions,
|
|
141
|
+
wrapperSize: { width: cameraWrapperLayout.width, height: cameraWrapperLayout.height }
|
|
117
142
|
});
|
|
118
143
|
|
|
119
|
-
|
|
144
|
+
const photo = await cameraRef.current.takePictureAsync(captureOptions);
|
|
145
|
+
|
|
146
|
+
// ✅ Validate captured photo dimensions
|
|
147
|
+
if (!photo.width || !photo.height || photo.width === 0 || photo.height === 0) {
|
|
148
|
+
throw new Error("Invalid photo dimensions received from camera");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const capturedAspectRatio = photo.width / photo.height;
|
|
152
|
+
console.log("✅ Photo captured with maximum quality:", {
|
|
120
153
|
uri: photo.uri,
|
|
121
154
|
width: photo.width,
|
|
122
155
|
height: photo.height,
|
|
123
|
-
|
|
156
|
+
aspectRatio: capturedAspectRatio.toFixed(3),
|
|
157
|
+
expectedRatio: "~1.33 (4:3) or ~1.78 (16:9)",
|
|
158
|
+
exif: photo.exif ? "present" : "missing",
|
|
159
|
+
fileSize: photo.uri ? "available" : "unknown"
|
|
124
160
|
});
|
|
125
161
|
|
|
126
162
|
// ✅ CRITICAL FIX: Use the same green frame coordinates that are used for rendering
|
|
127
163
|
// Fallback to recalculation if, for some reason, state is not yet set
|
|
128
164
|
const greenFrameCoords = greenFrame || calculateGreenFrameCoordinates();
|
|
129
165
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// C'est acceptable car l'utilisateur peut corriger via la rotation dans ImageCropper
|
|
134
|
-
const fixedUri = photo.uri;
|
|
166
|
+
if (!greenFrameCoords) {
|
|
167
|
+
throw new Error("Green frame coordinates not available");
|
|
168
|
+
}
|
|
135
169
|
|
|
136
|
-
//
|
|
137
|
-
|
|
170
|
+
// ✅ Send photo URI and frame data to ImageCropper
|
|
171
|
+
// The photo maintains its native resolution and aspect ratio
|
|
172
|
+
// ImageCropper will handle display and cropping while preserving quality
|
|
173
|
+
onPhotoCaptured(photo.uri, {
|
|
138
174
|
greenFrame: greenFrameCoords,
|
|
139
|
-
capturedImageSize: {
|
|
175
|
+
capturedImageSize: {
|
|
176
|
+
width: photo.width,
|
|
177
|
+
height: photo.height,
|
|
178
|
+
aspectRatio: capturedAspectRatio
|
|
179
|
+
}
|
|
140
180
|
});
|
|
181
|
+
|
|
141
182
|
setLoadingBeforeCapture(false);
|
|
142
183
|
} catch (error) {
|
|
143
|
-
console.error("Error capturing photo:", error);
|
|
184
|
+
console.error("❌ Error capturing photo:", error);
|
|
144
185
|
setLoadingBeforeCapture(false);
|
|
145
|
-
Alert.alert(
|
|
186
|
+
Alert.alert(
|
|
187
|
+
"Erreur",
|
|
188
|
+
`Impossible de capturer la photo: ${error.message || "Erreur inconnue"}. Veuillez réessayer.`
|
|
189
|
+
);
|
|
146
190
|
}
|
|
147
191
|
}
|
|
148
192
|
};
|
|
@@ -168,7 +212,13 @@ export default function CustomCamera({ onPhotoCaptured, onFrameCalculated }) {
|
|
|
168
212
|
style={styles.camera}
|
|
169
213
|
facing="back"
|
|
170
214
|
ref={cameraRef}
|
|
171
|
-
onCameraReady={() =>
|
|
215
|
+
onCameraReady={() => {
|
|
216
|
+
setIsReady(true);
|
|
217
|
+
console.log("✅ Camera ready - Maximum quality capture enabled");
|
|
218
|
+
}}
|
|
219
|
+
// ✅ Ensure camera uses native aspect ratio (typically 4:3 for most devices)
|
|
220
|
+
// The wrapper constrains the view, but capture uses full sensor resolution
|
|
221
|
+
// This ensures preview matches what will be captured
|
|
172
222
|
/>
|
|
173
223
|
|
|
174
224
|
{/* Loading overlay */}
|