react-native-expo-cropper 1.2.37 → 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 +72 -32
- package/dist/ImageCropper.js +952 -954
- package/dist/ImageCropperStyles.js +31 -5
- package/package.json +1 -1
- package/src/CustomCamera.js +76 -27
- package/src/ImageCropper.js +874 -887
- 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;
|
|
@@ -60,11 +64,10 @@ export default function CustomCamera({ onPhotoCaptured, onFrameCalculated }) {
|
|
|
60
64
|
return null;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
// ✅
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const frameX = (wrapperWidth - frameWidth) / 2; // Centered horizontally
|
|
67
|
+
// ✅ Calculate green frame as percentage of WRAPPER
|
|
68
|
+
const frameWidth = wrapperWidth * 0.85; // 85% of wrapper width
|
|
69
|
+
const frameHeight = wrapperHeight * 0.70; // 70% of wrapper height
|
|
70
|
+
const frameX = (wrapperWidth - frameWidth) / 2; // Centered horizontally
|
|
68
71
|
const frameY = (wrapperHeight - frameHeight) / 2; // Centered vertically
|
|
69
72
|
|
|
70
73
|
const frameCoords = {
|
|
@@ -77,8 +80,8 @@ export default function CustomCamera({ onPhotoCaptured, onFrameCalculated }) {
|
|
|
77
80
|
// ✅ Store percentages for easier mapping later
|
|
78
81
|
percentX: (frameX / wrapperWidth) * 100,
|
|
79
82
|
percentY: (frameY / wrapperHeight) * 100,
|
|
80
|
-
percentWidth:
|
|
81
|
-
percentHeight:
|
|
83
|
+
percentWidth: 85, // 85% of wrapper width
|
|
84
|
+
percentHeight: 70 // 70% of wrapper height
|
|
82
85
|
};
|
|
83
86
|
|
|
84
87
|
console.log("✅ Green frame coordinates calculated:", frameCoords);
|
|
@@ -105,45 +108,85 @@ export default function CustomCamera({ onPhotoCaptured, onFrameCalculated }) {
|
|
|
105
108
|
// Wait a bit before taking picture (works on iOS)
|
|
106
109
|
await waitForRender(2);
|
|
107
110
|
|
|
108
|
-
// ✅
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
111
118
|
shutterSound: false,
|
|
112
|
-
|
|
113
|
-
//
|
|
119
|
+
|
|
120
|
+
// ✅ CRITICAL: skipProcessing preserves original resolution and avoids interpolation
|
|
121
|
+
// This ensures pixel-perfect quality and prevents premature resizing
|
|
114
122
|
skipProcessing: true,
|
|
115
|
-
|
|
116
|
-
//
|
|
123
|
+
|
|
124
|
+
// Include EXIF metadata (orientation, camera settings, etc.)
|
|
117
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 }
|
|
118
142
|
});
|
|
119
143
|
|
|
120
|
-
|
|
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:", {
|
|
121
153
|
uri: photo.uri,
|
|
122
154
|
width: photo.width,
|
|
123
155
|
height: photo.height,
|
|
124
|
-
|
|
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"
|
|
125
160
|
});
|
|
126
161
|
|
|
127
162
|
// ✅ CRITICAL FIX: Use the same green frame coordinates that are used for rendering
|
|
128
163
|
// Fallback to recalculation if, for some reason, state is not yet set
|
|
129
164
|
const greenFrameCoords = greenFrame || calculateGreenFrameCoordinates();
|
|
130
165
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// C'est acceptable car l'utilisateur peut corriger via la rotation dans ImageCropper
|
|
135
|
-
const fixedUri = photo.uri;
|
|
166
|
+
if (!greenFrameCoords) {
|
|
167
|
+
throw new Error("Green frame coordinates not available");
|
|
168
|
+
}
|
|
136
169
|
|
|
137
|
-
//
|
|
138
|
-
|
|
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, {
|
|
139
174
|
greenFrame: greenFrameCoords,
|
|
140
|
-
capturedImageSize: {
|
|
175
|
+
capturedImageSize: {
|
|
176
|
+
width: photo.width,
|
|
177
|
+
height: photo.height,
|
|
178
|
+
aspectRatio: capturedAspectRatio
|
|
179
|
+
}
|
|
141
180
|
});
|
|
181
|
+
|
|
142
182
|
setLoadingBeforeCapture(false);
|
|
143
183
|
} catch (error) {
|
|
144
|
-
console.error("Error capturing photo:", error);
|
|
184
|
+
console.error("❌ Error capturing photo:", error);
|
|
145
185
|
setLoadingBeforeCapture(false);
|
|
146
|
-
Alert.alert(
|
|
186
|
+
Alert.alert(
|
|
187
|
+
"Erreur",
|
|
188
|
+
`Impossible de capturer la photo: ${error.message || "Erreur inconnue"}. Veuillez réessayer.`
|
|
189
|
+
);
|
|
147
190
|
}
|
|
148
191
|
}
|
|
149
192
|
};
|
|
@@ -169,7 +212,13 @@ export default function CustomCamera({ onPhotoCaptured, onFrameCalculated }) {
|
|
|
169
212
|
style={styles.camera}
|
|
170
213
|
facing="back"
|
|
171
214
|
ref={cameraRef}
|
|
172
|
-
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
|
|
173
222
|
/>
|
|
174
223
|
|
|
175
224
|
{/* Loading overlay */}
|