react-native-expo-cropper 1.2.36 → 1.2.38

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.
@@ -1,514 +1,1701 @@
1
- "use strict";
2
-
3
- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
- Object.defineProperty(exports, "__esModule", {
5
- value: true
6
- });
7
- exports["default"] = void 0;
8
- var _ImageCropperStyles = _interopRequireDefault(require("./ImageCropperStyles"));
9
- var _react = _interopRequireWildcard(require("react"));
10
- var _reactNative = require("react-native");
11
- var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
12
- var _reactNativeViewShot = require("react-native-view-shot");
13
- var _CustomCamera = _interopRequireDefault(require("./CustomCamera"));
14
- var _ImageProcessor = require("./ImageProcessor");
15
- var ImageManipulator = _interopRequireWildcard(require("expo-image-manipulator"));
16
- var _vectorIcons = require("@expo/vector-icons");
17
- var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
18
- function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t4 in e) "default" !== _t4 && {}.hasOwnProperty.call(e, _t4) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t4)) && (i.get || i.set) ? o(f, _t4, i) : f[_t4] = e[_t4]); return f; })(e, t); }
19
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
20
- function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
21
- function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
22
- function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
23
- function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
24
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
25
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
26
- function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
27
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
28
- function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
29
- function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
30
- function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
31
- function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
32
- function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
33
- function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
34
- function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
35
- function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
36
- function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
37
- function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
38
- function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
39
- var ImageCropper = function ImageCropper(_ref) {
40
- var onConfirm = _ref.onConfirm,
41
- openCameraFirst = _ref.openCameraFirst,
42
- initialImage = _ref.initialImage,
43
- addheight = _ref.addheight;
44
- var _useState = (0, _react.useState)(null),
45
- _useState2 = _slicedToArray(_useState, 2),
46
- image = _useState2[0],
47
- setImage = _useState2[1];
48
- var _useState3 = (0, _react.useState)([]),
49
- _useState4 = _slicedToArray(_useState3, 2),
50
- points = _useState4[0],
51
- setPoints = _useState4[1];
52
- var _useState5 = (0, _react.useState)(false),
53
- _useState6 = _slicedToArray(_useState5, 2),
54
- showResult = _useState6[0],
55
- setShowResult = _useState6[1];
56
- var _useState7 = (0, _react.useState)(false),
57
- _useState8 = _slicedToArray(_useState7, 2),
58
- showCustomCamera = _useState8[0],
59
- setShowCustomCamera = _useState8[1];
60
- var viewRef = (0, _react.useRef)(null);
61
- var imageMeasure = (0, _react.useRef)({
62
- x: 0,
63
- y: 0,
64
- width: 0,
65
- height: 0
66
- });
67
- var selectedPointIndex = (0, _react.useRef)(null);
68
- var lastTap = (0, _react.useRef)(null);
69
- var _useState9 = (0, _react.useState)(false),
70
- _useState0 = _slicedToArray(_useState9, 2),
71
- isLoading = _useState0[0],
72
- setIsLoading = _useState0[1];
73
- var _useState1 = (0, _react.useState)(false),
74
- _useState10 = _slicedToArray(_useState1, 2),
75
- showFullScreenCapture = _useState10[0],
76
- setShowFullScreenCapture = _useState10[1];
77
- var _useState11 = (0, _react.useState)(false),
78
- _useState12 = _slicedToArray(_useState11, 2),
79
- isRotating = _useState12[0],
80
- setIsRotating = _useState12[1];
81
- var lastValidPosition = (0, _react.useRef)(null);
82
- var insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
83
- (0, _react.useEffect)(function () {
84
- if (openCameraFirst) {
85
- setShowCustomCamera(true);
86
- } else if (initialImage) {
87
- setImage(initialImage);
88
- }
89
- }, [openCameraFirst, initialImage]);
90
- (0, _react.useEffect)(function () {
91
- if (!image) return;
92
- _reactNative.Image.getSize(image, function (imgWidth, imgHeight) {
93
- var screenRatio = SCREEN_WIDTH / SCREEN_HEIGHT;
94
- var imageRatio = imgWidth / imgHeight;
95
- if (imageRatio > screenRatio) {
96
- imageMeasure.current = {
97
- width: SCREEN_WIDTH,
98
- height: SCREEN_WIDTH / imageRatio
99
- };
100
- } else {
101
- imageMeasure.current = {
102
- width: SCREEN_HEIGHT * imageRatio,
103
- height: SCREEN_HEIGHT
104
- };
105
- }
106
- });
107
- }, [image]);
108
- var initializeCropBox = function initializeCropBox() {
109
- var _imageMeasure$current = imageMeasure.current,
110
- width = _imageMeasure$current.width,
111
- height = _imageMeasure$current.height;
112
- // if (width === 0 || height === 0 || points.length > 0) return;
113
- var boxWidth = width * 0.8;
114
- var boxHeight = height * 0.8;
115
- var centerX = width / 2;
116
- var centerY = height / 2;
117
- setPoints([{
118
- x: centerX - boxWidth / 2,
119
- y: centerY - boxHeight / 2
120
- }, {
121
- x: centerX + boxWidth / 2,
122
- y: centerY - boxHeight / 2
123
- }, {
124
- x: centerX + boxWidth / 2,
125
- y: centerY + boxHeight / 2
126
- }, {
127
- x: centerX - boxWidth / 2,
128
- y: centerY + boxHeight / 2
129
- }]);
130
- };
131
- var onImageLayout = function onImageLayout(e) {
132
- var layout = e.nativeEvent.layout;
133
- imageMeasure.current = {
134
- x: layout.x,
135
- y: layout.y,
136
- width: layout.width,
137
- height: layout.height
138
- };
139
- initializeCropBox();
140
- };
141
- var createPath = function createPath() {
142
- if (points.length < 1) return '';
143
- var path = "M ".concat(points[0].x, " ").concat(points[0].y, " ");
144
- points.forEach(function (point) {
145
- return path += "L ".concat(point.x, " ").concat(point.y, " ");
146
- });
147
- return path + 'Z';
148
- };
149
- var handleTap = function handleTap(e) {
150
- if (!image || showResult) return;
151
- var now = Date.now();
152
- var _e$nativeEvent = e.nativeEvent,
153
- tapX = _e$nativeEvent.locationX,
154
- tapY = _e$nativeEvent.locationY;
155
- if (lastTap.current && now - lastTap.current < 300) {
156
- var exists = points.some(function (p) {
157
- return Math.abs(p.x - tapX) < 15 && Math.abs(p.y - tapY) < 15;
158
- });
159
- if (!exists) setPoints([].concat(_toConsumableArray(points), [{
160
- x: tapX,
161
- y: tapY
162
- }]));
163
- lastTap.current = null;
164
- } else {
165
- var index = points.findIndex(function (p) {
166
- return Math.abs(p.x - tapX) < 20 && Math.abs(p.y - tapY) < 20;
167
- });
168
- if (index !== -1) {
169
- selectedPointIndex.current = index;
170
- lastValidPosition.current = _objectSpread({}, points[index]); // store original position before move
171
- }
172
- lastTap.current = now;
173
- }
174
- };
175
- var handleMove = function handleMove(e) {
176
- if (showResult || selectedPointIndex.current === null) return;
177
- var _e$nativeEvent2 = e.nativeEvent,
178
- moveX = _e$nativeEvent2.locationX,
179
- moveY = _e$nativeEvent2.locationY;
180
- var width = imageMeasure.current.width;
181
- var height = imageMeasure.current.height;
182
- var boundedX = Math.max(0, Math.min(moveX, width));
183
- var boundedY = Math.max(0, Math.min(moveY, height));
184
- var edgeThreshold = 10;
185
- var isNearTopOrBottomEdge = boundedY <= edgeThreshold || boundedY >= height - edgeThreshold;
186
- var isNearLeftOrRightEdge = boundedX <= edgeThreshold || boundedX >= width - edgeThreshold;
187
- if (isNearTopOrBottomEdge || isNearLeftOrRightEdge) {
188
- // Reset point to last known position
189
- if (lastValidPosition.current && selectedPointIndex.current !== null) {
190
- setPoints(function (prev) {
191
- return prev.map(function (p, i) {
192
- return i === selectedPointIndex.current ? lastValidPosition.current : p;
193
- });
194
- });
195
- }
196
- selectedPointIndex.current = null;
197
- return;
198
- }
199
-
200
- // Valid move update point and store as new last valid position
201
- var updatedPoint = {
202
- x: boundedX,
203
- y: boundedY
204
- };
205
- lastValidPosition.current = updatedPoint;
206
- setPoints(function (prev) {
207
- return prev.map(function (p, i) {
208
- return i === selectedPointIndex.current ? updatedPoint : p;
209
- });
210
- });
211
- };
212
- var handleRelease = function handleRelease() {
213
- selectedPointIndex.current = null;
214
- };
215
- var handleReset = function handleReset() {
216
- // setPoints([]);
217
- initializeCropBox();
218
- };
219
- var rotatePreviewImage = /*#__PURE__*/function () {
220
- var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(degrees) {
221
- var rotated, _t;
222
- return _regenerator().w(function (_context) {
223
- while (1) switch (_context.p = _context.n) {
224
- case 0:
225
- if (!(!image || isRotating)) {
226
- _context.n = 1;
227
- break;
228
- }
229
- return _context.a(2);
230
- case 1:
231
- setIsRotating(true);
232
- _context.p = 2;
233
- _context.n = 3;
234
- return ImageManipulator.manipulateAsync(image, [{
235
- rotate: degrees
236
- }], {
237
- compress: 0.85,
238
- format: ImageManipulator.SaveFormat.JPEG
239
- });
240
- case 3:
241
- rotated = _context.v;
242
- // Update image - onImageLayout will call initializeCropBox automatically
243
- setImage(rotated.uri);
244
- _context.n = 5;
245
- break;
246
- case 4:
247
- _context.p = 4;
248
- _t = _context.v;
249
- console.error("Error rotating image:", _t);
250
- alert("Error rotating image");
251
- case 5:
252
- _context.p = 5;
253
- setIsRotating(false);
254
- return _context.f(5);
255
- case 6:
256
- return _context.a(2);
257
- }
258
- }, _callee, null, [[2, 4, 5, 6]]);
259
- }));
260
- return function rotatePreviewImage(_x) {
261
- return _ref2.apply(this, arguments);
262
- };
263
- }();
264
-
265
- // Helper function to wait for multiple render cycles (works on iOS)
266
- var waitForRender = function waitForRender() {
267
- var cycles = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 5;
268
- return new Promise(function (resolve) {
269
- var count = 0;
270
- var _tick = function tick() {
271
- count++;
272
- if (count >= cycles) {
273
- resolve();
274
- } else {
275
- setImmediate(_tick);
276
- }
277
- };
278
- setImmediate(_tick);
279
- });
280
- };
281
- return /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
282
- style: _ImageCropperStyles["default"].container
283
- }, showCustomCamera ? /*#__PURE__*/_react["default"].createElement(_CustomCamera["default"], {
284
- onPhotoCaptured: function onPhotoCaptured(uri) {
285
- setImage(uri);
286
- setShowCustomCamera(false);
287
- },
288
- onCancel: function onCancel() {
289
- return setShowCustomCamera(false);
290
- }
291
- }) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, !showResult && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
292
- style: image ? _ImageCropperStyles["default"].buttonContainer : _ImageCropperStyles["default"].centerButtonsContainer
293
- }, image && _reactNative.Platform.OS === 'android' && /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
294
- style: _ImageCropperStyles["default"].iconButton,
295
- onPress: function onPress() {
296
- return rotatePreviewImage(90);
297
- },
298
- disabled: isRotating
299
- }, /*#__PURE__*/_react["default"].createElement(_vectorIcons.Ionicons, {
300
- name: "sync",
301
- size: 24,
302
- color: "white"
303
- }))), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
304
- style: _ImageCropperStyles["default"].button,
305
- onPress: handleReset
306
- }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
307
- style: _ImageCropperStyles["default"].buttonText
308
- }, "Reset")), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
309
- style: _ImageCropperStyles["default"].button,
310
- onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
311
- var capturedUri, enhancedUri, croppedUri, enhancedImageInfo, actualImageWidth, actualImageHeight, displayedWidth, displayedHeight, scaleX, scaleY, imagePoints, minX, minY, maxX, maxY, cropX, cropY, cropWidth, cropHeight, croppedResult, name, _t2, _t3;
312
- return _regenerator().w(function (_context2) {
313
- while (1) switch (_context2.p = _context2.n) {
314
- case 0:
315
- // setShowFullScreenCapture(true);
316
- setIsLoading(true);
317
- setShowResult(true);
318
- _context2.p = 1;
319
- if (!(_reactNative.Platform.OS === 'android')) {
320
- _context2.n = 3;
321
- break;
322
- }
323
- _context2.n = 2;
324
- return new Promise(function (resolve) {
325
- return requestAnimationFrame(resolve);
326
- });
327
- case 2:
328
- _context2.n = 4;
329
- break;
330
- case 3:
331
- _context2.n = 4;
332
- return waitForRender(5);
333
- case 4:
334
- console.log("Starting capture...");
335
- _context2.n = 5;
336
- return (0, _reactNativeViewShot.captureRef)(viewRef, {
337
- format: 'png',
338
- quality: 1
339
- });
340
- case 5:
341
- capturedUri = _context2.v;
342
- console.log("Capture successful:", capturedUri);
343
- if (capturedUri) {
344
- _context2.n = 6;
345
- break;
346
- }
347
- throw new Error("Capture returned empty URI");
348
- case 6:
349
- console.log("Enhancing image...");
350
- _context2.n = 7;
351
- return (0, _ImageProcessor.enhanceImage)(capturedUri, addheight);
352
- case 7:
353
- enhancedUri = _context2.v;
354
- console.log("Image enhanced:", enhancedUri);
355
-
356
- // Crop image based on points
357
- croppedUri = enhancedUri;
358
- if (!(points.length > 0)) {
359
- _context2.n = 14;
360
- break;
361
- }
362
- _context2.p = 8;
363
- console.log("Calculating crop boundaries from points...");
364
-
365
- // Get enhanced image dimensions
366
- _context2.n = 9;
367
- return ImageManipulator.manipulateAsync(enhancedUri, []);
368
- case 9:
369
- enhancedImageInfo = _context2.v;
370
- actualImageWidth = enhancedImageInfo.width;
371
- actualImageHeight = enhancedImageInfo.height; // Get displayed image dimensions
372
- displayedWidth = imageMeasure.current.width;
373
- displayedHeight = imageMeasure.current.height; // Calculate scale factors to convert screen coordinates to image coordinates
374
- scaleX = actualImageWidth / displayedWidth;
375
- scaleY = actualImageHeight / displayedHeight; // Convert points from screen coordinates to actual image coordinates
376
- imagePoints = points.map(function (point) {
377
- return {
378
- x: point.x * scaleX,
379
- y: point.y * scaleY
380
- };
381
- }); // Calculate bounding box: min X, min Y, max X, max Y
382
- minX = Math.min.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
383
- return p.x;
384
- })));
385
- minY = Math.min.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
386
- return p.y;
387
- })));
388
- maxX = Math.max.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
389
- return p.x;
390
- })));
391
- maxY = Math.max.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
392
- return p.y;
393
- }))); // Calculate crop dimensions and origin
394
- cropX = Math.max(0, Math.floor(minX));
395
- cropY = Math.max(0, Math.floor(minY));
396
- cropWidth = Math.min(actualImageWidth - cropX, Math.ceil(maxX - minX));
397
- cropHeight = Math.min(actualImageHeight - cropY, Math.ceil(maxY - minY));
398
- console.log("Crop parameters:", {
399
- x: cropX,
400
- y: cropY,
401
- width: cropWidth,
402
- height: cropHeight,
403
- imageWidth: actualImageWidth,
404
- imageHeight: actualImageHeight
405
- });
406
-
407
- // Crop the image
408
- if (!(cropWidth > 0 && cropHeight > 0)) {
409
- _context2.n = 11;
410
- break;
411
- }
412
- _context2.n = 10;
413
- return ImageManipulator.manipulateAsync(enhancedUri, [{
414
- crop: {
415
- originX: cropX,
416
- originY: cropY,
417
- width: cropWidth,
418
- height: cropHeight
419
- }
420
- }], {
421
- compress: 1,
422
- format: ImageManipulator.SaveFormat.PNG,
423
- base64: false
424
- });
425
- case 10:
426
- croppedResult = _context2.v;
427
- croppedUri = croppedResult.uri;
428
- console.log("Image cropped successfully:", croppedUri);
429
- _context2.n = 12;
430
- break;
431
- case 11:
432
- console.warn("Invalid crop dimensions, using enhanced image");
433
- case 12:
434
- _context2.n = 14;
435
- break;
436
- case 13:
437
- _context2.p = 13;
438
- _t2 = _context2.v;
439
- console.error("Error cropping image:", _t2);
440
- // Continue with enhanced image if cropping fails
441
- case 14:
442
- name = "IMAGE XTK".concat(Date.now(), ".png");
443
- if (onConfirm) {
444
- console.log("Calling onConfirm with:", croppedUri, name);
445
- onConfirm(croppedUri, name);
446
- }
447
- _context2.n = 16;
448
- break;
449
- case 15:
450
- _context2.p = 15;
451
- _t3 = _context2.v;
452
- console.error("Erreur lors de la capture :", _t3);
453
- alert("Erreur lors de la capture ! " + _t3.message);
454
- case 16:
455
- _context2.p = 16;
456
- setShowResult(false);
457
- setIsLoading(false);
458
- setShowFullScreenCapture(false);
459
- return _context2.f(16);
460
- case 17:
461
- return _context2.a(2);
462
- }
463
- }, _callee2, null, [[8, 13], [1, 15, 16, 17]]);
464
- }))
465
- }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
466
- style: _ImageCropperStyles["default"].buttonText
467
- }, "Confirm"))), image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
468
- ref: viewRef,
469
- collapsable: false,
470
- style: showFullScreenCapture ? _ImageCropperStyles["default"].fullscreenImageContainer : _ImageCropperStyles["default"].imageContainer,
471
- onStartShouldSetResponder: function onStartShouldSetResponder() {
472
- return true;
473
- },
474
- onResponderStart: handleTap,
475
- onResponderMove: handleMove,
476
- onResponderRelease: handleRelease
477
- }, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
478
- source: {
479
- uri: image
480
- },
481
- style: _ImageCropperStyles["default"].image,
482
- onLayout: onImageLayout
483
- }), /*#__PURE__*/_react["default"].createElement(_reactNativeSvg["default"], {
484
- style: _ImageCropperStyles["default"].overlay
485
- }, /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
486
- d: "M 0 0 H ".concat(imageMeasure.current.width, " V ").concat(imageMeasure.current.height, " H 0 Z ").concat(createPath()),
487
- fill: showResult ? 'white' : 'rgba(0, 0, 0, 0.8)',
488
- fillRule: "evenodd"
489
- }), !showResult && points.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
490
- d: createPath(),
491
- fill: "transparent",
492
- stroke: "white",
493
- strokeWidth: 2
494
- }), !showResult && points.map(function (point, index) {
495
- return /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Circle, {
496
- key: index,
497
- cx: point.x,
498
- cy: point.y,
499
- r: 10,
500
- fill: "white"
501
- });
502
- })))), /*#__PURE__*/_react["default"].createElement(_reactNative.Modal, {
503
- visible: isLoading,
504
- transparent: true,
505
- animationType: "fade"
506
- }, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
507
- style: _ImageCropperStyles["default"].loadingOverlay
508
- }, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
509
- source: require('../src/assets/loadingCamera.gif'),
510
- style: _ImageCropperStyles["default"].loadingGif,
511
- resizeMode: "contain"
512
- }))));
513
- };
1
+ "use strict";
2
+
3
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports["default"] = void 0;
8
+ var _ImageCropperStyles = _interopRequireDefault(require("./ImageCropperStyles"));
9
+ var _react = _interopRequireWildcard(require("react"));
10
+ var _reactNative = require("react-native");
11
+ var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
12
+ var _CustomCamera = _interopRequireDefault(require("./CustomCamera"));
13
+ var ImageManipulator = _interopRequireWildcard(require("expo-image-manipulator"));
14
+ var _vectorIcons = require("@expo/vector-icons");
15
+ var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
16
+ var _ImageMaskProcessor = require("./ImageMaskProcessor");
17
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t3 in e) "default" !== _t3 && {}.hasOwnProperty.call(e, _t3) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t3)) && (i.get || i.set) ? o(f, _t3, i) : f[_t3] = e[_t3]); return f; })(e, t); }
18
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
19
+ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
20
+ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
21
+ function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
22
+ function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
23
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
24
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
25
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
26
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
27
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
28
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
29
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
30
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
31
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
32
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
33
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
34
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
35
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
36
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
37
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
38
+ var ImageCropper = function ImageCropper(_ref) {
39
+ var _cameraFrameData$curr3;
40
+ var onConfirm = _ref.onConfirm,
41
+ openCameraFirst = _ref.openCameraFirst,
42
+ initialImage = _ref.initialImage,
43
+ addheight = _ref.addheight;
44
+ var _useState = (0, _react.useState)(null),
45
+ _useState2 = _slicedToArray(_useState, 2),
46
+ image = _useState2[0],
47
+ setImage = _useState2[1];
48
+ var _useState3 = (0, _react.useState)([]),
49
+ _useState4 = _slicedToArray(_useState3, 2),
50
+ points = _useState4[0],
51
+ setPoints = _useState4[1];
52
+ var _useState5 = (0, _react.useState)(false),
53
+ _useState6 = _slicedToArray(_useState5, 2),
54
+ showResult = _useState6[0],
55
+ setShowResult = _useState6[1];
56
+ var _useState7 = (0, _react.useState)(false),
57
+ _useState8 = _slicedToArray(_useState7, 2),
58
+ showCustomCamera = _useState8[0],
59
+ setShowCustomCamera = _useState8[1];
60
+ var viewRef = (0, _react.useRef)(null);
61
+ var maskViewRef = (0, _react.useRef)(null); // Ref pour la vue de masque (invisible)
62
+ var sourceImageUri = (0, _react.useRef)(null); // keep original image URI (full-res) for upload
63
+ var cameraFrameData = (0, _react.useRef)(null); // ✅ Store green frame coordinates from camera
64
+
65
+ // ✅ RÉFÉRENTIEL UNIQUE : Wrapper commun 9/16 (identique à CustomCamera)
66
+ // Ce wrapper est utilisé dans CustomCamera ET ImageCropper pour garantir pixel-perfect matching
67
+ var commonWrapperRef = (0, _react.useRef)(null);
68
+ var commonWrapperLayout = (0, _react.useRef)({
69
+ x: 0,
70
+ y: 0,
71
+ width: 0,
72
+ height: 0
73
+ });
74
+
75
+ // REFACTORISATION : Séparation claire entre dimensions originales et affichage
76
+ // Dimensions réelles de l'image originale (pixels)
77
+ var originalImageDimensions = (0, _react.useRef)({
78
+ width: 0,
79
+ height: 0
80
+ });
81
+ // Dimensions et position d'affichage à l'écran (pour le calcul des points de crop)
82
+ var displayedImageLayout = (0, _react.useRef)({
83
+ x: 0,
84
+ y: 0,
85
+ width: 0,
86
+ height: 0
87
+ });
88
+ // Conserver imageMeasure pour compatibilité avec le code existant (utilisé pour SVG overlay)
89
+ var imageMeasure = (0, _react.useRef)({
90
+ x: 0,
91
+ y: 0,
92
+ width: 0,
93
+ height: 0
94
+ });
95
+ // ✅ imageDisplayRect : Rectangle réel de l'image affichée (quand resizeMode='contain') à l'intérieur du wrapper commun
96
+ // C'est la zone où l'image est réellement visible (avec letterboxing si nécessaire)
97
+ // Les points de crop DOIVENT rester dans cette zone pour éviter de cropper hors de l'image
98
+ var imageDisplayRect = (0, _react.useRef)({
99
+ x: 0,
100
+ y: 0,
101
+ width: 0,
102
+ height: 0
103
+ });
104
+
105
+ // ✅ COMPATIBILITÉ : displayedContentRect reste pour le code existant, mais pointe vers imageDisplayRect
106
+ var displayedContentRect = imageDisplayRect;
107
+
108
+ // RÉFÉRENTIEL UNIQUE : Calculer imageDisplayRect à l'intérieur du wrapper commun
109
+ // - CAMERA: use "cover" mode → image fills wrapper, imageDisplayRect = full wrapper (same as preview)
110
+ // - GALLERY: use "contain" mode → imageDisplayRect = letterboxed area
111
+ var updateImageDisplayRect = function updateImageDisplayRect(wrapperWidth, wrapperHeight) {
112
+ var iw = originalImageDimensions.current.width;
113
+ var ih = originalImageDimensions.current.height;
114
+
115
+ // CAMERA IMAGE: Use full wrapper so green frame and white frame show same content
116
+ if (cameraFrameData.current && cameraFrameData.current.greenFrame && wrapperWidth > 0 && wrapperHeight > 0) {
117
+ imageDisplayRect.current = {
118
+ x: 0,
119
+ y: 0,
120
+ width: wrapperWidth,
121
+ height: wrapperHeight
122
+ };
123
+ console.log("✅ Image display rect (COVER mode for camera - full wrapper):", imageDisplayRect.current);
124
+ return;
125
+ }
126
+ console.log("🔄 updateImageDisplayRect called:", {
127
+ originalDimensions: {
128
+ width: iw,
129
+ height: ih
130
+ },
131
+ wrapperDimensions: {
132
+ width: wrapperWidth,
133
+ height: wrapperHeight
134
+ }
135
+ });
136
+ if (iw > 0 && ih > 0 && wrapperWidth > 0 && wrapperHeight > 0) {
137
+ // Calculer comment l'image s'affiche en "contain" dans le wrapper (gallery)
138
+ var scale = Math.min(wrapperWidth / iw, wrapperHeight / ih);
139
+ var imageDisplayWidth = iw * scale;
140
+ var imageDisplayHeight = ih * scale;
141
+ var offsetX = (wrapperWidth - imageDisplayWidth) / 2;
142
+ var offsetY = (wrapperHeight - imageDisplayHeight) / 2;
143
+ imageDisplayRect.current = {
144
+ x: offsetX,
145
+ y: offsetY,
146
+ width: imageDisplayWidth,
147
+ height: imageDisplayHeight
148
+ };
149
+ console.log("✅ Image display rect (contain in wrapper) calculated:", {
150
+ wrapper: {
151
+ width: wrapperWidth,
152
+ height: wrapperHeight
153
+ },
154
+ imageDisplayRect: imageDisplayRect.current,
155
+ scale: scale.toFixed(4)
156
+ });
157
+ return;
158
+ }
159
+ if (wrapperWidth > 0 && wrapperHeight > 0) {
160
+ imageDisplayRect.current = {
161
+ x: 0,
162
+ y: 0,
163
+ width: wrapperWidth,
164
+ height: wrapperHeight
165
+ };
166
+ console.log("⚠️ Using wrapper dimensions as fallback (original dimensions not available yet):", imageDisplayRect.current);
167
+ } else {
168
+ imageDisplayRect.current = {
169
+ x: 0,
170
+ y: 0,
171
+ width: 0,
172
+ height: 0
173
+ };
174
+ console.warn("❌ Cannot calculate imageDisplayRect: missing dimensions");
175
+ }
176
+ };
177
+
178
+ // COMPATIBILITÉ : Alias pour le code existant
179
+ var updateDisplayedContentRect = updateImageDisplayRect;
180
+ var selectedPointIndex = (0, _react.useRef)(null);
181
+ var lastTap = (0, _react.useRef)(null);
182
+
183
+ // CRITICAL: Guard to prevent double crop box initialization
184
+ // This ensures crop box is initialized only once, especially for camera images
185
+ var hasInitializedCropBox = (0, _react.useRef)(false);
186
+ var imageSource = (0, _react.useRef)(null); // 'camera' | 'gallery' | null
187
+
188
+ // FREE DRAG: Store initial touch position and point position for delta-based movement
189
+ var initialTouchPosition = (0, _react.useRef)(null); // { x, y } - initial touch position when drag starts
190
+ var initialPointPosition = (0, _react.useRef)(null); // { x, y } - initial point position when drag starts
191
+ var lastTouchPosition = (0, _react.useRef)(null); // { x, y } - last touch position for incremental delta calculation
192
+
193
+ // ✅ CRITICAL: dragBase stores the VISUAL position (can be overshoot) during drag
194
+ // This ensures smooth continuous drag without "dead zones" at boundaries
195
+ // dragBase is updated with applied position (overshoot) after each movement
196
+ // and is used as base for next delta calculation
197
+ var dragBase = (0, _react.useRef)(null); // { x, y } - visual position during drag (can be overshoot)
198
+
199
+ // ✅ NEW APPROACH: touchOffset stores the initial offset between point and touch
200
+ // This eliminates delta accumulation issues and "dead zones"
201
+ // Once set at drag start, it remains constant throughout the drag
202
+ var touchOffset = (0, _react.useRef)(null); // { x, y } - offset = pointPosition - touchPosition
203
+
204
+ // ✅ Track if point was clamped in previous frame (to detect transition)
205
+ var wasClampedLastFrame = (0, _react.useRef)({
206
+ x: false,
207
+ y: false
208
+ });
209
+
210
+ // Angle de rotation accumulé (pour éviter les rotations multiples)
211
+ var rotationAngle = (0, _react.useRef)(0);
212
+
213
+ // États pour la vue de masque temporaire
214
+ var _useState9 = (0, _react.useState)(null),
215
+ _useState0 = _slicedToArray(_useState9, 2),
216
+ maskImageUri = _useState0[0],
217
+ setMaskImageUri = _useState0[1];
218
+ var _useState1 = (0, _react.useState)([]),
219
+ _useState10 = _slicedToArray(_useState1, 2),
220
+ maskPoints = _useState10[0],
221
+ setMaskPoints = _useState10[1];
222
+ var _useState11 = (0, _react.useState)({
223
+ width: 0,
224
+ height: 0
225
+ }),
226
+ _useState12 = _slicedToArray(_useState11, 2),
227
+ maskDimensions = _useState12[0],
228
+ setMaskDimensions = _useState12[1];
229
+ var _useState13 = (0, _react.useState)(false),
230
+ _useState14 = _slicedToArray(_useState13, 2),
231
+ showMaskView = _useState14[0],
232
+ setShowMaskView = _useState14[1];
233
+ var _useState15 = (0, _react.useState)(false),
234
+ _useState16 = _slicedToArray(_useState15, 2),
235
+ isLoading = _useState16[0],
236
+ setIsLoading = _useState16[1];
237
+ var _useState17 = (0, _react.useState)(false),
238
+ _useState18 = _slicedToArray(_useState17, 2),
239
+ showFullScreenCapture = _useState18[0],
240
+ setShowFullScreenCapture = _useState18[1];
241
+ var _useState19 = (0, _react.useState)(false),
242
+ _useState20 = _slicedToArray(_useState19, 2),
243
+ isRotating = _useState20[0],
244
+ setIsRotating = _useState20[1];
245
+ var lastValidPosition = (0, _react.useRef)(null);
246
+ var insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
247
+
248
+ // NEW ARCH: mobile does NOT export the final crop.
249
+
250
+ // No view-shot / captureRef / bitmap masking on device.
251
+ var enableMask = false;
252
+ var enableRotation = false; // rotation would require careful coord transforms; keep off for pixel-perfect pipeline.
253
+
254
+ (0, _react.useEffect)(function () {
255
+ if (openCameraFirst) {
256
+ setShowCustomCamera(true);
257
+ } else if (initialImage) {
258
+ setImage(initialImage);
259
+ sourceImageUri.current = initialImage;
260
+ // CRITICAL: Reset points when loading a new image from gallery
261
+ // This ensures the crop box will be automatically initialized
262
+ setPoints([]);
263
+ rotationAngle.current = 0;
264
+ // Clear camera frame data for gallery images
265
+ cameraFrameData.current = null;
266
+ // CRITICAL: Reset initialization guard for new image
267
+ hasInitializedCropBox.current = false;
268
+ imageSource.current = null;
269
+ }
270
+ }, [openCameraFirst, initialImage]);
271
+
272
+ // ✅ REFACTORISATION : Stocker uniquement les dimensions originales (pas de calcul théorique)
273
+ // ✅ CRITICAL FIX: Single source of truth for image dimensions
274
+
275
+ (0, _react.useEffect)(function () {
276
+ if (!image) {
277
+ originalImageDimensions.current = {
278
+ width: 0,
279
+ height: 0
280
+ };
281
+ hasInitializedCropBox.current = false;
282
+ imageSource.current = null;
283
+ return;
284
+ }
285
+ if (!sourceImageUri.current) {
286
+ sourceImageUri.current = image;
287
+ }
288
+
289
+ // ✅ CRITICAL FIX #1: If we have capturedImageSize from camera, use it as SINGLE SOURCE OF TRUTH
290
+ // takePictureAsync returns physical dimensions, while Image.getSize() may return EXIF-oriented dimensions
291
+ // DO NOT call Image.getSize() for camera images - it can return swapped dimensions on Android
292
+ if (cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
293
+ var _cameraFrameData$curr = cameraFrameData.current.capturedImageSize,
294
+ capturedWidth = _cameraFrameData$curr.width,
295
+ capturedHeight = _cameraFrameData$curr.height;
296
+ originalImageDimensions.current = {
297
+ width: capturedWidth,
298
+ height: capturedHeight
299
+ };
300
+ imageSource.current = 'camera';
301
+ hasInitializedCropBox.current = false; // Reset guard for new camera image
302
+
303
+ console.log("✅ Using captured image dimensions from takePictureAsync (SINGLE SOURCE OF TRUTH):", {
304
+ width: capturedWidth,
305
+ height: capturedHeight,
306
+ source: 'takePictureAsync',
307
+ note: 'Image.getSize() will NOT be called for camera images'
308
+ });
309
+
310
+ // ✅ CRITICAL: Recalculate imageDisplayRect immediately with camera dimensions
311
+ var wrapper = commonWrapperLayout.current;
312
+ if (wrapper.width > 0 && wrapper.height > 0) {
313
+ updateImageDisplayRect(wrapper.width, wrapper.height);
314
+
315
+ // CRITICAL FIX #2: Initialize crop box immediately when cameraFrameData is available
316
+ // This ensures camera images are initialized from greenFrame BEFORE any other initialization
317
+ if (cameraFrameData.current && cameraFrameData.current.greenFrame && !hasInitializedCropBox.current) {
318
+ console.log("✅ Initializing crop box from cameraFrameData (immediate in useEffect):", {
319
+ hasGreenFrame: !!cameraFrameData.current.greenFrame,
320
+ wrapper: wrapper,
321
+ originalDimensions: originalImageDimensions.current
322
+ });
323
+ initializeCropBox();
324
+ }
325
+ }
326
+ return; // ✅ CRITICAL: Exit early - DO NOT call Image.getSize()
327
+ }
328
+
329
+ // ✅ FALLBACK: Use Image.getSize() ONLY for gallery images (no cameraFrameData)
330
+ // BUT: Check again right before calling to avoid race condition
331
+ if (cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
332
+ console.log("⚠️ cameraFrameData exists, skipping Image.getSize() call");
333
+ return;
334
+ }
335
+
336
+ // ✅ CRITICAL: Also check imageSource - if it's 'camera', don't call Image.getSize()
337
+ if (imageSource.current === 'camera') {
338
+ console.log("⚠️ imageSource is 'camera', skipping Image.getSize() call");
339
+ return;
340
+ }
341
+
342
+ // ✅ CRITICAL: Set imageSource to 'gallery' ONLY if we're sure it's not a camera image
343
+ // Don't set it yet - we'll set it in the callback after verifying
344
+ hasInitializedCropBox.current = false; // Reset guard for new image
345
+
346
+ _reactNative.Image.getSize(image, function (imgWidth, imgHeight) {
347
+ // CRITICAL SAFETY #1: Check if cameraFrameData appeared while Image.getSize() was resolving
348
+ // This is the PRIMARY check - cameraFrameData takes precedence
349
+ if (cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
350
+ console.warn("⚠️ Image.getSize() resolved but cameraFrameData exists - IGNORING Image.getSize() result to prevent dimension swap");
351
+ console.warn("⚠️ Camera dimensions (correct):", cameraFrameData.current.capturedImageSize);
352
+ console.warn("⚠️ Image.getSize() dimensions (potentially swapped):", {
353
+ width: imgWidth,
354
+ height: imgHeight
355
+ });
356
+ return; // CRITICAL: Exit early - do NOT update dimensions or initialize crop box
357
+ }
358
+
359
+ // CRITICAL SAFETY #2: Check imageSource (should be 'camera' if cameraFrameData was set)
360
+ if (imageSource.current === 'camera') {
361
+ console.warn("⚠️ Image.getSize() resolved but imageSource is 'camera' - IGNORING Image.getSize() result");
362
+ console.warn("⚠️ Image.getSize() dimensions (potentially swapped):", {
363
+ width: imgWidth,
364
+ height: imgHeight
365
+ });
366
+ return; // ✅ CRITICAL: Exit early - do NOT update dimensions or initialize crop box
367
+ }
368
+
369
+ // CRITICAL SAFETY #3: Check if crop box was already initialized (from camera)
370
+ if (hasInitializedCropBox.current) {
371
+ console.warn("⚠️ Image.getSize() resolved but crop box already initialized - IGNORING result to prevent double initialization");
372
+ return;
373
+ }
374
+
375
+ // SAFE: This is a gallery image, proceed with Image.getSize() result
376
+ imageSource.current = 'gallery';
377
+ originalImageDimensions.current = {
378
+ width: imgWidth,
379
+ height: imgHeight
380
+ };
381
+ console.log("✅ Image dimensions from Image.getSize() (gallery image):", {
382
+ width: imgWidth,
383
+ height: imgHeight,
384
+ platform: _reactNative.Platform.OS,
385
+ pixelRatio: _reactNative.PixelRatio.get(),
386
+ uri: image,
387
+ source: 'Image.getSize()'
388
+ });
389
+
390
+ // ✅ RÉFÉRENTIEL UNIQUE : Recalculer imageDisplayRect dans le wrapper commun
391
+ // dès qu'on connaît la taille originale de l'image
392
+ var wrapper = commonWrapperLayout.current;
393
+ if (wrapper.width > 0 && wrapper.height > 0) {
394
+ updateImageDisplayRect(wrapper.width, wrapper.height);
395
+ // IMPORTANT: pour les images de la galerie (pas de cameraFrameData),
396
+ // initialiser automatiquement le cadre blanc (70% du wrapper) une fois que
397
+ // nous connaissons à la fois le wrapper et les dimensions originales.
398
+ // ✅ CRITICAL: Guard against double initialization
399
+ if (!hasInitializedCropBox.current && points.length === 0 && imageSource.current === 'gallery') {
400
+ initializeCropBox();
401
+ hasInitializedCropBox.current = true;
402
+ }
403
+ }
404
+ }, function (error) {
405
+ console.error("Error getting image size:", error);
406
+ });
407
+ }, [image]);
408
+
409
+ // Le cadre blanc doit être calculé sur le MÊME wrapper que le cadre vert (9/16)
410
+ // Ensuite, on restreint les points pour qu'ils restent dans imageDisplayRect (image visible)
411
+ var initializeCropBox = function initializeCropBox() {
412
+ // CRITICAL FIX #2: Guard against double initialization
413
+ if (hasInitializedCropBox.current) {
414
+ console.log("⚠️ Crop box already initialized, skipping duplicate initialization");
415
+ return;
416
+ }
417
+
418
+ // ✅ CRITICAL: Ensure common wrapper layout is available
419
+ var wrapper = commonWrapperLayout.current;
420
+ if (wrapper.width === 0 || wrapper.height === 0) {
421
+ console.warn("Cannot initialize crop box: common wrapper layout not ready");
422
+ return;
423
+ }
424
+
425
+ // ✅ CRITICAL: Ensure imageDisplayRect is available (zone réelle de l'image dans le wrapper)
426
+ var imageRect = imageDisplayRect.current;
427
+ if (imageRect.width === 0 || imageRect.height === 0) {
428
+ // Recalculer si nécessaire
429
+ if (originalImageDimensions.current.width > 0 && originalImageDimensions.current.height > 0) {
430
+ updateImageDisplayRect(wrapper.width, wrapper.height);
431
+ imageRect = imageDisplayRect.current;
432
+ } else {
433
+ console.warn("Cannot initialize crop box: imageDisplayRect not available (original dimensions missing)");
434
+ return;
435
+ }
436
+ }
437
+
438
+ // CRITICAL FIX: Calculate crop box as percentage of VISIBLE IMAGE AREA (imageDisplayRect)
439
+ // NOT the wrapper. This ensures the crop box is truly 80% of the image, not 80% of wrapper then clamped.
440
+ // Calculate absolute position of imageDisplayRect within wrapper
441
+ var imageRectX = wrapper.x + imageRect.x;
442
+ var imageRectY = wrapper.y + imageRect.y;
443
+
444
+ // PRIORITY RULE #3: IF image comes from camera → Use EXACT green frame coordinates
445
+ // Image is displayed in "cover" mode (full wrapper), so green frame coords = white frame coords
446
+ if (cameraFrameData.current && cameraFrameData.current.greenFrame && originalImageDimensions.current.width > 0) {
447
+ var greenFrame = cameraFrameData.current.greenFrame;
448
+
449
+ // ✅ Image fills wrapper (cover mode), so use green frame position directly in wrapper space
450
+ var _boxX = wrapper.x + greenFrame.x;
451
+ var _boxY = wrapper.y + greenFrame.y;
452
+ var _boxWidth = greenFrame.width;
453
+ var _boxHeight = greenFrame.height;
454
+
455
+ // SAFETY: Validate calculated coordinates before creating points
456
+ var _isValidCoordinate = function _isValidCoordinate(val) {
457
+ return typeof val === 'number' && isFinite(val) && !isNaN(val);
458
+ };
459
+ if (!_isValidCoordinate(_boxX) || !_isValidCoordinate(_boxY) || !_isValidCoordinate(_boxWidth) || !_isValidCoordinate(_boxHeight)) {
460
+ console.warn("⚠️ Invalid coordinates calculated for crop box, skipping initialization");
461
+ return;
462
+ }
463
+
464
+ // ✅ Create points using EXACT greenFrame coordinates (mapped to ImageCropper wrapper)
465
+ var _newPoints = [{
466
+ x: _boxX,
467
+ y: _boxY
468
+ },
469
+ // Top-left
470
+ {
471
+ x: _boxX + _boxWidth,
472
+ y: _boxY
473
+ },
474
+ // Top-right
475
+ {
476
+ x: _boxX + _boxWidth,
477
+ y: _boxY + _boxHeight
478
+ },
479
+ // Bottom-right
480
+ {
481
+ x: _boxX,
482
+ y: _boxY + _boxHeight
483
+ } // Bottom-left
484
+ ];
485
+
486
+ // SAFETY: Validate all points before setting
487
+ var _validPoints = _newPoints.filter(function (p) {
488
+ return _isValidCoordinate(p.x) && _isValidCoordinate(p.y);
489
+ });
490
+ if (_validPoints.length !== _newPoints.length) {
491
+ console.warn("⚠️ Some points have invalid coordinates, skipping initialization");
492
+ return;
493
+ }
494
+ console.log("✅ Initializing crop box for camera image (COVER MODE - exact green frame):", {
495
+ greenFrame: {
496
+ x: greenFrame.x,
497
+ y: greenFrame.y,
498
+ width: greenFrame.width,
499
+ height: greenFrame.height
500
+ },
501
+ whiteFrame: {
502
+ x: _boxX.toFixed(2),
503
+ y: _boxY.toFixed(2),
504
+ width: _boxWidth.toFixed(2),
505
+ height: _boxHeight.toFixed(2)
506
+ },
507
+ note: "Image in cover mode - white frame = same position/size as green frame, same content"
508
+ });
509
+ setPoints(_newPoints);
510
+ hasInitializedCropBox.current = true; // ✅ CRITICAL: Mark as initialized
511
+ // ✅ CRITICAL: DO NOT nullify cameraFrameData here - keep it for Image.getSize() callback check
512
+ // It will be cleared when loading a new image
513
+ return;
514
+ }
515
+
516
+ // ✅ PRIORITY RULE #3: DEFAULT logic ONLY for gallery images (NOT camera)
517
+ // If we reach here and imageSource is 'camera', something went wrong
518
+ if (imageSource.current === 'camera') {
519
+ console.warn("⚠️ Camera image but no greenFrame found - this should not happen");
520
+ return;
521
+ }
522
+
523
+ // ✅ DEFAULT: Crop box (70% of VISIBLE IMAGE AREA - centered) - ONLY for gallery images
524
+ var boxWidth = imageRect.width * 0.70; // 70% of visible image width
525
+ var boxHeight = imageRect.height * 0.70; // 70% of visible image height
526
+ var boxX = imageRectX + (imageRect.width - boxWidth) / 2; // Centered in image area
527
+ var boxY = imageRectY + (imageRect.height - boxHeight) / 2; // Centered in image area
528
+
529
+ // ✅ SAFETY: Validate calculated coordinates before creating points
530
+ var isValidCoordinate = function isValidCoordinate(val) {
531
+ return typeof val === 'number' && isFinite(val) && !isNaN(val);
532
+ };
533
+ if (!isValidCoordinate(boxX) || !isValidCoordinate(boxY) || !isValidCoordinate(boxWidth) || !isValidCoordinate(boxHeight)) {
534
+ console.warn("⚠️ Invalid coordinates calculated for default crop box, skipping initialization");
535
+ return;
536
+ }
537
+ var newPoints = [{
538
+ x: boxX,
539
+ y: boxY
540
+ }, {
541
+ x: boxX + boxWidth,
542
+ y: boxY
543
+ }, {
544
+ x: boxX + boxWidth,
545
+ y: boxY + boxHeight
546
+ }, {
547
+ x: boxX,
548
+ y: boxY + boxHeight
549
+ }];
550
+
551
+ // ✅ SAFETY: Validate all points before setting
552
+ var validPoints = newPoints.filter(function (p) {
553
+ return isValidCoordinate(p.x) && isValidCoordinate(p.y);
554
+ });
555
+ if (validPoints.length !== newPoints.length) {
556
+ console.warn("⚠️ Some points have invalid coordinates in default crop box, skipping initialization");
557
+ return;
558
+ }
559
+ console.log("✅ Initializing crop box (default - 70% of visible image area, gallery only):", {
560
+ wrapper: {
561
+ width: wrapper.width,
562
+ height: wrapper.height
563
+ },
564
+ imageDisplayRect: imageRect,
565
+ boxInImage: {
566
+ x: boxX,
567
+ y: boxY,
568
+ width: boxWidth,
569
+ height: boxHeight
570
+ },
571
+ points: newPoints
572
+ });
573
+ setPoints(newPoints);
574
+ hasInitializedCropBox.current = true; // ✅ CRITICAL: Mark as initialized
575
+ };
576
+
577
+ // ✅ RÉFÉRENTIEL UNIQUE : Callback pour mettre à jour le layout du wrapper commun
578
+ // Ce wrapper a exactement les mêmes dimensions que le wrapper de CustomCamera (9/16, width = screenWidth)
579
+ var onCommonWrapperLayout = function onCommonWrapperLayout(e) {
580
+ var layout = e.nativeEvent.layout;
581
+ commonWrapperLayout.current = {
582
+ x: layout.x,
583
+ y: layout.y,
584
+ width: layout.width,
585
+ height: layout.height
586
+ };
587
+ console.log("✅ Common wrapper layout updated:", commonWrapperLayout.current);
588
+
589
+ // ✅ Recalculer imageDisplayRect dès que le wrapper est prêt
590
+ if (originalImageDimensions.current.width > 0 && originalImageDimensions.current.height > 0) {
591
+ updateImageDisplayRect(layout.width, layout.height);
592
+
593
+ // ✅ CRITICAL FIX #2: Initialize crop box ONLY if not already initialized
594
+ // For camera images: initialize ONLY from greenFrame (already done when cameraFrameData was set)
595
+ if (!hasInitializedCropBox.current && points.length === 0) {
596
+ // ✅ CRITICAL: Only initialize for gallery images here
597
+ // Camera images should be initialized when cameraFrameData is set, not here
598
+ if (imageSource.current !== 'camera') {
599
+ initializeCropBox();
600
+ }
601
+ }
602
+ }
603
+ };
604
+
605
+ // ✅ REFACTORISATION : Mettre à jour les dimensions d'affichage et les dimensions pour SVG
606
+ var onImageLayout = function onImageLayout(e) {
607
+ var layout = e.nativeEvent.layout;
608
+
609
+ // Stocker les dimensions d'affichage réelles (pour conversion de coordonnées)
610
+ displayedImageLayout.current = {
611
+ x: layout.x,
612
+ y: layout.y,
613
+ width: layout.width,
614
+ height: layout.height
615
+ };
616
+
617
+ // Conserver aussi dans imageMeasure pour compatibilité avec SVG overlay
618
+ imageMeasure.current = {
619
+ x: layout.x,
620
+ y: layout.y,
621
+ width: layout.width,
622
+ height: layout.height
623
+ };
624
+
625
+ // ✅ Si l'image vient de la caméra et que les dimensions originales ne sont pas encore définies,
626
+ // les initialiser à partir de cameraFrameData AVANT de calculer le contentRect.
627
+ if (originalImageDimensions.current.width === 0 && cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
628
+ var _cameraFrameData$curr2 = cameraFrameData.current.capturedImageSize,
629
+ width = _cameraFrameData$curr2.width,
630
+ height = _cameraFrameData$curr2.height;
631
+ originalImageDimensions.current = {
632
+ width: width,
633
+ height: height
634
+ };
635
+ console.log("✅ originalImageDimensions initialisées depuis cameraFrameData dans onImageLayout:", {
636
+ width: width,
637
+ height: height
638
+ });
639
+ }
640
+
641
+ // ✅ RÉFÉRENTIEL UNIQUE : Recalculer imageDisplayRect dans le wrapper commun
642
+ // Si le wrapper commun n'est pas encore prêt, on attendra onCommonWrapperLayout
643
+ var wrapper = commonWrapperLayout.current;
644
+ if (wrapper.width > 0 && wrapper.height > 0) {
645
+ updateImageDisplayRect(wrapper.width, wrapper.height);
646
+ }
647
+ console.log("Displayed image layout updated:", {
648
+ width: layout.width,
649
+ height: layout.height,
650
+ x: layout.x,
651
+ y: layout.y
652
+ });
653
+
654
+ // ✅ CRITICAL FIX #2: Do NOT initialize crop box in onImageLayout for camera images
655
+ // Camera images should be initialized ONLY when cameraFrameData is set (in useEffect)
656
+ // Gallery images can be initialized here if not already done
657
+ if (wrapper.width > 0 && wrapper.height > 0 && layout.width > 0 && layout.height > 0 && !hasInitializedCropBox.current && points.length === 0 && originalImageDimensions.current.width > 0 && originalImageDimensions.current.height > 0) {
658
+ // ✅ CRITICAL: Only initialize for gallery images here
659
+ // Camera images should be initialized when cameraFrameData is set, not here
660
+ if (imageSource.current !== 'camera') {
661
+ initializeCropBox();
662
+ } else if (cameraFrameData.current && cameraFrameData.current.greenFrame) {
663
+ // ✅ For camera images, initialize ONLY if greenFrame is available
664
+ initializeCropBox();
665
+ }
666
+ }
667
+ };
668
+ var createPath = function createPath() {
669
+ if (points.length < 1) return '';
670
+ var path = "M ".concat(points[0].x, " ").concat(points[0].y, " ");
671
+ points.forEach(function (point) {
672
+ return path += "L ".concat(point.x, " ").concat(point.y, " ");
673
+ });
674
+ return path + 'Z';
675
+ };
676
+
677
+ // ✅ Helper function: Find closest point on a line segment to a tap point
678
+ var findClosestPointOnLine = function findClosestPointOnLine(tapX, tapY, lineStartX, lineStartY, lineEndX, lineEndY) {
679
+ var dx = lineEndX - lineStartX;
680
+ var dy = lineEndY - lineStartY;
681
+ var lengthSquared = dx * dx + dy * dy;
682
+ if (lengthSquared === 0) {
683
+ // Line segment is a point
684
+ return {
685
+ x: lineStartX,
686
+ y: lineStartY,
687
+ distance: Math.sqrt(Math.pow(tapX - lineStartX, 2) + Math.pow(tapY - lineStartY, 2))
688
+ };
689
+ }
690
+
691
+ // Calculate projection parameter t (0 to 1)
692
+ var t = Math.max(0, Math.min(1, ((tapX - lineStartX) * dx + (tapY - lineStartY) * dy) / lengthSquared));
693
+
694
+ // Calculate closest point on line segment
695
+ var closestX = lineStartX + t * dx;
696
+ var closestY = lineStartY + t * dy;
697
+
698
+ // Calculate distance from tap to closest point
699
+ var distance = Math.sqrt(Math.pow(tapX - closestX, 2) + Math.pow(tapY - closestY, 2));
700
+ return {
701
+ x: closestX,
702
+ y: closestY,
703
+ distance: distance,
704
+ t: t
705
+ };
706
+ };
707
+
708
+ // ✅ Helper function: Check if tap is near any line segment and find closest point
709
+ var findClosestPointOnFrame = function findClosestPointOnFrame(tapX, tapY) {
710
+ var lineTolerance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 30;
711
+ if (points.length < 2) return null;
712
+ var closestPoint = null;
713
+ var minDistance = Infinity;
714
+ var insertIndex = -1;
715
+
716
+ // Check each line segment (closed polygon: last point connects to first)
717
+ for (var i = 0; i < points.length; i++) {
718
+ var start = points[i];
719
+ var end = points[(i + 1) % points.length];
720
+ var result = findClosestPointOnLine(tapX, tapY, start.x, start.y, end.x, end.y);
721
+ if (result.distance < minDistance && result.distance < lineTolerance) {
722
+ minDistance = result.distance;
723
+ closestPoint = {
724
+ x: result.x,
725
+ y: result.y
726
+ };
727
+ // Insert after the start point of this segment
728
+ insertIndex = i + 1;
729
+ }
730
+ }
731
+ return closestPoint ? {
732
+ point: closestPoint,
733
+ insertIndex: insertIndex
734
+ } : null;
735
+ };
736
+ var handleTap = function handleTap(e) {
737
+ if (!image || showResult) return;
738
+ var now = Date.now();
739
+ var _e$nativeEvent = e.nativeEvent,
740
+ tapX = _e$nativeEvent.locationX,
741
+ tapY = _e$nativeEvent.locationY;
742
+
743
+ // ✅ RÉFÉRENTIEL UNIQUE : Utiliser imageDisplayRect (zone réelle de l'image dans le wrapper)
744
+ // Les coordonnées du tap sont relatives au wrapper commun
745
+ var imageRect = imageDisplayRect.current;
746
+ var wrapper = commonWrapperLayout.current;
747
+
748
+ // Recalculate if not available
749
+ if (imageRect.width === 0 || imageRect.height === 0) {
750
+ if (wrapper.width > 0 && wrapper.height > 0 && originalImageDimensions.current.width > 0) {
751
+ updateImageDisplayRect(wrapper.width, wrapper.height);
752
+ imageRect = imageDisplayRect.current;
753
+ }
754
+ // If still not available, use wrapper as fallback
755
+ if (imageRect.width === 0 || imageRect.height === 0) {
756
+ if (wrapper.width > 0 && wrapper.height > 0) {
757
+ imageRect = {
758
+ x: wrapper.x,
759
+ y: wrapper.y,
760
+ width: wrapper.width,
761
+ height: wrapper.height
762
+ };
763
+ } else {
764
+ console.warn("⚠️ Cannot handle tap: wrapper or imageDisplayRect not available");
765
+ return;
766
+ }
767
+ }
768
+ }
769
+
770
+ // ✅ Clamp to real displayed image content (imageDisplayRect dans le wrapper)
771
+ // Les coordonnées tapX/tapY sont relatives au wrapper commun
772
+ var imageRectX = wrapper.x + imageRect.x;
773
+ var imageRectY = wrapper.y + imageRect.y;
774
+ var _x$y$width$height = {
775
+ x: imageRectX,
776
+ y: imageRectY,
777
+ width: imageRect.width,
778
+ height: imageRect.height
779
+ },
780
+ cx = _x$y$width$height.x,
781
+ cy = _x$y$width$height.y,
782
+ cw = _x$y$width$height.width,
783
+ ch = _x$y$width$height.height;
784
+ // ✅ Larger select radius for easier point selection (especially on touch screens)
785
+ var selectRadius = 50; // Increased from 28 to 50 for better UX
786
+
787
+ // ✅ CRITICAL: Check for existing point selection FIRST (using raw tap coordinates)
788
+ // Don't clamp tapX/Y for point selection - points can be anywhere in wrapper now
789
+ var index = points.findIndex(function (p) {
790
+ return Math.abs(p.x - tapX) < selectRadius && Math.abs(p.y - tapY) < selectRadius;
791
+ });
792
+ if (index !== -1) {
793
+ // ✅ Point found - select it for dragging
794
+ selectedPointIndex.current = index;
795
+
796
+ // Store initial positions
797
+ initialTouchPosition.current = {
798
+ x: tapX,
799
+ y: tapY
800
+ };
801
+ lastTouchPosition.current = {
802
+ x: tapX,
803
+ y: tapY
804
+ };
805
+ initialPointPosition.current = _objectSpread({}, points[index]);
806
+ lastValidPosition.current = _objectSpread({}, points[index]);
807
+
808
+ // Calculate offset between point and touch at drag start
809
+ touchOffset.current = {
810
+ x: points[index].x - tapX,
811
+ y: points[index].y - tapY
812
+ };
813
+ console.log("🎯 DRAG START - Offset calculated:", {
814
+ pointX: points[index].x.toFixed(2),
815
+ pointY: points[index].y.toFixed(2),
816
+ touchX: tapX.toFixed(2),
817
+ touchY: tapY.toFixed(2),
818
+ offsetX: touchOffset.current.x.toFixed(2),
819
+ offsetY: touchOffset.current.y.toFixed(2)
820
+ });
821
+
822
+ // Disable parent ScrollView scrolling when dragging
823
+ try {
824
+ var _findScrollView = function findScrollView(node) {
825
+ if (!node) return null;
826
+ if (node._component && node._component.setNativeProps) {
827
+ node._component.setNativeProps({
828
+ scrollEnabled: false
829
+ });
830
+ }
831
+ return _findScrollView(node._owner || node._parent);
832
+ };
833
+ } catch (e) {
834
+ // Ignore errors
835
+ }
836
+ } else {
837
+ // ✅ No point found - check if double-tap on a line to create new point
838
+ var isDoubleTap = lastTap.current && now - lastTap.current < 300;
839
+ if (isDoubleTap && points.length >= 2) {
840
+ // Find closest point on frame lines
841
+ var lineResult = findClosestPointOnFrame(tapX, tapY, 30); // 30px tolerance
842
+
843
+ if (lineResult) {
844
+ var point = lineResult.point,
845
+ insertIndex = lineResult.insertIndex;
846
+
847
+ // Check if a point already exists very close to this position
848
+ var exists = points.some(function (p) {
849
+ return Math.abs(p.x - point.x) < selectRadius && Math.abs(p.y - point.y) < selectRadius;
850
+ });
851
+ if (!exists) {
852
+ // Insert new point at the correct position in the polygon
853
+ var newPoints = _toConsumableArray(points);
854
+ newPoints.splice(insertIndex, 0, point);
855
+ setPoints(newPoints);
856
+ console.log("✅ New point created on frame line:", {
857
+ tap: {
858
+ x: tapX.toFixed(2),
859
+ y: tapY.toFixed(2)
860
+ },
861
+ newPoint: {
862
+ x: point.x.toFixed(2),
863
+ y: point.y.toFixed(2)
864
+ },
865
+ insertIndex: insertIndex,
866
+ totalPoints: newPoints.length
867
+ });
868
+ lastTap.current = null; // Reset to prevent triple-tap
869
+ return;
870
+ }
871
+ }
872
+ }
873
+ }
874
+ lastTap.current = now;
875
+ };
876
+ var handleMove = function handleMove(e) {
877
+ if (showResult || selectedPointIndex.current === null) return;
878
+
879
+ // ✅ FREE DRAG: Use delta-based movement for smooth, unconstrained dragging
880
+
881
+ var nativeEvent = e.nativeEvent;
882
+ var currentX = nativeEvent.locationX;
883
+ var currentY = nativeEvent.locationY;
884
+
885
+ // ✅ Validate coordinates
886
+ if (currentX === undefined || currentY === undefined || isNaN(currentX) || isNaN(currentY)) {
887
+ console.warn("⚠️ Cannot get touch coordinates", {
888
+ locationX: nativeEvent.locationX,
889
+ locationY: nativeEvent.locationY
890
+ });
891
+ return;
892
+ }
893
+
894
+ // This is more reliable when ScrollView affects coordinate updates
895
+ var deltaX, deltaY;
896
+ if (lastTouchPosition.current) {
897
+ // Calculate incremental delta from last touch position
898
+ deltaX = currentX - lastTouchPosition.current.x;
899
+ deltaY = currentY - lastTouchPosition.current.y;
900
+ } else if (initialTouchPosition.current) {
901
+ // Fallback to absolute delta if lastTouchPosition not set
902
+ deltaX = currentX - initialTouchPosition.current.x;
903
+ deltaY = currentY - initialTouchPosition.current.y;
904
+ } else {
905
+ console.warn("⚠️ No touch position reference available");
906
+ return;
907
+ }
908
+
909
+ // Les coordonnées de mouvement sont relatives au wrapper commun
910
+ var imageRect = imageDisplayRect.current;
911
+ var wrapper = commonWrapperLayout.current;
912
+
913
+ // Recalculate if not available
914
+ if (imageRect.width === 0 || imageRect.height === 0) {
915
+ if (wrapper.width > 0 && wrapper.height > 0 && originalImageDimensions.current.width > 0) {
916
+ updateImageDisplayRect(wrapper.width, wrapper.height);
917
+ imageRect = imageDisplayRect.current;
918
+ }
919
+ // If still not available, use wrapper as fallback
920
+ if (imageRect.width === 0 || imageRect.height === 0) {
921
+ if (wrapper.width > 0 && wrapper.height > 0) {
922
+ imageRect = {
923
+ x: wrapper.x,
924
+ y: wrapper.y,
925
+ width: wrapper.width,
926
+ height: wrapper.height
927
+ };
928
+ } else {
929
+ console.warn("⚠️ Cannot move point: wrapper or imageDisplayRect not available");
930
+ return;
931
+ }
932
+ }
933
+ }
934
+
935
+ // ✅ CRITICAL: Calculate absolute bounds of imageDisplayRect within the wrapper
936
+ // imageRect is relative to wrapper, so we need to add wrapper offset
937
+ var imageRectX = wrapper.x + imageRect.x;
938
+ var imageRectY = wrapper.y + imageRect.y;
939
+ var contentRect = {
940
+ x: imageRectX,
941
+ y: imageRectY,
942
+ width: imageRect.width,
943
+ height: imageRect.height
944
+ };
945
+
946
+ // ✅ FREE DRAG: Ensure initial positions are set
947
+ if (!initialPointPosition.current) {
948
+ var currentPoint = points[selectedPointIndex.current];
949
+ if (currentPoint && typeof currentPoint.x === 'number' && typeof currentPoint.y === 'number') {
950
+ initialPointPosition.current = _objectSpread({}, currentPoint);
951
+ } else {
952
+ console.warn("⚠️ No point found for selected index or invalid point data");
953
+ return;
954
+ }
955
+ }
956
+
957
+ // ✅ NEW APPROACH: Use touchOffset to map touch position directly to point position
958
+ // This eliminates delta accumulation and "dead zone" issues completely
959
+ if (!touchOffset.current) {
960
+ console.warn("⚠️ touchOffset not initialized, cannot move point");
961
+ return;
962
+ }
963
+
964
+ // ✅ DIRECT MAPPING: newPosition = touchPosition + offset
965
+ // No delta accumulation, no zone morte
966
+ var newX = currentX + touchOffset.current.x;
967
+ var newY = currentY + touchOffset.current.y;
968
+
969
+ // ✅ SEPARATE DRAG BOUNDS vs CROP BOUNDS
970
+ var cx = contentRect.x,
971
+ cy = contentRect.y,
972
+ cw = contentRect.width,
973
+ ch = contentRect.height;
974
+
975
+ // ✅ STRICT BOUNDS: For final crop safety (imageDisplayRect)
976
+ var strictMinX = cx;
977
+ var strictMaxX = cx + cw;
978
+ var strictMinY = cy;
979
+ var strictMaxY = cy + ch;
980
+
981
+ // ✅ DRAG BOUNDS: Allow movement ANYWHERE in wrapper during drag
982
+ // Points can move freely across the entire screen for maximum flexibility
983
+ // They will be clamped to imageDisplayRect only on release for safe cropping
984
+ var wrapperRect = wrapper;
985
+ var overshootMinX = wrapperRect.x;
986
+ var overshootMaxX = wrapperRect.x + wrapperRect.width;
987
+ var overshootMinY = wrapperRect.y;
988
+ var overshootMaxY = wrapperRect.y + wrapperRect.height;
989
+
990
+ // ✅ DRAG BOUNDS: Clamp ONLY to overshootBounds during drag (NOT strictBounds)
991
+ var dragX = Math.max(overshootMinX, Math.min(newX, overshootMaxX));
992
+ var dragY = Math.max(overshootMinY, Math.min(newY, overshootMaxY));
993
+
994
+ // ✅ UPDATE POINT: Use drag bounds (overshoot) - allows visual freedom
995
+ var updatedPoint = {
996
+ x: dragX,
997
+ y: dragY
998
+ };
999
+
1000
+ // ✅ CRITICAL: Detect if point is AT overshoot boundary (not just clamped)
1001
+ // Check if point is exactly at overshootMin/Max (within 1px tolerance)
1002
+ var isAtOvershootMinX = Math.abs(dragX - overshootMinX) < 1;
1003
+ var isAtOvershootMaxX = Math.abs(dragX - overshootMaxX) < 1;
1004
+ var isAtOvershootMinY = Math.abs(dragY - overshootMinY) < 1;
1005
+ var isAtOvershootMaxY = Math.abs(dragY - overshootMaxY) < 1;
1006
+ var isAtBoundaryX = isAtOvershootMinX || isAtOvershootMaxX;
1007
+ var isAtBoundaryY = isAtOvershootMinY || isAtOvershootMaxY;
1008
+
1009
+ // Only recalculate offset when FIRST hitting boundary (transition free → boundary)
1010
+ var justHitBoundaryX = isAtBoundaryX && !wasClampedLastFrame.current.x;
1011
+ var justHitBoundaryY = isAtBoundaryY && !wasClampedLastFrame.current.y;
1012
+ if (justHitBoundaryX || justHitBoundaryY) {
1013
+ // Point JUST hit overshoot boundary - recalculate offset once
1014
+ var newOffsetX = justHitBoundaryX ? dragX - currentX : touchOffset.current.x;
1015
+ var newOffsetY = justHitBoundaryY ? dragY - currentY : touchOffset.current.y;
1016
+ touchOffset.current = {
1017
+ x: newOffsetX,
1018
+ y: newOffsetY
1019
+ };
1020
+ console.log("✅ OFFSET RECALCULATED (hit boundary):", {
1021
+ axis: justHitBoundaryX ? 'X' : 'Y',
1022
+ touchY: currentY.toFixed(2),
1023
+ dragY: dragY.toFixed(2),
1024
+ newOffsetY: touchOffset.current.y.toFixed(2),
1025
+ note: "First contact with boundary - offset locked"
1026
+ });
1027
+ }
1028
+
1029
+ // Update boundary state for next frame
1030
+ wasClampedLastFrame.current = {
1031
+ x: isAtBoundaryX,
1032
+ y: isAtBoundaryY
1033
+ };
1034
+
1035
+ // ✅ DEBUG: Log when in overshoot zone (only when not at boundary)
1036
+ var isInOvershootY = dragY < strictMinY || dragY > strictMaxY;
1037
+ if (isInOvershootY && !isAtBoundaryY) {
1038
+ console.log("🎯 IN OVERSHOOT ZONE:", {
1039
+ touchY: currentY.toFixed(2),
1040
+ appliedY: dragY.toFixed(2),
1041
+ overshootRange: "".concat(overshootMinY.toFixed(2), " - ").concat(overshootMaxY.toFixed(2)),
1042
+ strictRange: "".concat(strictMinY.toFixed(2), " - ").concat(strictMaxY.toFixed(2))
1043
+ });
1044
+ }
1045
+
1046
+ // ✅ Update lastValidPosition ONLY if point is within strictBounds
1047
+ var isStrictlyValid = dragX >= strictMinX && dragX <= strictMaxX && dragY >= strictMinY && dragY <= strictMaxY;
1048
+ if (isStrictlyValid) {
1049
+ lastValidPosition.current = updatedPoint;
1050
+ }
1051
+
1052
+ // ✅ Update lastTouchPosition for next frame (simple tracking)
1053
+ lastTouchPosition.current = {
1054
+ x: currentX,
1055
+ y: currentY
1056
+ };
1057
+
1058
+ // ✅ DEBUG: Log the point update before setPoints
1059
+ console.log("📍 UPDATING POINT:", {
1060
+ index: selectedPointIndex.current,
1061
+ newX: updatedPoint.x.toFixed(2),
1062
+ newY: updatedPoint.y.toFixed(2),
1063
+ touchX: currentX.toFixed(2),
1064
+ touchY: currentY.toFixed(2),
1065
+ offsetX: touchOffset.current.x.toFixed(2),
1066
+ offsetY: touchOffset.current.y.toFixed(2)
1067
+ });
1068
+ setPoints(function (prev) {
1069
+ var _prev$pointIndex, _newPoints$pointIndex, _prev$pointIndex2, _newPoints$pointIndex2;
1070
+ // ✅ SAFETY: Ensure prev is a valid array
1071
+ if (!Array.isArray(prev) || prev.length === 0) {
1072
+ return prev;
1073
+ }
1074
+ var pointIndex = selectedPointIndex.current;
1075
+ // ✅ SAFETY: Validate pointIndex
1076
+ if (pointIndex === null || pointIndex === undefined || pointIndex < 0 || pointIndex >= prev.length) {
1077
+ return prev;
1078
+ }
1079
+
1080
+ // ✅ SAFETY: Filter out any invalid points and update the selected one
1081
+ var newPoints = prev.map(function (p, i) {
1082
+ if (i === pointIndex) {
1083
+ return updatedPoint;
1084
+ }
1085
+ // ✅ SAFETY: Ensure existing points are valid
1086
+ if (p && typeof p.x === 'number' && typeof p.y === 'number') {
1087
+ return p;
1088
+ }
1089
+ // If point is invalid, return a default point (shouldn't happen, but safety first)
1090
+ return {
1091
+ x: 0,
1092
+ y: 0
1093
+ };
1094
+ });
1095
+
1096
+ // ✅ DEBUG: Log the state update
1097
+ console.log("✅ STATE UPDATED:", {
1098
+ index: pointIndex,
1099
+ oldY: (_prev$pointIndex = prev[pointIndex]) === null || _prev$pointIndex === void 0 ? void 0 : _prev$pointIndex.y.toFixed(2),
1100
+ newY: (_newPoints$pointIndex = newPoints[pointIndex]) === null || _newPoints$pointIndex === void 0 ? void 0 : _newPoints$pointIndex.y.toFixed(2),
1101
+ changed: Math.abs(((_prev$pointIndex2 = prev[pointIndex]) === null || _prev$pointIndex2 === void 0 ? void 0 : _prev$pointIndex2.y) - ((_newPoints$pointIndex2 = newPoints[pointIndex]) === null || _newPoints$pointIndex2 === void 0 ? void 0 : _newPoints$pointIndex2.y)) > 0.01
1102
+ });
1103
+ return newPoints;
1104
+ });
1105
+ };
1106
+ var handleRelease = function handleRelease() {
1107
+ var wasDragging = selectedPointIndex.current !== null;
1108
+
1109
+ // ✅ CRITICAL: Reset drag state when drag ends
1110
+ touchOffset.current = null;
1111
+ wasClampedLastFrame.current = {
1112
+ x: false,
1113
+ y: false
1114
+ };
1115
+
1116
+ // ✅ VISUAL OVERSHOOT: Clamp points back to imageDisplayRect when drag ends
1117
+ // This ensures final crop is always within valid image bounds
1118
+ if (wasDragging && selectedPointIndex.current !== null) {
1119
+ var wrapper = commonWrapperLayout.current;
1120
+ var imageRect = imageDisplayRect.current;
1121
+
1122
+ // Recalculate imageDisplayRect if needed
1123
+ if (imageRect.width === 0 || imageRect.height === 0) {
1124
+ if (wrapper.width > 0 && wrapper.height > 0 && originalImageDimensions.current.width > 0) {
1125
+ updateImageDisplayRect(wrapper.width, wrapper.height);
1126
+ imageRect = imageDisplayRect.current;
1127
+ }
1128
+ }
1129
+ if (imageRect.width > 0 && imageRect.height > 0) {
1130
+ var imageRectX = wrapper.x + imageRect.x;
1131
+ var imageRectY = wrapper.y + imageRect.y;
1132
+ var imageRectMaxX = imageRectX + imageRect.width;
1133
+ var imageRectMaxY = imageRectY + imageRect.height;
1134
+
1135
+ // Clamp the dragged point back to strict image bounds
1136
+ setPoints(function (prev) {
1137
+ // ✅ SAFETY: Ensure prev is a valid array
1138
+ if (!Array.isArray(prev) || prev.length === 0) {
1139
+ return prev;
1140
+ }
1141
+ var pointIndex = selectedPointIndex.current;
1142
+ // ✅ SAFETY: Validate pointIndex and ensure point exists
1143
+ if (pointIndex === null || pointIndex === undefined || pointIndex < 0 || pointIndex >= prev.length) {
1144
+ return prev;
1145
+ }
1146
+ var point = prev[pointIndex];
1147
+ // ✅ SAFETY: Ensure point exists and has valid x/y properties
1148
+ if (!point || typeof point.x !== 'number' || typeof point.y !== 'number') {
1149
+ return prev;
1150
+ }
1151
+ var clampedPoint = {
1152
+ x: Math.max(imageRectX, Math.min(point.x, imageRectMaxX)),
1153
+ y: Math.max(imageRectY, Math.min(point.y, imageRectMaxY))
1154
+ };
1155
+
1156
+ // Only update if point was outside bounds
1157
+ if (point.x !== clampedPoint.x || point.y !== clampedPoint.y) {
1158
+ console.log("🔒 Clamping point back to image bounds on release:", {
1159
+ before: {
1160
+ x: point.x.toFixed(2),
1161
+ y: point.y.toFixed(2)
1162
+ },
1163
+ after: {
1164
+ x: clampedPoint.x.toFixed(2),
1165
+ y: clampedPoint.y.toFixed(2)
1166
+ },
1167
+ bounds: {
1168
+ minX: imageRectX.toFixed(2),
1169
+ maxX: imageRectMaxX.toFixed(2),
1170
+ minY: imageRectY.toFixed(2),
1171
+ maxY: imageRectMaxY.toFixed(2)
1172
+ }
1173
+ });
1174
+ return prev.map(function (p, i) {
1175
+ return i === pointIndex ? clampedPoint : p;
1176
+ });
1177
+ }
1178
+ return prev;
1179
+ });
1180
+ }
1181
+ }
1182
+
1183
+ // ✅ FREE DRAG: Clear initial positions when drag ends
1184
+ initialTouchPosition.current = null;
1185
+ initialPointPosition.current = null;
1186
+ lastValidPosition.current = null;
1187
+ selectedPointIndex.current = null;
1188
+
1189
+ // ✅ CRITICAL: Re-enable parent ScrollView scrolling when drag ends
1190
+ if (wasDragging) {
1191
+ try {
1192
+ // Re-enable scrolling after a short delay to avoid conflicts
1193
+ setTimeout(function () {
1194
+ // ScrollView will be re-enabled automatically when responder is released
1195
+ }, 100);
1196
+ } catch (e) {
1197
+ // Ignore errors
1198
+ }
1199
+ }
1200
+ };
1201
+ var handleReset = function handleReset() {
1202
+ // setPoints([]);
1203
+ hasInitializedCropBox.current = false; // ✅ CRITICAL: Reset guard to allow reinitialization
1204
+ initializeCropBox();
1205
+ };
1206
+
1207
+ // ✅ REFACTORISATION : Stocker l'angle de rotation au lieu de modifier l'image immédiatement
1208
+ // La rotation sera appliquée uniquement lors du crop final pour éviter les interpolations multiples
1209
+ var rotatePreviewImage = /*#__PURE__*/function () {
1210
+ var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(degrees) {
1211
+ var rotated, _t;
1212
+ return _regenerator().w(function (_context) {
1213
+ while (1) switch (_context.p = _context.n) {
1214
+ case 0:
1215
+ if (!(!image || isRotating)) {
1216
+ _context.n = 1;
1217
+ break;
1218
+ }
1219
+ return _context.a(2);
1220
+ case 1:
1221
+ setIsRotating(true);
1222
+ _context.p = 2;
1223
+ // ✅ CORRECTION : appliquer la rotation de façon incrémentale sur le fichier (pas cumulée sur un fichier déjà roté).
1224
+ // L'ancienne version rotait l'image déjà rotée par l'angle total, ce qui donnait des rotations incorrectes (90 + 180 => 270).
1225
+ rotationAngle.current = (rotationAngle.current + degrees) % 360;
1226
+ _context.n = 3;
1227
+ return ImageManipulator.manipulateAsync(image, [{
1228
+ rotate: degrees
1229
+ }], {
1230
+ compress: 1,
1231
+ // Qualité maximale (pas de compression)
1232
+ format: ImageManipulator.SaveFormat.PNG // Format sans perte
1233
+ });
1234
+ case 3:
1235
+ rotated = _context.v;
1236
+ // Update image pour l'aperçu - onImageLayout will call initializeCropBox automatically
1237
+ setImage(rotated.uri);
1238
+ console.log("Rotation applied (preview increment):", degrees, "degrees; accumulated:", rotationAngle.current);
1239
+ _context.n = 5;
1240
+ break;
1241
+ case 4:
1242
+ _context.p = 4;
1243
+ _t = _context.v;
1244
+ console.error("Error rotating image:", _t);
1245
+ alert("Error rotating image");
1246
+ case 5:
1247
+ _context.p = 5;
1248
+ setIsRotating(false);
1249
+ return _context.f(5);
1250
+ case 6:
1251
+ return _context.a(2);
1252
+ }
1253
+ }, _callee, null, [[2, 4, 5, 6]]);
1254
+ }));
1255
+ return function rotatePreviewImage(_x) {
1256
+ return _ref2.apply(this, arguments);
1257
+ };
1258
+ }();
1259
+
1260
+ // Helper function to wait for multiple render cycles (works on iOS)
1261
+ var waitForRender = function waitForRender() {
1262
+ var cycles = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 5;
1263
+ return new Promise(function (resolve) {
1264
+ var count = 0;
1265
+ var _tick = function tick() {
1266
+ count++;
1267
+ if (count >= cycles) {
1268
+ resolve();
1269
+ } else {
1270
+ setImmediate(_tick);
1271
+ }
1272
+ };
1273
+ setImmediate(_tick);
1274
+ });
1275
+ };
1276
+ return /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1277
+ style: _ImageCropperStyles["default"].container
1278
+ }, showCustomCamera ? /*#__PURE__*/_react["default"].createElement(_CustomCamera["default"], {
1279
+ onPhotoCaptured: function onPhotoCaptured(uri, frameData) {
1280
+ // ✅ CRITICAL FIX: Store green frame coordinates for coordinate conversion
1281
+ if (frameData && frameData.greenFrame) {
1282
+ cameraFrameData.current = {
1283
+ greenFrame: frameData.greenFrame,
1284
+ capturedImageSize: frameData.capturedImageSize
1285
+ };
1286
+ // ✅ CRITICAL: Set imageSource to 'camera' IMMEDIATELY to prevent Image.getSize() from being called
1287
+ imageSource.current = 'camera';
1288
+ hasInitializedCropBox.current = false; // Reset guard for new camera image
1289
+ console.log("✅ Camera frame data received:", cameraFrameData.current);
1290
+ }
1291
+ setImage(uri);
1292
+ setShowCustomCamera(false);
1293
+ // ✅ CORRECTION : Réinitialiser les points et l'angle de rotation quand une nouvelle photo est capturée
1294
+ setPoints([]);
1295
+ rotationAngle.current = 0;
1296
+ // ✅ CRITICAL: initializeCropBox will be called automatically when image layout is ready
1297
+ // The green frame coordinates are stored in cameraFrameData.current and will be used
1298
+ },
1299
+ onCancel: function onCancel() {
1300
+ return setShowCustomCamera(false);
1301
+ }
1302
+ }) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, !showResult && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1303
+ style: image ? _ImageCropperStyles["default"].buttonContainer : _ImageCropperStyles["default"].centerButtonsContainer
1304
+ }, image && _reactNative.Platform.OS === 'android' && /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1305
+ style: _ImageCropperStyles["default"].iconButton,
1306
+ onPress: function onPress() {
1307
+ return enableRotation && rotatePreviewImage(90);
1308
+ },
1309
+ disabled: isRotating
1310
+ }, /*#__PURE__*/_react["default"].createElement(_vectorIcons.Ionicons, {
1311
+ name: "sync",
1312
+ size: 24,
1313
+ color: "white"
1314
+ }))), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1315
+ style: _ImageCropperStyles["default"].button,
1316
+ onPress: handleReset
1317
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
1318
+ style: _ImageCropperStyles["default"].buttonText
1319
+ }, "Reset")), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1320
+ style: _ImageCropperStyles["default"].button,
1321
+ onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
1322
+ var actualImageWidth, actualImageHeight, layout, contentRect, displayedWidth, displayedHeight, isCoverMode, scale, coverOffsetX, coverOffsetY, scaledWidth, scaledHeight, scaleX, scaleY, originalUri, cropMeta, imagePoints, minX, minY, maxX, maxY, cropX, cropY, cropEndX, cropEndY, cropWidth, cropHeight, bbox, polygon, name, _t2;
1323
+ return _regenerator().w(function (_context2) {
1324
+ while (1) switch (_context2.p = _context2.n) {
1325
+ case 0:
1326
+ setIsLoading(true);
1327
+ _context2.p = 1;
1328
+ console.log("=== Starting pixel-perfect metadata export (no bitmap crop on mobile) ===");
1329
+
1330
+ // ✅ REFACTORISATION : Utiliser les dimensions stockées (plus efficace)
1331
+ actualImageWidth = originalImageDimensions.current.width;
1332
+ actualImageHeight = originalImageDimensions.current.height; // Vérifier que les dimensions sont valides
1333
+ if (!(actualImageWidth === 0 || actualImageHeight === 0)) {
1334
+ _context2.n = 2;
1335
+ break;
1336
+ }
1337
+ throw new Error("Original image dimensions not available. Please wait for image to load.");
1338
+ 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
+ layout = displayedImageLayout.current;
1347
+ console.log("🔍 Checking displayedContentRect before crop:", {
1348
+ displayedContentRect: displayedContentRect.current,
1349
+ displayedImageLayout: layout,
1350
+ originalImageDimensions: originalImageDimensions.current
1351
+ });
1352
+ if (layout.width > 0 && layout.height > 0) {
1353
+ updateDisplayedContentRect(layout.width, layout.height);
1354
+ }
1355
+ contentRect = displayedContentRect.current;
1356
+ displayedWidth = contentRect.width;
1357
+ displayedHeight = contentRect.height; // Vérifier que les dimensions d'affichage sont valides
1358
+ if (!(displayedWidth === 0 || displayedHeight === 0)) {
1359
+ _context2.n = 4;
1360
+ break;
1361
+ }
1362
+ if (!(layout.width > 0 && layout.height > 0)) {
1363
+ _context2.n = 3;
1364
+ break;
1365
+ }
1366
+ console.warn("⚠️ displayedContentRect not available, using displayedImageLayout as fallback");
1367
+ // Use layout dimensions as fallback (assuming no letterboxing)
1368
+ contentRect = {
1369
+ x: layout.x,
1370
+ y: layout.y,
1371
+ width: layout.width,
1372
+ height: layout.height
1373
+ };
1374
+ displayedWidth = contentRect.width;
1375
+ displayedHeight = contentRect.height;
1376
+ // Update the ref for consistency
1377
+ displayedContentRect.current = contentRect;
1378
+ _context2.n = 4;
1379
+ break;
1380
+ case 3:
1381
+ throw new Error("Displayed image dimensions not available. Image may not be laid out yet. Please wait a moment and try again.");
1382
+ 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
+ isCoverMode = !!(cameraFrameData.current && cameraFrameData.current.greenFrame);
1392
+ coverOffsetX = 0, coverOffsetY = 0;
1393
+ if (isCoverMode) {
1394
+ scale = Math.max(displayedWidth / actualImageWidth, displayedHeight / actualImageHeight);
1395
+ scaledWidth = actualImageWidth * scale;
1396
+ scaledHeight = actualImageHeight * scale;
1397
+ coverOffsetX = (scaledWidth - displayedWidth) / 2;
1398
+ coverOffsetY = (scaledHeight - displayedHeight) / 2;
1399
+ console.log("Scale factor (COVER mode for camera):", {
1400
+ scale: scale,
1401
+ coverOffsetX: coverOffsetX,
1402
+ coverOffsetY: coverOffsetY
1403
+ });
1404
+ } else {
1405
+ scaleX = actualImageWidth / displayedWidth;
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
+ });
1422
+ }
1423
+ originalUri = sourceImageUri.current || image;
1424
+ cropMeta = null;
1425
+ if (points.length > 0) {
1426
+ 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
+ imagePoints = points.map(function (point) {
1433
+ var clampedX, clampedY, origX, origY;
1434
+ if (isCoverMode) {
1435
+ // Cover: display = wrapper, scale = max, image centered with offset
1436
+ clampedX = Math.max(0, Math.min(point.x, contentRect.width));
1437
+ clampedY = Math.max(0, Math.min(point.y, contentRect.height));
1438
+ origX = (clampedX + coverOffsetX) / scale;
1439
+ origY = (clampedY + coverOffsetY) / scale;
1440
+ } else {
1441
+ clampedX = Math.max(contentRect.x, Math.min(point.x, contentRect.x + contentRect.width));
1442
+ clampedY = Math.max(contentRect.y, Math.min(point.y, contentRect.y + contentRect.height));
1443
+ origX = (clampedX - contentRect.x) * scale;
1444
+ origY = (clampedY - contentRect.y) * scale;
1445
+ }
1446
+ var finalX = Math.max(0, Math.min(origX, actualImageWidth));
1447
+ var finalY = Math.max(0, Math.min(origY, actualImageHeight));
1448
+ return {
1449
+ x: finalX,
1450
+ y: finalY
1451
+ };
1452
+ });
1453
+ console.log("Converted image points (original coordinates):", imagePoints);
1454
+
1455
+ // Calculer la bounding box : min X, min Y, max X, max Y
1456
+ minX = Math.min.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
1457
+ return p.x;
1458
+ })));
1459
+ minY = Math.min.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
1460
+ return p.y;
1461
+ })));
1462
+ maxX = Math.max.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
1463
+ return p.x;
1464
+ })));
1465
+ maxY = Math.max.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
1466
+ return p.y;
1467
+ }))); // ✅ CORRECTION : arrondi "conservateur" (floor origin + ceil end)
1468
+ // évite de rogner des pixels et réduit le risque de crop plus petit (perte de détails).
1469
+ cropX = Math.max(0, Math.floor(minX));
1470
+ cropY = Math.max(0, Math.floor(minY));
1471
+ cropEndX = Math.min(actualImageWidth, Math.ceil(maxX));
1472
+ cropEndY = Math.min(actualImageHeight, Math.ceil(maxY));
1473
+ cropWidth = Math.max(0, cropEndX - cropX);
1474
+ 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
+ if (cropWidth > 0 && cropHeight > 0) {
1490
+ // 1) bbox in ORIGINAL image pixel coords
1491
+ bbox = {
1492
+ x: cropX,
1493
+ y: cropY,
1494
+ width: cropWidth,
1495
+ height: cropHeight
1496
+ }; // 2) polygon points relative to bbox (still in ORIGINAL pixel grid)
1497
+ polygon = imagePoints.map(function (point) {
1498
+ return {
1499
+ x: point.x - cropX,
1500
+ y: point.y - cropY
1501
+ };
1502
+ });
1503
+ cropMeta = {
1504
+ bbox: bbox,
1505
+ polygon: polygon,
1506
+ rotation: 0,
1507
+ imageSize: {
1508
+ width: actualImageWidth,
1509
+ height: actualImageHeight
1510
+ }
1511
+ };
1512
+ console.log("Crop meta ready:", cropMeta);
1513
+ } else {
1514
+ console.warn("Invalid crop dimensions, cannot export crop meta");
1515
+ }
1516
+ } catch (cropError) {
1517
+ console.error("Error computing crop meta:", cropError);
1518
+ }
1519
+ } else {
1520
+ console.log("No crop points defined, using original image");
1521
+ }
1522
+ name = "IMAGE XTK".concat(Date.now());
1523
+ if (onConfirm) {
1524
+ console.log("Calling onConfirm with:", originalUri, name, cropMeta);
1525
+ onConfirm(originalUri, name, cropMeta);
1526
+ }
1527
+ _context2.n = 6;
1528
+ break;
1529
+ case 5:
1530
+ _context2.p = 5;
1531
+ _t2 = _context2.v;
1532
+ console.error("Erreur lors du crop :", _t2);
1533
+ alert("Erreur lors du crop ! " + _t2.message);
1534
+ case 6:
1535
+ _context2.p = 6;
1536
+ setShowResult(false);
1537
+ setIsLoading(false);
1538
+ setShowFullScreenCapture(false);
1539
+ return _context2.f(6);
1540
+ case 7:
1541
+ return _context2.a(2);
1542
+ }
1543
+ }, _callee2, null, [[1, 5, 6, 7]]);
1544
+ }))
1545
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
1546
+ style: _ImageCropperStyles["default"].buttonText
1547
+ }, "Confirm"))), image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1548
+ style: {
1549
+ width: _reactNative.Dimensions.get('window').width,
1550
+ aspectRatio: 9 / 16,
1551
+ borderRadius: 30,
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, {
1668
+ style: {
1669
+ position: 'absolute',
1670
+ left: -maskDimensions.width - 100,
1671
+ // Hors écran mais pas trop loin
1672
+ top: -maskDimensions.height - 100,
1673
+ width: maskDimensions.width,
1674
+ height: maskDimensions.height,
1675
+ opacity: 1,
1676
+ // Opacité normale pour la capture
1677
+ pointerEvents: 'none',
1678
+ zIndex: 9999,
1679
+ // Z-index élevé pour s'assurer qu'elle est au-dessus
1680
+ overflow: 'hidden'
1681
+ },
1682
+ collapsable: false
1683
+ }, /*#__PURE__*/_react["default"].createElement(_ImageMaskProcessor.MaskView, {
1684
+ ref: maskViewRef,
1685
+ imageUri: maskImageUri,
1686
+ points: maskPoints,
1687
+ width: maskDimensions.width,
1688
+ height: maskDimensions.height
1689
+ })), /*#__PURE__*/_react["default"].createElement(_reactNative.Modal, {
1690
+ visible: isLoading,
1691
+ transparent: true,
1692
+ animationType: "fade"
1693
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1694
+ style: _ImageCropperStyles["default"].loadingOverlay
1695
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
1696
+ source: require('../src/assets/loadingCamera.gif'),
1697
+ style: _ImageCropperStyles["default"].loadingGif,
1698
+ resizeMode: "contain"
1699
+ }))));
1700
+ };
514
1701
  var _default = exports["default"] = ImageCropper;