@umituz/react-native-image 1.1.6 → 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.
@@ -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,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
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Infrastructure - Shape Renderer
3
+ *
4
+ * Advanced shape drawing with different styles
5
+ */
6
+
7
+ export interface ShapeStyle {
8
+ fill?: string;
9
+ stroke?: string;
10
+ strokeWidth?: number;
11
+ dash?: number[];
12
+ opacity?: number;
13
+ }
14
+
15
+ export class ShapeRenderer {
16
+ static drawRoundedRect(
17
+ ctx: CanvasRenderingContext2D,
18
+ x: number,
19
+ y: number,
20
+ width: number,
21
+ height: number,
22
+ radius: number,
23
+ style: ShapeStyle
24
+ ): void {
25
+ ctx.save();
26
+
27
+ ctx.globalAlpha = style.opacity || 1;
28
+ ctx.strokeStyle = style.stroke || '#000000';
29
+ ctx.fillStyle = style.fill || 'transparent';
30
+ ctx.lineWidth = style.strokeWidth || 2;
31
+
32
+ if (style.dash) {
33
+ ctx.setLineDash(style.dash);
34
+ }
35
+
36
+ ctx.beginPath();
37
+ ctx.moveTo(x + radius, y);
38
+ ctx.lineTo(x + width - radius, y);
39
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
40
+ ctx.lineTo(x + width, y + height - radius);
41
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
42
+ ctx.lineTo(x + radius, y + height);
43
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
44
+ ctx.lineTo(x, y + radius);
45
+ ctx.quadraticCurveTo(x, y, x + radius, y);
46
+ ctx.closePath();
47
+
48
+ if (style.fill) ctx.fill();
49
+ if (style.stroke) ctx.stroke();
50
+
51
+ ctx.restore();
52
+ }
53
+
54
+ static drawStar(
55
+ ctx: CanvasRenderingContext2D,
56
+ cx: number,
57
+ cy: number,
58
+ outerRadius: number,
59
+ innerRadius: number,
60
+ points: number,
61
+ style: ShapeStyle
62
+ ): void {
63
+ ctx.save();
64
+
65
+ ctx.globalAlpha = style.opacity || 1;
66
+ ctx.strokeStyle = style.stroke || '#000000';
67
+ ctx.fillStyle = style.fill || 'transparent';
68
+ ctx.lineWidth = style.strokeWidth || 2;
69
+
70
+ ctx.beginPath();
71
+ for (let i = 0; i < points * 2; i++) {
72
+ const angle = (Math.PI * i) / points - Math.PI / 2;
73
+ const radius = i % 2 === 0 ? outerRadius : innerRadius;
74
+ const x = cx + Math.cos(angle) * radius;
75
+ const y = cy + Math.sin(angle) * radius;
76
+
77
+ if (i === 0) {
78
+ ctx.moveTo(x, y);
79
+ } else {
80
+ ctx.lineTo(x, y);
81
+ }
82
+ }
83
+ ctx.closePath();
84
+
85
+ if (style.fill) ctx.fill();
86
+ if (style.stroke) ctx.stroke();
87
+
88
+ ctx.restore();
89
+ }
90
+
91
+ static drawHeart(
92
+ ctx: CanvasRenderingContext2D,
93
+ x: number,
94
+ y: number,
95
+ size: number,
96
+ style: ShapeStyle
97
+ ): void {
98
+ ctx.save();
99
+
100
+ ctx.globalAlpha = style.opacity || 1;
101
+ ctx.strokeStyle = style.stroke || '#000000';
102
+ ctx.fillStyle = style.fill || 'transparent';
103
+ ctx.lineWidth = style.strokeWidth || 2;
104
+
105
+ ctx.beginPath();
106
+ const topCurveHeight = size * 0.3;
107
+ ctx.moveTo(x, y + topCurveHeight);
108
+
109
+ // Top left curve
110
+ ctx.bezierCurveTo(
111
+ x, y,
112
+ x - size / 2, y,
113
+ x - size / 2, y + topCurveHeight
114
+ );
115
+
116
+ // Bottom left curve
117
+ ctx.bezierCurveTo(
118
+ x - size / 2, y + (size + topCurveHeight) / 2,
119
+ x, y + (size + topCurveHeight) / 1.5,
120
+ x, y + size
121
+ );
122
+
123
+ // Bottom right curve
124
+ ctx.bezierCurveTo(
125
+ x, y + (size + topCurveHeight) / 1.5,
126
+ x + size / 2, y + (size + topCurveHeight) / 2,
127
+ x + size / 2, y + topCurveHeight
128
+ );
129
+
130
+ // Top right curve
131
+ ctx.bezierCurveTo(
132
+ x + size / 2, y,
133
+ x, y,
134
+ x, y + topCurveHeight
135
+ );
136
+
137
+ ctx.closePath();
138
+
139
+ if (style.fill) ctx.fill();
140
+ if (style.stroke) ctx.stroke();
141
+
142
+ ctx.restore();
143
+ }
144
+
145
+ static drawTriangle(
146
+ ctx: CanvasRenderingContext2D,
147
+ points: Array<{ x: number; y: number }>,
148
+ style: ShapeStyle
149
+ ): void {
150
+ ctx.save();
151
+
152
+ ctx.globalAlpha = style.opacity || 1;
153
+ ctx.strokeStyle = style.stroke || '#000000';
154
+ ctx.fillStyle = style.fill || 'transparent';
155
+ ctx.lineWidth = style.strokeWidth || 2;
156
+
157
+ ctx.beginPath();
158
+ ctx.moveTo(points[0].x, points[0].y);
159
+ ctx.lineTo(points[1].x, points[1].y);
160
+ ctx.lineTo(points[2].x, points[2].y);
161
+ ctx.closePath();
162
+
163
+ if (style.fill) ctx.fill();
164
+ if (style.stroke) ctx.stroke();
165
+
166
+ ctx.restore();
167
+ }
168
+ }