react-native-expo-cropper 1.0.31 → 1.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CustomCamera.js +24 -56
- package/dist/ImageCropper.js +86 -23
- package/dist/ImageCropperStyles.js +42 -1
- package/dist/ImageProcessor.js +6 -11
- package/package.json +3 -2
- package/src/CustomCamera.js +18 -38
- package/src/ImageCropper.js +329 -289
- package/src/ImageCropperStyles.js +41 -0
- package/src/ImageProcessor.js +27 -37
package/dist/CustomCamera.js
CHANGED
|
@@ -9,8 +9,7 @@ var _react = _interopRequireWildcard(require("react"));
|
|
|
9
9
|
var _reactNative = require("react-native");
|
|
10
10
|
var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
|
|
11
11
|
var _expoCamera = require("expo-camera");
|
|
12
|
-
var
|
|
13
|
-
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); }
|
|
12
|
+
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 _t2 in e) "default" !== _t2 && {}.hasOwnProperty.call(e, _t2) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t2)) && (i.get || i.set) ? o(f, _t2, i) : f[_t2] = e[_t2]); return f; })(e, t); }
|
|
14
13
|
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; }
|
|
15
14
|
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; }
|
|
16
15
|
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; }
|
|
@@ -78,84 +77,53 @@ function CustomCamera(_ref) {
|
|
|
78
77
|
setImmediate(_tick);
|
|
79
78
|
});
|
|
80
79
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
var _ref3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(uri) {
|
|
85
|
-
var fixedImage, _t;
|
|
80
|
+
var takePicture = /*#__PURE__*/function () {
|
|
81
|
+
var _ref3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
82
|
+
var photo, fixedUri, _t;
|
|
86
83
|
return _regenerator().w(function (_context2) {
|
|
87
84
|
while (1) switch (_context2.p = _context2.n) {
|
|
88
|
-
case 0:
|
|
89
|
-
_context2.p = 0;
|
|
90
|
-
_context2.n = 1;
|
|
91
|
-
return ImageManipulator.manipulateAsync(uri, [],
|
|
92
|
-
// Empty array = apply EXIF orientation automatically
|
|
93
|
-
{
|
|
94
|
-
compress: 1,
|
|
95
|
-
format: ImageManipulator.SaveFormat.PNG
|
|
96
|
-
});
|
|
97
|
-
case 1:
|
|
98
|
-
fixedImage = _context2.v;
|
|
99
|
-
return _context2.a(2, fixedImage.uri);
|
|
100
|
-
case 2:
|
|
101
|
-
_context2.p = 2;
|
|
102
|
-
_t = _context2.v;
|
|
103
|
-
console.error("Error fixing image orientation:", _t);
|
|
104
|
-
return _context2.a(2, uri);
|
|
105
|
-
}
|
|
106
|
-
}, _callee2, null, [[0, 2]]);
|
|
107
|
-
}));
|
|
108
|
-
return function fixImageOrientation(_x) {
|
|
109
|
-
return _ref3.apply(this, arguments);
|
|
110
|
-
};
|
|
111
|
-
}();
|
|
112
|
-
var takePicture = /*#__PURE__*/function () {
|
|
113
|
-
var _ref4 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
|
|
114
|
-
var photo, fixedUri, _t2;
|
|
115
|
-
return _regenerator().w(function (_context3) {
|
|
116
|
-
while (1) switch (_context3.p = _context3.n) {
|
|
117
85
|
case 0:
|
|
118
86
|
if (!cameraRef.current) {
|
|
119
|
-
|
|
87
|
+
_context2.n = 5;
|
|
120
88
|
break;
|
|
121
89
|
}
|
|
122
|
-
|
|
90
|
+
_context2.p = 1;
|
|
123
91
|
// Show loading after a delay (using setImmediate for iOS compatibility)
|
|
124
|
-
waitForRender(
|
|
92
|
+
waitForRender(5).then(function () {
|
|
125
93
|
setLoadingBeforeCapture(true);
|
|
126
94
|
});
|
|
127
95
|
|
|
128
96
|
// Wait a bit before taking picture (works on iOS)
|
|
129
|
-
|
|
130
|
-
return waitForRender(
|
|
97
|
+
_context2.n = 2;
|
|
98
|
+
return waitForRender(2);
|
|
131
99
|
case 2:
|
|
132
|
-
|
|
100
|
+
_context2.n = 3;
|
|
133
101
|
return cameraRef.current.takePictureAsync({
|
|
134
102
|
quality: 1,
|
|
135
|
-
shutterSound: false
|
|
103
|
+
shutterSound: false,
|
|
104
|
+
skipProcessing: false,
|
|
105
|
+
exif: true // Include EXIF data for orientation
|
|
136
106
|
});
|
|
137
107
|
case 3:
|
|
138
|
-
photo =
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
case 4:
|
|
142
|
-
fixedUri = _context3.v;
|
|
108
|
+
photo = _context2.v;
|
|
109
|
+
// Fix orientation on Android (iOS handles it automatically)
|
|
110
|
+
fixedUri = photo.uri; // Go directly to ImageCropper (preview will be shown there on Android)
|
|
143
111
|
onPhotoCaptured(fixedUri);
|
|
144
112
|
setLoadingBeforeCapture(false);
|
|
145
|
-
|
|
113
|
+
_context2.n = 5;
|
|
146
114
|
break;
|
|
147
|
-
case
|
|
148
|
-
|
|
149
|
-
|
|
115
|
+
case 4:
|
|
116
|
+
_context2.p = 4;
|
|
117
|
+
_t = _context2.v;
|
|
150
118
|
setLoadingBeforeCapture(false);
|
|
151
119
|
_reactNative.Alert.alert("Erreur");
|
|
152
|
-
case
|
|
153
|
-
return
|
|
120
|
+
case 5:
|
|
121
|
+
return _context2.a(2);
|
|
154
122
|
}
|
|
155
|
-
},
|
|
123
|
+
}, _callee2, null, [[1, 4]]);
|
|
156
124
|
}));
|
|
157
125
|
return function takePicture() {
|
|
158
|
-
return
|
|
126
|
+
return _ref3.apply(this, arguments);
|
|
159
127
|
};
|
|
160
128
|
}();
|
|
161
129
|
return /*#__PURE__*/_react["default"].createElement(_reactNative.SafeAreaView, {
|
package/dist/ImageCropper.js
CHANGED
|
@@ -12,7 +12,10 @@ var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
|
|
|
12
12
|
var _reactNativeViewShot = require("react-native-view-shot");
|
|
13
13
|
var _CustomCamera = _interopRequireDefault(require("./CustomCamera"));
|
|
14
14
|
var _ImageProcessor = require("./ImageProcessor");
|
|
15
|
-
|
|
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 _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); }
|
|
16
19
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
|
|
17
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 }; })(); }
|
|
18
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); }
|
|
@@ -71,7 +74,12 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
71
74
|
_useState10 = _slicedToArray(_useState1, 2),
|
|
72
75
|
showFullScreenCapture = _useState10[0],
|
|
73
76
|
setShowFullScreenCapture = _useState10[1];
|
|
77
|
+
var _useState11 = (0, _react.useState)(false),
|
|
78
|
+
_useState12 = _slicedToArray(_useState11, 2),
|
|
79
|
+
isRotating = _useState12[0],
|
|
80
|
+
setIsRotating = _useState12[1];
|
|
74
81
|
var lastValidPosition = (0, _react.useRef)(null);
|
|
82
|
+
var insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
|
|
75
83
|
(0, _react.useEffect)(function () {
|
|
76
84
|
if (openCameraFirst) {
|
|
77
85
|
setShowCustomCamera(true);
|
|
@@ -208,6 +216,51 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
208
216
|
// setPoints([]);
|
|
209
217
|
initializeCropBox();
|
|
210
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
|
+
}();
|
|
211
264
|
|
|
212
265
|
// Helper function to wait for multiple render cycles (works on iOS)
|
|
213
266
|
var waitForRender = function waitForRender() {
|
|
@@ -237,68 +290,78 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
237
290
|
}
|
|
238
291
|
}) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, !showResult && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
239
292
|
style: image ? _ImageCropperStyles["default"].buttonContainer : _ImageCropperStyles["default"].centerButtonsContainer
|
|
240
|
-
}, image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
|
|
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, {
|
|
241
304
|
style: _ImageCropperStyles["default"].button,
|
|
242
305
|
onPress: handleReset
|
|
243
306
|
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
|
|
244
307
|
style: _ImageCropperStyles["default"].buttonText
|
|
245
308
|
}, "Reset")), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
|
|
246
309
|
style: _ImageCropperStyles["default"].button,
|
|
247
|
-
onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function
|
|
248
|
-
var capturedUri, enhancedUri, name,
|
|
249
|
-
return _regenerator().w(function (
|
|
250
|
-
while (1) switch (
|
|
310
|
+
onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
311
|
+
var capturedUri, enhancedUri, name, _t2;
|
|
312
|
+
return _regenerator().w(function (_context2) {
|
|
313
|
+
while (1) switch (_context2.p = _context2.n) {
|
|
251
314
|
case 0:
|
|
252
315
|
// setShowFullScreenCapture(true);
|
|
253
316
|
setIsLoading(true);
|
|
254
317
|
setShowResult(true);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
return waitForRender(
|
|
318
|
+
_context2.p = 1;
|
|
319
|
+
_context2.n = 2;
|
|
320
|
+
return waitForRender(5);
|
|
258
321
|
case 2:
|
|
259
322
|
console.log("Starting capture...");
|
|
260
|
-
|
|
323
|
+
_context2.n = 3;
|
|
261
324
|
return (0, _reactNativeViewShot.captureRef)(viewRef, {
|
|
262
325
|
format: 'png',
|
|
263
326
|
quality: 1
|
|
264
327
|
});
|
|
265
328
|
case 3:
|
|
266
|
-
capturedUri =
|
|
329
|
+
capturedUri = _context2.v;
|
|
267
330
|
console.log("Capture successful:", capturedUri);
|
|
268
331
|
if (capturedUri) {
|
|
269
|
-
|
|
332
|
+
_context2.n = 4;
|
|
270
333
|
break;
|
|
271
334
|
}
|
|
272
335
|
throw new Error("Capture returned empty URI");
|
|
273
336
|
case 4:
|
|
274
337
|
console.log("Enhancing image...");
|
|
275
|
-
|
|
338
|
+
_context2.n = 5;
|
|
276
339
|
return (0, _ImageProcessor.enhanceImage)(capturedUri, addheight);
|
|
277
340
|
case 5:
|
|
278
|
-
enhancedUri =
|
|
341
|
+
enhancedUri = _context2.v;
|
|
279
342
|
console.log("Image enhanced:", enhancedUri);
|
|
280
343
|
name = "IMAGE XTK".concat(Date.now(), ".png");
|
|
281
344
|
if (onConfirm) {
|
|
282
345
|
console.log("Calling onConfirm with:", enhancedUri, name);
|
|
283
346
|
onConfirm(enhancedUri, name);
|
|
284
347
|
}
|
|
285
|
-
|
|
348
|
+
_context2.n = 7;
|
|
286
349
|
break;
|
|
287
350
|
case 6:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
console.error("Erreur lors de la capture :",
|
|
291
|
-
alert("Erreur lors de la capture ! " +
|
|
351
|
+
_context2.p = 6;
|
|
352
|
+
_t2 = _context2.v;
|
|
353
|
+
console.error("Erreur lors de la capture :", _t2);
|
|
354
|
+
alert("Erreur lors de la capture ! " + _t2.message);
|
|
292
355
|
case 7:
|
|
293
|
-
|
|
356
|
+
_context2.p = 7;
|
|
294
357
|
setShowResult(false);
|
|
295
358
|
setIsLoading(false);
|
|
296
359
|
setShowFullScreenCapture(false);
|
|
297
|
-
return
|
|
360
|
+
return _context2.f(7);
|
|
298
361
|
case 8:
|
|
299
|
-
return
|
|
362
|
+
return _context2.a(2);
|
|
300
363
|
}
|
|
301
|
-
},
|
|
364
|
+
}, _callee2, null, [[1, 6, 7, 8]]);
|
|
302
365
|
}))
|
|
303
366
|
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
|
|
304
367
|
style: _ImageCropperStyles["default"].buttonText
|
|
@@ -29,7 +29,17 @@ var styles = _reactNative.StyleSheet.create({
|
|
|
29
29
|
justifyContent: 'center',
|
|
30
30
|
alignItems: 'center',
|
|
31
31
|
paddingHorizontal: 10,
|
|
32
|
-
zIndex: 10
|
|
32
|
+
zIndex: 10,
|
|
33
|
+
gap: 10
|
|
34
|
+
},
|
|
35
|
+
iconButton: {
|
|
36
|
+
backgroundColor: PRIMARY_GREEN,
|
|
37
|
+
width: 60,
|
|
38
|
+
height: 60,
|
|
39
|
+
borderRadius: 30,
|
|
40
|
+
alignItems: 'center',
|
|
41
|
+
justifyContent: 'center',
|
|
42
|
+
marginRight: 5
|
|
33
43
|
},
|
|
34
44
|
button: {
|
|
35
45
|
flex: 1,
|
|
@@ -172,6 +182,37 @@ var styles = _reactNative.StyleSheet.create({
|
|
|
172
182
|
justifyContent: 'center',
|
|
173
183
|
alignItems: 'center',
|
|
174
184
|
zIndex: 9999
|
|
185
|
+
},
|
|
186
|
+
previewContainer: {
|
|
187
|
+
flex: 1,
|
|
188
|
+
backgroundColor: DEEP_BLACK,
|
|
189
|
+
justifyContent: 'center',
|
|
190
|
+
alignItems: 'center',
|
|
191
|
+
width: '100%'
|
|
192
|
+
},
|
|
193
|
+
previewImage: {
|
|
194
|
+
width: SCREEN_WIDTH,
|
|
195
|
+
height: '80%'
|
|
196
|
+
},
|
|
197
|
+
previewButtonContainer: {
|
|
198
|
+
position: 'absolute',
|
|
199
|
+
bottom: 50,
|
|
200
|
+
left: 0,
|
|
201
|
+
right: 0,
|
|
202
|
+
flexDirection: 'row',
|
|
203
|
+
justifyContent: 'space-around',
|
|
204
|
+
paddingHorizontal: 20
|
|
205
|
+
},
|
|
206
|
+
previewButton: {
|
|
207
|
+
backgroundColor: PRIMARY_GREEN,
|
|
208
|
+
width: 60,
|
|
209
|
+
height: 60,
|
|
210
|
+
borderRadius: 30,
|
|
211
|
+
alignItems: 'center',
|
|
212
|
+
justifyContent: 'center'
|
|
213
|
+
},
|
|
214
|
+
useButton: {
|
|
215
|
+
backgroundColor: GLOW_WHITE
|
|
175
216
|
}
|
|
176
217
|
});
|
|
177
218
|
var _default = exports["default"] = styles;
|
package/dist/ImageProcessor.js
CHANGED
|
@@ -13,26 +13,21 @@ function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.
|
|
|
13
13
|
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); }); }; }
|
|
14
14
|
var enhanceImage = exports.enhanceImage = /*#__PURE__*/function () {
|
|
15
15
|
var _ref = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(uri, addheight) {
|
|
16
|
-
var
|
|
16
|
+
var imageInfo, ratio, maxHeight, newWidth, newHeight, result, _t;
|
|
17
17
|
return _regenerator().w(function (_context) {
|
|
18
18
|
while (1) switch (_context.p = _context.n) {
|
|
19
19
|
case 0:
|
|
20
20
|
_context.p = 0;
|
|
21
21
|
_context.n = 1;
|
|
22
|
-
return ImageManipulator.manipulateAsync(uri, []
|
|
23
|
-
compress: 1,
|
|
24
|
-
format: ImageManipulator.SaveFormat.PNG
|
|
25
|
-
});
|
|
22
|
+
return ImageManipulator.manipulateAsync(uri, []);
|
|
26
23
|
case 1:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// The width and height properties reflect the actual image dimensions after orientation fix
|
|
30
|
-
ratio = fixedOrientationImage.height / fixedOrientationImage.width;
|
|
24
|
+
imageInfo = _context.v;
|
|
25
|
+
ratio = imageInfo.height / imageInfo.width;
|
|
31
26
|
maxHeight = addheight;
|
|
32
27
|
newWidth = Math.round(maxHeight / ratio);
|
|
33
|
-
newHeight = Math.round(newWidth * ratio);
|
|
28
|
+
newHeight = Math.round(newWidth * ratio);
|
|
34
29
|
_context.n = 2;
|
|
35
|
-
return ImageManipulator.manipulateAsync(
|
|
30
|
+
return ImageManipulator.manipulateAsync(uri, [{
|
|
36
31
|
resize: {
|
|
37
32
|
width: newWidth,
|
|
38
33
|
height: newHeight
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-expo-cropper",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.31",
|
|
4
4
|
"description": "Recadrage polygonal d'images.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "PCS AGRI",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"@babel/cli": "^7.28.3",
|
|
50
50
|
"@babel/core": "^7.28.5",
|
|
51
51
|
"@babel/preset-env": "^7.28.5",
|
|
52
|
-
"@babel/preset-react": "^7.28.5"
|
|
52
|
+
"@babel/preset-react": "^7.28.5",
|
|
53
|
+
"baseline-browser-mapping": "^2.9.6"
|
|
53
54
|
}
|
|
54
55
|
}
|
package/src/CustomCamera.js
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
} from 'react-native';
|
|
12
12
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
13
13
|
import { Camera, CameraView } from 'expo-camera';
|
|
14
|
-
import * as ImageManipulator from 'expo-image-manipulator';
|
|
15
14
|
const { width } = Dimensions.get('window');
|
|
16
15
|
|
|
17
16
|
export default function CustomCamera({ onPhotoCaptured}) {
|
|
@@ -48,51 +47,32 @@ useEffect(() => {
|
|
|
48
47
|
});
|
|
49
48
|
};
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
const fixImageOrientation = async (uri) => {
|
|
53
|
-
try {
|
|
54
|
-
// Use manipulateAsync with empty array to fix orientation based on EXIF data
|
|
55
|
-
// Empty array [] tells manipulateAsync to automatically apply EXIF orientation
|
|
56
|
-
// This ensures horizontal photos stay horizontal and vertical photos stay vertical
|
|
57
|
-
// The EXIF orientation tag is removed and the image is physically rotated if needed
|
|
58
|
-
const fixedImage = await ImageManipulator.manipulateAsync(
|
|
59
|
-
uri,
|
|
60
|
-
[], // Empty array = apply EXIF orientation automatically
|
|
61
|
-
{
|
|
62
|
-
compress: 1,
|
|
63
|
-
format: ImageManipulator.SaveFormat.PNG
|
|
64
|
-
}
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
return fixedImage.uri;
|
|
68
|
-
} catch (error) {
|
|
69
|
-
console.error("Error fixing image orientation:", error);
|
|
70
|
-
return uri; // Return original if fixing fails
|
|
71
|
-
}
|
|
72
|
-
};
|
|
50
|
+
|
|
73
51
|
|
|
74
52
|
const takePicture = async () => {
|
|
75
53
|
if (cameraRef.current) {
|
|
76
54
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
55
|
+
// Show loading after a delay (using setImmediate for iOS compatibility)
|
|
56
|
+
waitForRender(5).then(() => {
|
|
57
|
+
setLoadingBeforeCapture(true);
|
|
58
|
+
});
|
|
81
59
|
|
|
82
60
|
// Wait a bit before taking picture (works on iOS)
|
|
83
|
-
await waitForRender(
|
|
61
|
+
await waitForRender(2);
|
|
84
62
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
63
|
+
const photo = await cameraRef.current.takePictureAsync({
|
|
64
|
+
quality: 1,
|
|
65
|
+
shutterSound: false,
|
|
66
|
+
skipProcessing: false,
|
|
67
|
+
exif: true, // Include EXIF data for orientation
|
|
68
|
+
});
|
|
89
69
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
onPhotoCaptured(fixedUri);
|
|
70
|
+
// Fix orientation on Android (iOS handles it automatically)
|
|
71
|
+
const fixedUri = photo.uri;
|
|
94
72
|
|
|
95
|
-
|
|
73
|
+
// Go directly to ImageCropper (preview will be shown there on Android)
|
|
74
|
+
onPhotoCaptured(fixedUri);
|
|
75
|
+
setLoadingBeforeCapture(false);
|
|
96
76
|
} catch (error) {
|
|
97
77
|
setLoadingBeforeCapture(false);
|
|
98
78
|
Alert.alert("Erreur");
|
|
@@ -229,4 +209,4 @@ const styles = StyleSheet.create({
|
|
|
229
209
|
color: GLOW_WHITE,
|
|
230
210
|
fontWeight: '600',
|
|
231
211
|
},
|
|
232
|
-
});
|
|
212
|
+
});
|
package/src/ImageCropper.js
CHANGED
|
@@ -1,290 +1,330 @@
|
|
|
1
|
-
import styles from './ImageCropperStyles';
|
|
2
|
-
import React, { useState, useRef, useEffect } from 'react';
|
|
3
|
-
import { Modal,View, Image, Dimensions, TouchableOpacity, Animated, Text } from 'react-native';
|
|
4
|
-
import Svg, { Path, Circle } from 'react-native-svg';
|
|
5
|
-
import { captureRef } from 'react-native-view-shot';
|
|
6
|
-
import CustomCamera from './CustomCamera';
|
|
7
|
-
import { enhanceImage } from './ImageProcessor';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const [
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
imageMeasure.current = {
|
|
52
|
-
width:
|
|
53
|
-
height:
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
1
|
+
import styles from './ImageCropperStyles';
|
|
2
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
3
|
+
import { Modal,View, Image, Dimensions, TouchableOpacity, Animated, Text, Platform, SafeAreaView } from 'react-native';
|
|
4
|
+
import Svg, { Path, Circle } from 'react-native-svg';
|
|
5
|
+
import { captureRef } from 'react-native-view-shot';
|
|
6
|
+
import CustomCamera from './CustomCamera';
|
|
7
|
+
import { enhanceImage } from './ImageProcessor';
|
|
8
|
+
import * as ImageManipulator from 'expo-image-manipulator';
|
|
9
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
10
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
11
|
+
|
|
12
|
+
const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) => {
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const [image, setImage] = useState(null);
|
|
16
|
+
const [points, setPoints] = useState([]);
|
|
17
|
+
const [showResult, setShowResult] = useState(false);
|
|
18
|
+
const [showCustomCamera, setShowCustomCamera] = useState(false);
|
|
19
|
+
const viewRef = useRef(null);
|
|
20
|
+
const imageMeasure = useRef({ x: 0, y: 0, width: 0, height: 0 });
|
|
21
|
+
const selectedPointIndex = useRef(null);
|
|
22
|
+
const lastTap = useRef(null);
|
|
23
|
+
|
|
24
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
25
|
+
const [showFullScreenCapture, setShowFullScreenCapture] = useState(false);
|
|
26
|
+
const [isRotating, setIsRotating] = useState(false);
|
|
27
|
+
const lastValidPosition = useRef(null);
|
|
28
|
+
const insets = useSafeAreaInsets();
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (openCameraFirst) {
|
|
36
|
+
setShowCustomCamera(true);
|
|
37
|
+
} else if (initialImage) {
|
|
38
|
+
setImage(initialImage);
|
|
39
|
+
}
|
|
40
|
+
}, [openCameraFirst, initialImage]);
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!image) return;
|
|
45
|
+
|
|
46
|
+
Image.getSize(image, (imgWidth, imgHeight) => {
|
|
47
|
+
const screenRatio = SCREEN_WIDTH / SCREEN_HEIGHT;
|
|
48
|
+
const imageRatio = imgWidth / imgHeight;
|
|
49
|
+
|
|
50
|
+
if (imageRatio > screenRatio) {
|
|
51
|
+
imageMeasure.current = {
|
|
52
|
+
width: SCREEN_WIDTH,
|
|
53
|
+
height: SCREEN_WIDTH / imageRatio,
|
|
54
|
+
};
|
|
55
|
+
} else {
|
|
56
|
+
imageMeasure.current = {
|
|
57
|
+
width: SCREEN_HEIGHT * imageRatio,
|
|
58
|
+
height: SCREEN_HEIGHT,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}, [image]);
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
const initializeCropBox = () => {
|
|
66
|
+
const { width, height } = imageMeasure.current;
|
|
67
|
+
// if (width === 0 || height === 0 || points.length > 0) return;
|
|
68
|
+
const boxWidth = width * 0.8;
|
|
69
|
+
const boxHeight = height * 0.8;
|
|
70
|
+
const centerX = width / 2;
|
|
71
|
+
const centerY = height / 2;
|
|
72
|
+
setPoints([
|
|
73
|
+
{ x: centerX - boxWidth / 2, y: centerY - boxHeight / 2 },
|
|
74
|
+
{ x: centerX + boxWidth / 2, y: centerY - boxHeight / 2 },
|
|
75
|
+
{ x: centerX + boxWidth / 2, y: centerY + boxHeight / 2 },
|
|
76
|
+
{ x: centerX - boxWidth / 2, y: centerY + boxHeight / 2 },
|
|
77
|
+
]);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
const onImageLayout = (e) => {
|
|
82
|
+
const layout = e.nativeEvent.layout;
|
|
83
|
+
imageMeasure.current = {
|
|
84
|
+
x: layout.x,
|
|
85
|
+
y: layout.y,
|
|
86
|
+
width: layout.width,
|
|
87
|
+
height: layout.height
|
|
88
|
+
};
|
|
89
|
+
initializeCropBox();
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const createPath = () => {
|
|
93
|
+
if (points.length < 1) return '';
|
|
94
|
+
let path = `M ${points[0].x} ${points[0].y} `;
|
|
95
|
+
points.forEach(point => path += `L ${point.x} ${point.y} `);
|
|
96
|
+
return path + 'Z';
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handleTap = (e) => {
|
|
100
|
+
if (!image || showResult) return;
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
const { locationX: tapX, locationY: tapY } = e.nativeEvent;
|
|
103
|
+
|
|
104
|
+
if (lastTap.current && now - lastTap.current < 300) {
|
|
105
|
+
const exists = points.some(p => Math.abs(p.x - tapX) < 15 && Math.abs(p.y - tapY) < 15);
|
|
106
|
+
if (!exists) setPoints([...points, { x: tapX, y: tapY }]);
|
|
107
|
+
lastTap.current = null;
|
|
108
|
+
} else {
|
|
109
|
+
const index = points.findIndex(p => Math.abs(p.x - tapX) < 20 && Math.abs(p.y - tapY) < 20);
|
|
110
|
+
if (index !== -1) {
|
|
111
|
+
selectedPointIndex.current = index;
|
|
112
|
+
lastValidPosition.current = { ...points[index] }; // store original position before move
|
|
113
|
+
}
|
|
114
|
+
lastTap.current = now;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const handleMove = (e) => {
|
|
119
|
+
if (showResult || selectedPointIndex.current === null) return;
|
|
120
|
+
|
|
121
|
+
const { locationX: moveX, locationY: moveY } = e.nativeEvent;
|
|
122
|
+
const width = imageMeasure.current.width;
|
|
123
|
+
const height = imageMeasure.current.height;
|
|
124
|
+
|
|
125
|
+
const boundedX = Math.max(0, Math.min(moveX, width));
|
|
126
|
+
const boundedY = Math.max(0, Math.min(moveY, height));
|
|
127
|
+
|
|
128
|
+
const edgeThreshold = 10;
|
|
129
|
+
const isNearTopOrBottomEdge =
|
|
130
|
+
boundedY <= edgeThreshold || boundedY >= height - edgeThreshold;
|
|
131
|
+
|
|
132
|
+
const isNearLeftOrRightEdge =
|
|
133
|
+
boundedX <= edgeThreshold || boundedX >= width - edgeThreshold;
|
|
134
|
+
|
|
135
|
+
if (isNearTopOrBottomEdge || isNearLeftOrRightEdge) {
|
|
136
|
+
// Reset point to last known position
|
|
137
|
+
if (lastValidPosition.current && selectedPointIndex.current !== null) {
|
|
138
|
+
setPoints(prev =>
|
|
139
|
+
prev.map((p, i) =>
|
|
140
|
+
i === selectedPointIndex.current ? lastValidPosition.current : p
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
selectedPointIndex.current = null;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Valid move — update point and store as new last valid position
|
|
149
|
+
const updatedPoint = { x: boundedX, y: boundedY };
|
|
150
|
+
lastValidPosition.current = updatedPoint;
|
|
151
|
+
|
|
152
|
+
setPoints(prev =>
|
|
153
|
+
prev.map((p, i) =>
|
|
154
|
+
i === selectedPointIndex.current ? updatedPoint : p
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const handleRelease = () => {
|
|
160
|
+
selectedPointIndex.current = null;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const handleReset = () => {
|
|
164
|
+
// setPoints([]);
|
|
165
|
+
initializeCropBox();
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const rotatePreviewImage = async (degrees) => {
|
|
169
|
+
if (!image || isRotating) return;
|
|
170
|
+
setIsRotating(true);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
// Rotate the image with optimized settings for speed
|
|
174
|
+
// Using JPEG with 0.85 compression for faster processing while maintaining good quality
|
|
175
|
+
const rotated = await ImageManipulator.manipulateAsync(
|
|
176
|
+
image,
|
|
177
|
+
[{ rotate: degrees }],
|
|
178
|
+
{ compress: 0.85, format: ImageManipulator.SaveFormat.JPEG }
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Update image - onImageLayout will call initializeCropBox automatically
|
|
182
|
+
setImage(rotated.uri);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error("Error rotating image:", error);
|
|
185
|
+
alert("Error rotating image");
|
|
186
|
+
} finally {
|
|
187
|
+
setIsRotating(false);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Helper function to wait for multiple render cycles (works on iOS)
|
|
192
|
+
const waitForRender = (cycles = 5) => {
|
|
193
|
+
return new Promise((resolve) => {
|
|
194
|
+
let count = 0;
|
|
195
|
+
const tick = () => {
|
|
196
|
+
count++;
|
|
197
|
+
if (count >= cycles) {
|
|
198
|
+
resolve();
|
|
199
|
+
} else {
|
|
200
|
+
setImmediate(tick);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
setImmediate(tick);
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<View style={styles.container}>
|
|
210
|
+
|
|
211
|
+
{showCustomCamera ? (
|
|
212
|
+
<CustomCamera
|
|
213
|
+
onPhotoCaptured={(uri) => { setImage(uri); setShowCustomCamera(false); }}
|
|
214
|
+
onCancel={() => setShowCustomCamera(false)}
|
|
215
|
+
/>
|
|
216
|
+
) : (
|
|
217
|
+
<>
|
|
218
|
+
{!showResult && (
|
|
219
|
+
<View style={image ? styles.buttonContainer : styles.centerButtonsContainer}>
|
|
220
|
+
|
|
221
|
+
{image && Platform.OS === 'android' && (
|
|
222
|
+
<>
|
|
223
|
+
<TouchableOpacity
|
|
224
|
+
style={styles.iconButton}
|
|
225
|
+
onPress={() => rotatePreviewImage(90)}
|
|
226
|
+
disabled={isRotating}
|
|
227
|
+
>
|
|
228
|
+
<Ionicons name="sync" size={24} color="white" />
|
|
229
|
+
</TouchableOpacity>
|
|
230
|
+
</>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
{image && (
|
|
234
|
+
<TouchableOpacity style={styles.button} onPress={handleReset}>
|
|
235
|
+
<Text style={styles.buttonText}>Reset</Text>
|
|
236
|
+
</TouchableOpacity>
|
|
237
|
+
)}
|
|
238
|
+
{image && (
|
|
239
|
+
<TouchableOpacity
|
|
240
|
+
style={styles.button}
|
|
241
|
+
onPress={async () => {
|
|
242
|
+
// setShowFullScreenCapture(true);
|
|
243
|
+
setIsLoading(true);
|
|
244
|
+
setShowResult(true);
|
|
245
|
+
try {
|
|
246
|
+
// Wait for UI to render - give React time to update the SVG with white fill
|
|
247
|
+
await waitForRender(5);
|
|
248
|
+
|
|
249
|
+
console.log("Starting capture...");
|
|
250
|
+
const capturedUri = await captureRef(viewRef, {
|
|
251
|
+
format: 'png',
|
|
252
|
+
quality: 1,
|
|
253
|
+
});
|
|
254
|
+
console.log("Capture successful:", capturedUri);
|
|
255
|
+
|
|
256
|
+
if (!capturedUri) {
|
|
257
|
+
throw new Error("Capture returned empty URI");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log("Enhancing image...");
|
|
261
|
+
const enhancedUri = await enhanceImage(capturedUri, addheight);
|
|
262
|
+
console.log("Image enhanced:", enhancedUri);
|
|
263
|
+
|
|
264
|
+
const name = `IMAGE XTK${Date.now()}.png`;
|
|
265
|
+
|
|
266
|
+
if (onConfirm) {
|
|
267
|
+
console.log("Calling onConfirm with:", enhancedUri, name);
|
|
268
|
+
onConfirm(enhancedUri, name);
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error("Erreur lors de la capture :", error);
|
|
272
|
+
alert("Erreur lors de la capture ! " + error.message);
|
|
273
|
+
} finally {
|
|
274
|
+
setShowResult(false);
|
|
275
|
+
setIsLoading(false);
|
|
276
|
+
setShowFullScreenCapture(false);
|
|
277
|
+
}
|
|
278
|
+
}}
|
|
279
|
+
>
|
|
280
|
+
<Text style={styles.buttonText}>Confirm</Text>
|
|
281
|
+
</TouchableOpacity>
|
|
282
|
+
)}
|
|
283
|
+
|
|
284
|
+
</View>
|
|
285
|
+
)}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
{image && (
|
|
289
|
+
<View
|
|
290
|
+
ref={viewRef}
|
|
291
|
+
collapsable={false}
|
|
292
|
+
style={showFullScreenCapture ? styles.fullscreenImageContainer : styles.imageContainer}
|
|
293
|
+
onStartShouldSetResponder={() => true}
|
|
294
|
+
onResponderStart={handleTap}
|
|
295
|
+
onResponderMove={handleMove}
|
|
296
|
+
onResponderRelease={handleRelease}
|
|
297
|
+
>
|
|
298
|
+
<Image source={{ uri: image }} style={styles.image} onLayout={onImageLayout} />
|
|
299
|
+
<Svg style={styles.overlay}>
|
|
300
|
+
<Path
|
|
301
|
+
d={`M 0 0 H ${imageMeasure.current.width} V ${imageMeasure.current.height} H 0 Z ${createPath()}`}
|
|
302
|
+
fill={showResult ? 'white' : 'rgba(0, 0, 0, 0.8)'}
|
|
303
|
+
fillRule="evenodd"
|
|
304
|
+
/>
|
|
305
|
+
{!showResult && points.length > 0 && (
|
|
306
|
+
<Path d={createPath()} fill="transparent" stroke="white" strokeWidth={2} />
|
|
307
|
+
)}
|
|
308
|
+
{!showResult && points.map((point, index) => (
|
|
309
|
+
<Circle key={index} cx={point.x} cy={point.y} r={10} fill="white" />
|
|
310
|
+
))}
|
|
311
|
+
</Svg>
|
|
312
|
+
</View>
|
|
313
|
+
)}
|
|
314
|
+
</>
|
|
315
|
+
)}
|
|
316
|
+
|
|
317
|
+
<Modal visible={isLoading} transparent animationType="fade">
|
|
318
|
+
<View style={styles.loadingOverlay}>
|
|
319
|
+
<Image
|
|
320
|
+
source={require('../src/assets/loadingCamera.gif')}
|
|
321
|
+
style={styles.loadingGif}
|
|
322
|
+
resizeMode="contain"
|
|
323
|
+
/>
|
|
324
|
+
</View>
|
|
325
|
+
</Modal>
|
|
326
|
+
</View>
|
|
327
|
+
);
|
|
328
|
+
};
|
|
329
|
+
|
|
290
330
|
export default ImageCropper;
|
|
@@ -25,6 +25,16 @@ const styles = StyleSheet.create({
|
|
|
25
25
|
alignItems: 'center',
|
|
26
26
|
paddingHorizontal: 10,
|
|
27
27
|
zIndex: 10,
|
|
28
|
+
gap: 10,
|
|
29
|
+
},
|
|
30
|
+
iconButton: {
|
|
31
|
+
backgroundColor: PRIMARY_GREEN,
|
|
32
|
+
width: 60,
|
|
33
|
+
height: 60,
|
|
34
|
+
borderRadius: 30,
|
|
35
|
+
alignItems: 'center',
|
|
36
|
+
justifyContent: 'center',
|
|
37
|
+
marginRight: 5,
|
|
28
38
|
},
|
|
29
39
|
button: {
|
|
30
40
|
flex: 1,
|
|
@@ -173,6 +183,37 @@ fullscreenImageContainer: {
|
|
|
173
183
|
alignItems: 'center',
|
|
174
184
|
zIndex: 9999,
|
|
175
185
|
},
|
|
186
|
+
previewContainer: {
|
|
187
|
+
flex: 1,
|
|
188
|
+
backgroundColor: DEEP_BLACK,
|
|
189
|
+
justifyContent: 'center',
|
|
190
|
+
alignItems: 'center',
|
|
191
|
+
width: '100%',
|
|
192
|
+
},
|
|
193
|
+
previewImage: {
|
|
194
|
+
width: SCREEN_WIDTH,
|
|
195
|
+
height: '80%',
|
|
196
|
+
},
|
|
197
|
+
previewButtonContainer: {
|
|
198
|
+
position: 'absolute',
|
|
199
|
+
bottom: 50,
|
|
200
|
+
left: 0,
|
|
201
|
+
right: 0,
|
|
202
|
+
flexDirection: 'row',
|
|
203
|
+
justifyContent: 'space-around',
|
|
204
|
+
paddingHorizontal: 20,
|
|
205
|
+
},
|
|
206
|
+
previewButton: {
|
|
207
|
+
backgroundColor: PRIMARY_GREEN,
|
|
208
|
+
width: 60,
|
|
209
|
+
height: 60,
|
|
210
|
+
borderRadius: 30,
|
|
211
|
+
alignItems: 'center',
|
|
212
|
+
justifyContent: 'center',
|
|
213
|
+
},
|
|
214
|
+
useButton: {
|
|
215
|
+
backgroundColor: GLOW_WHITE,
|
|
216
|
+
},
|
|
176
217
|
|
|
177
218
|
|
|
178
219
|
});
|
package/src/ImageProcessor.js
CHANGED
|
@@ -1,38 +1,28 @@
|
|
|
1
|
-
import * as ImageManipulator from 'expo-image-manipulator';
|
|
2
|
-
|
|
3
|
-
export const enhanceImage = async (uri , addheight) => {
|
|
4
|
-
try {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
compress: 1,
|
|
29
|
-
format: ImageManipulator.SaveFormat.PNG
|
|
30
|
-
}
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
return result.uri;
|
|
34
|
-
} catch (error) {
|
|
35
|
-
console.error("Erreur T404K:", error);
|
|
36
|
-
return uri;
|
|
37
|
-
}
|
|
1
|
+
import * as ImageManipulator from 'expo-image-manipulator';
|
|
2
|
+
|
|
3
|
+
export const enhanceImage = async (uri , addheight) => {
|
|
4
|
+
try {
|
|
5
|
+
const imageInfo = await ImageManipulator.manipulateAsync(uri, []);
|
|
6
|
+
const ratio = imageInfo.height / imageInfo.width;
|
|
7
|
+
|
|
8
|
+
const maxHeight = addheight;
|
|
9
|
+
const newWidth = Math.round(maxHeight / ratio);
|
|
10
|
+
const newHeight = Math.round(newWidth * ratio);
|
|
11
|
+
|
|
12
|
+
const result = await ImageManipulator.manipulateAsync(
|
|
13
|
+
uri,
|
|
14
|
+
[
|
|
15
|
+
{ resize: { width: newWidth, height: newHeight } },
|
|
16
|
+
],
|
|
17
|
+
{
|
|
18
|
+
compress: 1,
|
|
19
|
+
format: ImageManipulator.SaveFormat.PNG
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
return result.uri;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error("Erreur T404K:", error);
|
|
26
|
+
return uri;
|
|
27
|
+
}
|
|
38
28
|
};
|