@umituz/react-native-design-system 2.6.110 → 2.6.112
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/image.ts +7 -0
- package/src/exports/infinite-scroll.ts +7 -0
- package/src/exports/uuid.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 +15 -0
- package/src/infinite-scroll/domain/interfaces/infinite-scroll-list-props.ts +67 -0
- package/src/infinite-scroll/domain/types/infinite-scroll-config.ts +108 -0
- package/src/infinite-scroll/domain/types/infinite-scroll-return.ts +40 -0
- package/src/infinite-scroll/domain/types/infinite-scroll-state.ts +58 -0
- package/src/infinite-scroll/domain/utils/pagination-utils.ts +63 -0
- package/src/infinite-scroll/domain/utils/type-guards.ts +53 -0
- package/src/infinite-scroll/index.ts +62 -0
- package/src/infinite-scroll/presentation/components/empty.tsx +44 -0
- package/src/infinite-scroll/presentation/components/error.tsx +66 -0
- package/src/infinite-scroll/presentation/components/infinite-scroll-list.tsx +120 -0
- package/src/infinite-scroll/presentation/components/loading-more.tsx +38 -0
- package/src/infinite-scroll/presentation/components/loading.tsx +40 -0
- package/src/infinite-scroll/presentation/components/types.ts +124 -0
- package/src/infinite-scroll/presentation/hooks/pagination.helper.ts +83 -0
- package/src/infinite-scroll/presentation/hooks/useInfiniteScroll.ts +327 -0
- package/src/uuid/index.ts +15 -0
- package/src/uuid/infrastructure/utils/UUIDUtils.ts +75 -0
- package/src/uuid/package-lock.json +14255 -0
- package/src/uuid/types/UUID.ts +36 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Infrastructure - Error Handler
|
|
3
|
+
*/
|
|
4
|
+
export class ImageError extends Error {
|
|
5
|
+
constructor(
|
|
6
|
+
message: string,
|
|
7
|
+
public readonly code: string,
|
|
8
|
+
public readonly operation?: string
|
|
9
|
+
) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'ImageError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const IMAGE_ERROR_CODES = {
|
|
16
|
+
INVALID_URI: 'INVALID_URI',
|
|
17
|
+
INVALID_DIMENSIONS: 'INVALID_DIMENSIONS',
|
|
18
|
+
INVALID_QUALITY: 'INVALID_QUALITY',
|
|
19
|
+
MANIPULATION_FAILED: 'MANIPULATION_FAILED',
|
|
20
|
+
CONVERSION_FAILED: 'CONVERSION_FAILED',
|
|
21
|
+
STORAGE_FAILED: 'STORAGE_FAILED',
|
|
22
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
export type ImageErrorCode = typeof IMAGE_ERROR_CODES[keyof typeof IMAGE_ERROR_CODES];
|
|
26
|
+
|
|
27
|
+
export class ImageErrorHandler {
|
|
28
|
+
static createError(message: string, code: ImageErrorCode, operation?: string): ImageError {
|
|
29
|
+
return new ImageError(message, code, operation);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static handleUnknownError(error: unknown, operation?: string): ImageError {
|
|
33
|
+
if (error instanceof ImageError) {
|
|
34
|
+
return error;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const message = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
38
|
+
return new ImageError(message, IMAGE_ERROR_CODES.MANIPULATION_FAILED, operation);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure - Filter Utils
|
|
3
|
+
*
|
|
4
|
+
* Legacy wrapper for backward compatibility. Use specific filter classes directly.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BasicFilters } from './filters/BasicFilters';
|
|
8
|
+
import { SpecialFilters } from './filters/SpecialFilters';
|
|
9
|
+
import { FilterHelpers } from './filters/FilterHelpers';
|
|
10
|
+
|
|
11
|
+
export class ImageFilterUtils {
|
|
12
|
+
static applyBrightness = BasicFilters.applyBrightness;
|
|
13
|
+
static applyContrast = BasicFilters.applyContrast;
|
|
14
|
+
static applySaturation = BasicFilters.applySaturation;
|
|
15
|
+
static applyVintage = SpecialFilters.applyVintage;
|
|
16
|
+
static applyBlur = SpecialFilters.applyBlur;
|
|
17
|
+
static applyIntensity = FilterHelpers.applyIntensity;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Re-export for convenience
|
|
21
|
+
export { BasicFilters, SpecialFilters, FilterHelpers };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Infrastructure - Quality Presets
|
|
3
|
+
*
|
|
4
|
+
* Predefined quality settings for different use cases
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { SaveFormat } from '../../domain/entities/ImageTypes';
|
|
8
|
+
import { IMAGE_CONSTANTS } from '../../domain/entities/ImageConstants';
|
|
9
|
+
|
|
10
|
+
export interface QualityPreset {
|
|
11
|
+
format: SaveFormat;
|
|
12
|
+
quality: number;
|
|
13
|
+
maxWidth?: number;
|
|
14
|
+
maxHeight?: number;
|
|
15
|
+
description: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface QualityPresets {
|
|
19
|
+
web: QualityPreset;
|
|
20
|
+
mobile: QualityPreset;
|
|
21
|
+
print: QualityPreset;
|
|
22
|
+
thumbnail: QualityPreset;
|
|
23
|
+
preview: QualityPreset;
|
|
24
|
+
archive: QualityPreset;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const IMAGE_QUALITY_PRESETS: QualityPresets = {
|
|
28
|
+
web: {
|
|
29
|
+
format: 'jpeg',
|
|
30
|
+
quality: 0.8,
|
|
31
|
+
maxWidth: 1920,
|
|
32
|
+
maxHeight: 1080,
|
|
33
|
+
description: 'Optimized for web use with good balance of quality and size',
|
|
34
|
+
},
|
|
35
|
+
mobile: {
|
|
36
|
+
format: 'jpeg',
|
|
37
|
+
quality: 0.7,
|
|
38
|
+
maxWidth: 1080,
|
|
39
|
+
maxHeight: 1920,
|
|
40
|
+
description: 'Optimized for mobile devices with smaller file size',
|
|
41
|
+
},
|
|
42
|
+
print: {
|
|
43
|
+
format: 'png',
|
|
44
|
+
quality: 1.0,
|
|
45
|
+
maxWidth: 3000,
|
|
46
|
+
maxHeight: 3000,
|
|
47
|
+
description: 'High quality for printing with maximum detail',
|
|
48
|
+
},
|
|
49
|
+
thumbnail: {
|
|
50
|
+
format: 'jpeg',
|
|
51
|
+
quality: 0.6,
|
|
52
|
+
maxWidth: IMAGE_CONSTANTS.thumbnailSize,
|
|
53
|
+
maxHeight: IMAGE_CONSTANTS.thumbnailSize,
|
|
54
|
+
description: 'Small thumbnail for preview use',
|
|
55
|
+
},
|
|
56
|
+
preview: {
|
|
57
|
+
format: 'jpeg',
|
|
58
|
+
quality: 0.5,
|
|
59
|
+
maxWidth: 800,
|
|
60
|
+
maxHeight: 600,
|
|
61
|
+
description: 'Quick preview with very small file size',
|
|
62
|
+
},
|
|
63
|
+
archive: {
|
|
64
|
+
format: 'png',
|
|
65
|
+
quality: 0.9,
|
|
66
|
+
description: 'High quality archival storage with lossless compression',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export class ImageQualityPresetService {
|
|
71
|
+
static getPreset(name: keyof QualityPresets): QualityPreset {
|
|
72
|
+
return IMAGE_QUALITY_PRESETS[name];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static getAllPresets(): QualityPresets {
|
|
76
|
+
return IMAGE_QUALITY_PRESETS;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static getCustomPreset(options: {
|
|
80
|
+
format?: SaveFormat;
|
|
81
|
+
quality?: number;
|
|
82
|
+
maxWidth?: number;
|
|
83
|
+
maxHeight?: number;
|
|
84
|
+
}): QualityPreset {
|
|
85
|
+
return {
|
|
86
|
+
format: options.format || 'jpeg',
|
|
87
|
+
quality: options.quality || IMAGE_CONSTANTS.defaultQuality,
|
|
88
|
+
maxWidth: options.maxWidth,
|
|
89
|
+
maxHeight: options.maxHeight,
|
|
90
|
+
description: 'Custom quality preset',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static optimizeForUseCase(
|
|
95
|
+
useCase: 'web' | 'mobile' | 'print' | 'thumbnail' | 'preview' | 'archive',
|
|
96
|
+
customOptions?: Partial<QualityPreset>
|
|
97
|
+
): QualityPreset {
|
|
98
|
+
const preset = IMAGE_QUALITY_PRESETS[useCase];
|
|
99
|
+
|
|
100
|
+
if (!customOptions) {
|
|
101
|
+
return preset;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
...preset,
|
|
106
|
+
...customOptions,
|
|
107
|
+
description: `${preset.description} (modified)`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure - Transform Utils
|
|
3
|
+
*
|
|
4
|
+
* Internal utilities for image transformations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as ImageManipulator from 'expo-image-manipulator';
|
|
8
|
+
import type { SaveFormat, ImageSaveOptions } from '../../domain/entities/ImageTypes';
|
|
9
|
+
import { IMAGE_CONSTANTS } from '../../domain/entities/ImageConstants';
|
|
10
|
+
|
|
11
|
+
export class ImageTransformUtils {
|
|
12
|
+
static mapFormat(format?: SaveFormat): ImageManipulator.SaveFormat {
|
|
13
|
+
if (format === 'png') return ImageManipulator.SaveFormat.PNG;
|
|
14
|
+
if (format === 'webp') return ImageManipulator.SaveFormat.WEBP;
|
|
15
|
+
return ImageManipulator.SaveFormat.JPEG;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static buildSaveOptions(options?: ImageSaveOptions): ImageManipulator.SaveOptions {
|
|
19
|
+
return {
|
|
20
|
+
compress: options?.compress ?? IMAGE_CONSTANTS.defaultQuality,
|
|
21
|
+
format: this.mapFormat(options?.format),
|
|
22
|
+
base64: options?.base64,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Infrastructure - Validation Utilities
|
|
3
|
+
*/
|
|
4
|
+
import { ImageDimensions } from '../../domain/entities/ImageTypes';
|
|
5
|
+
|
|
6
|
+
export interface ValidationResult {
|
|
7
|
+
isValid: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ImageValidator {
|
|
12
|
+
static validateUri(uri: string): ValidationResult {
|
|
13
|
+
if (!uri || typeof uri !== 'string') {
|
|
14
|
+
return { isValid: false, error: 'URI is required and must be a string' };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!uri.startsWith('file://') &&
|
|
18
|
+
!uri.startsWith('content://') &&
|
|
19
|
+
!uri.startsWith('http://') &&
|
|
20
|
+
!uri.startsWith('https://') &&
|
|
21
|
+
!uri.startsWith('data:image/')) {
|
|
22
|
+
return { isValid: false, error: 'Invalid URI format' };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { isValid: true };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static validateDimensions(dimensions: Partial<ImageDimensions>): ValidationResult {
|
|
29
|
+
if (dimensions.width !== undefined) {
|
|
30
|
+
if (typeof dimensions.width !== 'number' || dimensions.width <= 0) {
|
|
31
|
+
return { isValid: false, error: 'Width must be a positive number' };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (dimensions.height !== undefined) {
|
|
36
|
+
if (typeof dimensions.height !== 'number' || dimensions.height <= 0) {
|
|
37
|
+
return { isValid: false, error: 'Height must be a positive number' };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { isValid: true };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static validateQuality(quality: number): ValidationResult {
|
|
45
|
+
if (typeof quality !== 'number' || quality < 0 || quality > 1) {
|
|
46
|
+
return { isValid: false, error: 'Quality must be a number between 0 and 1' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { isValid: true };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static validateRotation(degrees: number): ValidationResult {
|
|
53
|
+
if (typeof degrees !== 'number') {
|
|
54
|
+
return { isValid: false, error: 'Degrees must be a number' };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { isValid: true };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure - Layer Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages editor layers with composition and rendering
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type LayerOperation = 'add' | 'remove' | 'move' | 'merge' | 'duplicate';
|
|
8
|
+
|
|
9
|
+
export interface LayerComposition {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
backgroundColor?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class LayerManager {
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
static mergeLayers(
|
|
19
|
+
layers: Array<{
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
elements: any[];
|
|
23
|
+
}>,
|
|
24
|
+
targetIds: string[]
|
|
25
|
+
): Array<{
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
elements: any[];
|
|
29
|
+
}> {
|
|
30
|
+
const targetLayers = layers.filter(layer => targetIds.includes(layer.id));
|
|
31
|
+
const otherLayers = layers.filter(layer => !targetIds.includes(layer.id));
|
|
32
|
+
|
|
33
|
+
if (targetLayers.length === 0) return layers;
|
|
34
|
+
|
|
35
|
+
// Merge elements from target layers
|
|
36
|
+
const mergedElements = targetLayers.flatMap(layer => layer.elements);
|
|
37
|
+
const mergedLayer = {
|
|
38
|
+
id: Math.random().toString(36).substr(2, 9),
|
|
39
|
+
name: targetLayers.map(l => l.name).join(' + '),
|
|
40
|
+
elements: mergedElements,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return [...otherLayers, mergedLayer];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static duplicateLayer(
|
|
47
|
+
layer: {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
elements: any[];
|
|
51
|
+
}
|
|
52
|
+
): {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
elements: any[];
|
|
56
|
+
} {
|
|
57
|
+
return {
|
|
58
|
+
id: Math.random().toString(36).substr(2, 9),
|
|
59
|
+
name: `${layer.name} Copy`,
|
|
60
|
+
elements: [...layer.elements],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static reorderLayers(
|
|
65
|
+
layers: Array<{ id: string; index?: number }>,
|
|
66
|
+
fromIndex: number,
|
|
67
|
+
toIndex: number
|
|
68
|
+
): Array<{ id: string; index?: number }> {
|
|
69
|
+
const result = [...layers];
|
|
70
|
+
const [moved] = result.splice(fromIndex, 1);
|
|
71
|
+
result.splice(toIndex, 0, moved);
|
|
72
|
+
|
|
73
|
+
return result.map((layer, index) => ({ ...layer, index }));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Infrastructure - Metadata Extractor Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for extracting metadata from images
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ImageMetadataExtractionOptions } from '../../infrastructure/services/ImageMetadataService';
|
|
8
|
+
import { ImageErrorHandler } from './ImageErrorHandler';
|
|
9
|
+
|
|
10
|
+
export class MetadataExtractor {
|
|
11
|
+
static async getImageDimensions(uri: string): Promise<{ width: number; height: number }> {
|
|
12
|
+
try {
|
|
13
|
+
// In a real implementation, we would use:
|
|
14
|
+
// - expo-image-manipulator for basic dimensions
|
|
15
|
+
// - react-native-image-picker for metadata
|
|
16
|
+
// - react-native-exif-reader for EXIF data
|
|
17
|
+
|
|
18
|
+
// Mock implementation
|
|
19
|
+
return {
|
|
20
|
+
width: Math.floor(Math.random() * 2000) + 100,
|
|
21
|
+
height: Math.floor(Math.random() * 2000) + 100,
|
|
22
|
+
};
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw ImageErrorHandler.handleUnknownError(error, 'getDimensions');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static async getFileSize(uri: string): Promise<number> {
|
|
29
|
+
try {
|
|
30
|
+
// In real implementation, use expo-file-system or similar
|
|
31
|
+
return Math.floor(Math.random() * 5000000) + 10000; // Random size between 10KB-5MB
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw ImageErrorHandler.handleUnknownError(error, 'getFileSize');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static async extractExifData(uri: string): Promise<any> {
|
|
38
|
+
try {
|
|
39
|
+
// Mock EXIF data extraction
|
|
40
|
+
return {
|
|
41
|
+
DateTimeOriginal: new Date().toISOString(),
|
|
42
|
+
Make: 'Mock Camera',
|
|
43
|
+
Model: 'Mock Phone',
|
|
44
|
+
ISO: Math.floor(Math.random() * 1600) + 100,
|
|
45
|
+
FocalLength: Math.random() * 50 + 10,
|
|
46
|
+
Flash: Math.random() > 0.5,
|
|
47
|
+
ExposureTime: `1/${Math.floor(Math.random() * 1000) + 100}`,
|
|
48
|
+
FNumber: Math.random() * 8 + 1.4,
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static async extractGPSData(uri: string): Promise<{ latitude: number; longitude: number } | null> {
|
|
56
|
+
try {
|
|
57
|
+
// Mock GPS data extraction
|
|
58
|
+
return Math.random() > 0.7 ? {
|
|
59
|
+
latitude: Math.random() * 180 - 90,
|
|
60
|
+
longitude: Math.random() * 360 - 180,
|
|
61
|
+
} : null;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static detectFormat(uri: string): string {
|
|
68
|
+
const extension = uri.toLowerCase().split('.').pop();
|
|
69
|
+
switch (extension) {
|
|
70
|
+
case 'jpg':
|
|
71
|
+
case 'jpeg':
|
|
72
|
+
return 'JPEG';
|
|
73
|
+
case 'png':
|
|
74
|
+
return 'PNG';
|
|
75
|
+
case 'webp':
|
|
76
|
+
return 'WebP';
|
|
77
|
+
case 'gif':
|
|
78
|
+
return 'GIF';
|
|
79
|
+
default:
|
|
80
|
+
return 'Unknown';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure - Basic Image Filters
|
|
3
|
+
*
|
|
4
|
+
* Core filter operations: brightness, contrast, saturation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class BasicFilters {
|
|
8
|
+
static applyBrightness(
|
|
9
|
+
data: Uint8ClampedArray,
|
|
10
|
+
brightness: number
|
|
11
|
+
): Uint8ClampedArray {
|
|
12
|
+
const result = new Uint8ClampedArray(data);
|
|
13
|
+
const adjustment = brightness * 2.55;
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < result.length; i += 4) {
|
|
16
|
+
result[i] = Math.min(255, Math.max(0, result[i] + adjustment));
|
|
17
|
+
result[i + 1] = Math.min(255, Math.max(0, result[i + 1] + adjustment));
|
|
18
|
+
result[i + 2] = Math.min(255, Math.max(0, result[i + 2] + adjustment));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static applyContrast(
|
|
25
|
+
data: Uint8ClampedArray,
|
|
26
|
+
contrast: number
|
|
27
|
+
): Uint8ClampedArray {
|
|
28
|
+
const result = new Uint8ClampedArray(data);
|
|
29
|
+
const factor = (259 * (contrast * 2.55 + 255)) / (255 * (259 - contrast * 2.55));
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < result.length; i += 4) {
|
|
32
|
+
result[i] = Math.min(255, Math.max(0, factor * (result[i] - 128) + 128));
|
|
33
|
+
result[i + 1] = Math.min(255, Math.max(0, factor * (result[i + 1] - 128) + 128));
|
|
34
|
+
result[i + 2] = Math.min(255, Math.max(0, factor * (result[i + 2] - 128) + 128));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static applySaturation(
|
|
41
|
+
data: Uint8ClampedArray,
|
|
42
|
+
saturation: number
|
|
43
|
+
): Uint8ClampedArray {
|
|
44
|
+
const result = new Uint8ClampedArray(data);
|
|
45
|
+
const adjustment = 1 + (saturation / 100);
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < result.length; i += 4) {
|
|
48
|
+
const r = result[i];
|
|
49
|
+
const g = result[i + 1];
|
|
50
|
+
const b = result[i + 2];
|
|
51
|
+
|
|
52
|
+
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
53
|
+
|
|
54
|
+
result[i] = Math.min(255, Math.max(0, gray + adjustment * (r - gray)));
|
|
55
|
+
result[i + 1] = Math.min(255, Math.max(0, gray + adjustment * (g - gray)));
|
|
56
|
+
result[i + 2] = Math.min(255, Math.max(0, gray + adjustment * (b - gray)));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure - Filter Helper Utilities
|
|
3
|
+
*
|
|
4
|
+
* Common helper functions for filter operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class FilterHelpers {
|
|
8
|
+
static applyIntensity(
|
|
9
|
+
originalData: Uint8ClampedArray,
|
|
10
|
+
processedData: Uint8ClampedArray,
|
|
11
|
+
intensity: number
|
|
12
|
+
): Uint8ClampedArray {
|
|
13
|
+
const result = new Uint8ClampedArray(originalData.length);
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < originalData.length; i++) {
|
|
16
|
+
result[i] = originalData[i] * (1 - intensity) + processedData[i] * intensity;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure - Special Image Filters
|
|
3
|
+
*
|
|
4
|
+
* Advanced filter operations: vintage, blur
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class SpecialFilters {
|
|
8
|
+
static applyVintage(
|
|
9
|
+
data: Uint8ClampedArray,
|
|
10
|
+
intensity: number,
|
|
11
|
+
warmth: number
|
|
12
|
+
): Uint8ClampedArray {
|
|
13
|
+
const result = new Uint8ClampedArray(data);
|
|
14
|
+
const factor = intensity / 100;
|
|
15
|
+
const warmFactor = warmth / 100;
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < result.length; i += 4) {
|
|
18
|
+
let r = result[i];
|
|
19
|
+
let g = result[i + 1];
|
|
20
|
+
let b = result[i + 2];
|
|
21
|
+
|
|
22
|
+
const tr = 0.393 * r + 0.769 * g + 0.189 * b;
|
|
23
|
+
const tg = 0.349 * r + 0.686 * g + 0.168 * b;
|
|
24
|
+
const tb = 0.272 * r + 0.534 * g + 0.131 * b;
|
|
25
|
+
|
|
26
|
+
r = r * (1 - factor) + tr * factor;
|
|
27
|
+
g = g * (1 - factor) + tg * factor;
|
|
28
|
+
b = b * (1 - factor) + tb * factor;
|
|
29
|
+
|
|
30
|
+
if (warmFactor > 0) {
|
|
31
|
+
result[i] = Math.min(255, r + warmFactor * 20);
|
|
32
|
+
result[i + 1] = Math.min(255, g + warmFactor * 10);
|
|
33
|
+
result[i + 2] = Math.min(255, b * (1 - warmFactor * 0.3));
|
|
34
|
+
} else {
|
|
35
|
+
result[i] = r;
|
|
36
|
+
result[i + 1] = g;
|
|
37
|
+
result[i + 2] = Math.min(255, b * (1 - Math.abs(warmFactor) * 0.3));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static applyBlur(
|
|
45
|
+
data: Uint8ClampedArray,
|
|
46
|
+
radius: number,
|
|
47
|
+
width: number,
|
|
48
|
+
height: number
|
|
49
|
+
): Uint8ClampedArray {
|
|
50
|
+
const result = new Uint8ClampedArray(data);
|
|
51
|
+
const size = Math.floor(radius) || 1;
|
|
52
|
+
|
|
53
|
+
for (let y = 0; y < height; y++) {
|
|
54
|
+
for (let x = 0; x < width; x++) {
|
|
55
|
+
let r = 0, g = 0, b = 0, a = 0;
|
|
56
|
+
let count = 0;
|
|
57
|
+
|
|
58
|
+
for (let dy = -size; dy <= size; dy++) {
|
|
59
|
+
for (let dx = -size; dx <= size; dx++) {
|
|
60
|
+
const ny = y + dy;
|
|
61
|
+
const nx = x + dx;
|
|
62
|
+
|
|
63
|
+
if (ny >= 0 && ny < height && nx >= 0 && nx < width) {
|
|
64
|
+
const idx = (ny * width + nx) * 4;
|
|
65
|
+
r += data[idx];
|
|
66
|
+
g += data[idx + 1];
|
|
67
|
+
b += data[idx + 2];
|
|
68
|
+
a += data[idx + 3];
|
|
69
|
+
count++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const idx = (y * width + x) * 4;
|
|
75
|
+
result[idx] = r / count;
|
|
76
|
+
result[idx + 1] = g / count;
|
|
77
|
+
result[idx + 2] = b / count;
|
|
78
|
+
result[idx + 3] = a / count;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Validator
|
|
3
|
+
* Single Responsibility: Validate image files and URIs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ValidationResult } from '../../../domain/entities/ValidationResult';
|
|
7
|
+
import {
|
|
8
|
+
validateImageExtension,
|
|
9
|
+
validateImageDataUrl,
|
|
10
|
+
getMimeTypeFromExtension,
|
|
11
|
+
getMimeTypeFromDataUrl,
|
|
12
|
+
} from './mime-type-validator';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if URI is a local file path
|
|
16
|
+
*/
|
|
17
|
+
function isLocalFileUrl(uri: string): boolean {
|
|
18
|
+
return uri.startsWith('file://');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if URI is a public URL
|
|
23
|
+
*/
|
|
24
|
+
function isPublicUrl(uri: string): boolean {
|
|
25
|
+
return uri.startsWith('http://') || uri.startsWith('https://');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if URI is a data URL
|
|
30
|
+
*/
|
|
31
|
+
function isDataUrl(uri: string): boolean {
|
|
32
|
+
return uri.startsWith('data:');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate image URI (supports file://, http://, https://, and data: URLs)
|
|
37
|
+
*/
|
|
38
|
+
export function validateImageUri(
|
|
39
|
+
uri: string,
|
|
40
|
+
fieldName: string = 'Image'
|
|
41
|
+
): ValidationResult {
|
|
42
|
+
if (!uri || uri.trim() === '') {
|
|
43
|
+
return { isValid: false, error: `${fieldName} is required` };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Validate public URLs (assume they're images if they have image extension)
|
|
47
|
+
if (isPublicUrl(uri)) {
|
|
48
|
+
return validateImageExtension(uri, fieldName);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Validate data URLs
|
|
52
|
+
if (isDataUrl(uri)) {
|
|
53
|
+
return validateImageDataUrl(uri, fieldName);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validate local file URLs
|
|
57
|
+
if (isLocalFileUrl(uri)) {
|
|
58
|
+
return validateImageExtension(uri, fieldName);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Unknown format
|
|
62
|
+
return {
|
|
63
|
+
isValid: false,
|
|
64
|
+
error: `${fieldName} must be a valid image URL or file path`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get MIME type from URI (supports all URI types)
|
|
70
|
+
*/
|
|
71
|
+
export function getImageMimeType(uri: string): string | null {
|
|
72
|
+
if (isDataUrl(uri)) {
|
|
73
|
+
return getMimeTypeFromDataUrl(uri);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return getMimeTypeFromExtension(uri);
|
|
77
|
+
}
|