@umituz/react-native-image 1.1.5 → 1.2.1

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 (37) hide show
  1. package/package.json +25 -10
  2. package/src/domain/entities/EditorTypes.ts +180 -0
  3. package/src/domain/entities/ImageFilterTypes.ts +70 -0
  4. package/src/index.ts +54 -0
  5. package/src/infrastructure/services/ImageAIEnhancementService.ts +136 -0
  6. package/src/infrastructure/services/ImageAnnotationService.ts +189 -0
  7. package/src/infrastructure/services/ImageBatchService.ts +199 -0
  8. package/src/infrastructure/services/ImageEditorService.ts +274 -0
  9. package/src/infrastructure/services/ImageFilterService.ts +168 -0
  10. package/src/infrastructure/services/ImageMetadataService.ts +187 -0
  11. package/src/infrastructure/services/ImageSpecializedEnhancementService.ts +57 -0
  12. package/src/infrastructure/utils/AIImageAnalysisUtils.ts +122 -0
  13. package/src/infrastructure/utils/CanvasRenderingService.ts +134 -0
  14. package/src/infrastructure/utils/CropTool.ts +260 -0
  15. package/src/infrastructure/utils/DrawingEngine.ts +210 -0
  16. package/src/infrastructure/utils/FilterEffects.ts +51 -0
  17. package/src/infrastructure/utils/FilterProcessor.ts +361 -0
  18. package/src/infrastructure/utils/ImageQualityPresets.ts +110 -0
  19. package/src/infrastructure/utils/LayerManager.ts +158 -0
  20. package/src/infrastructure/utils/ShapeRenderer.ts +168 -0
  21. package/src/infrastructure/utils/TextEditor.ts +273 -0
  22. package/src/presentation/components/CropComponent.tsx +183 -0
  23. package/src/presentation/components/Editor.tsx +261 -0
  24. package/src/presentation/components/EditorCanvas.tsx +135 -0
  25. package/src/presentation/components/EditorPanel.tsx +321 -0
  26. package/src/presentation/components/EditorToolbar.tsx +180 -0
  27. package/src/presentation/components/FilterSlider.tsx +123 -0
  28. package/src/presentation/components/GalleryHeader.tsx +87 -25
  29. package/src/presentation/components/ImageGallery.tsx +97 -48
  30. package/src/presentation/hooks/useEditorTools.ts +188 -0
  31. package/src/presentation/hooks/useImage.ts +33 -2
  32. package/src/presentation/hooks/useImageAIEnhancement.ts +33 -0
  33. package/src/presentation/hooks/useImageAnnotation.ts +32 -0
  34. package/src/presentation/hooks/useImageBatch.ts +33 -0
  35. package/src/presentation/hooks/useImageEditor.ts +165 -38
  36. package/src/presentation/hooks/useImageFilter.ts +38 -0
  37. package/src/presentation/hooks/useImageMetadata.ts +28 -0
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Infrastructure - Filter Processor
3
+ *
4
+ * Real-time filter processing with preview
5
+ */
6
+
7
+ export interface FilterPreset {
8
+ id: string;
9
+ name: string;
10
+ category: 'basic' | 'color' | 'artistic' | 'vintage';
11
+ parameters: FilterParameter[];
12
+ preview?: string;
13
+ }
14
+
15
+ export interface FilterParameter {
16
+ name: string;
17
+ type: 'slider' | 'color' | 'boolean';
18
+ min?: number;
19
+ max?: number;
20
+ value: number | string | boolean;
21
+ step?: number;
22
+ label: string;
23
+ }
24
+
25
+ export interface FilterState {
26
+ id: string;
27
+ intensity: number;
28
+ parameters: Record<string, any>;
29
+ enabled: boolean;
30
+ }
31
+
32
+ export class FilterProcessor {
33
+ private static readonly PRESETS: FilterPreset[] = [
34
+ {
35
+ id: 'brightness',
36
+ name: 'Brightness',
37
+ category: 'basic',
38
+ parameters: [
39
+ {
40
+ name: 'brightness',
41
+ type: 'slider',
42
+ min: -100,
43
+ max: 100,
44
+ value: 0,
45
+ label: 'Brightness',
46
+ },
47
+ ],
48
+ },
49
+ {
50
+ id: 'contrast',
51
+ name: 'Contrast',
52
+ category: 'basic',
53
+ parameters: [
54
+ {
55
+ name: 'contrast',
56
+ type: 'slider',
57
+ min: -100,
58
+ max: 100,
59
+ value: 0,
60
+ label: 'Contrast',
61
+ },
62
+ ],
63
+ },
64
+ {
65
+ id: 'saturation',
66
+ name: 'Saturation',
67
+ category: 'color',
68
+ parameters: [
69
+ {
70
+ name: 'saturation',
71
+ type: 'slider',
72
+ min: -100,
73
+ max: 100,
74
+ value: 0,
75
+ label: 'Saturation',
76
+ },
77
+ ],
78
+ },
79
+ {
80
+ id: 'vintage',
81
+ name: 'Vintage',
82
+ category: 'vintage',
83
+ parameters: [
84
+ {
85
+ name: 'intensity',
86
+ type: 'slider',
87
+ min: 0,
88
+ max: 100,
89
+ value: 50,
90
+ label: 'Intensity',
91
+ },
92
+ {
93
+ name: 'warmth',
94
+ type: 'slider',
95
+ min: 0,
96
+ max: 100,
97
+ value: 30,
98
+ label: 'Warmth',
99
+ },
100
+ ],
101
+ },
102
+ {
103
+ id: 'blur',
104
+ name: 'Blur',
105
+ category: 'artistic',
106
+ parameters: [
107
+ {
108
+ name: 'radius',
109
+ type: 'slider',
110
+ min: 0,
111
+ max: 20,
112
+ value: 0,
113
+ label: 'Blur Radius',
114
+ },
115
+ ],
116
+ },
117
+ ];
118
+
119
+ static getPresets(category?: string): FilterPreset[] {
120
+ if (category) {
121
+ return this.PRESETS.filter(preset => preset.category === category);
122
+ }
123
+ return this.PRESETS;
124
+ }
125
+
126
+ static getPreset(id: string): FilterPreset | undefined {
127
+ return this.PRESETS.find(preset => preset.id === id);
128
+ }
129
+
130
+ static createFilterState(presetId: string): FilterState {
131
+ const preset = this.getPreset(presetId);
132
+ if (!preset) {
133
+ throw new Error(`Filter preset not found: ${presetId}`);
134
+ }
135
+
136
+ const parameters: Record<string, any> = {};
137
+ preset.parameters.forEach(param => {
138
+ parameters[param.name] = param.value;
139
+ });
140
+
141
+ return {
142
+ id: presetId,
143
+ intensity: 100,
144
+ parameters,
145
+ enabled: true,
146
+ };
147
+ }
148
+
149
+ static applyFilter(
150
+ imageData: ImageData,
151
+ filterState: FilterState
152
+ ): ImageData {
153
+ const preset = this.getPreset(filterState.id);
154
+ if (!preset || !filterState.enabled) {
155
+ return imageData;
156
+ }
157
+
158
+ let processedData = new Uint8ClampedArray(imageData.data);
159
+
160
+ // Apply filter based on preset type
161
+ switch (filterState.id) {
162
+ case 'brightness':
163
+ processedData = this.applyBrightness(processedData, filterState.parameters.brightness) as any;
164
+ break;
165
+ case 'contrast':
166
+ processedData = this.applyContrast(processedData, filterState.parameters.contrast) as any;
167
+ break;
168
+ case 'saturation':
169
+ processedData = this.applySaturation(processedData, filterState.parameters.saturation) as any;
170
+ break;
171
+ case 'vintage':
172
+ processedData = this.applyVintage(processedData, filterState.parameters.intensity, filterState.parameters.warmth) as any;
173
+ break;
174
+ case 'blur':
175
+ processedData = this.applyBlur(processedData, filterState.parameters.radius) as any;
176
+ break;
177
+ }
178
+
179
+ // Apply intensity
180
+ if (filterState.intensity < 100) {
181
+ processedData = this.applyIntensity(imageData.data, processedData, filterState.intensity / 100) as any;
182
+ }
183
+
184
+ return {
185
+ ...imageData,
186
+ data: processedData,
187
+ };
188
+ }
189
+
190
+ private static applyBrightness(
191
+ data: Uint8ClampedArray,
192
+ brightness: number
193
+ ): Uint8ClampedArray {
194
+ const result = new Uint8ClampedArray(data);
195
+ const adjustment = brightness * 2.55; // Convert -100 to 100 to -255 to 255
196
+
197
+ for (let i = 0; i < result.length; i += 4) {
198
+ result[i] = Math.min(255, Math.max(0, result[i] + adjustment));
199
+ result[i + 1] = Math.min(255, Math.max(0, result[i + 1] + adjustment));
200
+ result[i + 2] = Math.min(255, Math.max(0, result[i + 2] + adjustment));
201
+ }
202
+
203
+ return result;
204
+ }
205
+
206
+ private static applyContrast(
207
+ data: Uint8ClampedArray,
208
+ contrast: number
209
+ ): Uint8ClampedArray {
210
+ const result = new Uint8ClampedArray(data);
211
+ const factor = (259 * (contrast * 255 + 255)) / (255 * (259 - contrast * 255));
212
+
213
+ for (let i = 0; i < result.length; i += 4) {
214
+ result[i] = Math.min(255, Math.max(0, factor * (result[i] - 128) + 128));
215
+ result[i + 1] = Math.min(255, Math.max(0, factor * (result[i + 1] - 128) + 128));
216
+ result[i + 2] = Math.min(255, Math.max(0, factor * (result[i + 2] - 128) + 128));
217
+ }
218
+
219
+ return result;
220
+ }
221
+
222
+ private static applySaturation(
223
+ data: Uint8ClampedArray,
224
+ saturation: number
225
+ ): Uint8ClampedArray {
226
+ const result = new Uint8ClampedArray(data);
227
+ const adjustment = 1 + (saturation / 100);
228
+
229
+ for (let i = 0; i < result.length; i += 4) {
230
+ const r = result[i];
231
+ const g = result[i + 1];
232
+ const b = result[i + 2];
233
+
234
+ const gray = 0.299 * r + 0.587 * g + 0.114 * b;
235
+
236
+ result[i] = Math.min(255, Math.max(0, gray + adjustment * (r - gray)));
237
+ result[i + 1] = Math.min(255, Math.max(0, gray + adjustment * (g - gray)));
238
+ result[i + 2] = Math.min(255, Math.max(0, gray + adjustment * (b - gray)));
239
+ }
240
+
241
+ return result;
242
+ }
243
+
244
+ private static applyVintage(
245
+ data: Uint8ClampedArray,
246
+ intensity: number,
247
+ warmth: number
248
+ ): Uint8ClampedArray {
249
+ const result = new Uint8ClampedArray(data);
250
+ const factor = intensity / 100;
251
+ const warmFactor = warmth / 100;
252
+
253
+ for (let i = 0; i < result.length; i += 4) {
254
+ let r = result[i];
255
+ let g = result[i + 1];
256
+ let b = result[i + 2];
257
+
258
+ // Apply sepia effect
259
+ const tr = 0.393 * r + 0.769 * g + 0.189 * b;
260
+ const tg = 0.349 * r + 0.686 * g + 0.168 * b;
261
+ const tb = 0.272 * r + 0.534 * g + 0.131 * b;
262
+
263
+ // Mix with original based on intensity
264
+ r = r * (1 - factor) + tr * factor;
265
+ g = g * (1 - factor) + tg * factor;
266
+ b = b * (1 - factor) + tb * factor;
267
+
268
+ // Apply warmth
269
+ if (warmFactor > 0) {
270
+ result[i] = Math.min(255, r + warmFactor * 20);
271
+ result[i + 1] = Math.min(255, g + warmFactor * 10);
272
+ result[i + 2] = Math.min(255, b * (1 - warmFactor * 0.3));
273
+ } else {
274
+ result[i] = r;
275
+ result[i + 1] = g;
276
+ result[i + 2] = Math.min(255, b * (1 - Math.abs(warmFactor) * 0.3));
277
+ }
278
+ }
279
+
280
+ return result;
281
+ }
282
+
283
+ private static applyBlur(
284
+ data: Uint8ClampedArray,
285
+ radius: number
286
+ ): Uint8ClampedArray {
287
+ // Simple box blur implementation
288
+ const result = new Uint8ClampedArray(data);
289
+ const width = Math.sqrt(data.length / 4);
290
+ const height = width;
291
+ const size = Math.floor(radius) || 1;
292
+
293
+ for (let y = 0; y < height; y++) {
294
+ for (let x = 0; x < width; x++) {
295
+ let r = 0, g = 0, b = 0, a = 0;
296
+ let count = 0;
297
+
298
+ for (let dy = -size; dy <= size; dy++) {
299
+ for (let dx = -size; dx <= size; dx++) {
300
+ const ny = y + dy;
301
+ const nx = x + dx;
302
+
303
+ if (ny >= 0 && ny < height && nx >= 0 && nx < width) {
304
+ const idx = (ny * width + nx) * 4;
305
+ r += data[idx];
306
+ g += data[idx + 1];
307
+ b += data[idx + 2];
308
+ a += data[idx + 3];
309
+ count++;
310
+ }
311
+ }
312
+ }
313
+
314
+ const idx = (y * width + x) * 4;
315
+ result[idx] = r / count;
316
+ result[idx + 1] = g / count;
317
+ result[idx + 2] = b / count;
318
+ result[idx + 3] = a / count;
319
+ }
320
+ }
321
+
322
+ return result;
323
+ }
324
+
325
+ private static applyIntensity(
326
+ originalData: Uint8ClampedArray,
327
+ processedData: Uint8ClampedArray,
328
+ intensity: number
329
+ ): Uint8ClampedArray {
330
+ const result = new Uint8ClampedArray(originalData.length);
331
+
332
+ for (let i = 0; i < originalData.length; i++) {
333
+ result[i] = originalData[i] * (1 - intensity) + processedData[i] * intensity;
334
+ }
335
+
336
+ return result;
337
+ }
338
+
339
+ static createPreview(
340
+ imageData: ImageData,
341
+ filterState: FilterState,
342
+ previewSize: { width: number; height: number }
343
+ ): ImageData {
344
+ // Create a smaller preview version
345
+ const previewCanvas = document.createElement('canvas') || {} as any;
346
+ previewCanvas.width = previewSize.width;
347
+ previewCanvas.height = previewSize.height;
348
+ const ctx = previewCanvas.getContext('2d');
349
+
350
+ if (!ctx) return imageData;
351
+
352
+ // Scale down the image for preview
353
+ ctx.drawImage(
354
+ {} as HTMLImageElement, // Would be the actual image
355
+ 0, 0, previewSize.width, previewSize.height
356
+ );
357
+
358
+ const previewImageData = ctx.getImageData(0, 0, previewSize.width, previewSize.height);
359
+ return this.applyFilter(previewImageData, filterState);
360
+ }
361
+ }
@@ -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,158 @@
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
+ static composeLayers(
17
+ layers: Array<{
18
+ canvas: HTMLCanvasElement | any;
19
+ opacity: number;
20
+ visible: boolean;
21
+ x?: number;
22
+ y?: number;
23
+ }>,
24
+ composition: LayerComposition
25
+ ): HTMLCanvasElement | any {
26
+ // Create composition canvas
27
+ const composedCanvas = document.createElement('canvas') || {} as any;
28
+ composedCanvas.width = composition.width;
29
+ composedCanvas.height = composition.height;
30
+ const ctx = composedCanvas.getContext('2d');
31
+
32
+ if (!ctx) return composedCanvas;
33
+
34
+ // Clear canvas with background color
35
+ ctx.fillStyle = composition.backgroundColor || '#ffffff';
36
+ ctx.fillRect(0, 0, composition.width, composition.height);
37
+
38
+ // Draw each layer
39
+ layers.forEach(layer => {
40
+ if (!layer.visible) return;
41
+
42
+ ctx.save();
43
+ ctx.globalAlpha = layer.opacity;
44
+ ctx.drawImage(
45
+ layer.canvas,
46
+ layer.x || 0,
47
+ layer.y || 0
48
+ );
49
+ ctx.restore();
50
+ });
51
+
52
+ return composedCanvas;
53
+ }
54
+
55
+ static mergeLayers(
56
+ layers: Array<{
57
+ id: string;
58
+ name: string;
59
+ elements: any[];
60
+ }>,
61
+ targetIds: string[]
62
+ ): Array<{
63
+ id: string;
64
+ name: string;
65
+ elements: any[];
66
+ }> {
67
+ const targetLayers = layers.filter(layer => targetIds.includes(layer.id));
68
+ const otherLayers = layers.filter(layer => !targetIds.includes(layer.id));
69
+
70
+ if (targetLayers.length === 0) return layers;
71
+
72
+ // Merge elements from target layers
73
+ const mergedElements = targetLayers.flatMap(layer => layer.elements);
74
+ const mergedLayer = {
75
+ id: Math.random().toString(36).substr(2, 9),
76
+ name: targetLayers.map(l => l.name).join(' + '),
77
+ elements: mergedElements,
78
+ };
79
+
80
+ return [...otherLayers, mergedLayer];
81
+ }
82
+
83
+ static duplicateLayer(
84
+ layer: {
85
+ id: string;
86
+ name: string;
87
+ elements: any[];
88
+ }
89
+ ): {
90
+ id: string;
91
+ name: string;
92
+ elements: any[];
93
+ } {
94
+ return {
95
+ id: Math.random().toString(36).substr(2, 9),
96
+ name: `${layer.name} Copy`,
97
+ elements: [...layer.elements],
98
+ };
99
+ }
100
+
101
+ static reorderLayers(
102
+ layers: Array<{ id: string; index?: number }>,
103
+ fromIndex: number,
104
+ toIndex: number
105
+ ): Array<{ id: string; index?: number }> {
106
+ const result = [...layers];
107
+ const [moved] = result.splice(fromIndex, 1);
108
+ result.splice(toIndex, 0, moved);
109
+
110
+ return result.map((layer, index) => ({ ...layer, index }));
111
+ }
112
+
113
+ static flattenLayers(
114
+ layers: Array<{
115
+ canvas: HTMLCanvasElement | any;
116
+ opacity: number;
117
+ visible: boolean;
118
+ }>,
119
+ composition: LayerComposition
120
+ ): HTMLCanvasElement | any {
121
+ return LayerManager.composeLayers(layers, composition);
122
+ }
123
+
124
+ static createLayerCanvas(
125
+ width: number,
126
+ height: number,
127
+ transparent: boolean = true
128
+ ): HTMLCanvasElement | any {
129
+ const canvas = document.createElement('canvas') || {} as any;
130
+ canvas.width = width;
131
+ canvas.height = height;
132
+
133
+ if (!transparent) {
134
+ const ctx = canvas.getContext('2d');
135
+ if (ctx) {
136
+ ctx.fillStyle = '#ffffff';
137
+ ctx.fillRect(0, 0, width, height);
138
+ }
139
+ }
140
+
141
+ return canvas;
142
+ }
143
+
144
+ static clearLayer(
145
+ canvas: HTMLCanvasElement | any,
146
+ preserveAlpha: boolean = true
147
+ ): void {
148
+ const ctx = canvas.getContext('2d');
149
+ if (!ctx) return;
150
+
151
+ if (preserveAlpha) {
152
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
153
+ } else {
154
+ ctx.fillStyle = 'transparent';
155
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
156
+ }
157
+ }
158
+ }