@umituz/react-native-image 1.1.6 → 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.
- package/package.json +25 -10
- package/src/domain/entities/EditorTypes.ts +180 -0
- package/src/index.ts +11 -0
- package/src/infrastructure/services/ImageEditorService.ts +274 -0
- package/src/infrastructure/utils/CropTool.ts +260 -0
- package/src/infrastructure/utils/DrawingEngine.ts +210 -0
- package/src/infrastructure/utils/FilterProcessor.ts +361 -0
- package/src/infrastructure/utils/LayerManager.ts +158 -0
- package/src/infrastructure/utils/ShapeRenderer.ts +168 -0
- package/src/infrastructure/utils/TextEditor.ts +273 -0
- package/src/presentation/components/CropComponent.tsx +183 -0
- package/src/presentation/components/Editor.tsx +261 -0
- package/src/presentation/components/EditorCanvas.tsx +135 -0
- package/src/presentation/components/EditorPanel.tsx +321 -0
- package/src/presentation/components/EditorToolbar.tsx +180 -0
- package/src/presentation/components/FilterSlider.tsx +123 -0
- package/src/presentation/components/GalleryHeader.tsx +87 -25
- package/src/presentation/components/ImageGallery.tsx +97 -48
- package/src/presentation/hooks/useEditorTools.ts +188 -0
- package/src/presentation/hooks/useImageEditor.ts +165 -38
|
@@ -1,55 +1,182 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Presentation - Image Editor Hook
|
|
3
|
-
*
|
|
4
|
-
* NOTE: This hook is deprecated - use useImageTransform instead
|
|
2
|
+
* Presentation - Advanced Image Editor Hook
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
|
-
import { useState, useCallback } from 'react';
|
|
8
|
-
import type {
|
|
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';
|
|
9
14
|
|
|
10
|
-
interface
|
|
11
|
-
onSave?: (
|
|
15
|
+
interface UseEditorConfig {
|
|
16
|
+
onSave?: (result: ImageManipulationResult) => void | Promise<void>;
|
|
17
|
+
onCancel?: () => void;
|
|
18
|
+
options?: EditorOptions;
|
|
12
19
|
}
|
|
13
20
|
|
|
14
|
-
export function useImageEditor({ onSave }:
|
|
15
|
-
const [
|
|
16
|
-
const [
|
|
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);
|
|
17
27
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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]);
|
|
22
44
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
const setTool = useCallback((tool: EditorTool) => {
|
|
46
|
+
if (!editorState) return;
|
|
47
|
+
|
|
48
|
+
const newState = ImageEditorService.setTool(editorState, tool);
|
|
49
|
+
setEditorState(newState);
|
|
50
|
+
}, [editorState]);
|
|
27
51
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
52
|
+
const addLayer = useCallback((name?: string) => {
|
|
53
|
+
if (!editorState) return;
|
|
54
|
+
|
|
55
|
+
const newState = ImageEditorService.addLayer(editorState, name);
|
|
56
|
+
setEditorState(newState);
|
|
57
|
+
}, [editorState]);
|
|
31
58
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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]);
|
|
36
69
|
|
|
37
|
-
|
|
38
|
-
|
|
70
|
+
const undo = useCallback(() => {
|
|
71
|
+
if (!editorState) return;
|
|
72
|
+
|
|
73
|
+
const newState = ImageEditorService.undo(editorState);
|
|
74
|
+
setEditorState(newState);
|
|
75
|
+
}, [editorState]);
|
|
39
76
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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);
|
|
43
97
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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]);
|
|
47
113
|
|
|
48
114
|
return {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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,
|
|
54
134
|
};
|
|
55
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
|
+
}
|