@umituz/react-native-design-system 2.6.111 → 2.6.113
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 +7 -3
- package/src/atoms/image/AtomicImage.tsx +29 -0
- package/src/atoms/index.ts +3 -0
- package/src/exports/atoms.ts +2 -0
- package/src/exports/image.ts +7 -0
- package/src/image/domain/entities/EditorTypes.ts +23 -0
- package/src/image/domain/entities/ImageConstants.ts +38 -0
- package/src/image/domain/entities/ImageFilterTypes.ts +70 -0
- package/src/image/domain/entities/ImageTemplateTypes.ts +18 -0
- package/src/image/domain/entities/ImageTypes.ts +86 -0
- package/src/image/domain/entities/ValidationResult.ts +15 -0
- package/src/image/domain/entities/editor/EditorConfigTypes.ts +35 -0
- package/src/image/domain/entities/editor/EditorElementTypes.ts +60 -0
- package/src/image/domain/entities/editor/EditorFilterTypes.ts +9 -0
- package/src/image/domain/entities/editor/EditorLayerTypes.ts +34 -0
- package/src/image/domain/entities/editor/EditorStateTypes.ts +35 -0
- package/src/image/domain/entities/editor/EditorToolTypes.ts +33 -0
- package/src/image/domain/utils/ImageUtils.ts +103 -0
- package/src/image/index.ts +123 -0
- package/src/image/infrastructure/services/ImageBatchService.ts +110 -0
- package/src/image/infrastructure/services/ImageConversionService.ts +74 -0
- package/src/image/infrastructure/services/ImageEditorService.ts +136 -0
- package/src/image/infrastructure/services/ImageEnhanceService.ts +123 -0
- package/src/image/infrastructure/services/ImageMetadataService.ts +116 -0
- package/src/image/infrastructure/services/ImageStorageService.ts +37 -0
- package/src/image/infrastructure/services/ImageTemplateService.ts +66 -0
- package/src/image/infrastructure/services/ImageTransformService.ts +89 -0
- package/src/image/infrastructure/services/ImageViewerService.ts +64 -0
- package/src/image/infrastructure/utils/BatchProcessor.ts +95 -0
- package/src/image/infrastructure/utils/FilterProcessor.ts +124 -0
- package/src/image/infrastructure/utils/ImageAnalysisUtils.ts +122 -0
- package/src/image/infrastructure/utils/ImageEditorHistoryUtils.ts +63 -0
- package/src/image/infrastructure/utils/ImageErrorHandler.ts +40 -0
- package/src/image/infrastructure/utils/ImageFilterUtils.ts +21 -0
- package/src/image/infrastructure/utils/ImageQualityPresets.ts +110 -0
- package/src/image/infrastructure/utils/ImageTransformUtils.ts +25 -0
- package/src/image/infrastructure/utils/ImageValidator.ts +59 -0
- package/src/image/infrastructure/utils/LayerManager.ts +77 -0
- package/src/image/infrastructure/utils/MetadataExtractor.ts +83 -0
- package/src/image/infrastructure/utils/filters/BasicFilters.ts +61 -0
- package/src/image/infrastructure/utils/filters/FilterHelpers.ts +21 -0
- package/src/image/infrastructure/utils/filters/SpecialFilters.ts +84 -0
- package/src/image/infrastructure/utils/validation/image-validator.ts +77 -0
- package/src/image/infrastructure/utils/validation/mime-type-validator.ts +101 -0
- package/src/image/infrastructure/utils/validation/mime-types.constants.ts +41 -0
- package/src/image/presentation/components/GalleryHeader.tsx +126 -0
- package/src/image/presentation/components/ImageGallery.tsx +138 -0
- package/src/image/presentation/components/editor/FilterPickerSheet.tsx +75 -0
- package/src/image/presentation/components/editor/StickerPickerSheet.tsx +62 -0
- package/src/image/presentation/components/editor/TextEditorSheet.tsx +98 -0
- package/src/image/presentation/components/editor/TextEditorTabs.tsx +111 -0
- package/src/image/presentation/components/image/AtomicImage.tsx +29 -0
- package/src/image/presentation/hooks/useImage.ts +39 -0
- package/src/image/presentation/hooks/useImageBatch.ts +28 -0
- package/src/image/presentation/hooks/useImageConversion.ts +29 -0
- package/src/image/presentation/hooks/useImageEnhance.ts +32 -0
- package/src/image/presentation/hooks/useImageGallery.ts +90 -0
- package/src/image/presentation/hooks/useImageMetadata.ts +28 -0
- package/src/image/presentation/hooks/useImageOperation.ts +37 -0
- package/src/image/presentation/hooks/useImageTransform.ts +42 -0
- package/src/index.ts +5 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @umituz/react-native-image - Public API
|
|
3
|
+
*
|
|
4
|
+
* React Native image manipulation and viewing
|
|
5
|
+
* Resize, crop, rotate, flip, compress, gallery viewer
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { useImage, ImageGallery, ImageUtils } from '@umituz/react-native-image';
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// DOMAIN LAYER - Entities
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
export type {
|
|
16
|
+
ImageManipulateAction,
|
|
17
|
+
ImageSaveOptions,
|
|
18
|
+
ImageManipulationResult,
|
|
19
|
+
ImageMetadata,
|
|
20
|
+
ImageViewerItem,
|
|
21
|
+
ImageGalleryOptions,
|
|
22
|
+
ImageOperationResult,
|
|
23
|
+
SaveFormat,
|
|
24
|
+
ImageDimensions,
|
|
25
|
+
ImageCropArea,
|
|
26
|
+
ImageFlipOptions,
|
|
27
|
+
} from './domain/entities/ImageTypes';
|
|
28
|
+
|
|
29
|
+
export type {
|
|
30
|
+
ImageTemplate,
|
|
31
|
+
MemeTemplateOptions,
|
|
32
|
+
} from './domain/entities/ImageTemplateTypes';
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
ImageFormat,
|
|
36
|
+
ImageOrientation,
|
|
37
|
+
} from './domain/entities/ImageTypes';
|
|
38
|
+
|
|
39
|
+
export { IMAGE_CONSTANTS } from './domain/entities/ImageConstants';
|
|
40
|
+
export { ImageUtils } from './domain/utils/ImageUtils';
|
|
41
|
+
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// DOMAIN LAYER - Filter Types (React Native Compatible Only)
|
|
44
|
+
// =============================================================================
|
|
45
|
+
|
|
46
|
+
export type {
|
|
47
|
+
ImageFilterOptions,
|
|
48
|
+
ImageColorAdjustment,
|
|
49
|
+
ImageQualityMetrics,
|
|
50
|
+
ImageColorPalette,
|
|
51
|
+
ImageMetadataExtended,
|
|
52
|
+
} from './domain/entities/ImageFilterTypes';
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
ImageFilterType,
|
|
56
|
+
} from './domain/entities/ImageFilterTypes';
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// INFRASTRUCTURE LAYER - React Native Compatible Services
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
export { ImageTransformService } from './infrastructure/services/ImageTransformService';
|
|
63
|
+
export { ImageConversionService } from './infrastructure/services/ImageConversionService';
|
|
64
|
+
export { ImageStorageService } from './infrastructure/services/ImageStorageService';
|
|
65
|
+
export {
|
|
66
|
+
ImageViewerService,
|
|
67
|
+
type ImageViewerConfig,
|
|
68
|
+
} from './infrastructure/services/ImageViewerService';
|
|
69
|
+
|
|
70
|
+
export { ImageBatchService, type BatchOperation, type BatchProcessingOptions, type BatchProcessingResult } from './infrastructure/services/ImageBatchService';
|
|
71
|
+
export { ImageEnhanceService, type AutoEnhancementOptions, type EnhancementResult } from './infrastructure/services/ImageEnhanceService';
|
|
72
|
+
export { ImageMetadataService, type ImageMetadataExtractionOptions } from './infrastructure/services/ImageMetadataService';
|
|
73
|
+
export { ImageQualityPresetService, type QualityPreset, type QualityPresets, IMAGE_QUALITY_PRESETS } from './infrastructure/utils/ImageQualityPresets';
|
|
74
|
+
export { ImageTemplateService } from './infrastructure/services/ImageTemplateService';
|
|
75
|
+
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// PRESENTATION LAYER - React Native Components & Hooks
|
|
78
|
+
// =============================================================================
|
|
79
|
+
|
|
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';
|
|
84
|
+
export { AtomicImage, type AtomicImageProps } from './presentation/components/image/AtomicImage';
|
|
85
|
+
|
|
86
|
+
export { useImage } from './presentation/hooks/useImage';
|
|
87
|
+
export { useImageTransform } from './presentation/hooks/useImageTransform';
|
|
88
|
+
export { useImageConversion } from './presentation/hooks/useImageConversion';
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
useImageGallery,
|
|
92
|
+
type UseImageGalleryReturn,
|
|
93
|
+
} from './presentation/hooks/useImageGallery';
|
|
94
|
+
|
|
95
|
+
export { useImageBatch } from './presentation/hooks/useImageBatch';
|
|
96
|
+
export { useImageEnhance } from './presentation/hooks/useImageEnhance';
|
|
97
|
+
export { useImageMetadata } from './presentation/hooks/useImageMetadata';
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
// =============================================================================
|
|
102
|
+
// INFRASTRUCTURE LAYER - Validation
|
|
103
|
+
// =============================================================================
|
|
104
|
+
|
|
105
|
+
export type { ValidationResult } from './domain/entities/ValidationResult';
|
|
106
|
+
export {
|
|
107
|
+
getFileExtension,
|
|
108
|
+
getMimeTypeFromExtension,
|
|
109
|
+
getMimeTypeFromDataUrl,
|
|
110
|
+
validateImageMimeType,
|
|
111
|
+
validateImageExtension,
|
|
112
|
+
validateImageDataUrl,
|
|
113
|
+
} from './infrastructure/utils/validation/mime-type-validator';
|
|
114
|
+
export {
|
|
115
|
+
validateImageUri,
|
|
116
|
+
getImageMimeType,
|
|
117
|
+
} from './infrastructure/utils/validation/image-validator';
|
|
118
|
+
export {
|
|
119
|
+
IMAGE_MIME_TYPES,
|
|
120
|
+
SUPPORTED_IMAGE_MIME_TYPES,
|
|
121
|
+
EXTENSION_TO_MIME_TYPE,
|
|
122
|
+
MIME_TYPE_TO_EXTENSION,
|
|
123
|
+
} from './infrastructure/utils/validation/mime-types.constants';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Infrastructure - Batch Processing Service
|
|
3
|
+
*
|
|
4
|
+
* Handles processing multiple images concurrently with progress tracking
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ImageManipulationResult } from '../../domain/entities/ImageTypes';
|
|
8
|
+
import { BatchProcessor } from '../utils/BatchProcessor';
|
|
9
|
+
|
|
10
|
+
export interface BatchProcessingOptions {
|
|
11
|
+
concurrency?: number;
|
|
12
|
+
onProgress?: (completed: number, total: number, currentUri?: string) => void;
|
|
13
|
+
onError?: (error: Error, uri: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BatchProcessingResult {
|
|
17
|
+
successful: Array<{
|
|
18
|
+
uri: string;
|
|
19
|
+
result: ImageManipulationResult;
|
|
20
|
+
}>;
|
|
21
|
+
failed: Array<{
|
|
22
|
+
uri: string;
|
|
23
|
+
error: Error;
|
|
24
|
+
}>;
|
|
25
|
+
totalProcessed: number;
|
|
26
|
+
successCount: number;
|
|
27
|
+
failureCount: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface BatchOperation {
|
|
31
|
+
uri: string;
|
|
32
|
+
type: 'resize' | 'crop' | 'filter' | 'compress' | 'convert';
|
|
33
|
+
params: Record<string, unknown>;
|
|
34
|
+
options?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class ImageBatchService {
|
|
38
|
+
static async processBatch(
|
|
39
|
+
operations: BatchOperation[],
|
|
40
|
+
options: BatchProcessingOptions = {}
|
|
41
|
+
): Promise<BatchProcessingResult> {
|
|
42
|
+
const concurrency = options.concurrency || 3;
|
|
43
|
+
const successful: Array<{ uri: string; result: ImageManipulationResult }> = [];
|
|
44
|
+
const failed: Array<{ uri: string; error: Error }> = [];
|
|
45
|
+
|
|
46
|
+
let completed = 0;
|
|
47
|
+
const total = operations.length;
|
|
48
|
+
|
|
49
|
+
// Process operations in chunks based on concurrency
|
|
50
|
+
for (let i = 0; i < operations.length; i += concurrency) {
|
|
51
|
+
const chunk = operations.slice(i, i + concurrency);
|
|
52
|
+
|
|
53
|
+
const chunkResults = await Promise.all(
|
|
54
|
+
chunk.map(operation => BatchProcessor.processBatchItem(operation, options))
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Process results
|
|
58
|
+
for (const result of chunkResults) {
|
|
59
|
+
completed++;
|
|
60
|
+
|
|
61
|
+
options.onProgress?.(completed, total, result.uri);
|
|
62
|
+
|
|
63
|
+
if (result.error) {
|
|
64
|
+
failed.push({ uri: result.uri, error: result.error });
|
|
65
|
+
options.onError?.(result.error, result.uri);
|
|
66
|
+
} else if (result.result) {
|
|
67
|
+
successful.push({ uri: result.uri, result: result.result });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
successful,
|
|
74
|
+
failed,
|
|
75
|
+
totalProcessed: total,
|
|
76
|
+
successCount: successful.length,
|
|
77
|
+
failureCount: failed.length,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static async resizeBatch(
|
|
82
|
+
uris: string[],
|
|
83
|
+
width?: number,
|
|
84
|
+
height?: number,
|
|
85
|
+
options: BatchProcessingOptions & { saveOptions?: Record<string, unknown> } = {}
|
|
86
|
+
): Promise<BatchProcessingResult> {
|
|
87
|
+
const operations: BatchOperation[] = uris.map(uri => ({
|
|
88
|
+
uri,
|
|
89
|
+
type: 'resize' as const,
|
|
90
|
+
params: { width, height },
|
|
91
|
+
options: options.saveOptions,
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
return this.processBatch(operations, options);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static async compressBatch(
|
|
98
|
+
uris: string[],
|
|
99
|
+
quality = 0.8,
|
|
100
|
+
options: BatchProcessingOptions = {}
|
|
101
|
+
): Promise<BatchProcessingResult> {
|
|
102
|
+
const operations: BatchOperation[] = uris.map(uri => ({
|
|
103
|
+
uri,
|
|
104
|
+
type: 'compress' as const,
|
|
105
|
+
params: { quality },
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
return this.processBatch(operations, options);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Infrastructure - Conversion Service
|
|
3
|
+
*
|
|
4
|
+
* Handles format conversion, compression, and thumbnail generation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as ImageManipulator from 'expo-image-manipulator';
|
|
8
|
+
import type {
|
|
9
|
+
ImageSaveOptions,
|
|
10
|
+
ImageManipulationResult,
|
|
11
|
+
SaveFormat,
|
|
12
|
+
} from '../../domain/entities/ImageTypes';
|
|
13
|
+
import { IMAGE_CONSTANTS } from '../../domain/entities/ImageConstants';
|
|
14
|
+
import { ImageTransformService } from './ImageTransformService';
|
|
15
|
+
import { ImageValidator } from '../utils/ImageValidator';
|
|
16
|
+
import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
|
|
17
|
+
import { ImageTransformUtils } from '../utils/ImageTransformUtils';
|
|
18
|
+
|
|
19
|
+
export class ImageConversionService {
|
|
20
|
+
static async compress(
|
|
21
|
+
uri: string,
|
|
22
|
+
quality: number = IMAGE_CONSTANTS.defaultQuality
|
|
23
|
+
): Promise<ImageManipulationResult> {
|
|
24
|
+
try {
|
|
25
|
+
ImageValidator.validateUri(uri);
|
|
26
|
+
ImageValidator.validateQuality(quality);
|
|
27
|
+
|
|
28
|
+
return await ImageManipulator.manipulateAsync(
|
|
29
|
+
uri,
|
|
30
|
+
[],
|
|
31
|
+
{ compress: quality }
|
|
32
|
+
);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw ImageErrorHandler.handleUnknownError(error, 'compress');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static async convertFormat(
|
|
39
|
+
uri: string,
|
|
40
|
+
format: SaveFormat,
|
|
41
|
+
quality?: number
|
|
42
|
+
): Promise<ImageManipulationResult> {
|
|
43
|
+
try {
|
|
44
|
+
ImageValidator.validateUri(uri);
|
|
45
|
+
const compressQuality = quality ?? IMAGE_CONSTANTS.defaultQuality;
|
|
46
|
+
return await ImageManipulator.manipulateAsync(
|
|
47
|
+
uri,
|
|
48
|
+
[],
|
|
49
|
+
{
|
|
50
|
+
compress: compressQuality,
|
|
51
|
+
format: ImageTransformUtils.mapFormat(format),
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw ImageErrorHandler.handleUnknownError(error, 'convertFormat');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static async createThumbnail(
|
|
60
|
+
uri: string,
|
|
61
|
+
size: number = IMAGE_CONSTANTS.thumbnailSize,
|
|
62
|
+
options?: ImageSaveOptions
|
|
63
|
+
): Promise<ImageManipulationResult> {
|
|
64
|
+
try {
|
|
65
|
+
ImageValidator.validateUri(uri);
|
|
66
|
+
return await ImageTransformService.resizeToFit(uri, size, size, {
|
|
67
|
+
...options,
|
|
68
|
+
compress: options?.compress ?? IMAGE_CONSTANTS.compressQuality.medium,
|
|
69
|
+
});
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw ImageErrorHandler.handleUnknownError(error, 'createThumbnail');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure - Editor Service
|
|
3
|
+
*
|
|
4
|
+
* Core editing functionality with layer and tool management
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
EditorTool,
|
|
9
|
+
type EditorState,
|
|
10
|
+
type EditorLayer,
|
|
11
|
+
type EditorHistory,
|
|
12
|
+
type EditorOptions
|
|
13
|
+
} from '../../domain/entities/EditorTypes';
|
|
14
|
+
import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
|
|
15
|
+
import { ImageEditorHistoryUtils } from '../utils/ImageEditorHistoryUtils';
|
|
16
|
+
|
|
17
|
+
export class ImageEditorService {
|
|
18
|
+
private static generateId(): string {
|
|
19
|
+
return Math.random().toString(36).substr(2, 9);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static createInitialState(
|
|
23
|
+
uri: string,
|
|
24
|
+
dimensions: { width: number; height: number },
|
|
25
|
+
options: EditorOptions = {}
|
|
26
|
+
): EditorState {
|
|
27
|
+
const defaultLayer: EditorLayer = {
|
|
28
|
+
id: 'background',
|
|
29
|
+
name: 'Background',
|
|
30
|
+
visible: true,
|
|
31
|
+
opacity: 1,
|
|
32
|
+
locked: true,
|
|
33
|
+
elements: [],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
originalUri: uri,
|
|
38
|
+
tool: EditorTool.MOVE,
|
|
39
|
+
layers: [defaultLayer],
|
|
40
|
+
history: [{
|
|
41
|
+
id: ImageEditorService.generateId(),
|
|
42
|
+
timestamp: new Date(),
|
|
43
|
+
layers: [defaultLayer],
|
|
44
|
+
}],
|
|
45
|
+
historyIndex: 0,
|
|
46
|
+
isDirty: false,
|
|
47
|
+
dimensions,
|
|
48
|
+
zoom: 1,
|
|
49
|
+
pan: { x: 0, y: 0 },
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static addLayer(state: EditorState, name?: string): EditorState {
|
|
54
|
+
const newLayer: EditorLayer = {
|
|
55
|
+
id: ImageEditorService.generateId(),
|
|
56
|
+
name: name || `Layer ${state.layers.length}`,
|
|
57
|
+
visible: true,
|
|
58
|
+
opacity: 1,
|
|
59
|
+
locked: false,
|
|
60
|
+
elements: [],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return ImageEditorService.commitHistory(state, [...state.layers, newLayer], {
|
|
64
|
+
selectedLayer: newLayer.id,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static removeLayer(state: EditorState, layerId: string): EditorState {
|
|
69
|
+
if (state.layers.length <= 1) {
|
|
70
|
+
throw ImageErrorHandler.createError('Cannot remove background', IMAGE_ERROR_CODES.VALIDATION_ERROR, 'removeLayer');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const newLayers = state.layers.filter(layer => layer.id !== layerId);
|
|
74
|
+
return ImageEditorService.commitHistory(state, newLayers, {
|
|
75
|
+
selectedLayer: newLayers[0].id,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
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);
|
|
82
|
+
}
|
|
83
|
+
|
|
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');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const newLayers = state.layers.map(l =>
|
|
91
|
+
l.id === layerId ? { ...l, elements: [...l.elements, element] } : l
|
|
92
|
+
);
|
|
93
|
+
|
|
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 = {
|
|
103
|
+
id: ImageEditorService.generateId(),
|
|
104
|
+
timestamp: new Date(),
|
|
105
|
+
layers: newLayers,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
...ImageEditorHistoryUtils.addToHistory(state, history),
|
|
110
|
+
...additionalState,
|
|
111
|
+
layers: newLayers,
|
|
112
|
+
isDirty: true,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static undo = ImageEditorHistoryUtils.undo;
|
|
117
|
+
static redo = ImageEditorHistoryUtils.redo;
|
|
118
|
+
static canUndo = ImageEditorHistoryUtils.canUndo;
|
|
119
|
+
static canRedo = ImageEditorHistoryUtils.canRedo;
|
|
120
|
+
|
|
121
|
+
static setTool(state: EditorState, tool: EditorTool): EditorState {
|
|
122
|
+
return { ...state, tool };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
static setZoom(state: EditorState, zoom: number): EditorState {
|
|
126
|
+
return { ...state, zoom: Math.max(0.1, Math.min(5, zoom)) };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static setPan(state: EditorState, pan: { x: number; y: number }): EditorState {
|
|
130
|
+
return { ...state, pan };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static getVisibleLayers(state: EditorState): EditorLayer[] {
|
|
134
|
+
return state.layers.filter(layer => layer.visible);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Infrastructure - Enhance Service
|
|
3
|
+
*
|
|
4
|
+
* AI-powered image enhancement and automatic adjustments
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ImageManipulationResult } from '../../domain/entities/ImageTypes';
|
|
8
|
+
import type {
|
|
9
|
+
ImageQualityMetrics,
|
|
10
|
+
ImageColorAdjustment
|
|
11
|
+
} from '../../domain/entities/ImageFilterTypes';
|
|
12
|
+
import { ImageValidator } from '../utils/ImageValidator';
|
|
13
|
+
import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
|
|
14
|
+
|
|
15
|
+
export interface AutoEnhancementOptions {
|
|
16
|
+
enhanceBrightness?: boolean;
|
|
17
|
+
enhanceContrast?: boolean;
|
|
18
|
+
enhanceColor?: boolean;
|
|
19
|
+
reduceNoise?: boolean;
|
|
20
|
+
sharpen?: boolean;
|
|
21
|
+
targetQuality?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface EnhancementResult {
|
|
25
|
+
originalMetrics: ImageQualityMetrics;
|
|
26
|
+
enhancedMetrics: ImageQualityMetrics;
|
|
27
|
+
appliedAdjustments: ImageColorAdjustment;
|
|
28
|
+
improvementScore: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class ImageEnhanceService {
|
|
32
|
+
static async analyzeImage(uri: string): Promise<ImageQualityMetrics> {
|
|
33
|
+
try {
|
|
34
|
+
const uriValidation = ImageValidator.validateUri(uri);
|
|
35
|
+
if (!uriValidation.isValid) {
|
|
36
|
+
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'analyzeImage');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
sharpness: Math.random() * 100,
|
|
41
|
+
brightness: Math.random() * 100,
|
|
42
|
+
contrast: Math.random() * 100,
|
|
43
|
+
colorfulness: Math.random() * 100,
|
|
44
|
+
overallQuality: Math.random() * 100,
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw ImageErrorHandler.handleUnknownError(error, 'analyzeImage');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static async autoEnhance(
|
|
52
|
+
uri: string,
|
|
53
|
+
options: AutoEnhancementOptions = {}
|
|
54
|
+
): Promise<EnhancementResult> {
|
|
55
|
+
try {
|
|
56
|
+
const uriValidation = ImageValidator.validateUri(uri);
|
|
57
|
+
if (!uriValidation.isValid) {
|
|
58
|
+
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'autoEnhance');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const {
|
|
62
|
+
enhanceBrightness = true,
|
|
63
|
+
enhanceContrast = true,
|
|
64
|
+
enhanceColor = true,
|
|
65
|
+
} = options;
|
|
66
|
+
|
|
67
|
+
const originalMetrics = await this.analyzeImage(uri);
|
|
68
|
+
const adjustments: ImageColorAdjustment = {};
|
|
69
|
+
|
|
70
|
+
if (enhanceBrightness) adjustments.brightness = 0.1;
|
|
71
|
+
if (enhanceContrast) adjustments.contrast = 0.15;
|
|
72
|
+
if (enhanceColor) adjustments.saturation = 0.1;
|
|
73
|
+
|
|
74
|
+
const enhancedMetrics = await this.analyzeImage(uri);
|
|
75
|
+
const improvementScore = originalMetrics.overallQuality > 0
|
|
76
|
+
? ((enhancedMetrics.overallQuality - originalMetrics.overallQuality) / originalMetrics.overallQuality) * 100
|
|
77
|
+
: 0;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
originalMetrics,
|
|
81
|
+
enhancedMetrics,
|
|
82
|
+
appliedAdjustments: adjustments,
|
|
83
|
+
improvementScore,
|
|
84
|
+
};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw ImageErrorHandler.handleUnknownError(error, 'autoEnhance');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Infrastructure - Metadata Service
|
|
3
|
+
*
|
|
4
|
+
* Extracts and manages image metadata including EXIF data
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ImageMetadataExtended } from '../../domain/entities/ImageFilterTypes';
|
|
8
|
+
import { ImageValidator } from '../utils/ImageValidator';
|
|
9
|
+
import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
|
|
10
|
+
import { MetadataExtractor } from '../utils/MetadataExtractor';
|
|
11
|
+
|
|
12
|
+
export interface ImageMetadataExtractionOptions {
|
|
13
|
+
includeExif?: boolean;
|
|
14
|
+
includeGPS?: boolean;
|
|
15
|
+
includeCamera?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class ImageMetadataService {
|
|
19
|
+
|
|
20
|
+
static async extractMetadata(
|
|
21
|
+
uri: string,
|
|
22
|
+
options: ImageMetadataExtractionOptions = {}
|
|
23
|
+
): Promise<ImageMetadataExtended> {
|
|
24
|
+
try {
|
|
25
|
+
const uriValidation = ImageValidator.validateUri(uri);
|
|
26
|
+
if (!uriValidation.isValid) {
|
|
27
|
+
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'extractMetadata');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
includeExif = true,
|
|
32
|
+
includeGPS = true,
|
|
33
|
+
includeCamera = true,
|
|
34
|
+
} = options;
|
|
35
|
+
|
|
36
|
+
// Get basic image info
|
|
37
|
+
const dimensions = await MetadataExtractor.getImageDimensions(uri);
|
|
38
|
+
const size = await MetadataExtractor.getFileSize(uri);
|
|
39
|
+
const format = MetadataExtractor.detectFormat(uri);
|
|
40
|
+
|
|
41
|
+
// Build metadata object
|
|
42
|
+
const metadata: ImageMetadataExtended = {
|
|
43
|
+
format,
|
|
44
|
+
size,
|
|
45
|
+
dimensions,
|
|
46
|
+
colorSpace: 'sRGB',
|
|
47
|
+
hasAlpha: format === 'PNG' || format === 'WebP',
|
|
48
|
+
orientation: 1,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Extract EXIF data if requested
|
|
52
|
+
if (includeExif) {
|
|
53
|
+
const exifData = await MetadataExtractor.extractExifData(uri);
|
|
54
|
+
if (exifData) {
|
|
55
|
+
metadata.creationDate = exifData.DateTimeOriginal ? new Date(exifData.DateTimeOriginal) : undefined;
|
|
56
|
+
metadata.modificationDate = new Date();
|
|
57
|
+
|
|
58
|
+
if (includeCamera) {
|
|
59
|
+
metadata.camera = {
|
|
60
|
+
make: exifData.Make,
|
|
61
|
+
model: exifData.Model,
|
|
62
|
+
iso: exifData.ISO,
|
|
63
|
+
flash: exifData.Flash,
|
|
64
|
+
focalLength: exifData.FocalLength,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Extract GPS data if requested
|
|
71
|
+
if (includeGPS) {
|
|
72
|
+
const gps = await MetadataExtractor.extractGPSData(uri);
|
|
73
|
+
metadata.gps = gps || undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return metadata;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
throw ImageErrorHandler.handleUnknownError(error, 'extractMetadata');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static async getBasicInfo(uri: string): Promise<{
|
|
83
|
+
format: string;
|
|
84
|
+
size: number;
|
|
85
|
+
dimensions: { width: number; height: number };
|
|
86
|
+
}> {
|
|
87
|
+
try {
|
|
88
|
+
const uriValidation = ImageValidator.validateUri(uri);
|
|
89
|
+
if (!uriValidation.isValid) {
|
|
90
|
+
throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'getBasicInfo');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const dimensions = await MetadataExtractor.getImageDimensions(uri);
|
|
94
|
+
const size = await MetadataExtractor.getFileSize(uri);
|
|
95
|
+
const format = MetadataExtractor.detectFormat(uri);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
format,
|
|
99
|
+
size,
|
|
100
|
+
dimensions,
|
|
101
|
+
};
|
|
102
|
+
} catch (error) {
|
|
103
|
+
throw ImageErrorHandler.handleUnknownError(error, 'getBasicInfo');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static async hasMetadata(uri: string): Promise<boolean> {
|
|
108
|
+
try {
|
|
109
|
+
const exifData = await MetadataExtractor.extractExifData(uri);
|
|
110
|
+
const gpsData = await MetadataExtractor.extractGPSData(uri);
|
|
111
|
+
return !!(exifData || gpsData);
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|