@umituz/react-native-image 1.3.8 → 1.3.10
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 +8 -2
- package/src/index.ts +5 -4
- package/src/infrastructure/services/ImageConversionService.ts +7 -32
- package/src/infrastructure/services/ImageEditorService.ts +41 -179
- package/src/infrastructure/services/{ImageAIEnhancementService.ts → ImageEnhanceService.ts} +42 -55
- package/src/infrastructure/services/ImageTemplateService.ts +10 -4
- package/src/infrastructure/services/ImageTransformService.ts +43 -93
- package/src/infrastructure/utils/FilterProcessor.ts +26 -263
- package/src/infrastructure/utils/{AIImageAnalysisUtils.ts → ImageAnalysisUtils.ts} +3 -3
- package/src/infrastructure/utils/ImageEditorHistoryUtils.ts +63 -0
- package/src/infrastructure/utils/ImageFilterUtils.ts +152 -0
- package/src/infrastructure/utils/ImageTransformUtils.ts +25 -0
- package/src/infrastructure/utils/LayerManager.ts +0 -81
- package/src/presentation/components/editor/FilterPickerSheet.tsx +75 -0
- package/src/presentation/components/editor/StickerPickerSheet.tsx +62 -0
- package/src/presentation/components/editor/TextEditorSheet.tsx +98 -0
- package/src/presentation/components/editor/TextEditorTabs.tsx +111 -0
- package/src/presentation/hooks/useImage.ts +5 -8
- package/src/presentation/hooks/useImageEnhance.ts +32 -0
- package/src/presentation/hooks/useImageTransform.ts +3 -4
- package/src/infrastructure/services/ImageAdvancedTransformService.ts +0 -106
- package/src/infrastructure/services/ImageSpecializedEnhancementService.ts +0 -57
- package/src/presentation/hooks/useImageAIEnhancement.ts +0 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-image",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.10",
|
|
4
4
|
"description": "Image manipulation and viewing for React Native apps - resize, crop, rotate, flip, compress, gallery viewer",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"react": ">=18.2.0",
|
|
41
41
|
"react-native": ">=0.74.0",
|
|
42
42
|
"react-native-gesture-handler": ">=2.0.0",
|
|
43
|
-
"react-native-reanimated": ">=3.0.0"
|
|
43
|
+
"react-native-reanimated": ">=3.0.0",
|
|
44
|
+
"@react-native-community/slider": ">=4.0.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@umituz/react-native-design-system": "latest",
|
|
@@ -53,6 +54,11 @@
|
|
|
53
54
|
"react-native": "0.81.5",
|
|
54
55
|
"react-native-gesture-handler": "~2.16.1",
|
|
55
56
|
"react-native-reanimated": "~3.16.1",
|
|
57
|
+
"react-native-safe-area-context": ">=4.0.0",
|
|
58
|
+
"@react-native-async-storage/async-storage": ">=1.0.0",
|
|
59
|
+
"zustand": ">=4.0.0",
|
|
60
|
+
"@umituz/react-native-filesystem": "latest",
|
|
61
|
+
"@react-native-community/slider": ">=4.0.0",
|
|
56
62
|
"typescript": "~5.9.2"
|
|
57
63
|
},
|
|
58
64
|
"publishConfig": {
|
package/src/index.ts
CHANGED
|
@@ -60,7 +60,6 @@ export {
|
|
|
60
60
|
// =============================================================================
|
|
61
61
|
|
|
62
62
|
export { ImageTransformService } from './infrastructure/services/ImageTransformService';
|
|
63
|
-
export { ImageAdvancedTransformService } from './infrastructure/services/ImageAdvancedTransformService';
|
|
64
63
|
export { ImageConversionService } from './infrastructure/services/ImageConversionService';
|
|
65
64
|
export { ImageStorageService } from './infrastructure/services/ImageStorageService';
|
|
66
65
|
export {
|
|
@@ -69,10 +68,9 @@ export {
|
|
|
69
68
|
} from './infrastructure/services/ImageViewerService';
|
|
70
69
|
|
|
71
70
|
export { ImageBatchService, type BatchOperation, type BatchProcessingOptions, type BatchProcessingResult } from './infrastructure/services/ImageBatchService';
|
|
72
|
-
export {
|
|
71
|
+
export { ImageEnhanceService, type AutoEnhancementOptions, type EnhancementResult } from './infrastructure/services/ImageEnhanceService';
|
|
73
72
|
export { ImageMetadataService, type ImageMetadataExtractionOptions } from './infrastructure/services/ImageMetadataService';
|
|
74
73
|
export { ImageQualityPresetService, type QualityPreset, type QualityPresets, IMAGE_QUALITY_PRESETS } from './infrastructure/utils/ImageQualityPresets';
|
|
75
|
-
export { ImageSpecializedEnhancementService } from './infrastructure/services/ImageSpecializedEnhancementService';
|
|
76
74
|
export { ImageTemplateService } from './infrastructure/services/ImageTemplateService';
|
|
77
75
|
|
|
78
76
|
// =============================================================================
|
|
@@ -80,6 +78,9 @@ export { ImageTemplateService } from './infrastructure/services/ImageTemplateSer
|
|
|
80
78
|
// =============================================================================
|
|
81
79
|
|
|
82
80
|
export { ImageGallery, type ImageGalleryProps } from './presentation/components/ImageGallery';
|
|
81
|
+
export { TextEditorSheet, type TextEditorSheetProps } from './presentation/components/editor/TextEditorSheet';
|
|
82
|
+
export { StickerPickerSheet, type StickerPickerSheetProps } from './presentation/components/editor/StickerPickerSheet';
|
|
83
|
+
export { FilterPickerSheet, type FilterPickerSheetProps } from './presentation/components/editor/FilterPickerSheet';
|
|
83
84
|
|
|
84
85
|
export { useImage } from './presentation/hooks/useImage';
|
|
85
86
|
export { useImageTransform } from './presentation/hooks/useImageTransform';
|
|
@@ -91,7 +92,7 @@ export {
|
|
|
91
92
|
} from './presentation/hooks/useImageGallery';
|
|
92
93
|
|
|
93
94
|
export { useImageBatch } from './presentation/hooks/useImageBatch';
|
|
94
|
-
export {
|
|
95
|
+
export { useImageEnhance } from './presentation/hooks/useImageEnhance';
|
|
95
96
|
export { useImageMetadata } from './presentation/hooks/useImageMetadata';
|
|
96
97
|
|
|
97
98
|
|
|
@@ -12,9 +12,9 @@ import type {
|
|
|
12
12
|
} from '../../domain/entities/ImageTypes';
|
|
13
13
|
import { IMAGE_CONSTANTS } from '../../domain/entities/ImageConstants';
|
|
14
14
|
import { ImageTransformService } from './ImageTransformService';
|
|
15
|
-
import { ImageAdvancedTransformService } from './ImageAdvancedTransformService';
|
|
16
15
|
import { ImageValidator } from '../utils/ImageValidator';
|
|
17
16
|
import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
|
|
17
|
+
import { ImageTransformUtils } from '../utils/ImageTransformUtils';
|
|
18
18
|
|
|
19
19
|
export class ImageConversionService {
|
|
20
20
|
static async compress(
|
|
@@ -22,15 +22,8 @@ export class ImageConversionService {
|
|
|
22
22
|
quality: number = IMAGE_CONSTANTS.defaultQuality
|
|
23
23
|
): Promise<ImageManipulationResult> {
|
|
24
24
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'compress');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const qualityValidation = ImageValidator.validateQuality(quality);
|
|
31
|
-
if (!qualityValidation.isValid) {
|
|
32
|
-
throw ImageErrorHandler.createError(qualityValidation.error!, IMAGE_ERROR_CODES.INVALID_QUALITY, 'compress');
|
|
33
|
-
}
|
|
25
|
+
ImageValidator.validateUri(uri);
|
|
26
|
+
ImageValidator.validateQuality(quality);
|
|
34
27
|
|
|
35
28
|
return await ImageManipulator.manipulateAsync(
|
|
36
29
|
uri,
|
|
@@ -48,23 +41,14 @@ export class ImageConversionService {
|
|
|
48
41
|
quality?: number
|
|
49
42
|
): Promise<ImageManipulationResult> {
|
|
50
43
|
try {
|
|
51
|
-
|
|
52
|
-
if (!uriValidation.isValid) {
|
|
53
|
-
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'convertFormat');
|
|
54
|
-
}
|
|
55
|
-
|
|
44
|
+
ImageValidator.validateUri(uri);
|
|
56
45
|
const compressQuality = quality ?? IMAGE_CONSTANTS.defaultQuality;
|
|
57
|
-
const qualityValidation = ImageValidator.validateQuality(compressQuality);
|
|
58
|
-
if (!qualityValidation.isValid) {
|
|
59
|
-
throw ImageErrorHandler.createError(qualityValidation.error!, IMAGE_ERROR_CODES.INVALID_QUALITY, 'convertFormat');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
46
|
return await ImageManipulator.manipulateAsync(
|
|
63
47
|
uri,
|
|
64
48
|
[],
|
|
65
49
|
{
|
|
66
50
|
compress: compressQuality,
|
|
67
|
-
format:
|
|
51
|
+
format: ImageTransformUtils.mapFormat(format),
|
|
68
52
|
}
|
|
69
53
|
);
|
|
70
54
|
} catch (error) {
|
|
@@ -78,17 +62,8 @@ export class ImageConversionService {
|
|
|
78
62
|
options?: ImageSaveOptions
|
|
79
63
|
): Promise<ImageManipulationResult> {
|
|
80
64
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'createThumbnail');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const dimValidation = ImageValidator.validateDimensions({ width: size, height: size });
|
|
87
|
-
if (!dimValidation.isValid) {
|
|
88
|
-
throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'createThumbnail');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return await ImageAdvancedTransformService.resizeToFit(uri, size, size, {
|
|
65
|
+
ImageValidator.validateUri(uri);
|
|
66
|
+
return await ImageTransformService.resizeToFit(uri, size, size, {
|
|
92
67
|
...options,
|
|
93
68
|
compress: options?.compress ?? IMAGE_CONSTANTS.compressQuality.medium,
|
|
94
69
|
});
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Infrastructure -
|
|
2
|
+
* Infrastructure - Editor Service
|
|
3
3
|
*
|
|
4
|
-
* Core editing functionality with
|
|
4
|
+
* Core editing functionality with layer and tool management
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import {
|
|
8
|
+
EditorTool,
|
|
9
|
+
type EditorState,
|
|
10
|
+
type EditorLayer,
|
|
11
|
+
type EditorHistory,
|
|
12
|
+
type EditorOptions
|
|
13
|
+
} from '../../domain/entities/EditorTypes';
|
|
10
14
|
import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
|
|
15
|
+
import { ImageEditorHistoryUtils } from '../utils/ImageEditorHistoryUtils';
|
|
11
16
|
|
|
12
17
|
export class ImageEditorService {
|
|
13
18
|
private static generateId(): string {
|
|
@@ -45,10 +50,7 @@ export class ImageEditorService {
|
|
|
45
50
|
};
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
static addLayer(
|
|
49
|
-
state: EditorState,
|
|
50
|
-
name?: string
|
|
51
|
-
): EditorState {
|
|
53
|
+
static addLayer(state: EditorState, name?: string): EditorState {
|
|
52
54
|
const newLayer: EditorLayer = {
|
|
53
55
|
id: ImageEditorService.generateId(),
|
|
54
56
|
name: name || `Layer ${state.layers.length}`,
|
|
@@ -58,177 +60,68 @@ export class ImageEditorService {
|
|
|
58
60
|
elements: [],
|
|
59
61
|
};
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
id: ImageEditorService.generateId(),
|
|
63
|
-
timestamp: new Date(),
|
|
64
|
-
layers: [...state.layers, newLayer],
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const newHistoryState = ImageEditorService.addToHistory(state, newHistory);
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
...newHistoryState,
|
|
71
|
-
layers: [...state.layers, newLayer],
|
|
63
|
+
return ImageEditorService.commitHistory(state, [...state.layers, newLayer], {
|
|
72
64
|
selectedLayer: newLayer.id,
|
|
73
|
-
|
|
74
|
-
};
|
|
65
|
+
});
|
|
75
66
|
}
|
|
76
67
|
|
|
77
|
-
static removeLayer(
|
|
78
|
-
state: EditorState,
|
|
79
|
-
layerId: string
|
|
80
|
-
): EditorState {
|
|
68
|
+
static removeLayer(state: EditorState, layerId: string): EditorState {
|
|
81
69
|
if (state.layers.length <= 1) {
|
|
82
|
-
throw ImageErrorHandler.createError(
|
|
83
|
-
'Cannot remove the last layer',
|
|
84
|
-
IMAGE_ERROR_CODES.VALIDATION_ERROR,
|
|
85
|
-
'removeLayer'
|
|
86
|
-
);
|
|
70
|
+
throw ImageErrorHandler.createError('Cannot remove background', IMAGE_ERROR_CODES.VALIDATION_ERROR, 'removeLayer');
|
|
87
71
|
}
|
|
88
72
|
|
|
89
73
|
const newLayers = state.layers.filter(layer => layer.id !== layerId);
|
|
90
|
-
|
|
91
|
-
id: ImageEditorService.generateId(),
|
|
92
|
-
timestamp: new Date(),
|
|
93
|
-
layers: newLayers,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const newHistoryState = ImageEditorService.addToHistory(state, newHistory);
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
...newHistoryState,
|
|
100
|
-
layers: newLayers,
|
|
74
|
+
return ImageEditorService.commitHistory(state, newLayers, {
|
|
101
75
|
selectedLayer: newLayers[0].id,
|
|
102
|
-
|
|
103
|
-
};
|
|
76
|
+
});
|
|
104
77
|
}
|
|
105
78
|
|
|
106
|
-
static updateLayer(
|
|
107
|
-
state:
|
|
108
|
-
|
|
109
|
-
updates: Partial<EditorLayer>
|
|
110
|
-
): EditorState {
|
|
111
|
-
const newLayers = state.layers.map(layer =>
|
|
112
|
-
layer.id === layerId ? { ...layer, ...updates } : layer
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
const newHistory: EditorHistory = {
|
|
116
|
-
id: ImageEditorService.generateId(),
|
|
117
|
-
timestamp: new Date(),
|
|
118
|
-
layers: newLayers,
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const newHistoryState = ImageEditorService.addToHistory(state, newHistory);
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
...newHistoryState,
|
|
125
|
-
layers: newLayers,
|
|
126
|
-
isDirty: true,
|
|
127
|
-
};
|
|
79
|
+
static updateLayer(state: EditorState, layerId: string, updates: Partial<EditorLayer>): EditorState {
|
|
80
|
+
const newLayers = state.layers.map(layer => layer.id === layerId ? { ...layer, ...updates } : layer);
|
|
81
|
+
return ImageEditorService.commitHistory(state, newLayers);
|
|
128
82
|
}
|
|
129
83
|
|
|
130
|
-
static addElementToLayer(
|
|
131
|
-
state
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
): EditorState {
|
|
135
|
-
const targetLayer = state.layers.find(layer => layer.id === layerId);
|
|
136
|
-
if (!targetLayer) {
|
|
137
|
-
throw ImageErrorHandler.createError(
|
|
138
|
-
'Layer not found',
|
|
139
|
-
IMAGE_ERROR_CODES.VALIDATION_ERROR,
|
|
140
|
-
'addElementToLayer'
|
|
141
|
-
);
|
|
84
|
+
static addElementToLayer(state: EditorState, layerId: string, element: any): EditorState {
|
|
85
|
+
const layer = state.layers.find(l => l.id === layerId);
|
|
86
|
+
if (!layer || layer.locked) {
|
|
87
|
+
throw ImageErrorHandler.createError('Invalid layer operation', IMAGE_ERROR_CODES.VALIDATION_ERROR, 'addElementToLayer');
|
|
142
88
|
}
|
|
143
89
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
'Cannot add element to locked layer',
|
|
147
|
-
IMAGE_ERROR_CODES.VALIDATION_ERROR,
|
|
148
|
-
'addElementToLayer'
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const newLayers = state.layers.map(layer =>
|
|
153
|
-
layer.id === layerId
|
|
154
|
-
? { ...layer, elements: [...layer.elements, element] }
|
|
155
|
-
: layer
|
|
90
|
+
const newLayers = state.layers.map(l =>
|
|
91
|
+
l.id === layerId ? { ...l, elements: [...l.elements, element] } : l
|
|
156
92
|
);
|
|
157
93
|
|
|
158
|
-
|
|
94
|
+
return ImageEditorService.commitHistory(state, newLayers);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private static commitHistory(
|
|
98
|
+
state: EditorState,
|
|
99
|
+
newLayers: EditorLayer[],
|
|
100
|
+
additionalState: Partial<EditorState> = {}
|
|
101
|
+
): EditorState {
|
|
102
|
+
const history: EditorHistory = {
|
|
159
103
|
id: ImageEditorService.generateId(),
|
|
160
104
|
timestamp: new Date(),
|
|
161
105
|
layers: newLayers,
|
|
162
106
|
};
|
|
163
107
|
|
|
164
|
-
const newHistoryState = ImageEditorService.addToHistory(state, newHistory);
|
|
165
|
-
|
|
166
108
|
return {
|
|
167
|
-
...
|
|
109
|
+
...ImageEditorHistoryUtils.addToHistory(state, history),
|
|
110
|
+
...additionalState,
|
|
168
111
|
layers: newLayers,
|
|
169
112
|
isDirty: true,
|
|
170
113
|
};
|
|
171
114
|
}
|
|
172
115
|
|
|
173
|
-
static undo
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const newIndex = state.historyIndex - 1;
|
|
179
|
-
const historyState = state.history[newIndex];
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
...state,
|
|
183
|
-
layers: historyState.layers,
|
|
184
|
-
historyIndex: newIndex,
|
|
185
|
-
isDirty: true,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
static redo(state: EditorState): EditorState {
|
|
190
|
-
if (state.historyIndex >= state.history.length - 1) {
|
|
191
|
-
return state;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const newIndex = state.historyIndex + 1;
|
|
195
|
-
const historyState = state.history[newIndex];
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
...state,
|
|
199
|
-
layers: historyState.layers,
|
|
200
|
-
historyIndex: newIndex,
|
|
201
|
-
isDirty: true,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private static addToHistory(
|
|
206
|
-
state: EditorState,
|
|
207
|
-
newHistory: EditorHistory,
|
|
208
|
-
maxHistory: number = 50
|
|
209
|
-
): EditorState {
|
|
210
|
-
const newHistoryArray = [...state.history.slice(0, state.historyIndex + 1), newHistory];
|
|
211
|
-
|
|
212
|
-
// Keep only the last maxHistory states
|
|
213
|
-
if (newHistoryArray.length > maxHistory) {
|
|
214
|
-
newHistoryArray.shift();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
...state,
|
|
219
|
-
history: newHistoryArray,
|
|
220
|
-
historyIndex: newHistoryArray.length - 1,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
116
|
+
static undo = ImageEditorHistoryUtils.undo;
|
|
117
|
+
static redo = ImageEditorHistoryUtils.redo;
|
|
118
|
+
static canUndo = ImageEditorHistoryUtils.canUndo;
|
|
119
|
+
static canRedo = ImageEditorHistoryUtils.canRedo;
|
|
223
120
|
|
|
224
121
|
static setTool(state: EditorState, tool: EditorTool): EditorState {
|
|
225
122
|
return { ...state, tool };
|
|
226
123
|
}
|
|
227
124
|
|
|
228
|
-
static setSelectedLayer(state: EditorState, layerId?: string): EditorState {
|
|
229
|
-
return { ...state, selectedLayer: layerId };
|
|
230
|
-
}
|
|
231
|
-
|
|
232
125
|
static setZoom(state: EditorState, zoom: number): EditorState {
|
|
233
126
|
return { ...state, zoom: Math.max(0.1, Math.min(5, zoom)) };
|
|
234
127
|
}
|
|
@@ -237,38 +130,7 @@ export class ImageEditorService {
|
|
|
237
130
|
return { ...state, pan };
|
|
238
131
|
}
|
|
239
132
|
|
|
240
|
-
static canUndo(state: EditorState): boolean {
|
|
241
|
-
return state.historyIndex > 0;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
static canRedo(state: EditorState): boolean {
|
|
245
|
-
return state.historyIndex < state.history.length - 1;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
133
|
static getVisibleLayers(state: EditorState): EditorLayer[] {
|
|
249
134
|
return state.layers.filter(layer => layer.visible);
|
|
250
135
|
}
|
|
251
|
-
|
|
252
|
-
static getActiveLayers(state: EditorState): EditorLayer[] {
|
|
253
|
-
return state.layers.filter(layer => layer.visible && !layer.locked);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
static exportState(state: EditorState): EditorState {
|
|
257
|
-
return {
|
|
258
|
-
...state,
|
|
259
|
-
currentUri: undefined,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
static importState(data: any, uri: string): EditorState {
|
|
264
|
-
try {
|
|
265
|
-
return {
|
|
266
|
-
...data,
|
|
267
|
-
originalUri: uri,
|
|
268
|
-
currentUri: undefined,
|
|
269
|
-
};
|
|
270
|
-
} catch (error) {
|
|
271
|
-
throw ImageErrorHandler.handleUnknownError(error, 'importState');
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
136
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Image Infrastructure -
|
|
2
|
+
* Image Infrastructure - Enhance Service
|
|
3
3
|
*
|
|
4
4
|
* AI-powered image enhancement and automatic adjustments
|
|
5
5
|
*/
|
|
@@ -11,7 +11,6 @@ import type {
|
|
|
11
11
|
} from '../../domain/entities/ImageFilterTypes';
|
|
12
12
|
import { ImageValidator } from '../utils/ImageValidator';
|
|
13
13
|
import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
|
|
14
|
-
import { AIImageAnalysisUtils } from '../utils/AIImageAnalysisUtils';
|
|
15
14
|
|
|
16
15
|
export interface AutoEnhancementOptions {
|
|
17
16
|
enhanceBrightness?: boolean;
|
|
@@ -29,49 +28,7 @@ export interface EnhancementResult {
|
|
|
29
28
|
improvementScore: number;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
export class
|
|
33
|
-
private static calculateHistogram(imageData: Uint8ClampedArray): number[] {
|
|
34
|
-
const histogram = new Array(256).fill(0);
|
|
35
|
-
for (let i = 0; i < imageData.length; i += 4) {
|
|
36
|
-
const gray = Math.round(0.299 * imageData[i] + 0.587 * imageData[i + 1] + 0.114 * imageData[i + 2]);
|
|
37
|
-
histogram[gray]++;
|
|
38
|
-
}
|
|
39
|
-
return histogram;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
private static calculateOptimalBrightness(histogram: number[]): number {
|
|
43
|
-
const totalPixels = histogram.reduce((sum, count) => sum + count, 0);
|
|
44
|
-
let sum = 0;
|
|
45
|
-
|
|
46
|
-
for (let i = 0; i < 256; i++) {
|
|
47
|
-
sum += histogram[i] * i;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const meanBrightness = sum / totalPixels;
|
|
51
|
-
const targetBrightness = 128;
|
|
52
|
-
|
|
53
|
-
return (targetBrightness - meanBrightness) / 255;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
private static calculateOptimalContrast(histogram: number[]): number {
|
|
57
|
-
const totalPixels = histogram.reduce((sum, count) => sum + count, 0);
|
|
58
|
-
let mean = 0;
|
|
59
|
-
let variance = 0;
|
|
60
|
-
|
|
61
|
-
for (let i = 0; i < 256; i++) {
|
|
62
|
-
mean += (histogram[i] / totalPixels) * i;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < 256; i++) {
|
|
66
|
-
variance += (histogram[i] / totalPixels) * Math.pow(i - mean, 2);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const standardDeviation = Math.sqrt(variance);
|
|
70
|
-
const targetStandardDeviation = 80;
|
|
71
|
-
|
|
72
|
-
return Math.max(-1, Math.min(1, (targetStandardDeviation - standardDeviation) / 100));
|
|
73
|
-
}
|
|
74
|
-
|
|
31
|
+
export class ImageEnhanceService {
|
|
75
32
|
static async analyzeImage(uri: string): Promise<ImageQualityMetrics> {
|
|
76
33
|
try {
|
|
77
34
|
const uriValidation = ImageValidator.validateUri(uri);
|
|
@@ -105,23 +62,19 @@ export class ImageAIEnhancementService {
|
|
|
105
62
|
enhanceBrightness = true,
|
|
106
63
|
enhanceContrast = true,
|
|
107
64
|
enhanceColor = true,
|
|
108
|
-
reduceNoise = false,
|
|
109
|
-
sharpen = false,
|
|
110
|
-
targetQuality = 85,
|
|
111
65
|
} = options;
|
|
112
66
|
|
|
113
|
-
const originalMetrics = await
|
|
67
|
+
const originalMetrics = await this.analyzeImage(uri);
|
|
114
68
|
const adjustments: ImageColorAdjustment = {};
|
|
115
69
|
|
|
116
70
|
if (enhanceBrightness) adjustments.brightness = 0.1;
|
|
117
71
|
if (enhanceContrast) adjustments.contrast = 0.15;
|
|
118
72
|
if (enhanceColor) adjustments.saturation = 0.1;
|
|
119
73
|
|
|
120
|
-
const enhancedMetrics = await
|
|
121
|
-
const improvementScore =
|
|
122
|
-
(enhancedMetrics.overallQuality - originalMetrics.overallQuality) /
|
|
123
|
-
|
|
124
|
-
) * 100;
|
|
74
|
+
const enhancedMetrics = await this.analyzeImage(uri);
|
|
75
|
+
const improvementScore = originalMetrics.overallQuality > 0
|
|
76
|
+
? ((enhancedMetrics.overallQuality - originalMetrics.overallQuality) / originalMetrics.overallQuality) * 100
|
|
77
|
+
: 0;
|
|
125
78
|
|
|
126
79
|
return {
|
|
127
80
|
originalMetrics,
|
|
@@ -133,4 +86,38 @@ export class ImageAIEnhancementService {
|
|
|
133
86
|
throw ImageErrorHandler.handleUnknownError(error, 'autoEnhance');
|
|
134
87
|
}
|
|
135
88
|
}
|
|
136
|
-
|
|
89
|
+
|
|
90
|
+
static async enhancePortrait(uri: string): Promise<ImageManipulationResult> {
|
|
91
|
+
try {
|
|
92
|
+
const uriValidation = ImageValidator.validateUri(uri);
|
|
93
|
+
if (!uriValidation.isValid) {
|
|
94
|
+
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'enhancePortrait');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
uri,
|
|
99
|
+
width: 0,
|
|
100
|
+
height: 0,
|
|
101
|
+
};
|
|
102
|
+
} catch (error) {
|
|
103
|
+
throw ImageErrorHandler.handleUnknownError(error, 'enhancePortrait');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static async enhanceLandscape(uri: string): Promise<ImageManipulationResult> {
|
|
108
|
+
try {
|
|
109
|
+
const uriValidation = ImageValidator.validateUri(uri);
|
|
110
|
+
if (!uriValidation.isValid) {
|
|
111
|
+
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'enhanceLandscape');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
uri,
|
|
116
|
+
width: 0,
|
|
117
|
+
height: 0,
|
|
118
|
+
};
|
|
119
|
+
} catch (error) {
|
|
120
|
+
throw ImageErrorHandler.handleUnknownError(error, 'enhanceLandscape');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -7,12 +7,16 @@ import { MemeTemplateOptions } from '../../domain/entities/ImageTemplateTypes';
|
|
|
7
7
|
*/
|
|
8
8
|
export class ImageTemplateService {
|
|
9
9
|
/**
|
|
10
|
-
* Generates a
|
|
10
|
+
* Generates a meme URL for a given template and texts
|
|
11
11
|
*
|
|
12
12
|
* @param options Meme configuration (templateKey, topText, bottomText)
|
|
13
|
-
* @
|
|
13
|
+
* @param baseUrl Optional base URL for the image generation service
|
|
14
|
+
* @returns Formatted image URL
|
|
14
15
|
*/
|
|
15
|
-
static generateMemeUrl(
|
|
16
|
+
static generateMemeUrl(
|
|
17
|
+
options: MemeTemplateOptions,
|
|
18
|
+
baseUrl: string = 'https://api.memegen.link'
|
|
19
|
+
): string {
|
|
16
20
|
const { templateKey, topText = '', bottomText = '', style, width, height } = options;
|
|
17
21
|
|
|
18
22
|
// Internal helper for memegen-specific encoding
|
|
@@ -32,7 +36,9 @@ export class ImageTemplateService {
|
|
|
32
36
|
const top = encodeMemeText(topText);
|
|
33
37
|
const bottom = encodeMemeText(bottomText);
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
// Ensure baseUrl doesn't have a trailing slash
|
|
40
|
+
const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
41
|
+
let url = `${base}/images/${templateKey}/${top}/${bottom}.png`;
|
|
36
42
|
|
|
37
43
|
const params: string[] = [];
|
|
38
44
|
if (style) params.push(`style=${style}`);
|