@umituz/react-native-image 1.1.4 → 1.1.6

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 (36) hide show
  1. package/package.json +2 -2
  2. package/src/domain/entities/ImageConstants.ts +32 -15
  3. package/src/domain/entities/ImageFilterTypes.ts +70 -0
  4. package/src/domain/entities/ImageTypes.ts +22 -7
  5. package/src/domain/utils/ImageUtils.ts +6 -6
  6. package/src/index.ts +47 -0
  7. package/src/infrastructure/services/ImageAIEnhancementService.ts +136 -0
  8. package/src/infrastructure/services/ImageAdvancedTransformService.ts +106 -0
  9. package/src/infrastructure/services/ImageAnnotationService.ts +189 -0
  10. package/src/infrastructure/services/ImageBatchService.ts +199 -0
  11. package/src/infrastructure/services/ImageConversionService.ts +51 -18
  12. package/src/infrastructure/services/ImageFilterService.ts +168 -0
  13. package/src/infrastructure/services/ImageMetadataService.ts +187 -0
  14. package/src/infrastructure/services/ImageSpecializedEnhancementService.ts +57 -0
  15. package/src/infrastructure/services/ImageStorageService.ts +22 -7
  16. package/src/infrastructure/services/ImageTransformService.ts +68 -101
  17. package/src/infrastructure/services/ImageViewerService.ts +3 -28
  18. package/src/infrastructure/utils/AIImageAnalysisUtils.ts +122 -0
  19. package/src/infrastructure/utils/CanvasRenderingService.ts +134 -0
  20. package/src/infrastructure/utils/FilterEffects.ts +51 -0
  21. package/src/infrastructure/utils/ImageErrorHandler.ts +40 -0
  22. package/src/infrastructure/utils/ImageQualityPresets.ts +110 -0
  23. package/src/infrastructure/utils/ImageValidator.ts +59 -0
  24. package/src/presentation/components/GalleryHeader.tsx +3 -4
  25. package/src/presentation/components/ImageGallery.tsx +7 -20
  26. package/src/presentation/hooks/useImage.ts +35 -5
  27. package/src/presentation/hooks/useImageAIEnhancement.ts +33 -0
  28. package/src/presentation/hooks/useImageAnnotation.ts +32 -0
  29. package/src/presentation/hooks/useImageBatch.ts +33 -0
  30. package/src/presentation/hooks/useImageConversion.ts +6 -3
  31. package/src/presentation/hooks/useImageEditor.ts +5 -11
  32. package/src/presentation/hooks/useImageFilter.ts +38 -0
  33. package/src/presentation/hooks/useImageGallery.ts +1 -60
  34. package/src/presentation/hooks/useImageMetadata.ts +28 -0
  35. package/src/presentation/hooks/useImageOperation.ts +14 -10
  36. package/src/presentation/hooks/useImageTransform.ts +13 -7
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Image Infrastructure - AI Image Analysis
3
+ *
4
+ * Advanced image analysis utilities
5
+ */
6
+
7
+ export class AIImageAnalysisUtils {
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,134 @@
1
+ /**
2
+ * Image Infrastructure - Canvas Rendering Service
3
+ *
4
+ * Canvas-based rendering utilities for annotations and filters
5
+ */
6
+
7
+ export class CanvasRenderingService {
8
+ static renderTextOnCanvas(
9
+ ctx: CanvasRenderingContext2D,
10
+ overlay: import('../services/ImageAnnotationService').TextOverlay
11
+ ): void {
12
+ ctx.save();
13
+
14
+ ctx.font = `${overlay.fontSize || 16}px ${overlay.fontFamily || 'Arial'}`;
15
+ ctx.fillStyle = overlay.color || '#000000';
16
+
17
+ if (overlay.backgroundColor) {
18
+ const metrics = ctx.measureText(overlay.text);
19
+ const padding = 4;
20
+ ctx.fillStyle = overlay.backgroundColor;
21
+ ctx.fillRect(
22
+ overlay.x - padding,
23
+ overlay.y - (overlay.fontSize || 16) - padding,
24
+ metrics.width + padding * 2,
25
+ (overlay.fontSize || 16) + padding * 2
26
+ );
27
+ ctx.fillStyle = overlay.color || '#000000';
28
+ }
29
+
30
+ if (overlay.rotation) {
31
+ ctx.translate(overlay.x, overlay.y);
32
+ ctx.rotate((overlay.rotation * Math.PI) / 180);
33
+ ctx.fillText(overlay.text, 0, 0);
34
+ } else {
35
+ ctx.fillText(overlay.text, overlay.x, overlay.y);
36
+ }
37
+
38
+ ctx.restore();
39
+ }
40
+
41
+ static renderDrawingOnCanvas(
42
+ ctx: CanvasRenderingContext2D,
43
+ drawing: import('../services/ImageAnnotationService').DrawingElement
44
+ ): void {
45
+ ctx.save();
46
+ ctx.strokeStyle = drawing.color || '#000000';
47
+ ctx.lineWidth = drawing.strokeWidth || 2;
48
+
49
+ if (drawing.fillColor) {
50
+ ctx.fillStyle = drawing.fillColor;
51
+ }
52
+
53
+ switch (drawing.type) {
54
+ case 'line':
55
+ if (drawing.points.length >= 2) {
56
+ ctx.beginPath();
57
+ ctx.moveTo(drawing.points[0].x, drawing.points[0].y);
58
+ for (let i = 1; i < drawing.points.length; i++) {
59
+ ctx.lineTo(drawing.points[i].x, drawing.points[i].y);
60
+ }
61
+ ctx.stroke();
62
+ }
63
+ break;
64
+
65
+ case 'rectangle':
66
+ if (drawing.points.length >= 2) {
67
+ const width = drawing.points[1].x - drawing.points[0].x;
68
+ const height = drawing.points[1].y - drawing.points[0].y;
69
+ if (drawing.fillColor) {
70
+ ctx.fillRect(drawing.points[0].x, drawing.points[0].y, width, height);
71
+ }
72
+ ctx.strokeRect(drawing.points[0].x, drawing.points[0].y, width, height);
73
+ }
74
+ break;
75
+
76
+ case 'circle':
77
+ if (drawing.points.length >= 2) {
78
+ const radius = Math.sqrt(
79
+ Math.pow(drawing.points[1].x - drawing.points[0].x, 2) +
80
+ Math.pow(drawing.points[1].y - drawing.points[0].y, 2)
81
+ );
82
+ ctx.beginPath();
83
+ ctx.arc(drawing.points[0].x, drawing.points[0].y, radius, 0, 2 * Math.PI);
84
+ if (drawing.fillColor) {
85
+ ctx.fill();
86
+ }
87
+ ctx.stroke();
88
+ }
89
+ break;
90
+
91
+ case 'arrow':
92
+ if (drawing.points.length >= 2) {
93
+ // Draw line
94
+ ctx.beginPath();
95
+ ctx.moveTo(drawing.points[0].x, drawing.points[0].y);
96
+ ctx.lineTo(drawing.points[1].x, drawing.points[1].y);
97
+ ctx.stroke();
98
+
99
+ // Draw arrowhead
100
+ const angle = Math.atan2(
101
+ drawing.points[1].y - drawing.points[0].y,
102
+ drawing.points[1].x - drawing.points[0].x
103
+ );
104
+ const arrowLength = 10;
105
+ ctx.beginPath();
106
+ ctx.moveTo(drawing.points[1].x, drawing.points[1].y);
107
+ ctx.lineTo(
108
+ drawing.points[1].x - arrowLength * Math.cos(angle - Math.PI / 6),
109
+ drawing.points[1].y - arrowLength * Math.sin(angle - Math.PI / 6)
110
+ );
111
+ ctx.moveTo(drawing.points[1].x, drawing.points[1].y);
112
+ ctx.lineTo(
113
+ drawing.points[1].x - arrowLength * Math.cos(angle + Math.PI / 6),
114
+ drawing.points[1].y - arrowLength * Math.sin(angle + Math.PI / 6)
115
+ );
116
+ ctx.stroke();
117
+ }
118
+ break;
119
+
120
+ case 'freehand':
121
+ if (drawing.points.length >= 2) {
122
+ ctx.beginPath();
123
+ ctx.moveTo(drawing.points[0].x, drawing.points[0].y);
124
+ for (let i = 1; i < drawing.points.length; i++) {
125
+ ctx.lineTo(drawing.points[i].x, drawing.points[i].y);
126
+ }
127
+ ctx.stroke();
128
+ }
129
+ break;
130
+ }
131
+
132
+ ctx.restore();
133
+ }
134
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Image Infrastructure - Filter Effects
3
+ *
4
+ * Advanced filter effects and color processing
5
+ */
6
+
7
+ export class FilterEffects {
8
+ static applyVintage(imageData: ImageData): ImageData {
9
+ let data = FilterEffects.applySepia(imageData, 0.8);
10
+ const { width, height } = imageData;
11
+ const centerX = width / 2;
12
+ const centerY = height / 2;
13
+ const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
14
+
15
+ for (let y = 0; y < height; y++) {
16
+ for (let x = 0; x < width; x++) {
17
+ const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
18
+ const vignette = 1 - (distance / maxDistance) * 0.7;
19
+ const i = (y * width + x) * 4;
20
+
21
+ data.data[i] *= vignette;
22
+ data.data[i + 1] *= vignette;
23
+ data.data[i + 2] *= vignette;
24
+ }
25
+ }
26
+
27
+ return data;
28
+ }
29
+
30
+ static applySepia(imageData: ImageData, intensity: number = 1): ImageData {
31
+ const data = new Uint8ClampedArray(imageData.data);
32
+ for (let i = 0; i < data.length; i += 4) {
33
+ const r = data[i];
34
+ const g = data[i + 1];
35
+ const b = data[i + 2];
36
+
37
+ data[i] = Math.min(255, (r * (1 - (0.607 * intensity))) + (g * (0.769 * intensity)) + (b * (0.189 * intensity)));
38
+ data[i + 1] = Math.min(255, (r * (0.349 * intensity)) + (g * (1 - (0.314 * intensity))) + (b * (0.168 * intensity)));
39
+ data[i + 2] = Math.min(255, (r * (0.272 * intensity)) + (g * (0.534 * intensity)) + (b * (1 - (0.869 * intensity))));
40
+ }
41
+ return FilterEffects.createCanvasImageData(imageData.width, imageData.height, data);
42
+ }
43
+
44
+ static createCanvasImageData(
45
+ width: number,
46
+ height: number,
47
+ data: Uint8ClampedArray
48
+ ): ImageData {
49
+ return { data, width, height } as ImageData;
50
+ }
51
+ }
@@ -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,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,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
+ }
@@ -1,9 +1,8 @@
1
1
  /**
2
- * GalleryHeader Component
3
- * Header for ImageGallery with optional edit button and close button
2
+ * Presentation - Gallery Header Component
4
3
  *
5
- * This component should be implemented by the consumer app
6
- * using their own design system and safe area handling.
4
+ * NOTE: This component should be implemented by consumer app
5
+ * using their design system and safe area handling
7
6
  */
8
7
 
9
8
  import React from 'react';
@@ -1,14 +1,12 @@
1
1
  /**
2
- * Image Gallery Component
2
+ * Presentation - Image Gallery Component
3
3
  *
4
- * A wrapper around react-native-image-viewing that provides
5
- * theme integration, standard configuration, and optional editing.
4
+ * Wrapper around react-native-image-viewing with theme integration
6
5
  */
7
6
 
8
7
  import React, { useCallback } from 'react';
9
8
  import ImageViewing from 'react-native-image-viewing';
10
- import * as ImageManipulator from 'expo-image-manipulator';
11
- import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
9
+ // import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
12
10
  import type { ImageViewerItem, ImageGalleryOptions } from '../../domain/entities/ImageTypes';
13
11
  import { GalleryHeader } from './GalleryHeader';
14
12
 
@@ -33,14 +31,14 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
33
31
  onImageChange,
34
32
  enableEditing = false,
35
33
  }) => {
36
- const tokens = useAppDesignTokens();
34
+ // const tokens = useAppDesignTokens();
37
35
  const [currentIndex, setCurrentIndex] = React.useState(index);
38
36
 
39
37
  React.useEffect(() => {
40
38
  setCurrentIndex(index);
41
39
  }, [index]);
42
40
 
43
- const bg = backgroundColor || tokens.colors.backgroundPrimary;
41
+ const bg = backgroundColor || '#000000';
44
42
 
45
43
  const viewerImages = React.useMemo(
46
44
  () => images.map((img) => ({ uri: img.uri })),
@@ -52,20 +50,9 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
52
50
  if (!currentImage || !onImageChange) return;
53
51
 
54
52
  try {
55
- const result = await ImageManipulator.manipulateAsync(
56
- currentImage.uri,
57
- [],
58
- {
59
- compress: 1,
60
- format: ImageManipulator.SaveFormat.JPEG,
61
- }
62
- );
63
-
64
- if (result.uri) {
65
- await onImageChange(result.uri, currentIndex);
66
- }
53
+ await onImageChange(currentImage.uri, currentIndex);
67
54
  } catch (error) {
68
- // Silent fail
55
+ // Consumer should handle editing logic
69
56
  }
70
57
  }, [images, currentIndex, onImageChange]);
71
58
 
@@ -1,22 +1,52 @@
1
1
  /**
2
- * useImage Hook
2
+ * Presentation - Image Hook
3
3
  *
4
- * Aggregator hook that combines transformation and conversion capabilities.
5
- * Kept simple and modular.
4
+ * Aggregator hook combining transformation and conversion capabilities
6
5
  */
7
6
 
8
7
  import { useImageTransform } from './useImageTransform';
9
8
  import { useImageConversion } from './useImageConversion';
9
+ import { useImageFilter } from './useImageFilter';
10
+ import { useImageBatch } from './useImageBatch';
11
+ import { useImageAIEnhancement } from './useImageAIEnhancement';
12
+ import { useImageAnnotation } from './useImageAnnotation';
13
+ import { useImageMetadata } from './useImageMetadata';
10
14
 
11
15
  export const useImage = () => {
12
16
  const transform = useImageTransform();
13
17
  const conversion = useImageConversion();
18
+ const filter = useImageFilter();
19
+ const batch = useImageBatch();
20
+ const aiEnhancement = useImageAIEnhancement();
21
+ const annotation = useImageAnnotation();
22
+ const metadata = useImageMetadata();
14
23
 
15
24
  return {
25
+ // Basic operations
16
26
  ...transform,
17
27
  ...conversion,
28
+ // Advanced operations
29
+ ...filter,
30
+ ...batch,
31
+ ...aiEnhancement,
32
+ ...annotation,
33
+ ...metadata,
18
34
  // Combined state
19
- isProcessing: transform.isTransforming || conversion.isConverting,
20
- error: transform.transformError || conversion.conversionError,
35
+ isProcessing:
36
+ transform.isTransforming ||
37
+ conversion.isConverting ||
38
+ filter.isFiltering ||
39
+ batch.isBatchProcessing ||
40
+ aiEnhancement.isEnhancing ||
41
+ annotation.isAnnotating ||
42
+ metadata.isExtracting,
43
+ error:
44
+ transform.transformError ||
45
+ conversion.conversionError ||
46
+ filter.filterError ||
47
+ batch.batchError ||
48
+ aiEnhancement.enhancementError ||
49
+ annotation.annotationError ||
50
+ metadata.metadataError,
21
51
  };
22
52
  };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Presentation - Image AI Enhancement Hook
3
+ */
4
+
5
+ import { useCallback } from 'react';
6
+ import { useImageOperation } from './useImageOperation';
7
+ import { ImageAIEnhancementService, type AutoEnhancementOptions, type EnhancementResult } from '../../infrastructure/services/ImageAIEnhancementService';
8
+ import { ImageSpecializedEnhancementService } from '../../infrastructure/services/ImageSpecializedEnhancementService';
9
+
10
+ export const useImageAIEnhancement = () => {
11
+ const { isProcessing, error, execute } = useImageOperation();
12
+
13
+ const autoEnhance = useCallback((uri: string, options?: AutoEnhancementOptions) =>
14
+ execute(() => ImageAIEnhancementService.autoEnhance(uri, options), 'Failed to auto enhance'), [execute]);
15
+
16
+ const enhancePortrait = useCallback((uri: string) =>
17
+ execute(() => ImageSpecializedEnhancementService.enhancePortrait(uri), 'Failed to enhance portrait'), [execute]);
18
+
19
+ const enhanceLandscape = useCallback((uri: string) =>
20
+ execute(() => ImageSpecializedEnhancementService.enhanceLandscape(uri), 'Failed to enhance landscape'), [execute]);
21
+
22
+ const analyzeImage = useCallback((uri: string) =>
23
+ execute(() => ImageAIEnhancementService.analyzeImage(uri), 'Failed to analyze image'), [execute]);
24
+
25
+ return {
26
+ autoEnhance,
27
+ enhancePortrait,
28
+ enhanceLandscape,
29
+ analyzeImage,
30
+ isEnhancing: isProcessing,
31
+ enhancementError: error,
32
+ };
33
+ };