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
package/dist/CustomCamera.js
CHANGED
|
@@ -97,6 +97,9 @@ function CustomCamera(_ref) {
|
|
|
97
97
|
// ✅ CRITICAL FIX: Calculate green frame coordinates relative to camera preview
|
|
98
98
|
// The green frame should be calculated on the wrapper (as it's visually drawn there)
|
|
99
99
|
// But we store it with wrapper dimensions so ImageCropper can map it correctly
|
|
100
|
+
//
|
|
101
|
+
// NOTE: Camera capture aspect ratio (typically 4:3) may differ from wrapper aspect ratio (9:16)
|
|
102
|
+
// This is handled in ImageCropper by using "cover" mode to match preview content
|
|
100
103
|
var calculateGreenFrameCoordinates = function calculateGreenFrameCoordinates() {
|
|
101
104
|
var wrapperWidth = cameraWrapperLayout.width;
|
|
102
105
|
var wrapperHeight = cameraWrapperLayout.height;
|
|
@@ -139,12 +142,12 @@ function CustomCamera(_ref) {
|
|
|
139
142
|
}, [cameraWrapperLayout]);
|
|
140
143
|
var takePicture = /*#__PURE__*/function () {
|
|
141
144
|
var _ref3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
142
|
-
var photo,
|
|
145
|
+
var captureOptions, photo, capturedAspectRatio, greenFrameCoords, _t;
|
|
143
146
|
return _regenerator().w(function (_context2) {
|
|
144
147
|
while (1) switch (_context2.p = _context2.n) {
|
|
145
148
|
case 0:
|
|
146
149
|
if (!cameraRef.current) {
|
|
147
|
-
_context2.n =
|
|
150
|
+
_context2.n = 7;
|
|
148
151
|
break;
|
|
149
152
|
}
|
|
150
153
|
_context2.p = 1;
|
|
@@ -157,54 +160,87 @@ function CustomCamera(_ref) {
|
|
|
157
160
|
_context2.n = 2;
|
|
158
161
|
return waitForRender(2);
|
|
159
162
|
case 2:
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
// ✅ OPTIMIZED: Capture with maximum quality and native camera ratio
|
|
164
|
+
// Platform-specific optimizations for best quality
|
|
165
|
+
captureOptions = _objectSpread(_objectSpread({
|
|
166
|
+
// Maximum quality (0-1, 1 = best quality, no compression)
|
|
162
167
|
quality: 1,
|
|
163
|
-
//
|
|
168
|
+
// Disable shutter sound for better UX
|
|
164
169
|
shutterSound: false,
|
|
165
|
-
//
|
|
166
|
-
//
|
|
170
|
+
// ✅ CRITICAL: skipProcessing preserves original resolution and avoids interpolation
|
|
171
|
+
// This ensures pixel-perfect quality and prevents premature resizing
|
|
167
172
|
skipProcessing: true,
|
|
168
|
-
//
|
|
169
|
-
// L'orientation sera gérée dans ImageCropper si nécessaire via la fonction de rotation
|
|
173
|
+
// Include EXIF metadata (orientation, camera settings, etc.)
|
|
170
174
|
exif: true
|
|
175
|
+
}, _reactNative.Platform.OS === 'ios' && {
|
|
176
|
+
// iOS: Use native capture format (typically 4:3 or 16:9 depending on device)
|
|
177
|
+
// No additional processing to preserve quality
|
|
178
|
+
}), _reactNative.Platform.OS === 'android' && {
|
|
179
|
+
// Android: Ensure maximum resolution capture
|
|
180
|
+
// skipProcessing already handles this, but we can add Android-specific options if needed
|
|
181
|
+
});
|
|
182
|
+
console.log("📸 Capturing photo with maximum quality settings:", {
|
|
183
|
+
platform: _reactNative.Platform.OS,
|
|
184
|
+
options: captureOptions,
|
|
185
|
+
wrapperSize: {
|
|
186
|
+
width: cameraWrapperLayout.width,
|
|
187
|
+
height: cameraWrapperLayout.height
|
|
188
|
+
}
|
|
171
189
|
});
|
|
190
|
+
_context2.n = 3;
|
|
191
|
+
return cameraRef.current.takePictureAsync(captureOptions);
|
|
172
192
|
case 3:
|
|
173
193
|
photo = _context2.v;
|
|
174
|
-
|
|
194
|
+
if (!(!photo.width || !photo.height || photo.width === 0 || photo.height === 0)) {
|
|
195
|
+
_context2.n = 4;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
throw new Error("Invalid photo dimensions received from camera");
|
|
199
|
+
case 4:
|
|
200
|
+
capturedAspectRatio = photo.width / photo.height;
|
|
201
|
+
console.log("✅ Photo captured with maximum quality:", {
|
|
175
202
|
uri: photo.uri,
|
|
176
203
|
width: photo.width,
|
|
177
204
|
height: photo.height,
|
|
178
|
-
|
|
205
|
+
aspectRatio: capturedAspectRatio.toFixed(3),
|
|
206
|
+
expectedRatio: "~1.33 (4:3) or ~1.78 (16:9)",
|
|
207
|
+
exif: photo.exif ? "present" : "missing",
|
|
208
|
+
fileSize: photo.uri ? "available" : "unknown"
|
|
179
209
|
});
|
|
180
210
|
|
|
181
211
|
// ✅ CRITICAL FIX: Use the same green frame coordinates that are used for rendering
|
|
182
212
|
// Fallback to recalculation if, for some reason, state is not yet set
|
|
183
|
-
greenFrameCoords = greenFrame || calculateGreenFrameCoordinates();
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
213
|
+
greenFrameCoords = greenFrame || calculateGreenFrameCoordinates();
|
|
214
|
+
if (greenFrameCoords) {
|
|
215
|
+
_context2.n = 5;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
throw new Error("Green frame coordinates not available");
|
|
219
|
+
case 5:
|
|
220
|
+
// ✅ Send photo URI and frame data to ImageCropper
|
|
221
|
+
// The photo maintains its native resolution and aspect ratio
|
|
222
|
+
// ImageCropper will handle display and cropping while preserving quality
|
|
223
|
+
onPhotoCaptured(photo.uri, {
|
|
189
224
|
greenFrame: greenFrameCoords,
|
|
190
225
|
capturedImageSize: {
|
|
191
226
|
width: photo.width,
|
|
192
|
-
height: photo.height
|
|
227
|
+
height: photo.height,
|
|
228
|
+
aspectRatio: capturedAspectRatio
|
|
193
229
|
}
|
|
194
230
|
});
|
|
195
231
|
setLoadingBeforeCapture(false);
|
|
196
|
-
_context2.n =
|
|
232
|
+
_context2.n = 7;
|
|
197
233
|
break;
|
|
198
|
-
case
|
|
199
|
-
_context2.p =
|
|
234
|
+
case 6:
|
|
235
|
+
_context2.p = 6;
|
|
200
236
|
_t = _context2.v;
|
|
201
|
-
console.error("Error capturing photo:", _t);
|
|
237
|
+
console.error("❌ Error capturing photo:", _t);
|
|
202
238
|
setLoadingBeforeCapture(false);
|
|
203
|
-
_reactNative.Alert.alert("Erreur", "Impossible de capturer la photo. Veuillez
|
|
204
|
-
case
|
|
239
|
+
_reactNative.Alert.alert("Erreur", "Impossible de capturer la photo: ".concat(_t.message || "Erreur inconnue", ". Veuillez r\xE9essayer."));
|
|
240
|
+
case 7:
|
|
205
241
|
return _context2.a(2);
|
|
206
242
|
}
|
|
207
|
-
}, _callee2, null, [[1,
|
|
243
|
+
}, _callee2, null, [[1, 6]]);
|
|
208
244
|
}));
|
|
209
245
|
return function takePicture() {
|
|
210
246
|
return _ref3.apply(this, arguments);
|
|
@@ -230,8 +266,12 @@ function CustomCamera(_ref) {
|
|
|
230
266
|
facing: "back",
|
|
231
267
|
ref: cameraRef,
|
|
232
268
|
onCameraReady: function onCameraReady() {
|
|
233
|
-
|
|
269
|
+
setIsReady(true);
|
|
270
|
+
console.log("✅ Camera ready - Maximum quality capture enabled");
|
|
234
271
|
}
|
|
272
|
+
// ✅ Ensure camera uses native aspect ratio (typically 4:3 for most devices)
|
|
273
|
+
// The wrapper constrains the view, but capture uses full sensor resolution
|
|
274
|
+
// This ensures preview matches what will be captured
|
|
235
275
|
}), loadingBeforeCapture && /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
236
276
|
style: styles.loadingOverlay
|
|
237
277
|
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
|
package/dist/ImageCropper.js
CHANGED
|
@@ -1299,10 +1299,124 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
1299
1299
|
onCancel: function onCancel() {
|
|
1300
1300
|
return setShowCustomCamera(false);
|
|
1301
1301
|
}
|
|
1302
|
-
}) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null,
|
|
1303
|
-
style:
|
|
1304
|
-
|
|
1305
|
-
|
|
1302
|
+
}) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1303
|
+
style: {
|
|
1304
|
+
width: _reactNative.Dimensions.get('window').width,
|
|
1305
|
+
aspectRatio: 9 / 16,
|
|
1306
|
+
borderRadius: 30,
|
|
1307
|
+
overflow: 'hidden',
|
|
1308
|
+
alignItems: 'center',
|
|
1309
|
+
justifyContent: 'center',
|
|
1310
|
+
position: 'relative',
|
|
1311
|
+
backgroundColor: 'black',
|
|
1312
|
+
marginBottom: 0 // ✅ Les boutons sont maintenant en position absolue en bas
|
|
1313
|
+
},
|
|
1314
|
+
ref: commonWrapperRef,
|
|
1315
|
+
onLayout: onCommonWrapperLayout
|
|
1316
|
+
}, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1317
|
+
ref: viewRef,
|
|
1318
|
+
collapsable: false,
|
|
1319
|
+
style: _reactNative.StyleSheet.absoluteFill,
|
|
1320
|
+
onStartShouldSetResponder: function onStartShouldSetResponder() {
|
|
1321
|
+
return true;
|
|
1322
|
+
},
|
|
1323
|
+
onMoveShouldSetResponder: function onMoveShouldSetResponder(evt, gestureState) {
|
|
1324
|
+
// ✅ CRITICAL: Always capture movement when a point is selected
|
|
1325
|
+
// This ensures vertical movement is captured correctly
|
|
1326
|
+
if (selectedPointIndex.current !== null) {
|
|
1327
|
+
return true;
|
|
1328
|
+
}
|
|
1329
|
+
// ✅ CRITICAL: Capture ANY movement immediately (even 0px) to prevent ScrollView interception
|
|
1330
|
+
// This is especially important for vertical movement which ScrollView tries to intercept
|
|
1331
|
+
// We return true for ANY movement to ensure we capture it before ScrollView
|
|
1332
|
+
var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
|
|
1333
|
+
if (hasMovement && Math.abs(gestureState.dy) > 5) {
|
|
1334
|
+
console.log("🔄 Vertical movement detected in responder:", {
|
|
1335
|
+
dx: gestureState.dx.toFixed(2),
|
|
1336
|
+
dy: gestureState.dy.toFixed(2),
|
|
1337
|
+
selectedPoint: selectedPointIndex.current
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
return true;
|
|
1341
|
+
},
|
|
1342
|
+
onResponderGrant: function onResponderGrant(e) {
|
|
1343
|
+
// ✅ CRITICAL: Grant responder immediately to prevent ScrollView from intercepting
|
|
1344
|
+
// This ensures we capture all movement, especially vertical
|
|
1345
|
+
// Handle tap to select point if needed
|
|
1346
|
+
if (selectedPointIndex.current === null) {
|
|
1347
|
+
handleTap(e);
|
|
1348
|
+
}
|
|
1349
|
+
},
|
|
1350
|
+
onResponderStart: handleTap,
|
|
1351
|
+
onResponderMove: function onResponderMove(e) {
|
|
1352
|
+
// ✅ CRITICAL: Always handle move events to ensure smooth movement in all directions
|
|
1353
|
+
// This is called for every move event, ensuring vertical movement is captured
|
|
1354
|
+
// handleMove now uses incremental delta calculation which is more reliable
|
|
1355
|
+
handleMove(e);
|
|
1356
|
+
},
|
|
1357
|
+
onResponderRelease: handleRelease,
|
|
1358
|
+
onResponderTerminationRequest: function onResponderTerminationRequest() {
|
|
1359
|
+
// ✅ CRITICAL: Never allow termination when dragging a point
|
|
1360
|
+
// This prevents ScrollView from stealing the responder during vertical movement
|
|
1361
|
+
return selectedPointIndex.current === null;
|
|
1362
|
+
}
|
|
1363
|
+
// ✅ CRITICAL: Prevent parent ScrollView from intercepting touches
|
|
1364
|
+
// Capture responder BEFORE parent ScrollView can intercept
|
|
1365
|
+
,
|
|
1366
|
+
onStartShouldSetResponderCapture: function onStartShouldSetResponderCapture() {
|
|
1367
|
+
// Always capture start events
|
|
1368
|
+
return true;
|
|
1369
|
+
},
|
|
1370
|
+
onMoveShouldSetResponderCapture: function onMoveShouldSetResponderCapture(evt, gestureState) {
|
|
1371
|
+
// ✅ CRITICAL: Always capture movement events before parent ScrollView
|
|
1372
|
+
// This is essential for vertical movement which ScrollView tries to intercept
|
|
1373
|
+
// Especially important when a point is selected or when there's any movement
|
|
1374
|
+
if (selectedPointIndex.current !== null) {
|
|
1375
|
+
return true;
|
|
1376
|
+
}
|
|
1377
|
+
// ✅ CRITICAL: Capture movement BEFORE ScrollView can intercept
|
|
1378
|
+
// This ensures we get vertical movement even if ScrollView tries to steal it
|
|
1379
|
+
var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
|
|
1380
|
+
return hasMovement;
|
|
1381
|
+
}
|
|
1382
|
+
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
|
|
1383
|
+
source: {
|
|
1384
|
+
uri: image
|
|
1385
|
+
},
|
|
1386
|
+
style: _ImageCropperStyles["default"].image,
|
|
1387
|
+
resizeMode: (_cameraFrameData$curr3 = cameraFrameData.current) !== null && _cameraFrameData$curr3 !== void 0 && _cameraFrameData$curr3.greenFrame ? 'cover' : 'contain',
|
|
1388
|
+
onLayout: onImageLayout
|
|
1389
|
+
}), /*#__PURE__*/_react["default"].createElement(_reactNativeSvg["default"], {
|
|
1390
|
+
style: _ImageCropperStyles["default"].overlay,
|
|
1391
|
+
pointerEvents: "none"
|
|
1392
|
+
}, function () {
|
|
1393
|
+
// ✅ Use wrapper dimensions for SVG path (wrapper coordinates)
|
|
1394
|
+
var wrapperWidth = commonWrapperLayout.current.width || _reactNative.Dimensions.get('window').width;
|
|
1395
|
+
var wrapperHeight = commonWrapperLayout.current.height || _reactNative.Dimensions.get('window').width * 16 / 9;
|
|
1396
|
+
return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
|
|
1397
|
+
d: "M 0 0 H ".concat(wrapperWidth, " V ").concat(wrapperHeight, " H 0 Z ").concat(createPath()),
|
|
1398
|
+
fill: showResult ? 'white' : 'rgba(0, 0, 0, 0.8)',
|
|
1399
|
+
fillRule: "evenodd"
|
|
1400
|
+
}), !showResult && points.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
|
|
1401
|
+
d: createPath(),
|
|
1402
|
+
fill: "transparent",
|
|
1403
|
+
stroke: "white",
|
|
1404
|
+
strokeWidth: 2
|
|
1405
|
+
}), !showResult && points.map(function (point, index) {
|
|
1406
|
+
return /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Circle, {
|
|
1407
|
+
key: index,
|
|
1408
|
+
cx: point.x,
|
|
1409
|
+
cy: point.y,
|
|
1410
|
+
r: 10,
|
|
1411
|
+
fill: "white"
|
|
1412
|
+
});
|
|
1413
|
+
}));
|
|
1414
|
+
}()))), !showResult && image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1415
|
+
style: [_ImageCropperStyles["default"].buttonContainerBelow, {
|
|
1416
|
+
paddingBottom: Math.max(insets.bottom, 16)
|
|
1417
|
+
}]
|
|
1418
|
+
}, _reactNative.Platform.OS === 'android' && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
|
|
1419
|
+
style: _ImageCropperStyles["default"].rotationButton,
|
|
1306
1420
|
onPress: function onPress() {
|
|
1307
1421
|
return enableRotation && rotatePreviewImage(90);
|
|
1308
1422
|
},
|
|
@@ -1311,50 +1425,36 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
1311
1425
|
name: "sync",
|
|
1312
1426
|
size: 24,
|
|
1313
1427
|
color: "white"
|
|
1314
|
-
}))
|
|
1428
|
+
})), /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
|
|
1315
1429
|
style: _ImageCropperStyles["default"].button,
|
|
1316
1430
|
onPress: handleReset
|
|
1317
1431
|
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
|
|
1318
1432
|
style: _ImageCropperStyles["default"].buttonText
|
|
1319
|
-
}, "Reset")),
|
|
1433
|
+
}, "Reset")), /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
|
|
1320
1434
|
style: _ImageCropperStyles["default"].button,
|
|
1321
1435
|
onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
1322
|
-
var actualImageWidth, actualImageHeight, layout, contentRect, displayedWidth, displayedHeight, isCoverMode, scale, coverOffsetX, coverOffsetY, scaledWidth, scaledHeight,
|
|
1436
|
+
var actualImageWidth, actualImageHeight, layout, contentRect, displayedWidth, displayedHeight, isCoverMode, scale, coverOffsetX, coverOffsetY, scaledWidth, scaledHeight, originalUri, cropMeta, imagePoints, minX, minY, maxX, maxY, cropX, cropY, cropEndX, cropEndY, cropWidth, cropHeight, bbox, polygon, name, _t2;
|
|
1323
1437
|
return _regenerator().w(function (_context2) {
|
|
1324
1438
|
while (1) switch (_context2.p = _context2.n) {
|
|
1325
1439
|
case 0:
|
|
1326
1440
|
setIsLoading(true);
|
|
1327
1441
|
_context2.p = 1;
|
|
1328
1442
|
console.log("=== Starting pixel-perfect metadata export (no bitmap crop on mobile) ===");
|
|
1329
|
-
|
|
1330
|
-
// ✅ REFACTORISATION : Utiliser les dimensions stockées (plus efficace)
|
|
1331
1443
|
actualImageWidth = originalImageDimensions.current.width;
|
|
1332
|
-
actualImageHeight = originalImageDimensions.current.height;
|
|
1444
|
+
actualImageHeight = originalImageDimensions.current.height;
|
|
1333
1445
|
if (!(actualImageWidth === 0 || actualImageHeight === 0)) {
|
|
1334
1446
|
_context2.n = 2;
|
|
1335
1447
|
break;
|
|
1336
1448
|
}
|
|
1337
1449
|
throw new Error("Original image dimensions not available. Please wait for image to load.");
|
|
1338
1450
|
case 2:
|
|
1339
|
-
console.log("Original image dimensions:", {
|
|
1340
|
-
width: actualImageWidth,
|
|
1341
|
-
height: actualImageHeight
|
|
1342
|
-
});
|
|
1343
|
-
|
|
1344
|
-
// ✅ CORRECTION : Utiliser le rectangle de contenu réel (contain)
|
|
1345
|
-
// ✅ CRITICAL: Recalculate displayedContentRect if not available
|
|
1346
1451
|
layout = displayedImageLayout.current;
|
|
1347
|
-
console.log("🔍 Checking displayedContentRect before crop:", {
|
|
1348
|
-
displayedContentRect: displayedContentRect.current,
|
|
1349
|
-
displayedImageLayout: layout,
|
|
1350
|
-
originalImageDimensions: originalImageDimensions.current
|
|
1351
|
-
});
|
|
1352
1452
|
if (layout.width > 0 && layout.height > 0) {
|
|
1353
1453
|
updateDisplayedContentRect(layout.width, layout.height);
|
|
1354
1454
|
}
|
|
1355
1455
|
contentRect = displayedContentRect.current;
|
|
1356
1456
|
displayedWidth = contentRect.width;
|
|
1357
|
-
displayedHeight = contentRect.height;
|
|
1457
|
+
displayedHeight = contentRect.height;
|
|
1358
1458
|
if (!(displayedWidth === 0 || displayedHeight === 0)) {
|
|
1359
1459
|
_context2.n = 4;
|
|
1360
1460
|
break;
|
|
@@ -1363,8 +1463,6 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
1363
1463
|
_context2.n = 3;
|
|
1364
1464
|
break;
|
|
1365
1465
|
}
|
|
1366
|
-
console.warn("⚠️ displayedContentRect not available, using displayedImageLayout as fallback");
|
|
1367
|
-
// Use layout dimensions as fallback (assuming no letterboxing)
|
|
1368
1466
|
contentRect = {
|
|
1369
1467
|
x: layout.x,
|
|
1370
1468
|
y: layout.y,
|
|
@@ -1373,21 +1471,12 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
1373
1471
|
};
|
|
1374
1472
|
displayedWidth = contentRect.width;
|
|
1375
1473
|
displayedHeight = contentRect.height;
|
|
1376
|
-
// Update the ref for consistency
|
|
1377
1474
|
displayedContentRect.current = contentRect;
|
|
1378
1475
|
_context2.n = 4;
|
|
1379
1476
|
break;
|
|
1380
1477
|
case 3:
|
|
1381
|
-
throw new Error("Displayed image dimensions not available.
|
|
1478
|
+
throw new Error("Displayed image dimensions not available.");
|
|
1382
1479
|
case 4:
|
|
1383
|
-
console.log("✅ Using contentRect for crop:", contentRect);
|
|
1384
|
-
console.log("Displayed image dimensions:", {
|
|
1385
|
-
width: displayedWidth,
|
|
1386
|
-
height: displayedHeight
|
|
1387
|
-
});
|
|
1388
|
-
|
|
1389
|
-
// ✅ CAMERA (cover mode): scale = max, image fills wrapper, has offset
|
|
1390
|
-
// ✅ GALLERY (contain mode): scale = min, uniform
|
|
1391
1480
|
isCoverMode = !!(cameraFrameData.current && cameraFrameData.current.greenFrame);
|
|
1392
1481
|
coverOffsetX = 0, coverOffsetY = 0;
|
|
1393
1482
|
if (isCoverMode) {
|
|
@@ -1396,43 +1485,16 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
1396
1485
|
scaledHeight = actualImageHeight * scale;
|
|
1397
1486
|
coverOffsetX = (scaledWidth - displayedWidth) / 2;
|
|
1398
1487
|
coverOffsetY = (scaledHeight - displayedHeight) / 2;
|
|
1399
|
-
console.log("Scale factor (COVER mode for camera):", {
|
|
1400
|
-
scale: scale,
|
|
1401
|
-
coverOffsetX: coverOffsetX,
|
|
1402
|
-
coverOffsetY: coverOffsetY
|
|
1403
|
-
});
|
|
1404
1488
|
} else {
|
|
1405
|
-
|
|
1406
|
-
scaleY = actualImageHeight / displayedHeight;
|
|
1407
|
-
if (Math.abs(scaleX - scaleY) > 0.01) {
|
|
1408
|
-
console.warn("Scale mismatch detected! This may cause incorrect crop coordinates.", {
|
|
1409
|
-
scaleX: scaleX,
|
|
1410
|
-
scaleY: scaleY,
|
|
1411
|
-
actualImageWidth: actualImageWidth,
|
|
1412
|
-
actualImageHeight: actualImageHeight,
|
|
1413
|
-
displayedWidth: displayedWidth,
|
|
1414
|
-
displayedHeight: displayedHeight
|
|
1415
|
-
});
|
|
1416
|
-
}
|
|
1417
|
-
scale = scaleX;
|
|
1418
|
-
console.log("Scale factor (contain, uniform):", {
|
|
1419
|
-
scale: scale,
|
|
1420
|
-
contentRect: contentRect
|
|
1421
|
-
});
|
|
1489
|
+
scale = actualImageWidth / displayedWidth;
|
|
1422
1490
|
}
|
|
1423
1491
|
originalUri = sourceImageUri.current || image;
|
|
1424
1492
|
cropMeta = null;
|
|
1425
1493
|
if (points.length > 0) {
|
|
1426
1494
|
try {
|
|
1427
|
-
console.log("Calculating crop boundaries from points...");
|
|
1428
|
-
console.log("Points (display coordinates):", points);
|
|
1429
|
-
console.log("Content rect (offsets):", contentRect);
|
|
1430
|
-
|
|
1431
|
-
// ✅ Conversion display -> image px (contain or cover)
|
|
1432
1495
|
imagePoints = points.map(function (point) {
|
|
1433
1496
|
var clampedX, clampedY, origX, origY;
|
|
1434
1497
|
if (isCoverMode) {
|
|
1435
|
-
// Cover: display = wrapper, scale = max, image centered with offset
|
|
1436
1498
|
clampedX = Math.max(0, Math.min(point.x, contentRect.width));
|
|
1437
1499
|
clampedY = Math.max(0, Math.min(point.y, contentRect.height));
|
|
1438
1500
|
origX = (clampedX + coverOffsetX) / scale;
|
|
@@ -1450,9 +1512,6 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
1450
1512
|
y: finalY
|
|
1451
1513
|
};
|
|
1452
1514
|
});
|
|
1453
|
-
console.log("Converted image points (original coordinates):", imagePoints);
|
|
1454
|
-
|
|
1455
|
-
// Calculer la bounding box : min X, min Y, max X, max Y
|
|
1456
1515
|
minX = Math.min.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
|
|
1457
1516
|
return p.x;
|
|
1458
1517
|
})));
|
|
@@ -1464,36 +1523,20 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
1464
1523
|
})));
|
|
1465
1524
|
maxY = Math.max.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
|
|
1466
1525
|
return p.y;
|
|
1467
|
-
})));
|
|
1468
|
-
// évite de rogner des pixels et réduit le risque de crop plus petit (perte de détails).
|
|
1526
|
+
})));
|
|
1469
1527
|
cropX = Math.max(0, Math.floor(minX));
|
|
1470
1528
|
cropY = Math.max(0, Math.floor(minY));
|
|
1471
1529
|
cropEndX = Math.min(actualImageWidth, Math.ceil(maxX));
|
|
1472
1530
|
cropEndY = Math.min(actualImageHeight, Math.ceil(maxY));
|
|
1473
1531
|
cropWidth = Math.max(0, cropEndX - cropX);
|
|
1474
1532
|
cropHeight = Math.max(0, cropEndY - cropY);
|
|
1475
|
-
console.log("Crop parameters (pixel-perfect):", {
|
|
1476
|
-
x: cropX,
|
|
1477
|
-
y: cropY,
|
|
1478
|
-
width: cropWidth,
|
|
1479
|
-
height: cropHeight,
|
|
1480
|
-
imageWidth: actualImageWidth,
|
|
1481
|
-
imageHeight: actualImageHeight,
|
|
1482
|
-
boundingBox: {
|
|
1483
|
-
minX: minX,
|
|
1484
|
-
minY: minY,
|
|
1485
|
-
maxX: maxX,
|
|
1486
|
-
maxY: maxY
|
|
1487
|
-
}
|
|
1488
|
-
});
|
|
1489
1533
|
if (cropWidth > 0 && cropHeight > 0) {
|
|
1490
|
-
// 1) bbox in ORIGINAL image pixel coords
|
|
1491
1534
|
bbox = {
|
|
1492
1535
|
x: cropX,
|
|
1493
1536
|
y: cropY,
|
|
1494
1537
|
width: cropWidth,
|
|
1495
1538
|
height: cropHeight
|
|
1496
|
-
};
|
|
1539
|
+
};
|
|
1497
1540
|
polygon = imagePoints.map(function (point) {
|
|
1498
1541
|
return {
|
|
1499
1542
|
x: point.x - cropX,
|
|
@@ -1509,19 +1552,13 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
1509
1552
|
height: actualImageHeight
|
|
1510
1553
|
}
|
|
1511
1554
|
};
|
|
1512
|
-
console.log("Crop meta ready:", cropMeta);
|
|
1513
|
-
} else {
|
|
1514
|
-
console.warn("Invalid crop dimensions, cannot export crop meta");
|
|
1515
1555
|
}
|
|
1516
1556
|
} catch (cropError) {
|
|
1517
1557
|
console.error("Error computing crop meta:", cropError);
|
|
1518
1558
|
}
|
|
1519
|
-
} else {
|
|
1520
|
-
console.log("No crop points defined, using original image");
|
|
1521
1559
|
}
|
|
1522
1560
|
name = "IMAGE XTK".concat(Date.now());
|
|
1523
1561
|
if (onConfirm) {
|
|
1524
|
-
console.log("Calling onConfirm with:", originalUri, name, cropMeta);
|
|
1525
1562
|
onConfirm(originalUri, name, cropMeta);
|
|
1526
1563
|
}
|
|
1527
1564
|
_context2.n = 6;
|
|
@@ -1544,139 +1581,20 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
1544
1581
|
}))
|
|
1545
1582
|
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
|
|
1546
1583
|
style: _ImageCropperStyles["default"].buttonText
|
|
1547
|
-
}, "Confirm"))), image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1548
|
-
style:
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
overflow: 'hidden',
|
|
1553
|
-
alignItems: 'center',
|
|
1554
|
-
justifyContent: 'center',
|
|
1555
|
-
position: 'relative',
|
|
1556
|
-
backgroundColor: 'black'
|
|
1557
|
-
},
|
|
1558
|
-
ref: commonWrapperRef,
|
|
1559
|
-
onLayout: onCommonWrapperLayout
|
|
1560
|
-
}, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1561
|
-
ref: viewRef,
|
|
1562
|
-
collapsable: false,
|
|
1563
|
-
style: _reactNative.StyleSheet.absoluteFill,
|
|
1564
|
-
onStartShouldSetResponder: function onStartShouldSetResponder() {
|
|
1565
|
-
return true;
|
|
1566
|
-
},
|
|
1567
|
-
onMoveShouldSetResponder: function onMoveShouldSetResponder(evt, gestureState) {
|
|
1568
|
-
// ✅ CRITICAL: Always capture movement when a point is selected
|
|
1569
|
-
// This ensures vertical movement is captured correctly
|
|
1570
|
-
if (selectedPointIndex.current !== null) {
|
|
1571
|
-
return true;
|
|
1572
|
-
}
|
|
1573
|
-
// ✅ CRITICAL: Capture ANY movement immediately (even 0px) to prevent ScrollView interception
|
|
1574
|
-
// This is especially important for vertical movement which ScrollView tries to intercept
|
|
1575
|
-
// We return true for ANY movement to ensure we capture it before ScrollView
|
|
1576
|
-
var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
|
|
1577
|
-
if (hasMovement && Math.abs(gestureState.dy) > 5) {
|
|
1578
|
-
console.log("🔄 Vertical movement detected in responder:", {
|
|
1579
|
-
dx: gestureState.dx.toFixed(2),
|
|
1580
|
-
dy: gestureState.dy.toFixed(2),
|
|
1581
|
-
selectedPoint: selectedPointIndex.current
|
|
1582
|
-
});
|
|
1583
|
-
}
|
|
1584
|
-
return true;
|
|
1585
|
-
},
|
|
1586
|
-
onResponderGrant: function onResponderGrant(e) {
|
|
1587
|
-
// ✅ CRITICAL: Grant responder immediately to prevent ScrollView from intercepting
|
|
1588
|
-
// This ensures we capture all movement, especially vertical
|
|
1589
|
-
// Handle tap to select point if needed
|
|
1590
|
-
if (selectedPointIndex.current === null) {
|
|
1591
|
-
handleTap(e);
|
|
1592
|
-
}
|
|
1593
|
-
},
|
|
1594
|
-
onResponderStart: handleTap,
|
|
1595
|
-
onResponderMove: function onResponderMove(e) {
|
|
1596
|
-
// ✅ CRITICAL: Always handle move events to ensure smooth movement in all directions
|
|
1597
|
-
// This is called for every move event, ensuring vertical movement is captured
|
|
1598
|
-
// handleMove now uses incremental delta calculation which is more reliable
|
|
1599
|
-
handleMove(e);
|
|
1600
|
-
},
|
|
1601
|
-
onResponderRelease: handleRelease,
|
|
1602
|
-
onResponderTerminationRequest: function onResponderTerminationRequest() {
|
|
1603
|
-
// ✅ CRITICAL: Never allow termination when dragging a point
|
|
1604
|
-
// This prevents ScrollView from stealing the responder during vertical movement
|
|
1605
|
-
return selectedPointIndex.current === null;
|
|
1606
|
-
}
|
|
1607
|
-
// ✅ CRITICAL: Prevent parent ScrollView from intercepting touches
|
|
1608
|
-
// Capture responder BEFORE parent ScrollView can intercept
|
|
1609
|
-
,
|
|
1610
|
-
onStartShouldSetResponderCapture: function onStartShouldSetResponderCapture() {
|
|
1611
|
-
// Always capture start events
|
|
1612
|
-
return true;
|
|
1613
|
-
},
|
|
1614
|
-
onMoveShouldSetResponderCapture: function onMoveShouldSetResponderCapture(evt, gestureState) {
|
|
1615
|
-
// ✅ CRITICAL: Always capture movement events before parent ScrollView
|
|
1616
|
-
// This is essential for vertical movement which ScrollView tries to intercept
|
|
1617
|
-
// Especially important when a point is selected or when there's any movement
|
|
1618
|
-
if (selectedPointIndex.current !== null) {
|
|
1619
|
-
return true;
|
|
1620
|
-
}
|
|
1621
|
-
// Also capture if there's any movement to prevent ScrollView from intercepting
|
|
1622
|
-
var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
|
|
1623
|
-
if (hasMovement && Math.abs(gestureState.dy) > 2) {
|
|
1624
|
-
console.log("🔄 Capturing vertical movement before ScrollView:", {
|
|
1625
|
-
dy: gestureState.dy.toFixed(2)
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
return hasMovement;
|
|
1629
|
-
}
|
|
1630
|
-
// ✅ CRITICAL: Prevent ScrollView from scrolling by stopping propagation
|
|
1631
|
-
,
|
|
1632
|
-
onResponderReject: function onResponderReject() {
|
|
1633
|
-
console.warn("⚠️ Responder rejected - ScrollView may intercept");
|
|
1634
|
-
}
|
|
1635
|
-
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
|
|
1636
|
-
source: {
|
|
1637
|
-
uri: image
|
|
1638
|
-
},
|
|
1639
|
-
style: _ImageCropperStyles["default"].image,
|
|
1640
|
-
resizeMode: (_cameraFrameData$curr3 = cameraFrameData.current) !== null && _cameraFrameData$curr3 !== void 0 && _cameraFrameData$curr3.greenFrame ? 'cover' : 'contain',
|
|
1641
|
-
onLayout: onImageLayout
|
|
1642
|
-
}), /*#__PURE__*/_react["default"].createElement(_reactNativeSvg["default"], {
|
|
1643
|
-
style: _ImageCropperStyles["default"].overlay,
|
|
1644
|
-
pointerEvents: "none"
|
|
1645
|
-
}, function () {
|
|
1646
|
-
// ✅ Use wrapper dimensions for SVG path (wrapper coordinates)
|
|
1647
|
-
var wrapperWidth = commonWrapperLayout.current.width || _reactNative.Dimensions.get('window').width;
|
|
1648
|
-
var wrapperHeight = commonWrapperLayout.current.height || _reactNative.Dimensions.get('window').width * 16 / 9;
|
|
1649
|
-
return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
|
|
1650
|
-
d: "M 0 0 H ".concat(wrapperWidth, " V ").concat(wrapperHeight, " H 0 Z ").concat(createPath()),
|
|
1651
|
-
fill: showResult ? 'white' : 'rgba(0, 0, 0, 0.8)',
|
|
1652
|
-
fillRule: "evenodd"
|
|
1653
|
-
}), !showResult && points.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
|
|
1654
|
-
d: createPath(),
|
|
1655
|
-
fill: "transparent",
|
|
1656
|
-
stroke: "white",
|
|
1657
|
-
strokeWidth: 2
|
|
1658
|
-
}), !showResult && points.map(function (point, index) {
|
|
1659
|
-
return /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Circle, {
|
|
1660
|
-
key: index,
|
|
1661
|
-
cx: point.x,
|
|
1662
|
-
cy: point.y,
|
|
1663
|
-
r: 10,
|
|
1664
|
-
fill: "white"
|
|
1665
|
-
});
|
|
1666
|
-
}));
|
|
1667
|
-
}())))), showMaskView && maskImageUri && maskPoints.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1584
|
+
}, "Confirm"))), !showResult && !image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1585
|
+
style: _ImageCropperStyles["default"].centerButtonsContainer
|
|
1586
|
+
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
|
|
1587
|
+
style: _ImageCropperStyles["default"].welcomeText
|
|
1588
|
+
}, "S\xE9lectionnez une image"))), showMaskView && maskImageUri && maskPoints.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1668
1589
|
style: {
|
|
1669
1590
|
position: 'absolute',
|
|
1670
1591
|
left: -maskDimensions.width - 100,
|
|
1671
|
-
// Hors écran mais pas trop loin
|
|
1672
1592
|
top: -maskDimensions.height - 100,
|
|
1673
1593
|
width: maskDimensions.width,
|
|
1674
1594
|
height: maskDimensions.height,
|
|
1675
1595
|
opacity: 1,
|
|
1676
|
-
// Opacité normale pour la capture
|
|
1677
1596
|
pointerEvents: 'none',
|
|
1678
1597
|
zIndex: 9999,
|
|
1679
|
-
// Z-index élevé pour s'assurer qu'elle est au-dessus
|
|
1680
1598
|
overflow: 'hidden'
|
|
1681
1599
|
},
|
|
1682
1600
|
collapsable: false
|