react-native-expo-cropper 1.0.0

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/.babelrc ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "presets": ["@babel/preset-env"]
3
+ }
4
+
package/App.js ADDED
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { SafeAreaView, StatusBar, StyleSheet } from 'react-native';
3
+ import ImageCropper from './src/ImageCropper';
4
+
5
+ const App = () => {
6
+ const handleCrop = (uri) => {
7
+ console.log('Cropped Image URI:', uri);
8
+ };
9
+
10
+ return (
11
+ <>
12
+ <StatusBar backgroundColor="black" barStyle="light-content" />
13
+ <SafeAreaView style={styles.container}>
14
+ <ImageCropper onCrop={handleCrop} />
15
+ </SafeAreaView>
16
+ </>
17
+ );
18
+ };
19
+
20
+ const styles = StyleSheet.create({
21
+ container: {
22
+ flex: 1,
23
+ backgroundColor: 'black',
24
+ },
25
+ });
26
+
27
+ export default App;
package/README.MD ADDED
@@ -0,0 +1,135 @@
1
+ # react-native-expo-cropper
2
+
3
+ Un composant React Native basé sur Expo pour recadrer une image de manière polygonale à partir de la galerie ou de la caméra.
4
+
5
+ ## 🔧 Installation
6
+
7
+ ```bash
8
+ npm install react-native-expo-cropper
9
+
10
+ Ou
11
+
12
+ yarn add react-native-expo-cropper
13
+
14
+
15
+ EXPO SDK 53 -------------------------------------------------------------
16
+
17
+ "peerDependencies": {
18
+ "expo": "53.0.9",
19
+ "react": "19.0.0",
20
+ "react-native": "0.79.2"
21
+ },
22
+ "dependencies": {
23
+ "expo": "53.0.9",
24
+ "expo-camera": "16.1.6",
25
+ "expo-image-manipulator": "13.1.6",
26
+ "expo-image-picker": "16.1.4",
27
+ "expo-media-library": "17.1.6",
28
+ "expo-screen-orientation": "8.1.6",
29
+ "@expo/vector-icons": "14.1.0",
30
+ "react-native-svg": "15.11.2",
31
+ "react-native-view-shot": "4.0.3"
32
+ },
33
+
34
+
35
+ ------------------------------ DO THIS IN YOUR APP SCREEN CODE !!!!!!!!!! :-------------------------------
36
+
37
+ import ImageCropper from 'react-native-expo-cropper/src/ImageCropper';
38
+
39
+
40
+ const [crop, setCrop] = useState(false);
41
+ const [useCameraInCropper, setUseCameraInCropper] = useState(false);
42
+ const [initialImageForCropper, setInitialImageForCropper] = useState(null);
43
+
44
+
45
+
46
+
47
+ TO OPEN THE GALERIE BEFORE THE CROPPER YOU NEED TO ADD THIS IN YOU APP SCREEN CODE TOO :
48
+
49
+ const handlePickAndCropImage = async () => {
50
+ try {
51
+ const result = await ImagePicker.launchImageLibraryAsync({
52
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
53
+ allowsEditing: false,
54
+ quality: 1,
55
+ });
56
+
57
+ if (!result.canceled && result.assets.length > 0) {
58
+ setInitialImageForCropper(result.assets[0].uri);
59
+ setUseCameraInCropper(false); // sécurité
60
+ setCrop(true);
61
+ }
62
+ } catch (error) {
63
+ console.log("Erreur lors du choix de l'image :", error);
64
+ }
65
+ };
66
+
67
+
68
+
69
+
70
+ YOUR CAMERA AND GALERY Bottons :
71
+
72
+
73
+ <TouchableOpacity
74
+ style={styles.buttonOutline}
75
+ onPress={() => {
76
+ setUseCameraInCropper(true);
77
+ setCrop(true);
78
+ }}
79
+ >
80
+ <Text style={styles.buttonTextB}>Take A photo</Text>
81
+ </TouchableOpacity>
82
+
83
+ <TouchableOpacity
84
+ style={styles.buttonOutline}
85
+ onPress={handlePickAndCropImage}
86
+ >
87
+ <Text style={styles.buttonTextB}>Chose image From the phone Galerie</Text>
88
+ </TouchableOpacity>
89
+
90
+
91
+
92
+
93
+ condition:
94
+
95
+ {crop ? (
96
+ <View style={{ flex: 1 }}>
97
+ <TouchableOpacity
98
+ onPress={() => {
99
+ setCrop(false);
100
+ setUseCameraInCropper(false);
101
+ }}
102
+ style={{
103
+ position: 'absolute',
104
+ top: 40,
105
+ right: 5,
106
+ zIndex: 9999,
107
+ backgroundColor: '#ffffffaa',
108
+ padding: 10,
109
+ borderRadius: 3,
110
+ }}
111
+ >
112
+ <Text style={{ fontSize: 20, fontWeight: 'bold' }}> X </Text>
113
+ </TouchableOpacity>
114
+
115
+ <ImageCropper
116
+ initialImage={initialImageForCropper}
117
+ openCameraFirst={useCameraInCropper}
118
+ onConfirm={(uri, name) => {
119
+ setImage(uri);
120
+ setURi(uri);
121
+ setImageName(name);
122
+ setCrop(false);
123
+ setUseCameraInCropper(false);
124
+ setInitialImageForCropper(null);
125
+ setIsReady(true);
126
+ }}
127
+ onCancel={() => {
128
+ setCrop(false);
129
+ setUseCameraInCropper(false);
130
+ setInitialImageForCropper(null);
131
+ }}
132
+ />
133
+
134
+ </View>
135
+ )}
package/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import { registerRootComponent } from 'expo';
2
+ import App from './App';
3
+
4
+ registerRootComponent(App);
5
+
6
+
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "react-native-expo-cropper",
3
+ "version": "1.0.0",
4
+ "description": "Recadrage polygonal d'images.",
5
+ "main": "index.js",
6
+ "author": "PCS AGRI",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "react-native",
10
+ "expo",
11
+ "image-cropper",
12
+ "camera",
13
+ "polygonal",
14
+ "image",
15
+ "react-native-component"
16
+ ],
17
+ "peerDependencies": {
18
+ "expo": "53.0.9",
19
+ "react": "19.0.0",
20
+ "react-native": "0.79.2"
21
+ },
22
+ "dependencies": {
23
+ "@expo/vector-icons": "14.1.0",
24
+ "expo": "53.0.9",
25
+ "expo-camera": "16.1.6",
26
+ "expo-image-manipulator": "13.1.6",
27
+ "expo-image-picker": "16.1.4",
28
+ "expo-media-library": "17.1.6",
29
+ "expo-screen-orientation": "8.1.6",
30
+ "react-native-svg": "15.11.2",
31
+ "react-native-view-shot": "4.0.3"
32
+ },
33
+ "engines": {
34
+ "node": ">=14"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/pcsagri/react-native-expo-cropper.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/pcsagri/react-native-expo-cropper/issues"
42
+ },
43
+ "homepage": "https://github.com/pcsagri/react-native-expo-cropper#readme",
44
+ "scripts": {
45
+ "test": "echo \"Error: no test specified\" && exit 1",
46
+ "build": "babel src --out-dir dist"
47
+ }
48
+ }
@@ -0,0 +1,185 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import {
3
+ StyleSheet,
4
+ Text,
5
+ View,
6
+ TouchableOpacity,
7
+ Alert,
8
+ SafeAreaView,
9
+ Dimensions,
10
+ Image,
11
+ } from 'react-native';
12
+ import { Camera, CameraView } from 'expo-camera';
13
+ const { width } = Dimensions.get('window');
14
+
15
+ export default function CustomCamera({ onPhotoCaptured}) {
16
+ const [isReady, setIsReady] = useState(false);
17
+ const [loadingBeforeCapture, setLoadingBeforeCapture] = useState(false);
18
+ const [setHasPermission] = useState(null);
19
+ const cameraRef = useRef(null);
20
+
21
+
22
+
23
+ useEffect(() => {
24
+ (async () => {
25
+ const { status } = await Camera.requestCameraPermissionsAsync();
26
+ setHasPermission(status === 'granted');
27
+ })();
28
+ }, []);
29
+
30
+
31
+
32
+ const takePicture = async () => {
33
+ if (cameraRef.current) {
34
+ try {
35
+
36
+ setTimeout(() => {
37
+ setLoadingBeforeCapture(true);
38
+ }, 1000);
39
+
40
+ await new Promise(resolve => setTimeout(resolve, 100));
41
+
42
+ const photo = await cameraRef.current.takePictureAsync({
43
+ quality: 1,
44
+ });
45
+
46
+
47
+ onPhotoCaptured(photo.uri);
48
+
49
+ setLoadingBeforeCapture(false);
50
+ } catch (error) {
51
+ setLoadingBeforeCapture(false);
52
+ Alert.alert("Erreur");
53
+ }
54
+ }
55
+ };
56
+
57
+
58
+ return (
59
+ <SafeAreaView style={styles.outerContainer}>
60
+ <View style={styles.cameraWrapper}>
61
+ <CameraView
62
+ style={styles.camera}
63
+ facing="back"
64
+ ref={cameraRef}
65
+ onCameraReady={() => setIsReady(true)}
66
+ />
67
+
68
+ {/* Loading overlay */}
69
+ {loadingBeforeCapture && (
70
+ <>
71
+ <View style={styles.loadingOverlay}>
72
+ <Image
73
+ source={require('../src/assets/loadingCamera.gif')}
74
+ style={styles.loadingGif}
75
+ resizeMode="contain"
76
+ />
77
+ </View>
78
+ <View style={styles.touchBlocker} pointerEvents="auto" />
79
+ </>
80
+ )}
81
+
82
+ {/* Cadre de scan */}
83
+ <View style={styles.scanFrame} />
84
+ </View>
85
+
86
+ <View style={styles.buttonContainer}>
87
+ <TouchableOpacity
88
+ style={styles.button}
89
+ onPress={takePicture}
90
+ disabled={!isReady || loadingBeforeCapture}
91
+ />
92
+ </View>
93
+ </SafeAreaView>
94
+ );
95
+ }
96
+
97
+ const PRIMARY_GREEN = '#198754';
98
+ const DEEP_BLACK = '#0B0B0B';
99
+ const GLOW_WHITE = 'rgba(255, 255, 255, 0.85)';
100
+
101
+ const styles = StyleSheet.create({
102
+ outerContainer: {
103
+ flex: 1,
104
+ backgroundColor: DEEP_BLACK,
105
+ justifyContent: 'center',
106
+ alignItems: 'center',
107
+ },
108
+ cameraWrapper: {
109
+ width: width,
110
+ aspectRatio: 9 / 16,
111
+ borderRadius: 30,
112
+ overflow: 'hidden',
113
+ alignItems: 'center',
114
+ justifyContent: 'center',
115
+ position: 'relative',
116
+ },
117
+ camera: {
118
+ ...StyleSheet.absoluteFillObject,
119
+ },
120
+ scanFrame: {
121
+ position: 'absolute',
122
+ width: '95%',
123
+ height: '80%',
124
+ borderWidth: 4,
125
+ borderColor: PRIMARY_GREEN,
126
+ borderRadius: 5,
127
+ backgroundColor: 'rgba(255,255,255,0.2)',
128
+ },
129
+ loadingOverlay: {
130
+ ...StyleSheet.absoluteFillObject,
131
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
132
+ zIndex: 20,
133
+ justifyContent: 'center',
134
+ alignItems: 'center',
135
+ },
136
+ loadingGif: {
137
+ width: 100,
138
+ height: 100,
139
+ },
140
+ touchBlocker: {
141
+ ...StyleSheet.absoluteFillObject,
142
+ zIndex: 21,
143
+ backgroundColor: 'transparent',
144
+ },
145
+ cancelIcon: {
146
+ position: 'absolute',
147
+ top: 20,
148
+ left: 20,
149
+ backgroundColor: PRIMARY_GREEN,
150
+ borderRadius: 5,
151
+ padding: 8,
152
+ },
153
+ buttonContainer: {
154
+ marginTop: 25,
155
+ marginBottom: 40,
156
+ flexDirection: 'row',
157
+ justifyContent: 'center',
158
+ },
159
+ button: {
160
+ width: 80,
161
+ height: 80,
162
+ borderRadius: 50,
163
+ backgroundColor: GLOW_WHITE,
164
+ borderWidth: 5,
165
+ borderColor: PRIMARY_GREEN,
166
+ alignItems: 'center',
167
+ justifyContent: 'center',
168
+ },
169
+ text: {
170
+ fontSize: 18,
171
+ color: GLOW_WHITE,
172
+ },
173
+ container: {
174
+ flex: 1,
175
+ backgroundColor: DEEP_BLACK,
176
+ justifyContent: 'center',
177
+ alignItems: 'center',
178
+ padding: 20,
179
+ },
180
+ iconText: {
181
+ fontSize: 18,
182
+ color: GLOW_WHITE,
183
+ fontWeight: '600',
184
+ },
185
+ });
@@ -0,0 +1,228 @@
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
+ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) => {
10
+
11
+
12
+ const [image, setImage] = useState(null);
13
+ const [points, setPoints] = useState([]);
14
+ const [showResult, setShowResult] = useState(false);
15
+ const [showCustomCamera, setShowCustomCamera] = useState(false);
16
+ const viewRef = useRef(null);
17
+ const imageMeasure = useRef({ x: 0, y: 0, width: 0, height: 0 });
18
+ const selectedPointIndex = useRef(null);
19
+ const lastTap = useRef(null);
20
+
21
+ const [isLoading, setIsLoading] = useState(false);
22
+ const [showFullScreenCapture, setShowFullScreenCapture] = useState(false);
23
+
24
+
25
+
26
+
27
+
28
+ useEffect(() => {
29
+ if (openCameraFirst) {
30
+ setShowCustomCamera(true);
31
+ } else if (initialImage) {
32
+ setImage(initialImage);
33
+ }
34
+ }, [openCameraFirst, initialImage]);
35
+
36
+
37
+ useEffect(() => {
38
+ if (!image) return;
39
+
40
+ Image.getSize(image, (imgWidth, imgHeight) => {
41
+ const screenRatio = SCREEN_WIDTH / SCREEN_HEIGHT;
42
+ const imageRatio = imgWidth / imgHeight;
43
+
44
+ if (imageRatio > screenRatio) {
45
+ imageMeasure.current = {
46
+ width: SCREEN_WIDTH,
47
+ height: SCREEN_WIDTH / imageRatio,
48
+ };
49
+ } else {
50
+ imageMeasure.current = {
51
+ width: SCREEN_HEIGHT * imageRatio,
52
+ height: SCREEN_HEIGHT,
53
+ };
54
+ }
55
+ });
56
+ }, [image]);
57
+
58
+
59
+ const initializeCropBox = () => {
60
+ const { width, height } = imageMeasure.current;
61
+ if (width === 0 || height === 0 || points.length > 0) return;
62
+ const boxWidth = width * 0.6;
63
+ const boxHeight = height * 0.6;
64
+ const centerX = width / 2;
65
+ const centerY = height / 2;
66
+ setPoints([
67
+ { x: centerX - boxWidth / 2, y: centerY - boxHeight / 2 },
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
+ ]);
72
+ };
73
+
74
+
75
+ const onImageLayout = (e) => {
76
+ const layout = e.nativeEvent.layout;
77
+ imageMeasure.current = {
78
+ x: layout.x,
79
+ y: layout.y,
80
+ width: layout.width,
81
+ height: layout.height
82
+ };
83
+ initializeCropBox();
84
+ };
85
+
86
+ const createPath = () => {
87
+ if (points.length < 1) return '';
88
+ let path = `M ${points[0].x} ${points[0].y} `;
89
+ points.forEach(point => path += `L ${point.x} ${point.y} `);
90
+ return path + 'Z';
91
+ };
92
+
93
+ const handleTap = (e) => {
94
+ if (!image || showResult) return;
95
+ const now = Date.now();
96
+ const { locationX: tapX, locationY: tapY } = e.nativeEvent;
97
+ if (lastTap.current && now - lastTap.current < 300) {
98
+ const exists = points.some(p => Math.abs(p.x - tapX) < 15 && Math.abs(p.y - tapY) < 15);
99
+ if (!exists) setPoints([...points, { x: tapX, y: tapY }]);
100
+ lastTap.current = null;
101
+ } else {
102
+ const index = points.findIndex(p => Math.abs(p.x - tapX) < 20 && Math.abs(p.y - tapY) < 20);
103
+ if (index !== -1) selectedPointIndex.current = index;
104
+ lastTap.current = now;
105
+ }
106
+ };
107
+
108
+ const handleMove = (e) => {
109
+ if (showResult || selectedPointIndex.current === null) return;
110
+ const { locationX: moveX, locationY: moveY } = e.nativeEvent;
111
+ const boundedX = Math.max(0, Math.min(moveX, imageMeasure.current.width));
112
+ const boundedY = Math.max(0, Math.min(moveY, imageMeasure.current.height));
113
+ setPoints(prev =>
114
+ prev.map((p, i) => i === selectedPointIndex.current ? { x: boundedX, y: boundedY } : p)
115
+ );
116
+ };
117
+
118
+ const handleRelease = () => {
119
+ selectedPointIndex.current = null;
120
+ };
121
+
122
+ const handleReset = () => {
123
+ setPoints([]);
124
+ initializeCropBox();
125
+ };
126
+
127
+
128
+ return (
129
+ <View style={styles.container}>
130
+
131
+ {showCustomCamera ? (
132
+ <CustomCamera
133
+ onPhotoCaptured={(uri) => { setImage(uri); setShowCustomCamera(false); }}
134
+ onCancel={() => setShowCustomCamera(false)}
135
+ />
136
+ ) : (
137
+ <>
138
+ {!showResult && (
139
+ <View style={image ? styles.buttonContainer : styles.centerButtonsContainer}>
140
+
141
+ {image && (
142
+ <TouchableOpacity style={styles.button} onPress={handleReset}>
143
+ <Text style={styles.buttonText}>Reset</Text>
144
+ </TouchableOpacity>
145
+ )}
146
+ {image && (
147
+ <TouchableOpacity
148
+ style={styles.button}
149
+ onPress={async () => {
150
+ setShowFullScreenCapture(true);
151
+ setIsLoading(true);
152
+ setShowResult(true);
153
+ try {
154
+ await new Promise((resolve) => requestAnimationFrame(resolve));
155
+ const capturedUri = await captureRef(viewRef, {
156
+ format: 'png',
157
+ quality: 1,
158
+ });
159
+
160
+
161
+ const enhancedUri = await enhanceImage(capturedUri ,addheight);
162
+ const name = `IMAGE XTK${Date.now()}.png`;
163
+
164
+
165
+ if (onConfirm) {
166
+ onConfirm(enhancedUri, name);
167
+ }
168
+ } catch (error) {
169
+ console.error("Erreur lors de la capture :", error);
170
+ alert("Erreur lors de la capture !");
171
+ } finally {
172
+ setShowResult(false);
173
+ setIsLoading(false);
174
+ setShowFullScreenCapture(false);
175
+ }
176
+ }}
177
+ >
178
+ <Text style={styles.buttonText}>Confirm</Text>
179
+ </TouchableOpacity>
180
+ )}
181
+
182
+ </View>
183
+ )}
184
+
185
+
186
+ {image && (
187
+ <View
188
+ ref={viewRef}
189
+ collapsable={false}
190
+ style={showFullScreenCapture ? styles.fullscreenImageContainer : styles.imageContainer}
191
+ onStartShouldSetResponder={() => true}
192
+ onResponderStart={handleTap}
193
+ onResponderMove={handleMove}
194
+ onResponderRelease={handleRelease}
195
+ >
196
+ <Image source={{ uri: image }} style={styles.image} onLayout={onImageLayout} />
197
+ <Svg style={styles.overlay}>
198
+ <Path
199
+ d={`M 0 0 H ${imageMeasure.current.width} V ${imageMeasure.current.height} H 0 Z ${createPath()}`}
200
+ fill={showResult ? 'black' : 'rgba(0, 0, 0, 0.7)'}
201
+ fillRule="evenodd"
202
+ />
203
+ {!showResult && points.length > 0 && (
204
+ <Path d={createPath()} fill="transparent" stroke="white" strokeWidth={2} />
205
+ )}
206
+ {!showResult && points.map((point, index) => (
207
+ <Circle key={index} cx={point.x} cy={point.y} r={10} fill="white" />
208
+ ))}
209
+ </Svg>
210
+ </View>
211
+ )}
212
+ </>
213
+ )}
214
+
215
+ <Modal visible={isLoading} transparent animationType="fade">
216
+ <View style={styles.loadingOverlay}>
217
+ <Image
218
+ source={require('../src/assets/loadingCamera.gif')}
219
+ style={styles.loadingGif}
220
+ resizeMode="contain"
221
+ />
222
+ </View>
223
+ </Modal>
224
+ </View>
225
+ );
226
+ };
227
+
228
+ export default ImageCropper;
@@ -0,0 +1,181 @@
1
+ import { StyleSheet, Dimensions } from 'react-native';
2
+
3
+ const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
4
+ const IMAGE_HEIGHT = SCREEN_HEIGHT;
5
+ const IMAGE_WIDTH = SCREEN_WIDTH;
6
+
7
+ const PRIMARY_GREEN = '#198754';
8
+ const DEEP_BLACK = '#0B0B0B';
9
+ const GLOW_WHITE = 'rgba(255, 255, 255, 0.85)';
10
+
11
+ const styles = StyleSheet.create({
12
+ container: {
13
+ flex: 1,
14
+ alignItems: 'center',
15
+ backgroundColor: DEEP_BLACK,
16
+ },
17
+ buttonContainer: {
18
+ position: 'absolute',
19
+ bottom: 50,
20
+ left: 0,
21
+ right: 0,
22
+ flexDirection: 'row',
23
+ flexWrap: 'wrap',
24
+ justifyContent: 'center',
25
+ alignItems: 'center',
26
+ paddingHorizontal: 10,
27
+ zIndex: 10,
28
+ },
29
+ button: {
30
+ backgroundColor: PRIMARY_GREEN,
31
+ paddingVertical: 18,
32
+ paddingHorizontal: 30,
33
+ borderRadius: 12,
34
+ margin: 6,
35
+ alignItems: 'center',
36
+ justifyContent: 'center',
37
+ borderWidth: 0,
38
+ width: SCREEN_WIDTH * 0.4,
39
+ height: 60,
40
+ },
41
+ buttonText: {
42
+ color: 'white',
43
+ fontSize: 18,
44
+ fontWeight: '600',
45
+ textAlign: 'center',
46
+ },
47
+ iconText: {
48
+ color: PRIMARY_GREEN,
49
+ fontSize: 16,
50
+ fontWeight: '600',
51
+ textAlign: 'center',
52
+ },
53
+ imageContainer: {
54
+ width: IMAGE_WIDTH,
55
+ height: IMAGE_HEIGHT,
56
+ justifyContent: 'center',
57
+ alignItems: 'center',
58
+ overflow: 'hidden',
59
+ backgroundColor: 'black',
60
+ },
61
+
62
+ image: {
63
+ width: '100%',
64
+ height: '100%',
65
+ resizeMode: 'contain',
66
+ },
67
+
68
+ overlay: {
69
+ position: 'absolute',
70
+ top: 0,
71
+ left: 0,
72
+ width: '100%',
73
+ height: '100%',
74
+ zIndex: 1,
75
+ },
76
+
77
+ croppedImage: {
78
+ width: IMAGE_WIDTH,
79
+ height: IMAGE_HEIGHT,
80
+ resizeMode: 'cover',
81
+ marginTop: 10,
82
+ borderWidth: 2,
83
+ borderColor: 'white',
84
+ borderRadius: 10,
85
+ },
86
+ fixedButtonContainer: {
87
+ position: 'absolute',
88
+ bottom: 100,
89
+ left: 0,
90
+ right: 0,
91
+ flexDirection: 'row',
92
+ justifyContent: 'center',
93
+ flexWrap: 'wrap',
94
+ gap: 10,
95
+ paddingHorizontal: 10,
96
+ },
97
+ fixedButton: {
98
+ backgroundColor: PRIMARY_GREEN,
99
+ paddingVertical: 18,
100
+ paddingHorizontal: 30,
101
+ borderRadius: 12,
102
+ margin: 6,
103
+ alignItems: 'center',
104
+ justifyContent: 'center',
105
+ borderWidth: 0,
106
+ width: SCREEN_WIDTH * 0.4,
107
+ height: 60,
108
+ },
109
+ centerButtonsContainer: {
110
+ position: 'absolute',
111
+ bottom: 20,
112
+ left: 0,
113
+ right: 0,
114
+ alignItems: 'center',
115
+ justifyContent: 'center',
116
+ zIndex: 10,
117
+ gap: 10,
118
+ },
119
+ welcomeText: {
120
+ position: 'absolute',
121
+ top: '40%',
122
+ left: 20,
123
+ right: 20,
124
+ textAlign: 'center',
125
+ color: GLOW_WHITE,
126
+ fontSize: 40,
127
+ fontWeight: '800',
128
+ zIndex: 10,
129
+ },
130
+ startButton: {
131
+ backgroundColor: PRIMARY_GREEN,
132
+ paddingVertical: 18,
133
+ paddingHorizontal: 30,
134
+ borderRadius: 12,
135
+ margin: 6,
136
+ alignItems: 'center',
137
+ justifyContent: 'center',
138
+ borderWidth: 0,
139
+ width: SCREEN_WIDTH * 0.9,
140
+ height: 70,
141
+ },
142
+ startButtonText: {
143
+ color: 'white',
144
+ fontSize: 18,
145
+ fontWeight: '600',
146
+ textAlign: 'center',
147
+ },
148
+
149
+ loadingOverlay: {
150
+ flex: 1,
151
+ backgroundColor: 'rgba(0,0,0,0.5)',
152
+ justifyContent: 'center',
153
+ alignItems: 'center',
154
+ position: 'absolute',
155
+ top: 0,
156
+ bottom: 0,
157
+ left: 0,
158
+ right: 0,
159
+ zIndex: 9999,
160
+ },
161
+ loadingGif: {
162
+ width: 120,
163
+ height: 120,
164
+ },
165
+
166
+ fullscreenImageContainer: {
167
+ position: 'absolute',
168
+ top: 0,
169
+ left: 0,
170
+ width: SCREEN_WIDTH,
171
+ height: SCREEN_HEIGHT,
172
+ backgroundColor: 'black',
173
+ justifyContent: 'center',
174
+ alignItems: 'center',
175
+ zIndex: 9999,
176
+ },
177
+
178
+
179
+ });
180
+
181
+ export default styles;
@@ -0,0 +1,28 @@
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
+ }
28
+ };
Binary file