@umituz/react-native-image 1.3.8 → 1.3.9
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/index.ts +2 -4
- package/src/infrastructure/services/{ImageAIEnhancementService.ts → ImageEnhanceService.ts} +42 -55
- package/src/infrastructure/services/ImageTemplateService.ts +10 -4
- package/src/infrastructure/services/ImageTransformService.ts +90 -0
- package/src/infrastructure/utils/FilterProcessor.ts +16 -38
- package/src/infrastructure/utils/{AIImageAnalysisUtils.ts → ImageAnalysisUtils.ts} +3 -3
- package/src/infrastructure/utils/LayerManager.ts +0 -81
- 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.9",
|
|
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",
|
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
|
// =============================================================================
|
|
@@ -91,7 +89,7 @@ export {
|
|
|
91
89
|
} from './presentation/hooks/useImageGallery';
|
|
92
90
|
|
|
93
91
|
export { useImageBatch } from './presentation/hooks/useImageBatch';
|
|
94
|
-
export {
|
|
92
|
+
export { useImageEnhance } from './presentation/hooks/useImageEnhance';
|
|
95
93
|
export { useImageMetadata } from './presentation/hooks/useImageMetadata';
|
|
96
94
|
|
|
97
95
|
|
|
@@ -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}`);
|
|
@@ -136,4 +136,94 @@ export class ImageTransformService {
|
|
|
136
136
|
throw ImageErrorHandler.handleUnknownError(error, 'flip');
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
|
|
140
|
+
static async manipulate(
|
|
141
|
+
uri: string,
|
|
142
|
+
action: ImageManipulateAction,
|
|
143
|
+
options?: ImageSaveOptions
|
|
144
|
+
): Promise<ImageManipulationResult> {
|
|
145
|
+
try {
|
|
146
|
+
const uriValidation = ImageValidator.validateUri(uri);
|
|
147
|
+
if (!uriValidation.isValid) {
|
|
148
|
+
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'manipulate');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const actions: ImageManipulator.Action[] = [];
|
|
152
|
+
|
|
153
|
+
if (action.resize) {
|
|
154
|
+
const dimValidation = ImageValidator.validateDimensions(action.resize);
|
|
155
|
+
if (!dimValidation.isValid) {
|
|
156
|
+
throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'manipulate');
|
|
157
|
+
}
|
|
158
|
+
actions.push({ resize: action.resize });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (action.crop) {
|
|
162
|
+
const dimValidation = ImageValidator.validateDimensions(action.crop);
|
|
163
|
+
if (!dimValidation.isValid) {
|
|
164
|
+
throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'manipulate');
|
|
165
|
+
}
|
|
166
|
+
actions.push({ crop: action.crop });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (action.rotate) {
|
|
170
|
+
const rotationValidation = ImageValidator.validateRotation(action.rotate);
|
|
171
|
+
if (!rotationValidation.isValid) {
|
|
172
|
+
throw ImageErrorHandler.createError(rotationValidation.error!, IMAGE_ERROR_CODES.VALIDATION_ERROR, 'manipulate');
|
|
173
|
+
}
|
|
174
|
+
actions.push({ rotate: action.rotate });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (action.flip) {
|
|
178
|
+
if (action.flip.horizontal) actions.push({ flip: ImageManipulator.FlipType.Horizontal });
|
|
179
|
+
if (action.flip.vertical) actions.push({ flip: ImageManipulator.FlipType.Vertical });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return await ImageManipulator.manipulateAsync(
|
|
183
|
+
uri,
|
|
184
|
+
actions,
|
|
185
|
+
this.buildSaveOptions(options)
|
|
186
|
+
);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
throw ImageErrorHandler.handleUnknownError(error, 'manipulate');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
static async resizeToFit(
|
|
193
|
+
uri: string,
|
|
194
|
+
maxWidth: number,
|
|
195
|
+
maxHeight: number,
|
|
196
|
+
options?: ImageSaveOptions
|
|
197
|
+
): Promise<ImageManipulationResult> {
|
|
198
|
+
try {
|
|
199
|
+
const dimValidation = ImageValidator.validateDimensions({ width: maxWidth, height: maxHeight });
|
|
200
|
+
if (!dimValidation.isValid) {
|
|
201
|
+
throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'resizeToFit');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const dimensions = ImageUtils.fitToSize(maxWidth, maxHeight, maxWidth, maxHeight);
|
|
205
|
+
return this.resize(uri, dimensions.width, dimensions.height, options);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
throw ImageErrorHandler.handleUnknownError(error, 'resizeToFit');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static async cropToSquare(
|
|
212
|
+
uri: string,
|
|
213
|
+
width: number,
|
|
214
|
+
height: number,
|
|
215
|
+
options?: ImageSaveOptions
|
|
216
|
+
): Promise<ImageManipulationResult> {
|
|
217
|
+
try {
|
|
218
|
+
const dimValidation = ImageValidator.validateDimensions({ width, height });
|
|
219
|
+
if (!dimValidation.isValid) {
|
|
220
|
+
throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'cropToSquare');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const cropArea = ImageUtils.getSquareCrop(width, height);
|
|
224
|
+
return this.crop(uri, cropArea, options);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
throw ImageErrorHandler.handleUnknownError(error, 'cropToSquare');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
139
229
|
}
|
|
@@ -147,44 +147,43 @@ export class FilterProcessor {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
static applyFilter(
|
|
150
|
-
imageData:
|
|
150
|
+
imageData: Uint8ClampedArray,
|
|
151
|
+
width: number,
|
|
152
|
+
height: number,
|
|
151
153
|
filterState: FilterState
|
|
152
|
-
):
|
|
154
|
+
): Uint8ClampedArray {
|
|
153
155
|
const preset = this.getPreset(filterState.id);
|
|
154
156
|
if (!preset || !filterState.enabled) {
|
|
155
157
|
return imageData;
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
let processedData = new Uint8ClampedArray(imageData
|
|
160
|
+
let processedData = new Uint8ClampedArray(imageData);
|
|
159
161
|
|
|
160
162
|
// Apply filter based on preset type
|
|
161
163
|
switch (filterState.id) {
|
|
162
164
|
case 'brightness':
|
|
163
|
-
processedData = this.applyBrightness(processedData, filterState.parameters.brightness)
|
|
165
|
+
processedData = this.applyBrightness(processedData, filterState.parameters.brightness);
|
|
164
166
|
break;
|
|
165
167
|
case 'contrast':
|
|
166
|
-
processedData = this.applyContrast(processedData, filterState.parameters.contrast)
|
|
168
|
+
processedData = this.applyContrast(processedData, filterState.parameters.contrast);
|
|
167
169
|
break;
|
|
168
170
|
case 'saturation':
|
|
169
|
-
processedData = this.applySaturation(processedData, filterState.parameters.saturation)
|
|
171
|
+
processedData = this.applySaturation(processedData, filterState.parameters.saturation);
|
|
170
172
|
break;
|
|
171
173
|
case 'vintage':
|
|
172
|
-
processedData = this.applyVintage(processedData, filterState.parameters.intensity, filterState.parameters.warmth)
|
|
174
|
+
processedData = this.applyVintage(processedData, filterState.parameters.intensity, filterState.parameters.warmth);
|
|
173
175
|
break;
|
|
174
176
|
case 'blur':
|
|
175
|
-
processedData = this.applyBlur(processedData, filterState.parameters.radius
|
|
177
|
+
processedData = this.applyBlur(processedData, filterState.parameters.radius, width, height);
|
|
176
178
|
break;
|
|
177
179
|
}
|
|
178
180
|
|
|
179
181
|
// Apply intensity
|
|
180
182
|
if (filterState.intensity < 100) {
|
|
181
|
-
processedData = this.applyIntensity(imageData
|
|
183
|
+
processedData = this.applyIntensity(imageData, processedData, filterState.intensity / 100);
|
|
182
184
|
}
|
|
183
185
|
|
|
184
|
-
return
|
|
185
|
-
...imageData,
|
|
186
|
-
data: processedData,
|
|
187
|
-
};
|
|
186
|
+
return processedData;
|
|
188
187
|
}
|
|
189
188
|
|
|
190
189
|
private static applyBrightness(
|
|
@@ -282,12 +281,12 @@ export class FilterProcessor {
|
|
|
282
281
|
|
|
283
282
|
private static applyBlur(
|
|
284
283
|
data: Uint8ClampedArray,
|
|
285
|
-
radius: number
|
|
284
|
+
radius: number,
|
|
285
|
+
width: number,
|
|
286
|
+
height: number
|
|
286
287
|
): Uint8ClampedArray {
|
|
287
288
|
// Simple box blur implementation
|
|
288
289
|
const result = new Uint8ClampedArray(data);
|
|
289
|
-
const width = Math.sqrt(data.length / 4);
|
|
290
|
-
const height = width;
|
|
291
290
|
const size = Math.floor(radius) || 1;
|
|
292
291
|
|
|
293
292
|
for (let y = 0; y < height; y++) {
|
|
@@ -336,26 +335,5 @@ export class FilterProcessor {
|
|
|
336
335
|
return result;
|
|
337
336
|
}
|
|
338
337
|
|
|
339
|
-
|
|
340
|
-
imageData: ImageData,
|
|
341
|
-
filterState: FilterState,
|
|
342
|
-
previewSize: { width: number; height: number }
|
|
343
|
-
): ImageData {
|
|
344
|
-
// Create a smaller preview version
|
|
345
|
-
const previewCanvas = document.createElement('canvas') || {} as any;
|
|
346
|
-
previewCanvas.width = previewSize.width;
|
|
347
|
-
previewCanvas.height = previewSize.height;
|
|
348
|
-
const ctx = previewCanvas.getContext('2d');
|
|
349
|
-
|
|
350
|
-
if (!ctx) return imageData;
|
|
351
|
-
|
|
352
|
-
// Scale down the image for preview
|
|
353
|
-
ctx.drawImage(
|
|
354
|
-
{} as HTMLImageElement, // Would be the actual image
|
|
355
|
-
0, 0, previewSize.width, previewSize.height
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
const previewImageData = ctx.getImageData(0, 0, previewSize.width, previewSize.height);
|
|
359
|
-
return this.applyFilter(previewImageData, filterState);
|
|
360
|
-
}
|
|
338
|
+
|
|
361
339
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Image Infrastructure -
|
|
2
|
+
* Image Infrastructure - Image Analysis
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Image analysis utilities
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export class
|
|
7
|
+
export class ImageAnalysisUtils {
|
|
8
8
|
static calculateColorBalance(imageData: Uint8ClampedArray): {
|
|
9
9
|
redBalance: number;
|
|
10
10
|
greenBalance: number;
|
|
@@ -13,44 +13,7 @@ export interface LayerComposition {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export class LayerManager {
|
|
16
|
-
static composeLayers(
|
|
17
|
-
layers: Array<{
|
|
18
|
-
canvas: HTMLCanvasElement | any;
|
|
19
|
-
opacity: number;
|
|
20
|
-
visible: boolean;
|
|
21
|
-
x?: number;
|
|
22
|
-
y?: number;
|
|
23
|
-
}>,
|
|
24
|
-
composition: LayerComposition
|
|
25
|
-
): HTMLCanvasElement | any {
|
|
26
|
-
// Create composition canvas
|
|
27
|
-
const composedCanvas = document.createElement('canvas') || {} as any;
|
|
28
|
-
composedCanvas.width = composition.width;
|
|
29
|
-
composedCanvas.height = composition.height;
|
|
30
|
-
const ctx = composedCanvas.getContext('2d');
|
|
31
|
-
|
|
32
|
-
if (!ctx) return composedCanvas;
|
|
33
|
-
|
|
34
|
-
// Clear canvas with background color
|
|
35
|
-
ctx.fillStyle = composition.backgroundColor || '#ffffff';
|
|
36
|
-
ctx.fillRect(0, 0, composition.width, composition.height);
|
|
37
|
-
|
|
38
|
-
// Draw each layer
|
|
39
|
-
layers.forEach(layer => {
|
|
40
|
-
if (!layer.visible) return;
|
|
41
16
|
|
|
42
|
-
ctx.save();
|
|
43
|
-
ctx.globalAlpha = layer.opacity;
|
|
44
|
-
ctx.drawImage(
|
|
45
|
-
layer.canvas,
|
|
46
|
-
layer.x || 0,
|
|
47
|
-
layer.y || 0
|
|
48
|
-
);
|
|
49
|
-
ctx.restore();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return composedCanvas;
|
|
53
|
-
}
|
|
54
17
|
|
|
55
18
|
static mergeLayers(
|
|
56
19
|
layers: Array<{
|
|
@@ -110,49 +73,5 @@ export class LayerManager {
|
|
|
110
73
|
return result.map((layer, index) => ({ ...layer, index }));
|
|
111
74
|
}
|
|
112
75
|
|
|
113
|
-
static flattenLayers(
|
|
114
|
-
layers: Array<{
|
|
115
|
-
canvas: HTMLCanvasElement | any;
|
|
116
|
-
opacity: number;
|
|
117
|
-
visible: boolean;
|
|
118
|
-
}>,
|
|
119
|
-
composition: LayerComposition
|
|
120
|
-
): HTMLCanvasElement | any {
|
|
121
|
-
return LayerManager.composeLayers(layers, composition);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
static createLayerCanvas(
|
|
125
|
-
width: number,
|
|
126
|
-
height: number,
|
|
127
|
-
transparent: boolean = true
|
|
128
|
-
): HTMLCanvasElement | any {
|
|
129
|
-
const canvas = document.createElement('canvas') || {} as any;
|
|
130
|
-
canvas.width = width;
|
|
131
|
-
canvas.height = height;
|
|
132
|
-
|
|
133
|
-
if (!transparent) {
|
|
134
|
-
const ctx = canvas.getContext('2d');
|
|
135
|
-
if (ctx) {
|
|
136
|
-
ctx.fillStyle = '#ffffff';
|
|
137
|
-
ctx.fillRect(0, 0, width, height);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
76
|
|
|
141
|
-
return canvas;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
static clearLayer(
|
|
145
|
-
canvas: HTMLCanvasElement | any,
|
|
146
|
-
preserveAlpha: boolean = true
|
|
147
|
-
): void {
|
|
148
|
-
const ctx = canvas.getContext('2d');
|
|
149
|
-
if (!ctx) return;
|
|
150
|
-
|
|
151
|
-
if (preserveAlpha) {
|
|
152
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
153
|
-
} else {
|
|
154
|
-
ctx.fillStyle = 'transparent';
|
|
155
|
-
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
77
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation - Image Enhance Hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
|
+
import { useImageOperation } from './useImageOperation';
|
|
7
|
+
import { ImageEnhanceService, type AutoEnhancementOptions } from '../../infrastructure/services/ImageEnhanceService';
|
|
8
|
+
|
|
9
|
+
export const useImageEnhance = () => {
|
|
10
|
+
const { isProcessing, error, execute } = useImageOperation();
|
|
11
|
+
|
|
12
|
+
const autoEnhance = useCallback((uri: string, options?: AutoEnhancementOptions) =>
|
|
13
|
+
execute(() => ImageEnhanceService.autoEnhance(uri, options), 'Failed to auto enhance'), [execute]);
|
|
14
|
+
|
|
15
|
+
const enhancePortrait = useCallback((uri: string) =>
|
|
16
|
+
execute(() => ImageEnhanceService.enhancePortrait(uri), 'Failed to enhance portrait'), [execute]);
|
|
17
|
+
|
|
18
|
+
const enhanceLandscape = useCallback((uri: string) =>
|
|
19
|
+
execute(() => ImageEnhanceService.enhanceLandscape(uri), 'Failed to enhance landscape'), [execute]);
|
|
20
|
+
|
|
21
|
+
const analyzeImage = useCallback((uri: string) =>
|
|
22
|
+
execute(() => ImageEnhanceService.analyzeImage(uri), 'Failed to analyze image'), [execute]);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
autoEnhance,
|
|
26
|
+
enhancePortrait,
|
|
27
|
+
enhanceLandscape,
|
|
28
|
+
analyzeImage,
|
|
29
|
+
isEnhancing: isProcessing,
|
|
30
|
+
enhancementError: error,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
import { useCallback } from 'react';
|
|
5
5
|
import { useImageOperation } from './useImageOperation';
|
|
6
6
|
import { ImageTransformService } from '../../infrastructure/services/ImageTransformService';
|
|
7
|
-
import { ImageAdvancedTransformService } from '../../infrastructure/services/ImageAdvancedTransformService';
|
|
8
7
|
import type {
|
|
9
8
|
ImageManipulateAction,
|
|
10
9
|
ImageSaveOptions,
|
|
@@ -28,13 +27,13 @@ export const useImageTransform = () => {
|
|
|
28
27
|
execute(() => ImageTransformService.flip(uri, flipParams, options), 'Failed to flip'), [execute]);
|
|
29
28
|
|
|
30
29
|
const manipulate = useCallback((uri: string, action: ImageManipulateAction, options?: ImageSaveOptions) =>
|
|
31
|
-
execute(() =>
|
|
30
|
+
execute(() => ImageTransformService.manipulate(uri, action, options), 'Failed to manipulate'), [execute]);
|
|
32
31
|
|
|
33
32
|
const resizeToFit = useCallback((uri: string, maxWidth: number, maxHeight: number, options?: ImageSaveOptions) =>
|
|
34
|
-
execute(() =>
|
|
33
|
+
execute(() => ImageTransformService.resizeToFit(uri, maxWidth, maxHeight, options), 'Failed to resize to fit'), [execute]);
|
|
35
34
|
|
|
36
35
|
const cropToSquare = useCallback((uri: string, width: number, height: number, options?: ImageSaveOptions) =>
|
|
37
|
-
execute(() =>
|
|
36
|
+
execute(() => ImageTransformService.cropToSquare(uri, width, height, options), 'Failed to crop square'), [execute]);
|
|
38
37
|
|
|
39
38
|
return {
|
|
40
39
|
resize, crop, rotate, flip, manipulate, resizeToFit, cropToSquare,
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image Infrastructure - Advanced Transform Service
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as ImageManipulator from 'expo-image-manipulator';
|
|
6
|
-
import type {
|
|
7
|
-
ImageManipulateAction,
|
|
8
|
-
ImageSaveOptions,
|
|
9
|
-
ImageManipulationResult,
|
|
10
|
-
} from '../../domain/entities/ImageTypes';
|
|
11
|
-
import { ImageTransformService } from './ImageTransformService';
|
|
12
|
-
import { ImageUtils } from '../../domain/utils/ImageUtils';
|
|
13
|
-
import { ImageValidator } from '../utils/ImageValidator';
|
|
14
|
-
import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
|
|
15
|
-
|
|
16
|
-
export class ImageAdvancedTransformService {
|
|
17
|
-
static async manipulate(
|
|
18
|
-
uri: string,
|
|
19
|
-
action: ImageManipulateAction,
|
|
20
|
-
options?: ImageSaveOptions
|
|
21
|
-
): Promise<ImageManipulationResult> {
|
|
22
|
-
try {
|
|
23
|
-
const uriValidation = ImageValidator.validateUri(uri);
|
|
24
|
-
if (!uriValidation.isValid) {
|
|
25
|
-
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'manipulate');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const actions: ImageManipulator.Action[] = [];
|
|
29
|
-
|
|
30
|
-
if (action.resize) {
|
|
31
|
-
const dimValidation = ImageValidator.validateDimensions(action.resize);
|
|
32
|
-
if (!dimValidation.isValid) {
|
|
33
|
-
throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'manipulate');
|
|
34
|
-
}
|
|
35
|
-
actions.push({ resize: action.resize });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (action.crop) {
|
|
39
|
-
const dimValidation = ImageValidator.validateDimensions(action.crop);
|
|
40
|
-
if (!dimValidation.isValid) {
|
|
41
|
-
throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'manipulate');
|
|
42
|
-
}
|
|
43
|
-
actions.push({ crop: action.crop });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (action.rotate) {
|
|
47
|
-
const rotationValidation = ImageValidator.validateRotation(action.rotate);
|
|
48
|
-
if (!rotationValidation.isValid) {
|
|
49
|
-
throw ImageErrorHandler.createError(rotationValidation.error!, IMAGE_ERROR_CODES.VALIDATION_ERROR, 'manipulate');
|
|
50
|
-
}
|
|
51
|
-
actions.push({ rotate: action.rotate });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (action.flip) {
|
|
55
|
-
if (action.flip.horizontal) actions.push({ flip: ImageManipulator.FlipType.Horizontal });
|
|
56
|
-
if (action.flip.vertical) actions.push({ flip: ImageManipulator.FlipType.Vertical });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return await ImageManipulator.manipulateAsync(
|
|
60
|
-
uri,
|
|
61
|
-
actions,
|
|
62
|
-
ImageTransformService['buildSaveOptions'](options)
|
|
63
|
-
);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
throw ImageErrorHandler.handleUnknownError(error, 'manipulate');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
static async resizeToFit(
|
|
70
|
-
uri: string,
|
|
71
|
-
maxWidth: number,
|
|
72
|
-
maxHeight: number,
|
|
73
|
-
options?: ImageSaveOptions
|
|
74
|
-
): Promise<ImageManipulationResult> {
|
|
75
|
-
try {
|
|
76
|
-
const dimValidation = ImageValidator.validateDimensions({ width: maxWidth, height: maxHeight });
|
|
77
|
-
if (!dimValidation.isValid) {
|
|
78
|
-
throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'resizeToFit');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const dimensions = ImageUtils.fitToSize(maxWidth, maxHeight, maxWidth, maxHeight);
|
|
82
|
-
return ImageTransformService.resize(uri, dimensions.width, dimensions.height, options);
|
|
83
|
-
} catch (error) {
|
|
84
|
-
throw ImageErrorHandler.handleUnknownError(error, 'resizeToFit');
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
static async cropToSquare(
|
|
89
|
-
uri: string,
|
|
90
|
-
width: number,
|
|
91
|
-
height: number,
|
|
92
|
-
options?: ImageSaveOptions
|
|
93
|
-
): Promise<ImageManipulationResult> {
|
|
94
|
-
try {
|
|
95
|
-
const dimValidation = ImageValidator.validateDimensions({ width, height });
|
|
96
|
-
if (!dimValidation.isValid) {
|
|
97
|
-
throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'cropToSquare');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const cropArea = ImageUtils.getSquareCrop(width, height);
|
|
101
|
-
return ImageTransformService.crop(uri, cropArea, options);
|
|
102
|
-
} catch (error) {
|
|
103
|
-
throw ImageErrorHandler.handleUnknownError(error, 'cropToSquare');
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image Infrastructure - Specialized Enhancement
|
|
3
|
-
*
|
|
4
|
-
* Portrait and landscape specific enhancement services
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { ImageManipulationResult } from '../../domain/entities/ImageTypes';
|
|
8
|
-
import { ImageValidator } from '../utils/ImageValidator';
|
|
9
|
-
import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
|
|
10
|
-
|
|
11
|
-
export class ImageSpecializedEnhancementService {
|
|
12
|
-
static async enhancePortrait(uri: string): Promise<ImageManipulationResult> {
|
|
13
|
-
try {
|
|
14
|
-
const uriValidation = ImageValidator.validateUri(uri);
|
|
15
|
-
if (!uriValidation.isValid) {
|
|
16
|
-
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'enhancePortrait');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Portrait-specific enhancements:
|
|
20
|
-
// - Skin smoothing
|
|
21
|
-
// - Eye enhancement
|
|
22
|
-
// - Face detection and lighting adjustment
|
|
23
|
-
// - Background blur (bokeh effect)
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
uri,
|
|
27
|
-
width: 0,
|
|
28
|
-
height: 0,
|
|
29
|
-
};
|
|
30
|
-
} catch (error) {
|
|
31
|
-
throw ImageErrorHandler.handleUnknownError(error, 'enhancePortrait');
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
static async enhanceLandscape(uri: string): Promise<ImageManipulationResult> {
|
|
36
|
-
try {
|
|
37
|
-
const uriValidation = ImageValidator.validateUri(uri);
|
|
38
|
-
if (!uriValidation.isValid) {
|
|
39
|
-
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'enhanceLandscape');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Landscape-specific enhancements:
|
|
43
|
-
// - Sky enhancement
|
|
44
|
-
// - Green tone adjustment
|
|
45
|
-
// - HDR effect simulation
|
|
46
|
-
// - Perspective correction
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
uri,
|
|
50
|
-
width: 0,
|
|
51
|
-
height: 0,
|
|
52
|
-
};
|
|
53
|
-
} catch (error) {
|
|
54
|
-
throw ImageErrorHandler.handleUnknownError(error, 'enhanceLandscape');
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
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
|
-
};
|