react-native-expo-cropper 1.0.13 → 1.0.14
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/lib/ImageCropper.js +92 -56
- package/package.json +1 -1
- package/src/ImageCropper.js +71 -63
package/lib/ImageCropper.js
CHANGED
|
@@ -35,6 +35,7 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
|
|
|
35
35
|
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
36
36
|
var ImageCropper = function ImageCropper(_ref) {
|
|
37
37
|
var onConfirm = _ref.onConfirm,
|
|
38
|
+
onCrop = _ref.onCrop,
|
|
38
39
|
openCameraFirst = _ref.openCameraFirst,
|
|
39
40
|
initialImage = _ref.initialImage,
|
|
40
41
|
addheight = _ref.addheight;
|
|
@@ -97,29 +98,6 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
97
98
|
}
|
|
98
99
|
});
|
|
99
100
|
}, [image]);
|
|
100
|
-
var initializeCropBox = function initializeCropBox() {
|
|
101
|
-
var _imageMeasure$current = imageMeasure.current,
|
|
102
|
-
width = _imageMeasure$current.width,
|
|
103
|
-
height = _imageMeasure$current.height;
|
|
104
|
-
// if (width === 0 || height === 0 || points.length > 0) return;
|
|
105
|
-
var boxWidth = width * 0.6;
|
|
106
|
-
var boxHeight = height * 0.6;
|
|
107
|
-
var centerX = width / 2;
|
|
108
|
-
var centerY = height / 2;
|
|
109
|
-
setPoints([{
|
|
110
|
-
x: centerX - boxWidth / 2,
|
|
111
|
-
y: centerY - boxHeight / 2
|
|
112
|
-
}, {
|
|
113
|
-
x: centerX + boxWidth / 2,
|
|
114
|
-
y: centerY - boxHeight / 2
|
|
115
|
-
}, {
|
|
116
|
-
x: centerX + boxWidth / 2,
|
|
117
|
-
y: centerY + boxHeight / 2
|
|
118
|
-
}, {
|
|
119
|
-
x: centerX - boxWidth / 2,
|
|
120
|
-
y: centerY + boxHeight / 2
|
|
121
|
-
}]);
|
|
122
|
-
};
|
|
123
101
|
var onImageLayout = function onImageLayout(e) {
|
|
124
102
|
var layout = e.nativeEvent.layout;
|
|
125
103
|
imageMeasure.current = {
|
|
@@ -208,6 +186,31 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
208
186
|
// setPoints([]);
|
|
209
187
|
initializeCropBox();
|
|
210
188
|
};
|
|
189
|
+
var SCREEN_WIDTH = _reactNative.Dimensions.get('window').width;
|
|
190
|
+
var SCREEN_HEIGHT = _reactNative.Dimensions.get('window').height;
|
|
191
|
+
var initializeCropBox = function initializeCropBox() {
|
|
192
|
+
var _imageMeasure$current = imageMeasure.current,
|
|
193
|
+
width = _imageMeasure$current.width,
|
|
194
|
+
height = _imageMeasure$current.height;
|
|
195
|
+
// if (width === 0 || height === 0 || points.length > 0) return;
|
|
196
|
+
var boxWidth = width * 0.6;
|
|
197
|
+
var boxHeight = height * 0.6;
|
|
198
|
+
var centerX = width / 2;
|
|
199
|
+
var centerY = height / 2;
|
|
200
|
+
setPoints([{
|
|
201
|
+
x: centerX - boxWidth / 2,
|
|
202
|
+
y: centerY - boxHeight / 2
|
|
203
|
+
}, {
|
|
204
|
+
x: centerX + boxWidth / 2,
|
|
205
|
+
y: centerY - boxHeight / 2
|
|
206
|
+
}, {
|
|
207
|
+
x: centerX + boxWidth / 2,
|
|
208
|
+
y: centerY + boxHeight / 2
|
|
209
|
+
}, {
|
|
210
|
+
x: centerX - boxWidth / 2,
|
|
211
|
+
y: centerY + boxHeight / 2
|
|
212
|
+
}]);
|
|
213
|
+
};
|
|
211
214
|
return /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
212
215
|
style: _ImageCropperStyles["default"].container
|
|
213
216
|
}, showCustomCamera ? /*#__PURE__*/_react["default"].createElement(_CustomCamera["default"], {
|
|
@@ -227,52 +230,85 @@ var ImageCropper = function ImageCropper(_ref) {
|
|
|
227
230
|
style: _ImageCropperStyles["default"].buttonText
|
|
228
231
|
}, "Reset")), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
|
|
229
232
|
style: _ImageCropperStyles["default"].button,
|
|
230
|
-
onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function
|
|
231
|
-
var
|
|
232
|
-
return _regenerator().w(function (
|
|
233
|
-
while (1) switch (
|
|
233
|
+
onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
234
|
+
var name, finalUri, capturePromise, timeoutPromise, callback, _t;
|
|
235
|
+
return _regenerator().w(function (_context2) {
|
|
236
|
+
while (1) switch (_context2.p = _context2.n) {
|
|
234
237
|
case 0:
|
|
235
|
-
//
|
|
236
|
-
setIsLoading(true);
|
|
238
|
+
// Do NOT show loading modal during confirm to avoid UI stalls
|
|
237
239
|
setShowResult(true);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
+
if (viewRef.current) {
|
|
241
|
+
_context2.n = 1;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
console.error('View ref not ready for capture');
|
|
245
|
+
alert('Vue non prête pour la capture');
|
|
246
|
+
setShowResult(false);
|
|
247
|
+
return _context2.a(2);
|
|
248
|
+
case 1:
|
|
249
|
+
name = "IMAGE XTK".concat(Date.now(), ".png");
|
|
250
|
+
finalUri = null;
|
|
251
|
+
_context2.p = 2;
|
|
252
|
+
_context2.n = 3;
|
|
240
253
|
return new Promise(function (resolve) {
|
|
241
254
|
return requestAnimationFrame(resolve);
|
|
242
255
|
});
|
|
243
|
-
case 2:
|
|
244
|
-
_context.n = 3;
|
|
245
|
-
return (0, _reactNativeViewShot.captureRef)(viewRef, {
|
|
246
|
-
format: 'png',
|
|
247
|
-
quality: 1
|
|
248
|
-
});
|
|
249
256
|
case 3:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
257
|
+
_context2.n = 4;
|
|
258
|
+
return new Promise(function (resolve) {
|
|
259
|
+
return _reactNative.InteractionManager.runAfterInteractions(resolve);
|
|
260
|
+
});
|
|
253
261
|
case 4:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
262
|
+
capturePromise = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
263
|
+
var capturedUri;
|
|
264
|
+
return _regenerator().w(function (_context) {
|
|
265
|
+
while (1) switch (_context.n) {
|
|
266
|
+
case 0:
|
|
267
|
+
_context.n = 1;
|
|
268
|
+
return (0, _reactNativeViewShot.captureRef)(viewRef.current, {
|
|
269
|
+
format: 'png',
|
|
270
|
+
quality: 1,
|
|
271
|
+
result: 'tmpfile'
|
|
272
|
+
});
|
|
273
|
+
case 1:
|
|
274
|
+
capturedUri = _context.v;
|
|
275
|
+
return _context.a(2, (0, _ImageProcessor.enhanceImage)(capturedUri, addheight));
|
|
276
|
+
}
|
|
277
|
+
}, _callee);
|
|
278
|
+
}))();
|
|
279
|
+
timeoutPromise = new Promise(function (_, reject) {
|
|
280
|
+
return setTimeout(function () {
|
|
281
|
+
return reject(new Error('Capture timed out'));
|
|
282
|
+
}, 7000);
|
|
283
|
+
});
|
|
284
|
+
_context2.n = 5;
|
|
285
|
+
return Promise.race([capturePromise, timeoutPromise]);
|
|
261
286
|
case 5:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
alert("Erreur lors de la capture !");
|
|
287
|
+
finalUri = _context2.v;
|
|
288
|
+
_context2.n = 7;
|
|
289
|
+
break;
|
|
266
290
|
case 6:
|
|
267
|
-
|
|
291
|
+
_context2.p = 6;
|
|
292
|
+
_t = _context2.v;
|
|
293
|
+
console.error('Erreur lors de la capture :', _t);
|
|
294
|
+
alert('Erreur lors de la capture !');
|
|
295
|
+
case 7:
|
|
296
|
+
_context2.p = 7;
|
|
268
297
|
setShowResult(false);
|
|
269
298
|
setIsLoading(false);
|
|
270
299
|
setShowFullScreenCapture(false);
|
|
271
|
-
return
|
|
272
|
-
case
|
|
273
|
-
|
|
300
|
+
return _context2.f(7);
|
|
301
|
+
case 8:
|
|
302
|
+
callback = onConfirm || (typeof onCrop === 'function' ? onCrop : null);
|
|
303
|
+
if (callback && finalUri) {
|
|
304
|
+
setTimeout(function () {
|
|
305
|
+
return callback(finalUri, name);
|
|
306
|
+
}, 0);
|
|
307
|
+
}
|
|
308
|
+
case 9:
|
|
309
|
+
return _context2.a(2);
|
|
274
310
|
}
|
|
275
|
-
},
|
|
311
|
+
}, _callee2, null, [[2, 6, 7, 8]]);
|
|
276
312
|
}))
|
|
277
313
|
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
|
|
278
314
|
style: _ImageCropperStyles["default"].buttonText
|
package/package.json
CHANGED
package/src/ImageCropper.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import styles from './ImageCropperStyles';
|
|
2
2
|
import React, { useState, useRef, useEffect } from 'react';
|
|
3
|
-
import { Modal,View, Image, Dimensions, TouchableOpacity, Animated, Text } from 'react-native';
|
|
3
|
+
import { Modal,View, Image, Dimensions, TouchableOpacity, Animated, Text, InteractionManager } from 'react-native';
|
|
4
4
|
import Svg, { Path, Circle } from 'react-native-svg';
|
|
5
5
|
import { captureRef } from 'react-native-view-shot';
|
|
6
6
|
import CustomCamera from './CustomCamera';
|
|
7
7
|
import { enhanceImage } from './ImageProcessor';
|
|
8
8
|
|
|
9
|
-
const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) => {
|
|
9
|
+
const ImageCropper = ({ onConfirm, onCrop, openCameraFirst, initialImage ,addheight}) => {
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
const [image, setImage] = useState(null);
|
|
@@ -56,23 +56,6 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
|
|
|
56
56
|
});
|
|
57
57
|
}, [image]);
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
const initializeCropBox = () => {
|
|
61
|
-
const { width, height } = imageMeasure.current;
|
|
62
|
-
// if (width === 0 || height === 0 || points.length > 0) return;
|
|
63
|
-
const boxWidth = width * 0.6;
|
|
64
|
-
const boxHeight = height * 0.6;
|
|
65
|
-
const centerX = width / 2;
|
|
66
|
-
const centerY = height / 2;
|
|
67
|
-
setPoints([
|
|
68
|
-
{ x: centerX - boxWidth / 2, y: centerY - boxHeight / 2 },
|
|
69
|
-
{ x: centerX + boxWidth / 2, y: centerY - boxHeight / 2 },
|
|
70
|
-
{ x: centerX + boxWidth / 2, y: centerY + boxHeight / 2 },
|
|
71
|
-
{ x: centerX - boxWidth / 2, y: centerY + boxHeight / 2 },
|
|
72
|
-
]);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
|
|
76
59
|
const onImageLayout = (e) => {
|
|
77
60
|
const layout = e.nativeEvent.layout;
|
|
78
61
|
imageMeasure.current = {
|
|
@@ -95,7 +78,7 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
|
|
|
95
78
|
if (!image || showResult) return;
|
|
96
79
|
const now = Date.now();
|
|
97
80
|
const { locationX: tapX, locationY: tapY } = e.nativeEvent;
|
|
98
|
-
|
|
81
|
+
|
|
99
82
|
if (lastTap.current && now - lastTap.current < 300) {
|
|
100
83
|
const exists = points.some(p => Math.abs(p.x - tapX) < 15 && Math.abs(p.y - tapY) < 15);
|
|
101
84
|
if (!exists) setPoints([...points, { x: tapX, y: tapY }]);
|
|
@@ -112,43 +95,30 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
|
|
|
112
95
|
|
|
113
96
|
const handleMove = (e) => {
|
|
114
97
|
if (showResult || selectedPointIndex.current === null) return;
|
|
115
|
-
|
|
116
98
|
const { locationX: moveX, locationY: moveY } = e.nativeEvent;
|
|
117
99
|
const width = imageMeasure.current.width;
|
|
118
100
|
const height = imageMeasure.current.height;
|
|
119
|
-
|
|
101
|
+
|
|
120
102
|
const boundedX = Math.max(0, Math.min(moveX, width));
|
|
121
103
|
const boundedY = Math.max(0, Math.min(moveY, height));
|
|
122
|
-
|
|
104
|
+
|
|
123
105
|
const edgeThreshold = 10;
|
|
124
|
-
const isNearTopOrBottomEdge =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const isNearLeftOrRightEdge =
|
|
128
|
-
boundedX <= edgeThreshold || boundedX >= width - edgeThreshold;
|
|
129
|
-
|
|
106
|
+
const isNearTopOrBottomEdge = boundedY <= edgeThreshold || boundedY >= height - edgeThreshold;
|
|
107
|
+
const isNearLeftOrRightEdge = boundedX <= edgeThreshold || boundedX >= width - edgeThreshold;
|
|
108
|
+
|
|
130
109
|
if (isNearTopOrBottomEdge || isNearLeftOrRightEdge) {
|
|
131
110
|
// Reset point to last known position
|
|
132
111
|
if (lastValidPosition.current && selectedPointIndex.current !== null) {
|
|
133
|
-
setPoints(prev =>
|
|
134
|
-
prev.map((p, i) =>
|
|
135
|
-
i === selectedPointIndex.current ? lastValidPosition.current : p
|
|
136
|
-
)
|
|
137
|
-
);
|
|
112
|
+
setPoints(prev => prev.map((p, i) => (i === selectedPointIndex.current ? lastValidPosition.current : p)));
|
|
138
113
|
}
|
|
139
114
|
selectedPointIndex.current = null;
|
|
140
115
|
return;
|
|
141
116
|
}
|
|
142
|
-
|
|
117
|
+
|
|
143
118
|
// Valid move — update point and store as new last valid position
|
|
144
119
|
const updatedPoint = { x: boundedX, y: boundedY };
|
|
145
120
|
lastValidPosition.current = updatedPoint;
|
|
146
|
-
|
|
147
|
-
setPoints(prev =>
|
|
148
|
-
prev.map((p, i) =>
|
|
149
|
-
i === selectedPointIndex.current ? updatedPoint : p
|
|
150
|
-
)
|
|
151
|
-
);
|
|
121
|
+
setPoints(prev => prev.map((p, i) => (i === selectedPointIndex.current ? updatedPoint : p)));
|
|
152
122
|
};
|
|
153
123
|
|
|
154
124
|
const handleRelease = () => {
|
|
@@ -161,6 +131,26 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
|
|
|
161
131
|
};
|
|
162
132
|
|
|
163
133
|
|
|
134
|
+
const SCREEN_WIDTH = Dimensions.get('window').width;
|
|
135
|
+
const SCREEN_HEIGHT = Dimensions.get('window').height;
|
|
136
|
+
|
|
137
|
+
const initializeCropBox = () => {
|
|
138
|
+
const { width, height } = imageMeasure.current;
|
|
139
|
+
// if (width === 0 || height === 0 || points.length > 0) return;
|
|
140
|
+
const boxWidth = width * 0.6;
|
|
141
|
+
const boxHeight = height * 0.6;
|
|
142
|
+
const centerX = width / 2;
|
|
143
|
+
const centerY = height / 2;
|
|
144
|
+
|
|
145
|
+
setPoints([
|
|
146
|
+
{ x: centerX - boxWidth / 2, y: centerY - boxHeight / 2 },
|
|
147
|
+
{ x: centerX + boxWidth / 2, y: centerY - boxHeight / 2 },
|
|
148
|
+
{ x: centerX + boxWidth / 2, y: centerY + boxHeight / 2 },
|
|
149
|
+
{ x: centerX - boxWidth / 2, y: centerY + boxHeight / 2 },
|
|
150
|
+
]);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
|
|
164
154
|
return (
|
|
165
155
|
<View style={styles.container}>
|
|
166
156
|
|
|
@@ -183,33 +173,51 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
|
|
|
183
173
|
<TouchableOpacity
|
|
184
174
|
style={styles.button}
|
|
185
175
|
onPress={async () => {
|
|
186
|
-
//
|
|
187
|
-
setIsLoading(true);
|
|
176
|
+
// Do NOT show loading modal during confirm to avoid UI stalls
|
|
188
177
|
setShowResult(true);
|
|
178
|
+
|
|
179
|
+
if (!viewRef.current) {
|
|
180
|
+
console.error('View ref not ready for capture');
|
|
181
|
+
alert('Vue non prête pour la capture');
|
|
182
|
+
setShowResult(false);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const name = `IMAGE XTK${Date.now()}.png`;
|
|
187
|
+
let finalUri = null;
|
|
189
188
|
try {
|
|
189
|
+
// Yield UI work completely, then perform capture
|
|
190
190
|
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
191
|
-
|
|
192
|
-
format: 'png',
|
|
193
|
-
quality: 1,
|
|
194
|
-
});
|
|
191
|
+
await new Promise((resolve) => InteractionManager.runAfterInteractions(resolve));
|
|
195
192
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
const capturePromise = (async () => {
|
|
194
|
+
const capturedUri = await captureRef(viewRef.current, {
|
|
195
|
+
format: 'png',
|
|
196
|
+
quality: 1,
|
|
197
|
+
result: 'tmpfile',
|
|
198
|
+
});
|
|
199
|
+
return enhanceImage(capturedUri, addheight);
|
|
200
|
+
})();
|
|
201
|
+
|
|
202
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
203
|
+
setTimeout(() => reject(new Error('Capture timed out')), 7000)
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
finalUri = await Promise.race([capturePromise, timeoutPromise]);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error('Erreur lors de la capture :', error);
|
|
209
|
+
alert('Erreur lors de la capture !');
|
|
210
|
+
} finally {
|
|
211
|
+
setShowResult(false);
|
|
212
|
+
setIsLoading(false);
|
|
213
|
+
setShowFullScreenCapture(false);
|
|
214
|
+
}
|
|
199
215
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
console.error("Erreur lors de la capture :", error);
|
|
206
|
-
alert("Erreur lors de la capture !");
|
|
207
|
-
} finally {
|
|
208
|
-
setShowResult(false);
|
|
209
|
-
setIsLoading(false);
|
|
210
|
-
setShowFullScreenCapture(false);
|
|
211
|
-
}
|
|
212
|
-
}}
|
|
216
|
+
const callback = onConfirm || (typeof onCrop === 'function' ? onCrop : null);
|
|
217
|
+
if (callback && finalUri) {
|
|
218
|
+
setTimeout(() => callback(finalUri, name), 0);
|
|
219
|
+
}
|
|
220
|
+
}}
|
|
213
221
|
>
|
|
214
222
|
<Text style={styles.buttonText}>Confirm</Text>
|
|
215
223
|
</TouchableOpacity>
|