@umituz/react-native-image 1.3.1 → 1.3.2
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 +1 -1
- package/src/infrastructure/services/ImageAnnotationService.ts +0 -189
- package/src/infrastructure/services/ImageFilterService.ts +0 -168
- package/src/infrastructure/utils/CanvasRenderingService.ts +0 -134
- package/src/infrastructure/utils/CropTool.ts +0 -260
- package/src/infrastructure/utils/DrawingEngine.ts +0 -210
- package/src/infrastructure/utils/FilterEffects.ts +0 -51
- package/src/infrastructure/utils/ShapeRenderer.ts +0 -168
- package/src/infrastructure/utils/TextEditor.ts +0 -273
- package/src/presentation/components/CropComponent.tsx +0 -183
- package/src/presentation/components/Editor.tsx +0 -261
- package/src/presentation/components/EditorCanvas.tsx +0 -135
- package/src/presentation/components/EditorPanel.tsx +0 -321
- package/src/presentation/components/EditorToolbar.tsx +0 -180
- package/src/presentation/components/FilterSlider.tsx +0 -118
- package/src/presentation/hooks/useEditorTools.ts +0 -188
- package/src/presentation/hooks/useImageAnnotation.ts +0 -32
- package/src/presentation/hooks/useImageEditor.ts +0 -182
- package/src/presentation/hooks/useImageFilter.ts +0 -38
|
@@ -1,118 +0,0 @@
|
|
|
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
|
-
},
|
|
118
|
-
});
|
|
@@ -1,188 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Presentation - Advanced Image Editor Hook
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useState, useCallback, useRef } from 'react';
|
|
6
|
-
import type {
|
|
7
|
-
EditorState,
|
|
8
|
-
EditorTool,
|
|
9
|
-
EditorOptions,
|
|
10
|
-
EditorExportOptions,
|
|
11
|
-
} from '../../domain/entities/EditorTypes';
|
|
12
|
-
import { ImageEditorService } from '../../infrastructure/services/ImageEditorService';
|
|
13
|
-
import type { ImageManipulationResult } from '../../domain/entities/ImageTypes';
|
|
14
|
-
|
|
15
|
-
interface UseEditorConfig {
|
|
16
|
-
onSave?: (result: ImageManipulationResult) => void | Promise<void>;
|
|
17
|
-
onCancel?: () => void;
|
|
18
|
-
options?: EditorOptions;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function useImageEditor({ onSave, onCancel, options }: UseEditorConfig = {}) {
|
|
22
|
-
const [editorState, setEditorState] = useState<EditorState | null>(null);
|
|
23
|
-
const [isProcessing, setIsProcessing] = useState(false);
|
|
24
|
-
const [error, setError] = useState<string | null>(null);
|
|
25
|
-
|
|
26
|
-
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
27
|
-
|
|
28
|
-
const initializeEditor = useCallback(async (uri: string) => {
|
|
29
|
-
try {
|
|
30
|
-
setError(null);
|
|
31
|
-
setIsProcessing(true);
|
|
32
|
-
|
|
33
|
-
// Get image dimensions
|
|
34
|
-
const dimensions = await getImageDimensions(uri);
|
|
35
|
-
const state = ImageEditorService.createInitialState(uri, dimensions, options);
|
|
36
|
-
|
|
37
|
-
setEditorState(state);
|
|
38
|
-
setIsProcessing(false);
|
|
39
|
-
} catch (err) {
|
|
40
|
-
setError(err instanceof Error ? err.message : 'Failed to initialize editor');
|
|
41
|
-
setIsProcessing(false);
|
|
42
|
-
}
|
|
43
|
-
}, [options]);
|
|
44
|
-
|
|
45
|
-
const setTool = useCallback((tool: EditorTool) => {
|
|
46
|
-
if (!editorState) return;
|
|
47
|
-
|
|
48
|
-
const newState = ImageEditorService.setTool(editorState, tool);
|
|
49
|
-
setEditorState(newState);
|
|
50
|
-
}, [editorState]);
|
|
51
|
-
|
|
52
|
-
const addLayer = useCallback((name?: string) => {
|
|
53
|
-
if (!editorState) return;
|
|
54
|
-
|
|
55
|
-
const newState = ImageEditorService.addLayer(editorState, name);
|
|
56
|
-
setEditorState(newState);
|
|
57
|
-
}, [editorState]);
|
|
58
|
-
|
|
59
|
-
const removeLayer = useCallback((layerId: string) => {
|
|
60
|
-
if (!editorState) return;
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
const newState = ImageEditorService.removeLayer(editorState, layerId);
|
|
64
|
-
setEditorState(newState);
|
|
65
|
-
} catch (err) {
|
|
66
|
-
setError(err instanceof Error ? err.message : 'Failed to remove layer');
|
|
67
|
-
}
|
|
68
|
-
}, [editorState]);
|
|
69
|
-
|
|
70
|
-
const undo = useCallback(() => {
|
|
71
|
-
if (!editorState) return;
|
|
72
|
-
|
|
73
|
-
const newState = ImageEditorService.undo(editorState);
|
|
74
|
-
setEditorState(newState);
|
|
75
|
-
}, [editorState]);
|
|
76
|
-
|
|
77
|
-
const redo = useCallback(() => {
|
|
78
|
-
if (!editorState) return;
|
|
79
|
-
|
|
80
|
-
const newState = ImageEditorService.redo(editorState);
|
|
81
|
-
setEditorState(newState);
|
|
82
|
-
}, [editorState]);
|
|
83
|
-
|
|
84
|
-
const exportImage = useCallback(async (exportOptions?: EditorExportOptions) => {
|
|
85
|
-
if (!editorState || !canvasRef.current) return null;
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
setIsProcessing(true);
|
|
89
|
-
setError(null);
|
|
90
|
-
|
|
91
|
-
// Compose all layers
|
|
92
|
-
const canvas = canvasRef.current;
|
|
93
|
-
const result = await composeAndExport(canvas, editorState, exportOptions);
|
|
94
|
-
|
|
95
|
-
if (onSave) {
|
|
96
|
-
await onSave(result);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
setIsProcessing(false);
|
|
100
|
-
return result;
|
|
101
|
-
} catch (err) {
|
|
102
|
-
setError(err instanceof Error ? err.message : 'Failed to export image');
|
|
103
|
-
setIsProcessing(false);
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
}, [editorState, onSave]);
|
|
107
|
-
|
|
108
|
-
const cancel = useCallback(() => {
|
|
109
|
-
setEditorState(null);
|
|
110
|
-
setError(null);
|
|
111
|
-
onCancel?.();
|
|
112
|
-
}, [onCancel]);
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
// State
|
|
116
|
-
editorState,
|
|
117
|
-
isProcessing,
|
|
118
|
-
error,
|
|
119
|
-
canUndo: editorState ? ImageEditorService.canUndo(editorState) : false,
|
|
120
|
-
canRedo: editorState ? ImageEditorService.canRedo(editorState) : false,
|
|
121
|
-
|
|
122
|
-
// Actions
|
|
123
|
-
initializeEditor,
|
|
124
|
-
setTool,
|
|
125
|
-
addLayer,
|
|
126
|
-
removeLayer,
|
|
127
|
-
undo,
|
|
128
|
-
redo,
|
|
129
|
-
exportImage,
|
|
130
|
-
cancel,
|
|
131
|
-
|
|
132
|
-
// Refs
|
|
133
|
-
canvasRef,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function getImageDimensions(uri: string): Promise<{ width: number; height: number }> {
|
|
138
|
-
return new Promise((resolve) => {
|
|
139
|
-
const img = new Image();
|
|
140
|
-
img.onload = () => {
|
|
141
|
-
resolve({ width: img.width, height: img.height });
|
|
142
|
-
};
|
|
143
|
-
img.src = uri;
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async function composeAndExport(
|
|
148
|
-
canvas: HTMLCanvasElement,
|
|
149
|
-
state: EditorState,
|
|
150
|
-
options?: EditorExportOptions
|
|
151
|
-
): Promise<ImageManipulationResult> {
|
|
152
|
-
const ctx = canvas.getContext('2d');
|
|
153
|
-
if (!ctx) throw new Error('Failed to get canvas context');
|
|
154
|
-
|
|
155
|
-
// Clear canvas
|
|
156
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
157
|
-
|
|
158
|
-
// Apply all layers
|
|
159
|
-
const visibleLayers = ImageEditorService.getVisibleLayers(state);
|
|
160
|
-
|
|
161
|
-
// Background layer (original image)
|
|
162
|
-
if (visibleLayers.length > 0) {
|
|
163
|
-
const backgroundLayer = visibleLayers[0];
|
|
164
|
-
// Would render original image here
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Export canvas to blob
|
|
168
|
-
return new Promise((resolve) => {
|
|
169
|
-
canvas.toBlob(async (blob) => {
|
|
170
|
-
if (!blob) throw new Error('Failed to create blob');
|
|
171
|
-
|
|
172
|
-
const url = URL.createObjectURL(blob);
|
|
173
|
-
resolve({
|
|
174
|
-
uri: url,
|
|
175
|
-
width: canvas.width,
|
|
176
|
-
height: canvas.height,
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
URL.revokeObjectURL(url);
|
|
180
|
-
}, options?.format || 'jpeg', options?.quality || 0.9);
|
|
181
|
-
});
|
|
182
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Presentation - Image Filter Hook
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useCallback } from 'react';
|
|
6
|
-
import { useImageOperation } from './useImageOperation';
|
|
7
|
-
import { ImageFilterService } from '../../infrastructure/services/ImageFilterService';
|
|
8
|
-
import type {
|
|
9
|
-
ImageFilter,
|
|
10
|
-
ImageColorAdjustment,
|
|
11
|
-
ImageQualityMetrics,
|
|
12
|
-
ImageColorPalette,
|
|
13
|
-
} from '../../domain/entities/ImageFilterTypes';
|
|
14
|
-
|
|
15
|
-
export const useImageFilter = () => {
|
|
16
|
-
const { isProcessing, error, execute } = useImageOperation();
|
|
17
|
-
|
|
18
|
-
const applyFilter = useCallback((uri: string, filter: ImageFilter) =>
|
|
19
|
-
execute(() => ImageFilterService.applyFilter(uri, filter), 'Failed to apply filter'), [execute]);
|
|
20
|
-
|
|
21
|
-
const applyColorAdjustment = useCallback((uri: string, adjustment: ImageColorAdjustment) =>
|
|
22
|
-
execute(() => ImageFilterService.applyColorAdjustment(uri, adjustment), 'Failed to adjust colors'), [execute]);
|
|
23
|
-
|
|
24
|
-
const analyzeQuality = useCallback((uri: string) =>
|
|
25
|
-
execute(() => ImageFilterService.analyzeQuality(uri), 'Failed to analyze quality'), [execute]);
|
|
26
|
-
|
|
27
|
-
const extractColorPalette = useCallback((uri: string, colorCount?: number) =>
|
|
28
|
-
execute(() => ImageFilterService.extractColorPalette(uri, colorCount), 'Failed to extract colors'), [execute]);
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
applyFilter,
|
|
32
|
-
applyColorAdjustment,
|
|
33
|
-
analyzeQuality,
|
|
34
|
-
extractColorPalette,
|
|
35
|
-
isFiltering: isProcessing,
|
|
36
|
-
filterError: error,
|
|
37
|
-
};
|
|
38
|
-
};
|