@umituz/react-native-image 1.1.5 → 1.2.1

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.
Files changed (37) hide show
  1. package/package.json +25 -10
  2. package/src/domain/entities/EditorTypes.ts +180 -0
  3. package/src/domain/entities/ImageFilterTypes.ts +70 -0
  4. package/src/index.ts +54 -0
  5. package/src/infrastructure/services/ImageAIEnhancementService.ts +136 -0
  6. package/src/infrastructure/services/ImageAnnotationService.ts +189 -0
  7. package/src/infrastructure/services/ImageBatchService.ts +199 -0
  8. package/src/infrastructure/services/ImageEditorService.ts +274 -0
  9. package/src/infrastructure/services/ImageFilterService.ts +168 -0
  10. package/src/infrastructure/services/ImageMetadataService.ts +187 -0
  11. package/src/infrastructure/services/ImageSpecializedEnhancementService.ts +57 -0
  12. package/src/infrastructure/utils/AIImageAnalysisUtils.ts +122 -0
  13. package/src/infrastructure/utils/CanvasRenderingService.ts +134 -0
  14. package/src/infrastructure/utils/CropTool.ts +260 -0
  15. package/src/infrastructure/utils/DrawingEngine.ts +210 -0
  16. package/src/infrastructure/utils/FilterEffects.ts +51 -0
  17. package/src/infrastructure/utils/FilterProcessor.ts +361 -0
  18. package/src/infrastructure/utils/ImageQualityPresets.ts +110 -0
  19. package/src/infrastructure/utils/LayerManager.ts +158 -0
  20. package/src/infrastructure/utils/ShapeRenderer.ts +168 -0
  21. package/src/infrastructure/utils/TextEditor.ts +273 -0
  22. package/src/presentation/components/CropComponent.tsx +183 -0
  23. package/src/presentation/components/Editor.tsx +261 -0
  24. package/src/presentation/components/EditorCanvas.tsx +135 -0
  25. package/src/presentation/components/EditorPanel.tsx +321 -0
  26. package/src/presentation/components/EditorToolbar.tsx +180 -0
  27. package/src/presentation/components/FilterSlider.tsx +123 -0
  28. package/src/presentation/components/GalleryHeader.tsx +87 -25
  29. package/src/presentation/components/ImageGallery.tsx +97 -48
  30. package/src/presentation/hooks/useEditorTools.ts +188 -0
  31. package/src/presentation/hooks/useImage.ts +33 -2
  32. package/src/presentation/hooks/useImageAIEnhancement.ts +33 -0
  33. package/src/presentation/hooks/useImageAnnotation.ts +32 -0
  34. package/src/presentation/hooks/useImageBatch.ts +33 -0
  35. package/src/presentation/hooks/useImageEditor.ts +165 -38
  36. package/src/presentation/hooks/useImageFilter.ts +38 -0
  37. package/src/presentation/hooks/useImageMetadata.ts +28 -0
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Presentation - Editor Panel Component
3
+ *
4
+ * Configuration panel for selected tools
5
+ */
6
+
7
+ import React, { useState, useCallback } from 'react';
8
+ import { View, StyleSheet, TouchableOpacity, Text } from 'react-native';
9
+ import Slider from '@react-native-community/slider';
10
+ import { EditorTool } from '../../domain/entities/EditorTypes';
11
+
12
+ interface EditorPanelProps {
13
+ selectedTool: EditorTool;
14
+ onToolConfigChange?: (config: any) => void;
15
+ backgroundColor?: string;
16
+ }
17
+
18
+ export function EditorPanel({ selectedTool, onToolConfigChange, backgroundColor = '#f8f9fa' }: EditorPanelProps) {
19
+ const [brushSize, setBrushSize] = useState(5);
20
+ const [brushOpacity, setBrushOpacity] = useState(1);
21
+ const [brushColor, setBrushColor] = useState('#000000');
22
+ const [brushStyle, setBrushStyle] = useState('normal');
23
+
24
+ const [fontSize, setFontSize] = useState(16);
25
+ const [textColor, setTextColor] = useState('#000000');
26
+
27
+ const [cropAspectRatio, setCropAspectRatio] = useState(0);
28
+ const [cropGrid, setCropGrid] = useState(true);
29
+
30
+ const handleConfigChange = useCallback((config: any) => {
31
+ onToolConfigChange?.(config);
32
+ }, [onToolConfigChange]);
33
+
34
+ const renderBrushPanel = () => (
35
+ <View style={styles.panel}>
36
+ <View style={styles.configRow}>
37
+ <Text style={styles.label}>Size</Text>
38
+ <Slider
39
+ style={styles.slider}
40
+ minimumValue={1}
41
+ maximumValue={50}
42
+ value={brushSize}
43
+ onValueChange={setBrushSize}
44
+ onSlidingComplete={() => handleConfigChange({
45
+ size: brushSize,
46
+ opacity: brushOpacity,
47
+ color: brushColor,
48
+ style: brushStyle
49
+ })}
50
+ />
51
+ <Text style={styles.value}>{Math.round(brushSize)}</Text>
52
+ </View>
53
+
54
+ <View style={styles.configRow}>
55
+ <Text style={styles.label}>Opacity</Text>
56
+ <Slider
57
+ style={styles.slider}
58
+ minimumValue={0}
59
+ maximumValue={1}
60
+ value={brushOpacity}
61
+ onValueChange={setBrushOpacity}
62
+ onSlidingComplete={() => handleConfigChange({
63
+ size: brushSize,
64
+ opacity: brushOpacity,
65
+ color: brushColor,
66
+ style: brushStyle
67
+ })}
68
+ />
69
+ <Text style={styles.value}>{Math.round(brushOpacity * 100)}%</Text>
70
+ </View>
71
+
72
+ <View style={styles.configRow}>
73
+ <Text style={styles.label}>Color</Text>
74
+ <View style={styles.colorRow}>
75
+ {['#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff', '#ffff00'].map(color => (
76
+ <TouchableOpacity
77
+ key={color}
78
+ style={[styles.colorButton, { backgroundColor: color }]}
79
+ onPress={() => {
80
+ setBrushColor(color);
81
+ handleConfigChange({
82
+ size: brushSize,
83
+ opacity: brushOpacity,
84
+ color,
85
+ style: brushStyle
86
+ });
87
+ }}
88
+ />
89
+ ))}
90
+ </View>
91
+ </View>
92
+
93
+ <View style={styles.configRow}>
94
+ <Text style={styles.label}>Style</Text>
95
+ <View style={styles.styleRow}>
96
+ {['normal', 'marker', 'spray'].map(style => (
97
+ <TouchableOpacity
98
+ key={style}
99
+ style={[styles.styleButton, brushStyle === style && styles.selectedStyle]}
100
+ onPress={() => {
101
+ setBrushStyle(style);
102
+ handleConfigChange({
103
+ size: brushSize,
104
+ opacity: brushOpacity,
105
+ color: brushColor,
106
+ style
107
+ });
108
+ }}
109
+ >
110
+ <Text style={styles.styleText}>{style}</Text>
111
+ </TouchableOpacity>
112
+ ))}
113
+ </View>
114
+ </View>
115
+ </View>
116
+ );
117
+
118
+ const renderTextPanel = () => (
119
+ <View style={styles.panel}>
120
+ <View style={styles.configRow}>
121
+ <Text style={styles.label}>Font Size</Text>
122
+ <Slider
123
+ style={styles.slider}
124
+ minimumValue={8}
125
+ maximumValue={72}
126
+ value={fontSize}
127
+ onValueChange={setFontSize}
128
+ onSlidingComplete={() => handleConfigChange({ fontSize, color: textColor })}
129
+ />
130
+ <Text style={styles.value}>{Math.round(fontSize)}</Text>
131
+ </View>
132
+
133
+ <View style={styles.configRow}>
134
+ <Text style={styles.label}>Color</Text>
135
+ <View style={styles.colorRow}>
136
+ {['#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff', '#ffff00'].map(color => (
137
+ <TouchableOpacity
138
+ key={color}
139
+ style={[styles.colorButton, { backgroundColor: color }]}
140
+ onPress={() => {
141
+ setTextColor(color);
142
+ handleConfigChange({ fontSize, color });
143
+ }}
144
+ />
145
+ ))}
146
+ </View>
147
+ </View>
148
+ </View>
149
+ );
150
+
151
+ const renderCropPanel = () => (
152
+ <View style={styles.panel}>
153
+ <View style={styles.configRow}>
154
+ <Text style={styles.label}>Aspect Ratio</Text>
155
+ <View style={styles.ratioRow}>
156
+ {[
157
+ { label: 'Free', value: 0 },
158
+ { label: '1:1', value: 1 },
159
+ { label: '4:3', value: 1.333333333 },
160
+ { label: '16:9', value: 1.777777777 },
161
+ ].map(ratio => (
162
+ <TouchableOpacity
163
+ key={ratio.value}
164
+ style={[styles.ratioButton, cropAspectRatio === ratio.value && styles.selectedRatio]}
165
+ onPress={() => {
166
+ setCropAspectRatio(ratio.value);
167
+ handleConfigChange({ aspectRatio: ratio.value, grid: cropGrid });
168
+ }}
169
+ >
170
+ <Text style={styles.ratioText}>{ratio.label}</Text>
171
+ </TouchableOpacity>
172
+ ))}
173
+ </View>
174
+ </View>
175
+
176
+ <View style={styles.configRow}>
177
+ <Text style={styles.label}>Show Grid</Text>
178
+ <TouchableOpacity
179
+ style={[styles.toggleButton, cropGrid && styles.selectedToggle]}
180
+ onPress={() => {
181
+ setCropGrid(!cropGrid);
182
+ handleConfigChange({ aspectRatio: cropAspectRatio, grid: !cropGrid });
183
+ }}
184
+ >
185
+ <Text style={styles.toggleText}>{cropGrid ? 'ON' : 'OFF'}</Text>
186
+ </TouchableOpacity>
187
+ </View>
188
+ </View>
189
+ );
190
+
191
+ const renderPanel = () => {
192
+ switch (selectedTool) {
193
+ case EditorTool.BRUSH:
194
+ case EditorTool.ERASER:
195
+ return renderBrushPanel();
196
+ case EditorTool.TEXT:
197
+ return renderTextPanel();
198
+ case EditorTool.CROP:
199
+ return renderCropPanel();
200
+ default:
201
+ return null;
202
+ }
203
+ };
204
+
205
+ return (
206
+ <View style={[styles.container, { backgroundColor }]}>
207
+ {renderPanel()}
208
+ </View>
209
+ );
210
+ }
211
+
212
+ const styles = StyleSheet.create({
213
+ container: {
214
+ padding: 16,
215
+ minHeight: 200,
216
+ borderTopWidth: 1,
217
+ borderTopColor: '#e0e0e0',
218
+ },
219
+ panel: {
220
+ gap: 16,
221
+ },
222
+ configRow: {
223
+ gap: 8,
224
+ },
225
+ label: {
226
+ fontSize: 14,
227
+ fontWeight: '600',
228
+ color: '#333333',
229
+ marginBottom: 8,
230
+ },
231
+ value: {
232
+ fontSize: 12,
233
+ color: '#666666',
234
+ textAlign: 'center',
235
+ minWidth: 40,
236
+ },
237
+ slider: {
238
+ flex: 1,
239
+ height: 40,
240
+ },
241
+ colorRow: {
242
+ flexDirection: 'row',
243
+ gap: 8,
244
+ flexWrap: 'wrap',
245
+ },
246
+ colorButton: {
247
+ width: 30,
248
+ height: 30,
249
+ borderRadius: 6,
250
+ borderWidth: 2,
251
+ borderColor: '#d0d0d0',
252
+ },
253
+ styleRow: {
254
+ flexDirection: 'row',
255
+ gap: 8,
256
+ },
257
+ styleButton: {
258
+ paddingVertical: 8,
259
+ paddingHorizontal: 12,
260
+ borderRadius: 6,
261
+ borderWidth: 1,
262
+ borderColor: '#d0d0d0',
263
+ backgroundColor: '#ffffff',
264
+ },
265
+ selectedStyle: {
266
+ backgroundColor: '#007bff',
267
+ borderColor: '#007bff',
268
+ },
269
+ styleText: {
270
+ fontSize: 12,
271
+ fontWeight: '500',
272
+ color: '#333333',
273
+ },
274
+ styleTextSelected: {
275
+ color: '#ffffff',
276
+ },
277
+ ratioRow: {
278
+ flexDirection: 'row',
279
+ gap: 8,
280
+ },
281
+ ratioButton: {
282
+ paddingVertical: 8,
283
+ paddingHorizontal: 12,
284
+ borderRadius: 6,
285
+ borderWidth: 1,
286
+ borderColor: '#d0d0d0',
287
+ backgroundColor: '#ffffff',
288
+ },
289
+ selectedRatio: {
290
+ backgroundColor: '#007bff',
291
+ borderColor: '#007bff',
292
+ },
293
+ ratioText: {
294
+ fontSize: 12,
295
+ fontWeight: '500',
296
+ color: '#333333',
297
+ },
298
+ ratioTextSelected: {
299
+ color: '#ffffff',
300
+ },
301
+ toggleButton: {
302
+ paddingVertical: 8,
303
+ paddingHorizontal: 16,
304
+ borderRadius: 6,
305
+ borderWidth: 1,
306
+ borderColor: '#d0d0d0',
307
+ backgroundColor: '#ffffff',
308
+ },
309
+ selectedToggle: {
310
+ backgroundColor: '#007bff',
311
+ borderColor: '#007bff',
312
+ },
313
+ toggleText: {
314
+ fontSize: 12,
315
+ fontWeight: '500',
316
+ color: '#333333',
317
+ },
318
+ toggleTextSelected: {
319
+ color: '#ffffff',
320
+ },
321
+ });
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Presentation - Editor Toolbar Component
3
+ *
4
+ * Toolbar with tools and configuration options
5
+ */
6
+
7
+ import React from 'react';
8
+ import { View, TouchableOpacity, StyleSheet, ScrollView, Text } from 'react-native';
9
+ import { EditorTool, ShapeType } from '../../domain/entities/EditorTypes';
10
+
11
+ interface EditorToolbarProps {
12
+ selectedTool: EditorTool;
13
+ onToolSelect: (tool: EditorTool) => void;
14
+ onShapeSelect?: (shape: ShapeType) => void;
15
+ showShapes?: boolean;
16
+ backgroundColor?: string;
17
+ }
18
+
19
+ interface ToolButtonProps {
20
+ tool: EditorTool;
21
+ icon: string;
22
+ label: string;
23
+ isSelected: boolean;
24
+ onPress: () => void;
25
+ }
26
+
27
+ function ToolButton({ tool, icon, label, isSelected, onPress }: ToolButtonProps) {
28
+ return (
29
+ <TouchableOpacity
30
+ style={[styles.toolButton, isSelected && styles.selectedTool]}
31
+ onPress={onPress}
32
+ activeOpacity={0.7}
33
+ >
34
+ <View style={styles.toolIcon}>
35
+ <Text style={[styles.iconText, isSelected && styles.iconTextSelected]}>{icon}</Text>
36
+ </View>
37
+ <Text style={[styles.toolLabel, isSelected && styles.toolLabelSelected]}>{label}</Text>
38
+ </TouchableOpacity>
39
+ );
40
+ }
41
+
42
+ export function EditorToolbar({
43
+ selectedTool,
44
+ onToolSelect,
45
+ onShapeSelect,
46
+ showShapes = false,
47
+ backgroundColor = '#f8f9fa',
48
+ }: EditorToolbarProps) {
49
+ const mainTools = [
50
+ { tool: EditorTool.MOVE as EditorTool, icon: '↔', label: 'Move' },
51
+ { tool: EditorTool.BRUSH as EditorTool, icon: '✏', label: 'Brush' },
52
+ { tool: EditorTool.ERASER as EditorTool, icon: '⌫', label: 'Eraser' },
53
+ { tool: EditorTool.TEXT as EditorTool, icon: 'T', label: 'Text' },
54
+ { tool: EditorTool.SHAPE as EditorTool, icon: '◇', label: 'Shape' },
55
+ { tool: EditorTool.CROP as EditorTool, icon: '✂', label: 'Crop' },
56
+ { tool: EditorTool.FILTER as EditorTool, icon: '🎨', label: 'Filter' },
57
+ ];
58
+
59
+ const shapeTools = [
60
+ { shape: ShapeType.RECTANGLE, icon: '▢', label: 'Rectangle' },
61
+ { shape: ShapeType.CIRCLE, icon: '○', label: 'Circle' },
62
+ { shape: ShapeType.LINE, icon: '╱', label: 'Line' },
63
+ { shape: ShapeType.ARROW, icon: '→', label: 'Arrow' },
64
+ { shape: ShapeType.TRIANGLE, icon: '△', label: 'Triangle' },
65
+ { shape: ShapeType.STAR, icon: '★', label: 'Star' },
66
+ { shape: ShapeType.HEART, icon: '♥', label: 'Heart' },
67
+ ];
68
+
69
+ return (
70
+ <View style={[styles.container, { backgroundColor }]}>
71
+ <ScrollView
72
+ horizontal
73
+ showsHorizontalScrollIndicator={false}
74
+ style={styles.scrollContainer}
75
+ >
76
+ <View style={styles.toolGroup}>
77
+ {mainTools.map(({ tool, icon, label }) => (
78
+ <ToolButton
79
+ key={tool}
80
+ tool={tool}
81
+ icon={icon}
82
+ label={label}
83
+ isSelected={selectedTool === tool}
84
+ onPress={() => onToolSelect(tool)}
85
+ />
86
+ ))}
87
+ </View>
88
+
89
+ {showShapes && onShapeSelect && (
90
+ <View style={[styles.toolGroup, styles.shapeGroup]}>
91
+ {shapeTools.map(({ shape, icon, label }) => (
92
+ <TouchableOpacity
93
+ key={shape}
94
+ style={styles.shapeButton}
95
+ onPress={() => onShapeSelect(shape)}
96
+ activeOpacity={0.7}
97
+ >
98
+ <View style={styles.toolIcon}>
99
+ <Text style={styles.iconText}>{icon}</Text>
100
+ </View>
101
+ <Text style={styles.toolLabel}>{label}</Text>
102
+ </TouchableOpacity>
103
+ ))}
104
+ </View>
105
+ )}
106
+ </ScrollView>
107
+ </View>
108
+ );
109
+ }
110
+
111
+ const styles = StyleSheet.create({
112
+ container: {
113
+ paddingVertical: 8,
114
+ paddingHorizontal: 4,
115
+ borderBottomWidth: 1,
116
+ borderBottomColor: '#e0e0e0',
117
+ backgroundColor: '#f8f9fa',
118
+ },
119
+ scrollContainer: {
120
+ flex: 1,
121
+ },
122
+ toolGroup: {
123
+ flexDirection: 'row',
124
+ alignItems: 'center',
125
+ paddingHorizontal: 8,
126
+ },
127
+ shapeGroup: {
128
+ borderLeftWidth: 1,
129
+ borderLeftColor: '#e0e0e0',
130
+ marginLeft: 16,
131
+ },
132
+ toolButton: {
133
+ alignItems: 'center',
134
+ justifyContent: 'center',
135
+ paddingVertical: 8,
136
+ paddingHorizontal: 12,
137
+ marginHorizontal: 2,
138
+ borderRadius: 8,
139
+ backgroundColor: '#ffffff',
140
+ borderWidth: 1,
141
+ borderColor: '#d0d0d0',
142
+ minWidth: 60,
143
+ },
144
+ selectedTool: {
145
+ backgroundColor: '#007bff',
146
+ borderColor: '#007bff',
147
+ },
148
+ shapeButton: {
149
+ alignItems: 'center',
150
+ justifyContent: 'center',
151
+ paddingVertical: 6,
152
+ paddingHorizontal: 8,
153
+ marginHorizontal: 2,
154
+ borderRadius: 6,
155
+ backgroundColor: '#ffffff',
156
+ borderWidth: 1,
157
+ borderColor: '#d0d0d0',
158
+ minWidth: 50,
159
+ },
160
+ toolIcon: {
161
+ marginBottom: 2,
162
+ },
163
+ iconText: {
164
+ fontSize: 16,
165
+ fontWeight: '600',
166
+ color: '#333333',
167
+ },
168
+ iconTextSelected: {
169
+ color: '#ffffff',
170
+ },
171
+ toolLabel: {
172
+ fontSize: 10,
173
+ fontWeight: '500',
174
+ color: '#666666',
175
+ textAlign: 'center',
176
+ },
177
+ toolLabelSelected: {
178
+ color: '#ffffff',
179
+ },
180
+ });
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Presentation - Filter Slider Component
3
+ *
4
+ * Slider for adjusting filter parameters
5
+ */
6
+
7
+ import React from 'react';
8
+ import { View, StyleSheet, Text } from 'react-native';
9
+ import Slider from '@react-native-community/slider';
10
+
11
+ interface FilterSliderProps {
12
+ label: string;
13
+ value: number;
14
+ onValueChange: (value: number) => void;
15
+ minimumValue?: number;
16
+ maximumValue?: number;
17
+ step?: number;
18
+ valueFormat?: (value: number) => string;
19
+ color?: string;
20
+ }
21
+
22
+ export function FilterSlider({
23
+ label,
24
+ value,
25
+ onValueChange,
26
+ minimumValue = 0,
27
+ maximumValue = 100,
28
+ step = 1,
29
+ valueFormat,
30
+ color = '#007bff',
31
+ }: FilterSliderProps) {
32
+ const formatValue = (val: number): string => {
33
+ if (valueFormat) return valueFormat(val);
34
+ return `${Math.round(val)}`;
35
+ };
36
+
37
+ return (
38
+ <View style={styles.container}>
39
+ <View style={styles.labelContainer}>
40
+ <Text style={styles.label}>{label}</Text>
41
+ <Text style={[styles.value, { color }]}>
42
+ {formatValue(value)}
43
+ </Text>
44
+ </View>
45
+
46
+ <View style={[styles.sliderTrack, { backgroundColor: `${color}20` }]}>
47
+ <View
48
+ style={[
49
+ styles.sliderFill,
50
+ {
51
+ width: `${((value - minimumValue) / (maximumValue - minimumValue)) * 100}%`,
52
+ backgroundColor: color
53
+ }
54
+ ]}
55
+ />
56
+ <Slider
57
+ style={styles.slider}
58
+ minimumValue={minimumValue}
59
+ maximumValue={maximumValue}
60
+ value={value}
61
+ onValueChange={onValueChange}
62
+ step={step}
63
+ minimumTrackTintColor="transparent"
64
+ maximumTrackTintColor="transparent"
65
+ thumbTintColor={color}
66
+ />
67
+ </View>
68
+ </View>
69
+ );
70
+ }
71
+
72
+ const styles = StyleSheet.create({
73
+ container: {
74
+ paddingVertical: 8,
75
+ gap: 8,
76
+ },
77
+ labelContainer: {
78
+ flexDirection: 'row',
79
+ justifyContent: 'space-between',
80
+ alignItems: 'center',
81
+ },
82
+ label: {
83
+ fontSize: 14,
84
+ fontWeight: '500',
85
+ color: '#333333',
86
+ },
87
+ value: {
88
+ fontSize: 14,
89
+ fontWeight: '600',
90
+ },
91
+ sliderTrack: {
92
+ height: 6,
93
+ borderRadius: 3,
94
+ position: 'relative',
95
+ },
96
+ sliderFill: {
97
+ height: '100%',
98
+ borderRadius: 3,
99
+ position: 'absolute',
100
+ left: 0,
101
+ top: 0,
102
+ },
103
+ slider: {
104
+ position: 'absolute',
105
+ left: 0,
106
+ top: 0,
107
+ width: '100%',
108
+ height: '100%',
109
+ opacity: 0,
110
+ },
111
+ thumb: {
112
+ width: 20,
113
+ height: 20,
114
+ borderRadius: 10,
115
+ borderWidth: 2,
116
+ borderColor: '#ffffff',
117
+ shadowColor: '#000000',
118
+ shadowOffset: { width: 0, height: 2 },
119
+ shadowOpacity: 0.2,
120
+ shadowRadius: 2,
121
+ elevation: 3,
122
+ },
123
+ });