@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.
Files changed (23) hide show
  1. package/package.json +8 -2
  2. package/src/index.ts +5 -4
  3. package/src/infrastructure/services/ImageConversionService.ts +7 -32
  4. package/src/infrastructure/services/ImageEditorService.ts +41 -179
  5. package/src/infrastructure/services/{ImageAIEnhancementService.ts → ImageEnhanceService.ts} +42 -55
  6. package/src/infrastructure/services/ImageTemplateService.ts +10 -4
  7. package/src/infrastructure/services/ImageTransformService.ts +43 -93
  8. package/src/infrastructure/utils/FilterProcessor.ts +26 -263
  9. package/src/infrastructure/utils/{AIImageAnalysisUtils.ts → ImageAnalysisUtils.ts} +3 -3
  10. package/src/infrastructure/utils/ImageEditorHistoryUtils.ts +63 -0
  11. package/src/infrastructure/utils/ImageFilterUtils.ts +152 -0
  12. package/src/infrastructure/utils/ImageTransformUtils.ts +25 -0
  13. package/src/infrastructure/utils/LayerManager.ts +0 -81
  14. package/src/presentation/components/editor/FilterPickerSheet.tsx +75 -0
  15. package/src/presentation/components/editor/StickerPickerSheet.tsx +62 -0
  16. package/src/presentation/components/editor/TextEditorSheet.tsx +98 -0
  17. package/src/presentation/components/editor/TextEditorTabs.tsx +111 -0
  18. package/src/presentation/hooks/useImage.ts +5 -8
  19. package/src/presentation/hooks/useImageEnhance.ts +32 -0
  20. package/src/presentation/hooks/useImageTransform.ts +3 -4
  21. package/src/infrastructure/services/ImageAdvancedTransformService.ts +0 -106
  22. package/src/infrastructure/services/ImageSpecializedEnhancementService.ts +0 -57
  23. 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.8",
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 { ImageAIEnhancementService, type AutoEnhancementOptions, type EnhancementResult } from './infrastructure/services/ImageAIEnhancementService';
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 { useImageAIEnhancement } from './presentation/hooks/useImageAIEnhancement';
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
- const uriValidation = ImageValidator.validateUri(uri);
26
- if (!uriValidation.isValid) {
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
- const uriValidation = ImageValidator.validateUri(uri);
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: ImageTransformService['mapFormat'](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
- const uriValidation = ImageValidator.validateUri(uri);
82
- if (!uriValidation.isValid) {
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 - Advanced Editor Service
2
+ * Infrastructure - Editor Service
3
3
  *
4
- * Core editing functionality with history management
4
+ * Core editing functionality with layer and tool management
5
5
  */
6
6
 
7
- import { EditorTool, type EditorState, type EditorLayer, type EditorHistory, type EditorOptions } from '../../domain/entities/EditorTypes';
8
- import type { ImageManipulationResult } from '../../domain/entities/ImageTypes';
9
- import { ImageValidator } from '../utils/ImageValidator';
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
- const newHistory: EditorHistory = {
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
- isDirty: true,
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
- const newHistory: EditorHistory = {
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
- isDirty: true,
103
- };
76
+ });
104
77
  }
105
78
 
106
- static updateLayer(
107
- state: EditorState,
108
- layerId: string,
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: EditorState,
132
- layerId: string,
133
- element: any
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
- if (targetLayer.locked) {
145
- throw ImageErrorHandler.createError(
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
- const newHistory: EditorHistory = {
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
- ...newHistoryState,
109
+ ...ImageEditorHistoryUtils.addToHistory(state, history),
110
+ ...additionalState,
168
111
  layers: newLayers,
169
112
  isDirty: true,
170
113
  };
171
114
  }
172
115
 
173
- static undo(state: EditorState): EditorState {
174
- if (state.historyIndex <= 0) {
175
- return state;
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 - AI Enhancement Service
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 ImageAIEnhancementService {
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 ImageAIEnhancementService.analyzeImage(uri);
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 ImageAIEnhancementService.analyzeImage(uri);
121
- const improvementScore = (
122
- (enhancedMetrics.overallQuality - originalMetrics.overallQuality) /
123
- originalMetrics.overallQuality
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 Memegen.link URL for a given template and texts
10
+ * Generates a meme URL for a given template and texts
11
11
  *
12
12
  * @param options Meme configuration (templateKey, topText, bottomText)
13
- * @returns Formatted memegen.link URL
13
+ * @param baseUrl Optional base URL for the image generation service
14
+ * @returns Formatted image URL
14
15
  */
15
- static generateMemeUrl(options: MemeTemplateOptions): string {
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
- let url = `https://api.memegen.link/images/${templateKey}/${top}/${bottom}.png`;
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}`);