@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,37 @@
1
+ /**
2
+ * Image Infrastructure - Storage Service
3
+ *
4
+ * Handles saving images to the device filesystem
5
+ */
6
+
7
+ import { FileSystemService } from '@umituz/react-native-filesystem';
8
+ import { ImageValidator } from '../utils/ImageValidator';
9
+ import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
10
+
11
+ export class ImageStorageService {
12
+ static async saveImage(
13
+ uri: string,
14
+ filename?: string
15
+ ): Promise<string> {
16
+ try {
17
+ const uriValidation = ImageValidator.validateUri(uri);
18
+ if (!uriValidation.isValid) {
19
+ throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'saveImage');
20
+ }
21
+
22
+ const result = await FileSystemService.copyToDocuments(uri, filename);
23
+
24
+ if (!result.success || !result.uri) {
25
+ throw ImageErrorHandler.createError(
26
+ 'Failed to save image to filesystem',
27
+ IMAGE_ERROR_CODES.STORAGE_FAILED,
28
+ 'saveImage'
29
+ );
30
+ }
31
+
32
+ return result.uri;
33
+ } catch (error) {
34
+ throw ImageErrorHandler.handleUnknownError(error, 'saveImage');
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,66 @@
1
+ import { MemeTemplateOptions } from '../../domain/entities/ImageTemplateTypes';
2
+
3
+ /**
4
+ * ImageTemplateService
5
+ *
6
+ * Provides utilities for generating image template URLs (e.g. Memegen.link)
7
+ */
8
+ export class ImageTemplateService {
9
+ /**
10
+ * Generates a meme URL for a given template and texts
11
+ *
12
+ * @param options Meme configuration (templateKey, topText, bottomText)
13
+ * @param baseUrl Optional base URL for the image generation service
14
+ * @returns Formatted image URL
15
+ */
16
+ static generateMemeUrl(
17
+ options: MemeTemplateOptions,
18
+ baseUrl: string = 'https://api.memegen.link'
19
+ ): string {
20
+ const { templateKey, topText = '', bottomText = '', style, width, height } = options;
21
+
22
+ // Internal helper for memegen-specific encoding
23
+ const encodeMemeText = (text: string) => {
24
+ const sanitized = text.trim();
25
+ if (!sanitized) return "_";
26
+
27
+ return encodeURIComponent(
28
+ sanitized
29
+ .replace(/ /g, "_")
30
+ .replace(/\?/g, "~q")
31
+ .replace(/#/g, "~h")
32
+ .replace(/\//g, "~s")
33
+ );
34
+ };
35
+
36
+ const top = encodeMemeText(topText);
37
+ const bottom = encodeMemeText(bottomText);
38
+
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`;
42
+
43
+ const params: string[] = [];
44
+ if (style) params.push(`style=${style}`);
45
+ if (width) params.push(`width=${width}`);
46
+ if (height) params.push(`height=${height}`);
47
+
48
+ if (params.length > 0) {
49
+ url += `?${params.join('&')}`;
50
+ }
51
+
52
+ return url;
53
+ }
54
+
55
+ /**
56
+ * Extracts template key from a memegen.link URL or filename
57
+ */
58
+ static extractTemplateKey(url: string): string {
59
+ if (!url) return 'custom';
60
+
61
+ // Example: https://api.memegen.link/images/distracted.png -> distracted
62
+ const parts = url.split('/');
63
+ const lastPart = parts[parts.length - 1] || '';
64
+ return lastPart.split('.')[0] || 'custom';
65
+ }
66
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Image Infrastructure - Transform Service
3
+ *
4
+ * Handles geometric transformations: resize, crop, rotate, flip
5
+ */
6
+
7
+ import * as ImageManipulator from 'expo-image-manipulator';
8
+ import type {
9
+ ImageManipulateAction,
10
+ ImageSaveOptions,
11
+ ImageManipulationResult,
12
+ ImageCropArea,
13
+ ImageFlipOptions,
14
+ } from '../../domain/entities/ImageTypes';
15
+ import { ImageUtils } from '../../domain/utils/ImageUtils';
16
+ import { ImageValidator } from '../utils/ImageValidator';
17
+ import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
18
+ import { ImageTransformUtils } from '../utils/ImageTransformUtils';
19
+
20
+ export class ImageTransformService {
21
+ static async resize(uri: string, width?: number, height?: number, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
22
+ try {
23
+ ImageValidator.validateUri(uri);
24
+ ImageValidator.validateDimensions({ width, height });
25
+ return await ImageManipulator.manipulateAsync(uri, [{ resize: { width, height } }], ImageTransformUtils.buildSaveOptions(options));
26
+ } catch (error) {
27
+ throw ImageErrorHandler.handleUnknownError(error, 'resize');
28
+ }
29
+ }
30
+
31
+ static async crop(uri: string, cropArea: ImageCropArea, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
32
+ try {
33
+ ImageValidator.validateUri(uri);
34
+ ImageValidator.validateDimensions(cropArea);
35
+ return await ImageManipulator.manipulateAsync(uri, [{ crop: cropArea }], ImageTransformUtils.buildSaveOptions(options));
36
+ } catch (error) {
37
+ throw ImageErrorHandler.handleUnknownError(error, 'crop');
38
+ }
39
+ }
40
+
41
+ static async rotate(uri: string, degrees: number, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
42
+ try {
43
+ ImageValidator.validateUri(uri);
44
+ ImageValidator.validateRotation(degrees);
45
+ return await ImageManipulator.manipulateAsync(uri, [{ rotate: degrees }], ImageTransformUtils.buildSaveOptions(options));
46
+ } catch (error) {
47
+ throw ImageErrorHandler.handleUnknownError(error, 'rotate');
48
+ }
49
+ }
50
+
51
+ static async flip(uri: string, flip: ImageFlipOptions, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
52
+ try {
53
+ ImageValidator.validateUri(uri);
54
+ const actions: ImageManipulator.Action[] = [];
55
+ if (flip.horizontal) actions.push({ flip: ImageManipulator.FlipType.Horizontal });
56
+ if (flip.vertical) actions.push({ flip: ImageManipulator.FlipType.Vertical });
57
+ return await ImageManipulator.manipulateAsync(uri, actions, ImageTransformUtils.buildSaveOptions(options));
58
+ } catch (error) {
59
+ throw ImageErrorHandler.handleUnknownError(error, 'flip');
60
+ }
61
+ }
62
+
63
+ static async manipulate(uri: string, action: ImageManipulateAction, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
64
+ try {
65
+ ImageValidator.validateUri(uri);
66
+ const actions: ImageManipulator.Action[] = [];
67
+ if (action.resize) actions.push({ resize: action.resize });
68
+ if (action.crop) actions.push({ crop: action.crop });
69
+ if (action.rotate) actions.push({ rotate: action.rotate });
70
+ if (action.flip) {
71
+ if (action.flip.horizontal) actions.push({ flip: ImageManipulator.FlipType.Horizontal });
72
+ if (action.flip.vertical) actions.push({ flip: ImageManipulator.FlipType.Vertical });
73
+ }
74
+ return await ImageManipulator.manipulateAsync(uri, actions, ImageTransformUtils.buildSaveOptions(options));
75
+ } catch (error) {
76
+ throw ImageErrorHandler.handleUnknownError(error, 'manipulate');
77
+ }
78
+ }
79
+
80
+ static async resizeToFit(uri: string, maxWidth: number, maxHeight: number, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
81
+ const dimensions = ImageUtils.fitToSize(maxWidth, maxHeight, maxWidth, maxHeight);
82
+ return this.resize(uri, dimensions.width, dimensions.height, options);
83
+ }
84
+
85
+ static async cropToSquare(uri: string, width: number, height: number, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
86
+ const cropArea = ImageUtils.getSquareCrop(width, height);
87
+ return this.crop(uri, cropArea, options);
88
+ }
89
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Image Infrastructure - Viewer Service
3
+ *
4
+ * Provides configuration for react-native-image-viewing component
5
+ */
6
+
7
+ import type {
8
+ ImageViewerItem,
9
+ ImageGalleryOptions,
10
+ } from '../../domain/entities/ImageTypes';
11
+
12
+ export interface ImageViewerConfig {
13
+ images: ImageViewerItem[];
14
+ index?: number;
15
+ visible: boolean;
16
+ onDismiss?: () => void;
17
+ options?: ImageGalleryOptions;
18
+ }
19
+
20
+ export class ImageViewerService {
21
+ static prepareImages(uris: string[]): ImageViewerItem[] {
22
+ return uris.map(uri => ({ uri }));
23
+ }
24
+
25
+ static prepareImagesWithMetadata(items: ImageViewerItem[]): ImageViewerItem[] {
26
+ return items.map(item => ({
27
+ uri: item.uri,
28
+ title: item.title,
29
+ description: item.description,
30
+ width: item.width,
31
+ height: item.height,
32
+ }));
33
+ }
34
+
35
+ static createViewerConfig(
36
+ images: ImageViewerItem[],
37
+ startIndex: number = 0,
38
+ onDismiss?: () => void,
39
+ options?: ImageGalleryOptions
40
+ ): ImageViewerConfig {
41
+ return {
42
+ images,
43
+ index: options?.index ?? startIndex,
44
+ visible: true,
45
+ onDismiss: onDismiss || options?.onDismiss,
46
+ options: {
47
+ backgroundColor: options?.backgroundColor || '#000000',
48
+ swipeToCloseEnabled: options?.swipeToCloseEnabled ?? true,
49
+ doubleTapToZoomEnabled: options?.doubleTapToZoomEnabled ?? true,
50
+ onIndexChange: options?.onIndexChange,
51
+ },
52
+ };
53
+ }
54
+
55
+ static getDefaultOptions(): ImageGalleryOptions {
56
+ return {
57
+ index: 0,
58
+ backgroundColor: '#000000',
59
+ swipeToCloseEnabled: true,
60
+ doubleTapToZoomEnabled: true,
61
+ };
62
+ }
63
+ }
64
+
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Image Infrastructure - Batch Processor Utilities
3
+ *
4
+ * Helper functions for batch processing operations
5
+ */
6
+
7
+ import type { ImageManipulationResult, ImageCropArea, SaveFormat } from '../../domain/entities/ImageTypes';
8
+ import type { BatchOperation, BatchProcessingOptions } from '../services/ImageBatchService';
9
+ import { ImageTransformService } from '../services/ImageTransformService';
10
+ import { ImageConversionService } from '../services/ImageConversionService';
11
+ import { ImageValidator } from './ImageValidator';
12
+ import { ImageErrorHandler, IMAGE_ERROR_CODES } from './ImageErrorHandler';
13
+
14
+ export class BatchProcessor {
15
+ static async processBatchItem(
16
+ operation: BatchOperation,
17
+ options: BatchProcessingOptions = {}
18
+ ): Promise<{ uri: string; result: ImageManipulationResult | null; error?: Error }> {
19
+ try {
20
+ const uriValidation = ImageValidator.validateUri(operation.uri);
21
+ if (!uriValidation.isValid) {
22
+ throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'batchProcess');
23
+ }
24
+
25
+ let result: ImageManipulationResult;
26
+
27
+ switch (operation.type) {
28
+ case 'resize':
29
+ result = await ImageTransformService.resize(
30
+ operation.uri,
31
+ operation.params.width as number | undefined,
32
+ operation.params.height as number | undefined,
33
+ operation.options
34
+ );
35
+ break;
36
+
37
+ case 'crop':
38
+ result = await ImageTransformService.crop(
39
+ operation.uri,
40
+ operation.params as unknown as ImageCropArea,
41
+ operation.options
42
+ );
43
+ break;
44
+
45
+ case 'compress':
46
+ result = await ImageConversionService.compress(
47
+ operation.uri,
48
+ operation.params.quality as number
49
+ );
50
+ break;
51
+
52
+ case 'convert':
53
+ result = await ImageConversionService.convertFormat(
54
+ operation.uri,
55
+ operation.params.format as SaveFormat,
56
+ operation.params.quality as number
57
+ );
58
+ break;
59
+
60
+ default:
61
+ throw ImageErrorHandler.createError(
62
+ `Unknown operation type: ${operation.type}`,
63
+ IMAGE_ERROR_CODES.VALIDATION_ERROR,
64
+ 'batchProcess'
65
+ );
66
+ }
67
+
68
+ return { uri: operation.uri, result };
69
+ } catch (error) {
70
+ return {
71
+ uri: operation.uri,
72
+ result: null,
73
+ error: error instanceof Error ? error : new Error('Unknown error')
74
+ };
75
+ }
76
+ }
77
+
78
+ static async processBatchInChunks<T>(
79
+ items: T[],
80
+ processor: (item: T) => Promise<unknown>,
81
+ concurrency: number,
82
+ onProgress?: (completed: number, total: number) => void
83
+ ): Promise<void> {
84
+ let completed = 0;
85
+ const total = items.length;
86
+
87
+ for (let i = 0; i < items.length; i += concurrency) {
88
+ const chunk = items.slice(i, i + concurrency);
89
+ await Promise.all(chunk.map(processor));
90
+ completed += chunk.length;
91
+ onProgress?.(completed, total);
92
+ }
93
+ }
94
+ }
95
+
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Infrastructure - Filter Processor
3
+ *
4
+ * Filter processing with preset management
5
+ */
6
+
7
+ import { ImageFilterUtils } from './ImageFilterUtils';
8
+
9
+ export interface FilterPreset {
10
+ id: string;
11
+ name: string;
12
+ category: 'basic' | 'color' | 'artistic' | 'vintage';
13
+ parameters: FilterParameter[];
14
+ preview?: string;
15
+ }
16
+
17
+ export interface FilterParameter {
18
+ name: string;
19
+ type: 'slider' | 'color' | 'boolean';
20
+ min?: number;
21
+ max?: number;
22
+ value: number | string | boolean;
23
+ step?: number;
24
+ label: string;
25
+ }
26
+
27
+ export interface FilterState {
28
+ id: string;
29
+ intensity: number;
30
+ parameters: Record<string, any>;
31
+ enabled: boolean;
32
+ }
33
+
34
+ export class FilterProcessor {
35
+ private static readonly PRESETS: FilterPreset[] = [
36
+ {
37
+ id: 'brightness',
38
+ name: 'Brightness',
39
+ category: 'basic',
40
+ parameters: [{ name: 'brightness', type: 'slider', min: -100, max: 100, value: 0, label: 'Brightness' }],
41
+ },
42
+ {
43
+ id: 'contrast',
44
+ name: 'Contrast',
45
+ category: 'basic',
46
+ parameters: [{ name: 'contrast', type: 'slider', min: -100, max: 100, value: 0, label: 'Contrast' }],
47
+ },
48
+ {
49
+ id: 'saturation',
50
+ name: 'Saturation',
51
+ category: 'color',
52
+ parameters: [{ name: 'saturation', type: 'slider', min: -100, max: 100, value: 0, label: 'Saturation' }],
53
+ },
54
+ {
55
+ id: 'vintage',
56
+ name: 'Vintage',
57
+ category: 'vintage',
58
+ parameters: [
59
+ { name: 'intensity', type: 'slider', min: 0, max: 100, value: 50, label: 'Intensity' },
60
+ { name: 'warmth', type: 'slider', min: 0, max: 100, value: 30, label: 'Warmth' },
61
+ ],
62
+ },
63
+ {
64
+ id: 'blur',
65
+ name: 'Blur',
66
+ category: 'artistic',
67
+ parameters: [{ name: 'radius', type: 'slider', min: 0, max: 20, value: 0, label: 'Blur Radius' }],
68
+ },
69
+ ];
70
+
71
+ static getPresets(category?: string): FilterPreset[] {
72
+ return category ? this.PRESETS.filter(preset => preset.category === category) : this.PRESETS;
73
+ }
74
+
75
+ static getPreset(id: string): FilterPreset | undefined {
76
+ return this.PRESETS.find(preset => preset.id === id);
77
+ }
78
+
79
+ static createFilterState(presetId: string): FilterState {
80
+ const preset = this.getPreset(presetId);
81
+ if (!preset) throw new Error(`Filter preset not found: ${presetId}`);
82
+
83
+ const parameters: Record<string, any> = {};
84
+ preset.parameters.forEach(param => { parameters[param.name] = param.value; });
85
+
86
+ return { id: presetId, intensity: 100, parameters, enabled: true };
87
+ }
88
+
89
+ static applyFilter(
90
+ imageData: Uint8ClampedArray,
91
+ width: number,
92
+ height: number,
93
+ filterState: FilterState
94
+ ): Uint8ClampedArray {
95
+ const preset = this.getPreset(filterState.id);
96
+ if (!preset || !filterState.enabled) return imageData;
97
+
98
+ let processedData = new Uint8ClampedArray(imageData);
99
+
100
+ switch (filterState.id) {
101
+ case 'brightness':
102
+ processedData = ImageFilterUtils.applyBrightness(processedData, filterState.parameters.brightness) as any;
103
+ break;
104
+ case 'contrast':
105
+ processedData = ImageFilterUtils.applyContrast(processedData, filterState.parameters.contrast) as any;
106
+ break;
107
+ case 'saturation':
108
+ processedData = ImageFilterUtils.applySaturation(processedData, filterState.parameters.saturation) as any;
109
+ break;
110
+ case 'vintage':
111
+ processedData = ImageFilterUtils.applyVintage(processedData, filterState.parameters.intensity, filterState.parameters.warmth) as any;
112
+ break;
113
+ case 'blur':
114
+ processedData = ImageFilterUtils.applyBlur(processedData, filterState.parameters.radius, width, height) as any;
115
+ break;
116
+ }
117
+
118
+ if (filterState.intensity < 100) {
119
+ processedData = ImageFilterUtils.applyIntensity(imageData, processedData, filterState.intensity / 100) as any;
120
+ }
121
+
122
+ return processedData as any;
123
+ }
124
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Image Infrastructure - Image Analysis
3
+ *
4
+ * Image analysis utilities
5
+ */
6
+
7
+ export class ImageAnalysisUtils {
8
+ static calculateColorBalance(imageData: Uint8ClampedArray): {
9
+ redBalance: number;
10
+ greenBalance: number;
11
+ blueBalance: number;
12
+ } {
13
+ let redSum = 0, greenSum = 0, blueSum = 0;
14
+ const pixelCount = imageData.length / 4;
15
+
16
+ for (let i = 0; i < imageData.length; i += 4) {
17
+ redSum += imageData[i];
18
+ greenSum += imageData[i + 1];
19
+ blueSum += imageData[i + 2];
20
+ }
21
+
22
+ const redMean = redSum / pixelCount;
23
+ const greenMean = greenSum / pixelCount;
24
+ const blueMean = blueSum / pixelCount;
25
+ const grayMean = (redMean + greenMean + blueMean) / 3;
26
+
27
+ return {
28
+ redBalance: (grayMean - redMean) / 255,
29
+ greenBalance: (grayMean - greenMean) / 255,
30
+ blueBalance: (grayMean - blueMean) / 255,
31
+ };
32
+ }
33
+
34
+ static detectNoise(imageData: Uint8ClampedArray): number {
35
+ let noiseLevel = 0;
36
+ let sampleCount = 0;
37
+
38
+ for (let i = 0; i < imageData.length - 40; i += 40) {
39
+ const r1 = imageData[i];
40
+ const g1 = imageData[i + 1];
41
+ const b1 = imageData[i + 2];
42
+
43
+ const r2 = imageData[i + 4];
44
+ const g2 = imageData[i + 5];
45
+ const b2 = imageData[i + 6];
46
+
47
+ const diff = Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2);
48
+ noiseLevel += diff;
49
+ sampleCount++;
50
+ }
51
+
52
+ return noiseLevel / (sampleCount * 3 * 255);
53
+ }
54
+
55
+ static applySharpening(imageData: Uint8ClampedArray): Uint8ClampedArray {
56
+ const result = new Uint8ClampedArray(imageData.length);
57
+ const width = Math.sqrt(imageData.length / 4);
58
+ const kernel = [
59
+ 0, -1, 0,
60
+ -1, 5, -1,
61
+ 0, -1, 0
62
+ ];
63
+
64
+ for (let i = 0; i < imageData.length; i += 4) {
65
+ const pixelIndex = i / 4;
66
+ const x = pixelIndex % width;
67
+ const y = Math.floor(pixelIndex / width);
68
+
69
+ for (let c = 0; c < 3; c++) {
70
+ let sum = 0;
71
+ for (let ky = -1; ky <= 1; ky++) {
72
+ for (let kx = -1; kx <= 1; kx++) {
73
+ const nx = x + kx;
74
+ const ny = y + ky;
75
+
76
+ if (nx >= 0 && nx < width && ny >= 0 && ny < width) {
77
+ const neighborIndex = (ny * width + nx) * 4 + c;
78
+ sum += imageData[neighborIndex] * kernel[(ky + 1) * 3 + (kx + 1)];
79
+ }
80
+ }
81
+ }
82
+ result[i + c] = Math.min(255, Math.max(0, sum));
83
+ }
84
+ result[i + 3] = imageData[i + 3];
85
+ }
86
+
87
+ return result;
88
+ }
89
+
90
+ static applyNoiseReduction(imageData: Uint8ClampedArray): Uint8ClampedArray {
91
+ const result = new Uint8ClampedArray(imageData.length);
92
+ const width = Math.sqrt(imageData.length / 4);
93
+
94
+ for (let i = 0; i < imageData.length; i += 4) {
95
+ const pixelIndex = i / 4;
96
+ const x = pixelIndex % width;
97
+ const y = Math.floor(pixelIndex / width);
98
+
99
+ for (let c = 0; c < 3; c++) {
100
+ const values = [];
101
+
102
+ for (let dy = -1; dy <= 1; dy++) {
103
+ for (let dx = -1; dx <= 1; dx++) {
104
+ const nx = x + dx;
105
+ const ny = y + dy;
106
+
107
+ if (nx >= 0 && nx < width && ny >= 0 && ny < width) {
108
+ const neighborIndex = (ny * width + nx) * 4 + c;
109
+ values.push(imageData[neighborIndex]);
110
+ }
111
+ }
112
+ }
113
+
114
+ values.sort((a, b) => a - b);
115
+ result[i + c] = values[Math.floor(values.length / 2)];
116
+ }
117
+ result[i + 3] = imageData[i + 3];
118
+ }
119
+
120
+ return result;
121
+ }
122
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Infrastructure - Editor History Utils
3
+ *
4
+ * Logic for managing editor state history and undo/redo
5
+ */
6
+
7
+ import { type EditorState, type EditorHistory } from '../../domain/entities/EditorTypes';
8
+
9
+ export class ImageEditorHistoryUtils {
10
+ static addToHistory(
11
+ state: EditorState,
12
+ newHistory: EditorHistory,
13
+ maxHistory: number = 50
14
+ ): EditorState {
15
+ const newHistoryArray = [...state.history.slice(0, state.historyIndex + 1), newHistory];
16
+
17
+ if (newHistoryArray.length > maxHistory) {
18
+ newHistoryArray.shift();
19
+ }
20
+
21
+ return {
22
+ ...state,
23
+ history: newHistoryArray,
24
+ historyIndex: newHistoryArray.length - 1,
25
+ };
26
+ }
27
+
28
+ static undo(state: EditorState): EditorState {
29
+ if (state.historyIndex <= 0) return state;
30
+
31
+ const newIndex = state.historyIndex - 1;
32
+ const historyState = state.history[newIndex];
33
+
34
+ return {
35
+ ...state,
36
+ layers: historyState.layers,
37
+ historyIndex: newIndex,
38
+ isDirty: true,
39
+ };
40
+ }
41
+
42
+ static redo(state: EditorState): EditorState {
43
+ if (state.historyIndex >= state.history.length - 1) return state;
44
+
45
+ const newIndex = state.historyIndex + 1;
46
+ const historyState = state.history[newIndex];
47
+
48
+ return {
49
+ ...state,
50
+ layers: historyState.layers,
51
+ historyIndex: newIndex,
52
+ isDirty: true,
53
+ };
54
+ }
55
+
56
+ static canUndo(state: EditorState): boolean {
57
+ return state.historyIndex > 0;
58
+ }
59
+
60
+ static canRedo(state: EditorState): boolean {
61
+ return state.historyIndex < state.history.length - 1;
62
+ }
63
+ }