react-native-expo-cropper 1.0.25 → 1.0.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-expo-cropper",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "Recadrage polygonal d'images.",
5
5
  "main": "index.js",
6
6
  "author": "PCS AGRI",
@@ -40,26 +40,9 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
40
40
  }, [openCameraFirst, initialImage]);
41
41
 
42
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]);
43
+ // Measure based strictly on actual layout, not screen ratio
44
+ // to avoid mismatches that truncate the selectable bottom area
45
+ // (onImageLayout handles measurement updates)
63
46
 
64
47
  // Perform capture after UI commits (avoids iOS timer/RAF awaits)
65
48
  // iOS capture logic using useEffect with overlay readiness
@@ -96,6 +79,53 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
96
79
  return () => { cancelled = true; };
97
80
  }, [captureRequested, showResult, overlayReady, addheight, onConfirm]);
98
81
 
82
+ // Helpers to compute crop rectangle in original image pixels
83
+ const getImageSizeAsync = (uri) =>
84
+ new Promise((resolve, reject) => {
85
+ Image.getSize(uri, (w, h) => resolve({ width: w, height: h }), reject);
86
+ });
87
+
88
+ const computeCropRect = (pts, measure, origW, origH) => {
89
+ const containerW = measure.width;
90
+ const containerH = measure.height;
91
+ if (!containerW || !containerH || !origW || !origH || !pts || pts.length === 0) return null;
92
+
93
+ const ratio = origH / origW;
94
+ let dispW, dispH, offX, offY;
95
+ if (containerW * ratio <= containerH) {
96
+ // image constrained by width
97
+ dispW = containerW;
98
+ dispH = containerW * ratio;
99
+ offX = 0;
100
+ offY = (containerH - dispH) / 2;
101
+ } else {
102
+ // image constrained by height
103
+ dispH = containerH;
104
+ dispW = containerH / ratio;
105
+ offY = 0;
106
+ offX = (containerW - dispW) / 2;
107
+ }
108
+
109
+ const xs = pts.map(p => Math.max(0, Math.min(dispW, p.x - offX)));
110
+ const ys = pts.map(p => Math.max(0, Math.min(dispH, p.y - offY)));
111
+ const minX = Math.min(...xs);
112
+ const maxX = Math.max(...xs);
113
+ const minY = Math.min(...ys);
114
+ const maxY = Math.max(...ys);
115
+
116
+ const normMinX = minX / dispW;
117
+ const normMaxX = maxX / dispW;
118
+ const normMinY = minY / dispH;
119
+ const normMaxY = maxY / dispH;
120
+
121
+ const originX = Math.round(normMinX * origW);
122
+ const originY = Math.round(normMinY * origH);
123
+ const widthPx = Math.round((normMaxX - normMinX) * origW);
124
+ const heightPx = Math.round((normMaxY - normMinY) * origH);
125
+
126
+ return { x: originX, y: originY, width: widthPx, height: heightPx };
127
+ };
128
+
99
129
 
100
130
  const initializeCropBox = () => {
101
131
  const { width, height } = imageMeasure.current;
@@ -160,26 +190,6 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
160
190
  const boundedX = Math.max(0, Math.min(moveX, width));
161
191
  const boundedY = Math.max(0, Math.min(moveY, height));
162
192
 
163
- const edgeThreshold = 10;
164
- const isNearTopOrBottomEdge =
165
- boundedY <= edgeThreshold || boundedY >= height - edgeThreshold;
166
-
167
- const isNearLeftOrRightEdge =
168
- boundedX <= edgeThreshold || boundedX >= width - edgeThreshold;
169
-
170
- if (isNearTopOrBottomEdge || isNearLeftOrRightEdge) {
171
- // Reset point to last known position
172
- if (lastValidPosition.current && selectedPointIndex.current !== null) {
173
- setPoints(prev =>
174
- prev.map((p, i) =>
175
- i === selectedPointIndex.current ? lastValidPosition.current : p
176
- )
177
- );
178
- }
179
- selectedPointIndex.current = null;
180
- return;
181
- }
182
-
183
193
  // Valid move — update point and store as new last valid position
184
194
  const updatedPoint = { x: boundedX, y: boundedY };
185
195
  lastValidPosition.current = updatedPoint;
@@ -211,8 +221,8 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
211
221
  />
212
222
  ) : (
213
223
  <>
214
- {!showResult && (
215
- <View style={image ? styles.buttonContainer : styles.centerButtonsContainer}>
224
+ {!showResult && !image && (
225
+ <View pointerEvents="box-none" style={styles.centerButtonsContainer}>
216
226
 
217
227
  {image && (
218
228
  <TouchableOpacity style={styles.button} onPress={handleReset}>
@@ -255,7 +265,7 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
255
265
  }
256
266
  }
257
267
  }}
258
- >
268
+ >
259
269
  <Text style={styles.buttonText}>Confirm</Text>
260
270
  </TouchableOpacity>
261
271
  )}
@@ -276,6 +286,7 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
276
286
  >
277
287
  <Image source={{ uri: image }} style={styles.image} onLayout={onImageLayout} />
278
288
  <Svg
289
+ pointerEvents="none"
279
290
  key={showResult ? 'mask' : 'edit'}
280
291
  style={styles.overlay}
281
292
  onLayout={() => {
@@ -294,6 +305,33 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
294
305
  <Circle key={index} cx={point.x} cy={point.y} r={10} fill="white" />
295
306
  ))}
296
307
  </Svg>
308
+ </View>
309
+ )}
310
+ {!showResult && image && (
311
+ <View style={styles.buttonContainer}>
312
+ <TouchableOpacity style={styles.button} onPress={handleReset}>
313
+ <Text style={styles.buttonText}>Reset</Text>
314
+ </TouchableOpacity>
315
+ <TouchableOpacity
316
+ style={styles.button}
317
+ onPress={async () => {
318
+ try {
319
+ setIsLoading(true);
320
+ const { width: origW, height: origH } = await getImageSizeAsync(image);
321
+ const rect = computeCropRect(points, imageMeasure.current, origW, origH);
322
+ const enhancedUri = await enhanceImage(image, addheight, rect);
323
+ const name = `IMAGE XTK${Date.now()}.png`;
324
+ if (onConfirm) onConfirm(enhancedUri, name);
325
+ } catch (error) {
326
+ console.error('Erreur lors de la capture :', error);
327
+ alert('Erreur lors de la capture !');
328
+ } finally {
329
+ setIsLoading(false);
330
+ }
331
+ }}
332
+ >
333
+ <Text style={styles.buttonText}>Confirm</Text>
334
+ </TouchableOpacity>
297
335
  </View>
298
336
  )}
299
337
  </>
@@ -15,27 +15,22 @@ const styles = StyleSheet.create({
15
15
  backgroundColor: DEEP_BLACK,
16
16
  },
17
17
  buttonContainer: {
18
- position: 'absolute',
19
- bottom: 50,
20
- left: 0,
21
- right: 0,
22
18
  flexDirection: 'row',
23
- flexWrap: 'wrap',
24
- justifyContent: 'center',
19
+ justifyContent: 'space-between',
25
20
  alignItems: 'center',
26
- paddingHorizontal: 10,
27
- zIndex: 10,
21
+ paddingHorizontal: 16,
22
+ paddingVertical: 12,
23
+ width: '100%',
24
+ backgroundColor: DEEP_BLACK,
25
+ gap: 10,
28
26
  },
29
27
  button: {
30
28
  flex: 1,
31
- width: "100%",
32
- padding: 10,
29
+ paddingVertical: 12,
33
30
  alignItems: "center",
34
31
  justifyContent: "center",
35
32
  backgroundColor: "#549433",
36
- borderRadius: 5,
37
- marginBottom: 20,
38
- marginRight:5,
33
+ borderRadius: 8,
39
34
  },
40
35
  buttonText: {
41
36
  color: 'white',
@@ -50,13 +45,13 @@ const styles = StyleSheet.create({
50
45
  textAlign: 'center',
51
46
  },
52
47
  imageContainer: {
53
- width: IMAGE_WIDTH,
54
- height: "80%",
55
- justifyContent: 'center',
56
- alignItems: 'center',
57
- overflow: 'hidden',
58
- backgroundColor: 'black',
59
- },
48
+ width: IMAGE_WIDTH,
49
+ flex: 1,
50
+ justifyContent: 'center',
51
+ alignItems: 'center',
52
+ overflow: 'hidden',
53
+ backgroundColor: 'black',
54
+ },
60
55
 
61
56
  image: {
62
57
  width: '100%',
@@ -1,28 +1,45 @@
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
- }
1
+ import * as ImageManipulator from 'expo-image-manipulator';
2
+
3
+ // Crop the image to the provided rectangle (in original image pixels), then resize.
4
+ // cropRect: { x, y, width, height } in original image coordinates
5
+ export const enhanceImage = async (uri, addheight, cropRect) => {
6
+ try {
7
+ const actions = [];
8
+ if (cropRect && cropRect.width > 0 && cropRect.height > 0) {
9
+ actions.push({
10
+ crop: {
11
+ originX: Math.round(cropRect.x),
12
+ originY: Math.round(cropRect.y),
13
+ width: Math.round(cropRect.width),
14
+ height: Math.round(cropRect.height),
15
+ },
16
+ });
17
+ }
18
+
19
+ // First: crop (if requested)
20
+ const cropped = await ImageManipulator.manipulateAsync(uri, actions, {
21
+ compress: 1,
22
+ format: ImageManipulator.SaveFormat.PNG,
23
+ });
24
+
25
+ // Then: resize to requested height while preserving aspect ratio
26
+ const targetHeight = addheight || cropped.height;
27
+ const ratio = cropped.height / cropped.width;
28
+ const newWidth = Math.round(targetHeight / ratio);
29
+ const newHeight = Math.round(newWidth * ratio);
30
+
31
+ const result = await ImageManipulator.manipulateAsync(
32
+ cropped.uri,
33
+ [{ resize: { width: newWidth, height: newHeight } }],
34
+ {
35
+ compress: 1,
36
+ format: ImageManipulator.SaveFormat.PNG,
37
+ }
38
+ );
39
+
40
+ return result.uri;
41
+ } catch (error) {
42
+ console.error('Erreur T404K:', error);
43
+ return uri;
44
+ }
28
45
  };