@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
@@ -1,32 +1,66 @@
1
1
  /**
2
2
  * Presentation - Gallery Header Component
3
3
  *
4
- * NOTE: This component should be implemented by consumer app
5
- * using their design system and safe area handling
4
+ * High-performance, premium header for the Image Gallery.
5
+ * Uses design system tokens and handles safe areas.
6
6
  */
7
7
 
8
8
  import React from 'react';
9
- import { View, TouchableOpacity, StyleSheet, Text } from 'react-native';
9
+ import { View, TouchableOpacity, StyleSheet } from 'react-native';
10
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
11
+ import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
12
+ import { AtomicText } from '@umituz/react-native-design-system-atoms';
10
13
 
11
14
  interface GalleryHeaderProps {
12
15
  onEdit?: () => void;
13
16
  onClose: () => void;
17
+ title?: string;
14
18
  }
15
19
 
16
- export function GalleryHeader({ onEdit, onClose }: GalleryHeaderProps) {
20
+ export function GalleryHeader({ onEdit, onClose, title }: GalleryHeaderProps) {
21
+ const insets = useSafeAreaInsets();
22
+ const tokens = useAppDesignTokens();
23
+
17
24
  return (
18
- <View style={styles.container}>
19
- {onEdit ? (
20
- <TouchableOpacity style={styles.button} onPress={onEdit}>
21
- <Text style={styles.buttonText}>Edit</Text>
22
- </TouchableOpacity>
23
- ) : (
24
- <View style={styles.spacer} />
25
- )}
25
+ <View style={[
26
+ styles.container,
27
+ {
28
+ paddingTop: Math.max(insets.top, 24),
29
+ backgroundColor: 'rgba(0, 0, 0, 0.4)'
30
+ }
31
+ ]}>
32
+ <View style={styles.leftSection}>
33
+ {onEdit ? (
34
+ <TouchableOpacity
35
+ style={[styles.actionButton, { backgroundColor: 'rgba(255, 255, 255, 0.15)' }]}
36
+ onPress={onEdit}
37
+ activeOpacity={0.7}
38
+ >
39
+ <AtomicText style={styles.buttonText}>Edit</AtomicText>
40
+ </TouchableOpacity>
41
+ ) : (
42
+ <View style={styles.spacer} />
43
+ )}
44
+ </View>
45
+
46
+ <View style={styles.centerSection}>
47
+ {title ? (
48
+ <AtomicText type="bodyMedium" style={styles.titleText}>
49
+ {title}
50
+ </AtomicText>
51
+ ) : null}
52
+ </View>
26
53
 
27
- <TouchableOpacity style={styles.button} onPress={onClose}>
28
- <Text style={styles.buttonText}>✕</Text>
29
- </TouchableOpacity>
54
+ <View style={styles.rightSection}>
55
+ <TouchableOpacity
56
+ style={[styles.closeButton, { backgroundColor: 'rgba(0, 0, 0, 0.4)' }]}
57
+ onPress={onClose}
58
+ activeOpacity={0.7}
59
+ hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
60
+ >
61
+ <AtomicText style={styles.closeIcon}>✕</AtomicText>
62
+ </TouchableOpacity>
63
+ </View>
30
64
  </View>
31
65
  );
32
66
  }
@@ -37,28 +71,56 @@ const styles = StyleSheet.create({
37
71
  top: 0,
38
72
  left: 0,
39
73
  right: 0,
40
- paddingTop: 48,
41
- paddingHorizontal: 16,
74
+ paddingHorizontal: 20,
42
75
  paddingBottom: 16,
43
76
  flexDirection: 'row',
44
77
  justifyContent: 'space-between',
45
78
  alignItems: 'center',
46
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
79
+ zIndex: 1000,
80
+ },
81
+ leftSection: {
82
+ flex: 1,
83
+ alignItems: 'flex-start',
84
+ },
85
+ centerSection: {
86
+ flex: 2,
87
+ alignItems: 'center',
88
+ },
89
+ rightSection: {
90
+ flex: 1,
91
+ alignItems: 'flex-end',
47
92
  },
48
93
  spacer: {
49
- width: 48,
94
+ width: 44,
95
+ height: 44,
50
96
  },
51
- button: {
52
- flexDirection: 'row',
97
+ actionButton: {
98
+ paddingVertical: 8,
99
+ paddingHorizontal: 16,
100
+ borderRadius: 20,
101
+ justifyContent: 'center',
102
+ alignItems: 'center',
103
+ },
104
+ closeButton: {
105
+ width: 50,
106
+ height: 50,
107
+ borderRadius: 25,
108
+ justifyContent: 'center',
53
109
  alignItems: 'center',
54
- gap: 8,
55
- padding: 12,
56
- borderRadius: 8,
57
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
58
110
  },
59
111
  buttonText: {
60
112
  fontSize: 14,
113
+ color: '#FFFFFF',
114
+ fontWeight: 'bold',
115
+ },
116
+ closeIcon: {
117
+ fontSize: 28,
118
+ color: '#FFFFFF',
119
+ fontWeight: '300',
120
+ },
121
+ titleText: {
61
122
  color: '#FFFFFF',
62
123
  fontWeight: '600',
124
+ textAlign: 'center',
63
125
  },
64
126
  });
@@ -1,15 +1,19 @@
1
1
  /**
2
2
  * Presentation - Image Gallery Component
3
3
  *
4
- * Wrapper around react-native-image-viewing with theme integration
4
+ * High-performance, premium image gallery using expo-image.
5
+ * Replaces slow standard image components for instant loading.
5
6
  */
6
7
 
7
- import React, { useCallback } from 'react';
8
- import ImageViewing from 'react-native-image-viewing';
9
- // import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
8
+ import React, { useCallback, useMemo, useState, useEffect } from 'react';
9
+ import { Modal, View, StyleSheet, FlatList, Dimensions, TouchableOpacity } from 'react-native';
10
+ import { Image } from 'expo-image';
11
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
10
12
  import type { ImageViewerItem, ImageGalleryOptions } from '../../domain/entities/ImageTypes';
11
13
  import { GalleryHeader } from './GalleryHeader';
12
14
 
15
+ const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
16
+
13
17
  export interface ImageGalleryProps extends ImageGalleryOptions {
14
18
  images: ImageViewerItem[];
15
19
  visible: boolean;
@@ -17,6 +21,7 @@ export interface ImageGalleryProps extends ImageGalleryOptions {
17
21
  index?: number;
18
22
  onImageChange?: (uri: string, index: number) => void | Promise<void>;
19
23
  enableEditing?: boolean;
24
+ title?: string;
20
25
  }
21
26
 
22
27
  export const ImageGallery: React.FC<ImageGalleryProps> = ({
@@ -24,66 +29,110 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
24
29
  visible,
25
30
  onDismiss,
26
31
  index = 0,
27
- backgroundColor,
28
- swipeToCloseEnabled = true,
29
- doubleTapToZoomEnabled = true,
32
+ backgroundColor = '#000000',
30
33
  onIndexChange,
31
34
  onImageChange,
32
35
  enableEditing = false,
36
+ title,
33
37
  }) => {
34
- // const tokens = useAppDesignTokens();
35
- const [currentIndex, setCurrentIndex] = React.useState(index);
36
-
37
- React.useEffect(() => {
38
- setCurrentIndex(index);
39
- }, [index]);
40
-
41
- const bg = backgroundColor || '#000000';
38
+ const insets = useSafeAreaInsets();
39
+ const [currentIndex, setCurrentIndex] = useState(index);
42
40
 
43
- const viewerImages = React.useMemo(
44
- () => images.map((img) => ({ uri: img.uri })),
45
- [images]
46
- );
41
+ useEffect(() => {
42
+ if (visible) setCurrentIndex(index);
43
+ }, [visible, index]);
47
44
 
48
45
  const handleEdit = useCallback(async () => {
49
46
  const currentImage = images[currentIndex];
50
47
  if (!currentImage || !onImageChange) return;
51
-
52
- try {
53
- await onImageChange(currentImage.uri, currentIndex);
54
- } catch (error) {
55
- // Consumer should handle editing logic
56
- }
48
+ await onImageChange(currentImage.uri, currentIndex);
57
49
  }, [images, currentIndex, onImageChange]);
58
50
 
59
- const handleIndexChange = useCallback(
60
- (newIndex: number) => {
61
- setCurrentIndex(newIndex);
62
- onIndexChange?.(newIndex);
63
- },
64
- [onIndexChange]
65
- );
51
+ const handleScroll = useCallback((event: any) => {
52
+ const nextIndex = Math.round(event.nativeEvent.contentOffset.x / SCREEN_WIDTH);
53
+ if (nextIndex !== currentIndex) {
54
+ setCurrentIndex(nextIndex);
55
+ onIndexChange?.(nextIndex);
56
+ }
57
+ }, [currentIndex, onIndexChange]);
66
58
 
67
- const headerComponent = useCallback(() => {
68
- return (
69
- <GalleryHeader
70
- onEdit={enableEditing ? handleEdit : undefined}
71
- onClose={onDismiss}
59
+ const renderItem = useCallback(({ item }: { item: ImageViewerItem }) => (
60
+ <View style={styles.imageWrapper}>
61
+ <Image
62
+ source={{ uri: item.uri }}
63
+ style={styles.fullImage}
64
+ contentFit="contain"
65
+ transition={200}
66
+ cachePolicy="memory-disk"
72
67
  />
73
- );
74
- }, [enableEditing, handleEdit, onDismiss]);
68
+ </View>
69
+ ), []);
70
+
71
+ if (!visible && !currentIndex) return null;
75
72
 
76
73
  return (
77
- <ImageViewing
78
- images={viewerImages}
79
- imageIndex={currentIndex}
74
+ <Modal
80
75
  visible={visible}
76
+ transparent
77
+ animationType="fade"
81
78
  onRequestClose={onDismiss}
82
- onImageIndexChange={handleIndexChange}
83
- backgroundColor={bg}
84
- swipeToCloseEnabled={swipeToCloseEnabled}
85
- doubleTapToZoomEnabled={doubleTapToZoomEnabled}
86
- HeaderComponent={headerComponent}
87
- />
79
+ statusBarTranslucent
80
+ >
81
+ <View style={[styles.container, { backgroundColor }]}>
82
+ <GalleryHeader
83
+ onClose={onDismiss}
84
+ onEdit={enableEditing ? handleEdit : undefined}
85
+ title={title || `${currentIndex + 1} / ${images.length}`}
86
+ />
87
+
88
+ <FlatList
89
+ data={images}
90
+ renderItem={renderItem}
91
+ horizontal
92
+ pagingEnabled
93
+ showsHorizontalScrollIndicator={false}
94
+ initialScrollIndex={index}
95
+ getItemLayout={(_, i) => ({
96
+ length: SCREEN_WIDTH,
97
+ offset: SCREEN_WIDTH * i,
98
+ index: i,
99
+ })}
100
+ onScroll={handleScroll}
101
+ scrollEventThrottle={16}
102
+ keyExtractor={(item, i) => `${item.uri}-${i}`}
103
+ style={styles.list}
104
+ />
105
+
106
+ <View style={[styles.footer, { paddingBottom: Math.max(insets.bottom, 20) }]}>
107
+ {/* Potential for thumbnail strip or captions in future */}
108
+ </View>
109
+ </View>
110
+ </Modal>
88
111
  );
89
112
  };
113
+
114
+ const styles = StyleSheet.create({
115
+ container: {
116
+ flex: 1,
117
+ },
118
+ list: {
119
+ flex: 1,
120
+ },
121
+ imageWrapper: {
122
+ width: SCREEN_WIDTH,
123
+ height: SCREEN_HEIGHT,
124
+ justifyContent: 'center',
125
+ alignItems: 'center',
126
+ },
127
+ fullImage: {
128
+ width: '100%',
129
+ height: '100%',
130
+ },
131
+ footer: {
132
+ position: 'absolute',
133
+ bottom: 0,
134
+ left: 0,
135
+ right: 0,
136
+ alignItems: 'center',
137
+ }
138
+ });
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Presentation - Editor Tool Hook
3
+ *
4
+ * Manages individual editor tools and their states
5
+ */
6
+
7
+ import { useState, useCallback } from 'react';
8
+ import { DrawingEngine, type DrawingPath, type DrawingConfig } from '../../infrastructure/utils/DrawingEngine';
9
+ import { CropTool, type CropConfig } from '../../infrastructure/utils/CropTool';
10
+ import { TextEditor, type TextFormatting } from '../../infrastructure/utils/TextEditor';
11
+ import { ShapeRenderer, type ShapeStyle } from '../../infrastructure/utils/ShapeRenderer';
12
+
13
+ interface ToolState {
14
+ isDrawing: boolean;
15
+ currentPath: DrawingPath;
16
+ startPoint?: { x: number; y: number };
17
+ currentTool: string;
18
+ }
19
+
20
+ interface UseEditorToolsOptions {
21
+ onStateChange?: (state: any) => void;
22
+ canvas?: HTMLCanvasElement | null;
23
+ }
24
+
25
+ export function useEditorTools({ onStateChange, canvas }: UseEditorToolsOptions = {}) {
26
+ const [toolState, setToolState] = useState<ToolState>({
27
+ isDrawing: false,
28
+ currentPath: [],
29
+ currentTool: 'move',
30
+ });
31
+
32
+ const [drawingConfig, setDrawingConfig] = useState<DrawingConfig>({
33
+ color: '#000000',
34
+ size: 5,
35
+ opacity: 1,
36
+ style: 'normal',
37
+ smoothing: true,
38
+ });
39
+
40
+ const [cropConfig, setCropConfig] = useState<CropConfig>({
41
+ aspectRatio: undefined,
42
+ lockAspectRatio: false,
43
+ showGrid: true,
44
+ });
45
+
46
+ const [textFormatting, setTextFormatting] = useState<TextFormatting>({
47
+ fontSize: 16,
48
+ fontFamily: 'Arial',
49
+ fontWeight: 'normal',
50
+ fontStyle: 'normal',
51
+ color: '#000000',
52
+ textAlign: 'left',
53
+ opacity: 1,
54
+ });
55
+
56
+ const setTool = useCallback((tool: string) => {
57
+ const newState = { ...toolState, currentTool: tool };
58
+ setToolState(newState);
59
+ onStateChange?.(newState);
60
+ }, [toolState, onStateChange]);
61
+
62
+ const startDrawing = useCallback((point: { x: number; y: number }) => {
63
+ if (!canvas) return;
64
+
65
+ const ctx = canvas.getContext('2d');
66
+ if (!ctx) return;
67
+
68
+ const newState = {
69
+ ...toolState,
70
+ isDrawing: true,
71
+ startPoint: point,
72
+ currentPath: [point],
73
+ };
74
+ setToolState(newState);
75
+ }, [toolState, canvas]);
76
+
77
+ const draw = useCallback((point: { x: number; y: number }) => {
78
+ if (!toolState.isDrawing || !canvas) return;
79
+
80
+ const ctx = canvas.getContext('2d');
81
+ if (!ctx) return;
82
+
83
+ const newPath = [...toolState.currentPath, point];
84
+
85
+ setToolState(prev => ({
86
+ ...prev,
87
+ currentPath: newPath,
88
+ }));
89
+
90
+ // Real-time drawing feedback
91
+ const tempPath = [...toolState.currentPath, point];
92
+ DrawingEngine.drawStroke(ctx, tempPath, drawingConfig);
93
+ }, [toolState.isDrawing, toolState.currentPath, drawingConfig, canvas]);
94
+
95
+ const stopDrawing = useCallback(() => {
96
+ if (!toolState.isDrawing || !canvas) return;
97
+
98
+ const ctx = canvas.getContext('2d');
99
+ if (!ctx) return;
100
+
101
+ // Finalize the stroke
102
+ DrawingEngine.drawStroke(ctx, toolState.currentPath, drawingConfig);
103
+
104
+ const newState = {
105
+ ...toolState,
106
+ isDrawing: false,
107
+ currentPath: [],
108
+ startPoint: undefined,
109
+ };
110
+ setToolState(newState);
111
+ onStateChange?.(newState);
112
+ }, [toolState, drawingConfig, canvas, onStateChange]);
113
+
114
+ const drawShape = useCallback((
115
+ type: 'rectangle' | 'circle' | 'line' | 'arrow',
116
+ startPoint: { x: number; y: number },
117
+ endPoint: { x: number; y: number },
118
+ style: ShapeStyle
119
+ ) => {
120
+ if (!canvas) return;
121
+
122
+ const ctx = canvas.getContext('2d');
123
+ if (!ctx) return;
124
+
125
+ DrawingEngine.drawShape(ctx, type, startPoint, endPoint, style as any);
126
+ onStateChange?.({ toolState, shape: { type, startPoint, endPoint, style } });
127
+ }, [canvas, onStateChange, toolState]);
128
+
129
+ const addText = useCallback((
130
+ text: string,
131
+ position: { x: number; y: number },
132
+ formatting?: Partial<TextFormatting>
133
+ ) => {
134
+ if (!canvas) return;
135
+
136
+ const ctx = canvas.getContext('2d');
137
+ if (!ctx) return;
138
+
139
+ const finalFormatting = { ...textFormatting, ...formatting };
140
+ TextEditor.renderText(ctx, text, position, finalFormatting);
141
+
142
+ setTextFormatting(finalFormatting);
143
+ onStateChange?.({ toolState, text: { text, position, formatting: finalFormatting } });
144
+ }, [textFormatting, canvas, onStateChange, toolState]);
145
+
146
+ const setCropArea = useCallback((cropArea: any) => {
147
+ if (!canvas) return;
148
+
149
+ const ctx = canvas.getContext('2d');
150
+ if (!ctx) return;
151
+
152
+ CropTool.drawCropOverlay(ctx, cropArea, cropConfig);
153
+ onStateChange?.({ toolState, cropArea });
154
+ }, [cropConfig, canvas, onStateChange, toolState]);
155
+
156
+ const clearCanvas = useCallback(() => {
157
+ if (!canvas) return;
158
+
159
+ const ctx = canvas.getContext('2d');
160
+ if (!ctx) return;
161
+
162
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
163
+ onStateChange?.({ toolState, cleared: true });
164
+ }, [canvas, onStateChange, toolState]);
165
+
166
+ return {
167
+ // State
168
+ toolState,
169
+ drawingConfig,
170
+ cropConfig,
171
+ textFormatting,
172
+
173
+ // Actions
174
+ setTool,
175
+ startDrawing,
176
+ draw,
177
+ stopDrawing,
178
+ drawShape,
179
+ addText,
180
+ setCropArea,
181
+ clearCanvas,
182
+
183
+ // Config setters
184
+ setDrawingConfig,
185
+ setCropConfig,
186
+ setTextFormatting,
187
+ };
188
+ }
@@ -6,16 +6,47 @@
6
6
 
7
7
  import { useImageTransform } from './useImageTransform';
8
8
  import { useImageConversion } from './useImageConversion';
9
+ import { useImageFilter } from './useImageFilter';
10
+ import { useImageBatch } from './useImageBatch';
11
+ import { useImageAIEnhancement } from './useImageAIEnhancement';
12
+ import { useImageAnnotation } from './useImageAnnotation';
13
+ import { useImageMetadata } from './useImageMetadata';
9
14
 
10
15
  export const useImage = () => {
11
16
  const transform = useImageTransform();
12
17
  const conversion = useImageConversion();
18
+ const filter = useImageFilter();
19
+ const batch = useImageBatch();
20
+ const aiEnhancement = useImageAIEnhancement();
21
+ const annotation = useImageAnnotation();
22
+ const metadata = useImageMetadata();
13
23
 
14
24
  return {
25
+ // Basic operations
15
26
  ...transform,
16
27
  ...conversion,
28
+ // Advanced operations
29
+ ...filter,
30
+ ...batch,
31
+ ...aiEnhancement,
32
+ ...annotation,
33
+ ...metadata,
17
34
  // Combined state
18
- isProcessing: transform.isTransforming || conversion.isConverting,
19
- error: transform.transformError || conversion.conversionError,
35
+ isProcessing:
36
+ transform.isTransforming ||
37
+ conversion.isConverting ||
38
+ filter.isFiltering ||
39
+ batch.isBatchProcessing ||
40
+ aiEnhancement.isEnhancing ||
41
+ annotation.isAnnotating ||
42
+ metadata.isExtracting,
43
+ error:
44
+ transform.transformError ||
45
+ conversion.conversionError ||
46
+ filter.filterError ||
47
+ batch.batchError ||
48
+ aiEnhancement.enhancementError ||
49
+ annotation.annotationError ||
50
+ metadata.metadataError,
20
51
  };
21
52
  };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Presentation - Image AI Enhancement Hook
3
+ */
4
+
5
+ import { useCallback } from 'react';
6
+ import { useImageOperation } from './useImageOperation';
7
+ import { ImageAIEnhancementService, type AutoEnhancementOptions, type EnhancementResult } from '../../infrastructure/services/ImageAIEnhancementService';
8
+ import { ImageSpecializedEnhancementService } from '../../infrastructure/services/ImageSpecializedEnhancementService';
9
+
10
+ export const useImageAIEnhancement = () => {
11
+ const { isProcessing, error, execute } = useImageOperation();
12
+
13
+ const autoEnhance = useCallback((uri: string, options?: AutoEnhancementOptions) =>
14
+ execute(() => ImageAIEnhancementService.autoEnhance(uri, options), 'Failed to auto enhance'), [execute]);
15
+
16
+ const enhancePortrait = useCallback((uri: string) =>
17
+ execute(() => ImageSpecializedEnhancementService.enhancePortrait(uri), 'Failed to enhance portrait'), [execute]);
18
+
19
+ const enhanceLandscape = useCallback((uri: string) =>
20
+ execute(() => ImageSpecializedEnhancementService.enhanceLandscape(uri), 'Failed to enhance landscape'), [execute]);
21
+
22
+ const analyzeImage = useCallback((uri: string) =>
23
+ execute(() => ImageAIEnhancementService.analyzeImage(uri), 'Failed to analyze image'), [execute]);
24
+
25
+ return {
26
+ autoEnhance,
27
+ enhancePortrait,
28
+ enhanceLandscape,
29
+ analyzeImage,
30
+ isEnhancing: isProcessing,
31
+ enhancementError: error,
32
+ };
33
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Presentation - Image Annotation Hook
3
+ */
4
+
5
+ import { useCallback } from 'react';
6
+ import { useImageOperation } from './useImageOperation';
7
+ import { ImageAnnotationService, type ImageAnnotation, type TextOverlay, type DrawingElement, type WatermarkOptions } from '../../infrastructure/services/ImageAnnotationService';
8
+
9
+ export const useImageAnnotation = () => {
10
+ const { isProcessing, error, execute } = useImageOperation();
11
+
12
+ const addTextOverlay = useCallback((uri: string, overlay: TextOverlay) =>
13
+ execute(() => ImageAnnotationService.addTextOverlay(uri, overlay), 'Failed to add text'), [execute]);
14
+
15
+ const addDrawingElements = useCallback((uri: string, elements: DrawingElement[]) =>
16
+ execute(() => ImageAnnotationService.addDrawingElements(uri, elements), 'Failed to add drawing'), [execute]);
17
+
18
+ const addWatermark = useCallback((uri: string, options: WatermarkOptions) =>
19
+ execute(() => ImageAnnotationService.addWatermark(uri, options), 'Failed to add watermark'), [execute]);
20
+
21
+ const applyAnnotation = useCallback((uri: string, annotation: ImageAnnotation) =>
22
+ execute(() => ImageAnnotationService.applyAnnotation(uri, annotation), 'Failed to apply annotation'), [execute]);
23
+
24
+ return {
25
+ addTextOverlay,
26
+ addDrawingElements,
27
+ addWatermark,
28
+ applyAnnotation,
29
+ isAnnotating: isProcessing,
30
+ annotationError: error,
31
+ };
32
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Presentation - Image Batch Hook
3
+ */
4
+
5
+ import { useCallback } from 'react';
6
+ import { useImageOperation } from './useImageOperation';
7
+ import { ImageBatchService, type BatchOperation, type BatchProcessingOptions } from '../../infrastructure/services/ImageBatchService';
8
+ import type { ImageFilter } from '../../domain/entities/ImageFilterTypes';
9
+
10
+ export const useImageBatch = () => {
11
+ const { isProcessing, error, execute } = useImageOperation();
12
+
13
+ const processBatch = useCallback((operations: BatchOperation[], options?: BatchProcessingOptions) =>
14
+ execute(() => ImageBatchService.processBatch(operations, options), 'Failed to process batch'), [execute]);
15
+
16
+ const resizeBatch = useCallback((uris: string[], width?: number, height?: number, options?: BatchProcessingOptions & { saveOptions?: any }) =>
17
+ execute(() => ImageBatchService.resizeBatch(uris, width, height, options), 'Failed to resize batch'), [execute]);
18
+
19
+ const compressBatch = useCallback((uris: string[], quality?: number, options?: BatchProcessingOptions) =>
20
+ execute(() => ImageBatchService.compressBatch(uris, quality, options), 'Failed to compress batch'), [execute]);
21
+
22
+ const filterBatch = useCallback((uris: string[], filter: ImageFilter, options?: BatchProcessingOptions) =>
23
+ execute(() => ImageBatchService.filterBatch(uris, filter, options), 'Failed to filter batch'), [execute]);
24
+
25
+ return {
26
+ processBatch,
27
+ resizeBatch,
28
+ compressBatch,
29
+ filterBatch,
30
+ isBatchProcessing: isProcessing,
31
+ batchError: error,
32
+ };
33
+ };