@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.
Files changed (81) hide show
  1. package/package.json +7 -3
  2. package/src/atoms/image/AtomicImage.tsx +29 -0
  3. package/src/atoms/index.ts +3 -0
  4. package/src/exports/image.ts +7 -0
  5. package/src/exports/infinite-scroll.ts +7 -0
  6. package/src/exports/uuid.ts +7 -0
  7. package/src/image/domain/entities/EditorTypes.ts +23 -0
  8. package/src/image/domain/entities/ImageConstants.ts +38 -0
  9. package/src/image/domain/entities/ImageFilterTypes.ts +70 -0
  10. package/src/image/domain/entities/ImageTemplateTypes.ts +18 -0
  11. package/src/image/domain/entities/ImageTypes.ts +86 -0
  12. package/src/image/domain/entities/ValidationResult.ts +15 -0
  13. package/src/image/domain/entities/editor/EditorConfigTypes.ts +35 -0
  14. package/src/image/domain/entities/editor/EditorElementTypes.ts +60 -0
  15. package/src/image/domain/entities/editor/EditorFilterTypes.ts +9 -0
  16. package/src/image/domain/entities/editor/EditorLayerTypes.ts +34 -0
  17. package/src/image/domain/entities/editor/EditorStateTypes.ts +35 -0
  18. package/src/image/domain/entities/editor/EditorToolTypes.ts +33 -0
  19. package/src/image/domain/utils/ImageUtils.ts +103 -0
  20. package/src/image/index.ts +123 -0
  21. package/src/image/infrastructure/services/ImageBatchService.ts +110 -0
  22. package/src/image/infrastructure/services/ImageConversionService.ts +74 -0
  23. package/src/image/infrastructure/services/ImageEditorService.ts +136 -0
  24. package/src/image/infrastructure/services/ImageEnhanceService.ts +123 -0
  25. package/src/image/infrastructure/services/ImageMetadataService.ts +116 -0
  26. package/src/image/infrastructure/services/ImageStorageService.ts +37 -0
  27. package/src/image/infrastructure/services/ImageTemplateService.ts +66 -0
  28. package/src/image/infrastructure/services/ImageTransformService.ts +89 -0
  29. package/src/image/infrastructure/services/ImageViewerService.ts +64 -0
  30. package/src/image/infrastructure/utils/BatchProcessor.ts +95 -0
  31. package/src/image/infrastructure/utils/FilterProcessor.ts +124 -0
  32. package/src/image/infrastructure/utils/ImageAnalysisUtils.ts +122 -0
  33. package/src/image/infrastructure/utils/ImageEditorHistoryUtils.ts +63 -0
  34. package/src/image/infrastructure/utils/ImageErrorHandler.ts +40 -0
  35. package/src/image/infrastructure/utils/ImageFilterUtils.ts +21 -0
  36. package/src/image/infrastructure/utils/ImageQualityPresets.ts +110 -0
  37. package/src/image/infrastructure/utils/ImageTransformUtils.ts +25 -0
  38. package/src/image/infrastructure/utils/ImageValidator.ts +59 -0
  39. package/src/image/infrastructure/utils/LayerManager.ts +77 -0
  40. package/src/image/infrastructure/utils/MetadataExtractor.ts +83 -0
  41. package/src/image/infrastructure/utils/filters/BasicFilters.ts +61 -0
  42. package/src/image/infrastructure/utils/filters/FilterHelpers.ts +21 -0
  43. package/src/image/infrastructure/utils/filters/SpecialFilters.ts +84 -0
  44. package/src/image/infrastructure/utils/validation/image-validator.ts +77 -0
  45. package/src/image/infrastructure/utils/validation/mime-type-validator.ts +101 -0
  46. package/src/image/infrastructure/utils/validation/mime-types.constants.ts +41 -0
  47. package/src/image/presentation/components/GalleryHeader.tsx +126 -0
  48. package/src/image/presentation/components/ImageGallery.tsx +138 -0
  49. package/src/image/presentation/components/editor/FilterPickerSheet.tsx +75 -0
  50. package/src/image/presentation/components/editor/StickerPickerSheet.tsx +62 -0
  51. package/src/image/presentation/components/editor/TextEditorSheet.tsx +98 -0
  52. package/src/image/presentation/components/editor/TextEditorTabs.tsx +111 -0
  53. package/src/image/presentation/components/image/AtomicImage.tsx +29 -0
  54. package/src/image/presentation/hooks/useImage.ts +39 -0
  55. package/src/image/presentation/hooks/useImageBatch.ts +28 -0
  56. package/src/image/presentation/hooks/useImageConversion.ts +29 -0
  57. package/src/image/presentation/hooks/useImageEnhance.ts +32 -0
  58. package/src/image/presentation/hooks/useImageGallery.ts +90 -0
  59. package/src/image/presentation/hooks/useImageMetadata.ts +28 -0
  60. package/src/image/presentation/hooks/useImageOperation.ts +37 -0
  61. package/src/image/presentation/hooks/useImageTransform.ts +42 -0
  62. package/src/index.ts +15 -0
  63. package/src/infinite-scroll/domain/interfaces/infinite-scroll-list-props.ts +67 -0
  64. package/src/infinite-scroll/domain/types/infinite-scroll-config.ts +108 -0
  65. package/src/infinite-scroll/domain/types/infinite-scroll-return.ts +40 -0
  66. package/src/infinite-scroll/domain/types/infinite-scroll-state.ts +58 -0
  67. package/src/infinite-scroll/domain/utils/pagination-utils.ts +63 -0
  68. package/src/infinite-scroll/domain/utils/type-guards.ts +53 -0
  69. package/src/infinite-scroll/index.ts +62 -0
  70. package/src/infinite-scroll/presentation/components/empty.tsx +44 -0
  71. package/src/infinite-scroll/presentation/components/error.tsx +66 -0
  72. package/src/infinite-scroll/presentation/components/infinite-scroll-list.tsx +120 -0
  73. package/src/infinite-scroll/presentation/components/loading-more.tsx +38 -0
  74. package/src/infinite-scroll/presentation/components/loading.tsx +40 -0
  75. package/src/infinite-scroll/presentation/components/types.ts +124 -0
  76. package/src/infinite-scroll/presentation/hooks/pagination.helper.ts +83 -0
  77. package/src/infinite-scroll/presentation/hooks/useInfiniteScroll.ts +327 -0
  78. package/src/uuid/index.ts +15 -0
  79. package/src/uuid/infrastructure/utils/UUIDUtils.ts +75 -0
  80. package/src/uuid/package-lock.json +14255 -0
  81. 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
+ }