@umituz/react-native-image 1.3.1 → 1.3.3
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.
- package/package.json +1 -1
- package/src/infrastructure/services/ImageBatchService.ts +0 -9
- package/src/presentation/hooks/useImage.ts +10 -20
- package/src/infrastructure/services/ImageAnnotationService.ts +0 -189
- package/src/infrastructure/services/ImageFilterService.ts +0 -168
- package/src/infrastructure/utils/CanvasRenderingService.ts +0 -134
- package/src/infrastructure/utils/CropTool.ts +0 -260
- package/src/infrastructure/utils/DrawingEngine.ts +0 -210
- package/src/infrastructure/utils/FilterEffects.ts +0 -51
- package/src/infrastructure/utils/ShapeRenderer.ts +0 -168
- package/src/infrastructure/utils/TextEditor.ts +0 -273
- package/src/presentation/components/CropComponent.tsx +0 -183
- package/src/presentation/components/Editor.tsx +0 -261
- package/src/presentation/components/EditorCanvas.tsx +0 -135
- package/src/presentation/components/EditorPanel.tsx +0 -321
- package/src/presentation/components/EditorToolbar.tsx +0 -180
- package/src/presentation/components/FilterSlider.tsx +0 -118
- package/src/presentation/hooks/useEditorTools.ts +0 -188
- package/src/presentation/hooks/useImageAnnotation.ts +0 -32
- package/src/presentation/hooks/useImageEditor.ts +0 -182
- package/src/presentation/hooks/useImageFilter.ts +0 -38
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Infrastructure - Crop Tool
|
|
3
|
-
*
|
|
4
|
-
* Advanced cropping with aspect ratio and grid guides
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export interface CropConfig {
|
|
8
|
-
aspectRatio?: number;
|
|
9
|
-
lockAspectRatio?: boolean;
|
|
10
|
-
showGrid?: boolean;
|
|
11
|
-
minSize?: { width: number; height: number };
|
|
12
|
-
maxSize?: { width: number; height: number };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface CropArea {
|
|
16
|
-
x: number;
|
|
17
|
-
y: number;
|
|
18
|
-
width: number;
|
|
19
|
-
height: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface CropPreset {
|
|
23
|
-
name: string;
|
|
24
|
-
aspectRatio: number;
|
|
25
|
-
icon: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class CropTool {
|
|
29
|
-
private static readonly PRESETS: CropPreset[] = [
|
|
30
|
-
{ name: 'Free', aspectRatio: 0, icon: 'crop-free' },
|
|
31
|
-
{ name: '1:1', aspectRatio: 1, icon: 'crop-square' },
|
|
32
|
-
{ name: '4:3', aspectRatio: 4/3, icon: 'crop-landscape' },
|
|
33
|
-
{ name: '3:4', aspectRatio: 3/4, icon: 'crop-portrait' },
|
|
34
|
-
{ name: '16:9', aspectRatio: 16/9, icon: 'crop-widescreen' },
|
|
35
|
-
{ name: '9:16', aspectRatio: 9/16, icon: 'crop-vertical' },
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
static getPresets(): CropPreset[] {
|
|
39
|
-
return this.PRESETS;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
static constrainToAspectRatio(
|
|
43
|
-
width: number,
|
|
44
|
-
height: number,
|
|
45
|
-
aspectRatio: number
|
|
46
|
-
): { width: number; height: number } {
|
|
47
|
-
if (aspectRatio === 0) return { width, height };
|
|
48
|
-
|
|
49
|
-
const currentRatio = width / height;
|
|
50
|
-
|
|
51
|
-
if (currentRatio > aspectRatio) {
|
|
52
|
-
// Width is too large, constrain it
|
|
53
|
-
return {
|
|
54
|
-
width: height * aspectRatio,
|
|
55
|
-
height,
|
|
56
|
-
};
|
|
57
|
-
} else {
|
|
58
|
-
// Height is too large, constrain it
|
|
59
|
-
return {
|
|
60
|
-
width,
|
|
61
|
-
height: width / aspectRatio,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
static constrainToBounds(
|
|
67
|
-
area: CropArea,
|
|
68
|
-
bounds: { width: number; height: number }
|
|
69
|
-
): CropArea {
|
|
70
|
-
let { x, y, width, height } = area;
|
|
71
|
-
|
|
72
|
-
// Ensure area is within bounds
|
|
73
|
-
if (x < 0) {
|
|
74
|
-
width += x;
|
|
75
|
-
x = 0;
|
|
76
|
-
}
|
|
77
|
-
if (y < 0) {
|
|
78
|
-
height += y;
|
|
79
|
-
y = 0;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (x + width > bounds.width) {
|
|
83
|
-
width = bounds.width - x;
|
|
84
|
-
}
|
|
85
|
-
if (y + height > bounds.height) {
|
|
86
|
-
height = bounds.height - y;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return { x, y, width, height };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
static applyMinimumSize(
|
|
93
|
-
area: CropArea,
|
|
94
|
-
minSize: { width: number; height: number }
|
|
95
|
-
): CropArea {
|
|
96
|
-
const { width, height } = area;
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
...area,
|
|
100
|
-
width: Math.max(width, minSize.width),
|
|
101
|
-
height: Math.max(height, minSize.height),
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
static applyMaximumSize(
|
|
106
|
-
area: CropArea,
|
|
107
|
-
maxSize: { width: number; height: number }
|
|
108
|
-
): CropArea {
|
|
109
|
-
const { width, height } = area;
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
...area,
|
|
113
|
-
width: Math.min(width, maxSize.width),
|
|
114
|
-
height: Math.min(height, maxSize.height),
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
static centerCrop(
|
|
119
|
-
imageWidth: number,
|
|
120
|
-
imageHeight: number,
|
|
121
|
-
targetWidth?: number,
|
|
122
|
-
targetHeight?: number
|
|
123
|
-
): CropArea {
|
|
124
|
-
if (!targetWidth && !targetHeight) {
|
|
125
|
-
return {
|
|
126
|
-
x: 0,
|
|
127
|
-
y: 0,
|
|
128
|
-
width: imageWidth,
|
|
129
|
-
height: imageHeight,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (!targetHeight) {
|
|
134
|
-
targetHeight = targetWidth! * (imageHeight / imageWidth);
|
|
135
|
-
}
|
|
136
|
-
if (!targetWidth) {
|
|
137
|
-
targetWidth = targetHeight! * (imageWidth / imageHeight);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
x: (imageWidth - targetWidth) / 2,
|
|
142
|
-
y: (imageHeight - targetHeight) / 2,
|
|
143
|
-
width: targetWidth,
|
|
144
|
-
height: targetHeight,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
static drawCropOverlay(
|
|
149
|
-
ctx: CanvasRenderingContext2D,
|
|
150
|
-
cropArea: CropArea,
|
|
151
|
-
config: CropConfig
|
|
152
|
-
): void {
|
|
153
|
-
const { x, y, width, height } = cropArea;
|
|
154
|
-
|
|
155
|
-
// Draw semi-transparent overlay outside crop area
|
|
156
|
-
ctx.save();
|
|
157
|
-
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
|
158
|
-
|
|
159
|
-
// Top overlay
|
|
160
|
-
ctx.fillRect(0, 0, ctx.canvas.width, y);
|
|
161
|
-
// Bottom overlay
|
|
162
|
-
ctx.fillRect(0, y + height, ctx.canvas.width, ctx.canvas.height - y - height);
|
|
163
|
-
// Left overlay
|
|
164
|
-
ctx.fillRect(0, y, x, height);
|
|
165
|
-
// Right overlay
|
|
166
|
-
ctx.fillRect(x + width, y, ctx.canvas.width - x - width, height);
|
|
167
|
-
|
|
168
|
-
// Draw crop area border
|
|
169
|
-
ctx.strokeStyle = '#ffffff';
|
|
170
|
-
ctx.lineWidth = 2;
|
|
171
|
-
ctx.setLineDash([5, 5]);
|
|
172
|
-
ctx.strokeRect(x, y, width, height);
|
|
173
|
-
|
|
174
|
-
// Draw corner handles
|
|
175
|
-
ctx.fillStyle = '#ffffff';
|
|
176
|
-
const handleSize = 8;
|
|
177
|
-
ctx.fillRect(x - handleSize/2, y - handleSize/2, handleSize, handleSize);
|
|
178
|
-
ctx.fillRect(x + width - handleSize/2, y - handleSize/2, handleSize, handleSize);
|
|
179
|
-
ctx.fillRect(x - handleSize/2, y + height - handleSize/2, handleSize, handleSize);
|
|
180
|
-
ctx.fillRect(x + width - handleSize/2, y + height - handleSize/2, handleSize, handleSize);
|
|
181
|
-
|
|
182
|
-
ctx.setLineDash([]);
|
|
183
|
-
ctx.restore();
|
|
184
|
-
|
|
185
|
-
// Draw grid if enabled
|
|
186
|
-
if (config.showGrid) {
|
|
187
|
-
this.drawGrid(ctx, cropArea);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
private static drawGrid(
|
|
192
|
-
ctx: CanvasRenderingContext2D,
|
|
193
|
-
cropArea: CropArea
|
|
194
|
-
): void {
|
|
195
|
-
const { x, y, width, height } = cropArea;
|
|
196
|
-
|
|
197
|
-
ctx.save();
|
|
198
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
199
|
-
ctx.lineWidth = 1;
|
|
200
|
-
ctx.setLineDash([2, 3]);
|
|
201
|
-
|
|
202
|
-
// Third lines
|
|
203
|
-
ctx.beginPath();
|
|
204
|
-
ctx.moveTo(x + width/3, y);
|
|
205
|
-
ctx.lineTo(x + width/3, y + height);
|
|
206
|
-
ctx.moveTo(x + 2*width/3, y);
|
|
207
|
-
ctx.lineTo(x + 2*width/3, y + height);
|
|
208
|
-
ctx.moveTo(x, y + height/3);
|
|
209
|
-
ctx.lineTo(x + width, y + height/3);
|
|
210
|
-
ctx.moveTo(x, y + 2*height/3);
|
|
211
|
-
ctx.lineTo(x + width, y + 2*height/3);
|
|
212
|
-
ctx.stroke();
|
|
213
|
-
|
|
214
|
-
// Center lines
|
|
215
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
|
|
216
|
-
ctx.setLineDash([5, 2]);
|
|
217
|
-
ctx.beginPath();
|
|
218
|
-
ctx.moveTo(x + width/2, y);
|
|
219
|
-
ctx.lineTo(x + width/2, y + height);
|
|
220
|
-
ctx.moveTo(x, y + height/2);
|
|
221
|
-
ctx.lineTo(x + width, y + height/2);
|
|
222
|
-
ctx.stroke();
|
|
223
|
-
|
|
224
|
-
ctx.restore();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
static getCropFromSelection(
|
|
228
|
-
startPoint: { x: number; y: number },
|
|
229
|
-
endPoint: { x: number; y: number },
|
|
230
|
-
config: CropConfig
|
|
231
|
-
): CropArea {
|
|
232
|
-
const x = Math.min(startPoint.x, endPoint.x);
|
|
233
|
-
const y = Math.min(startPoint.y, endPoint.y);
|
|
234
|
-
const width = Math.abs(endPoint.x - startPoint.x);
|
|
235
|
-
const height = Math.abs(endPoint.y - startPoint.y);
|
|
236
|
-
|
|
237
|
-
let cropArea = { x, y, width, height };
|
|
238
|
-
|
|
239
|
-
// Apply aspect ratio constraint if locked
|
|
240
|
-
if (config.lockAspectRatio && config.aspectRatio) {
|
|
241
|
-
const constrained = this.constrainToAspectRatio(width, height, config.aspectRatio);
|
|
242
|
-
cropArea = {
|
|
243
|
-
x,
|
|
244
|
-
y,
|
|
245
|
-
width: constrained.width,
|
|
246
|
-
height: constrained.height,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Apply size constraints
|
|
251
|
-
if (config.minSize) {
|
|
252
|
-
cropArea = this.applyMinimumSize(cropArea, config.minSize);
|
|
253
|
-
}
|
|
254
|
-
if (config.maxSize) {
|
|
255
|
-
cropArea = this.applyMaximumSize(cropArea, config.maxSize);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return cropArea;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Infrastructure - Drawing Engine
|
|
3
|
-
*
|
|
4
|
-
* Core drawing functionality for editor tools
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export type DrawingPath = Array<{ x: number; y: number; pressure?: number }>;
|
|
8
|
-
|
|
9
|
-
export interface DrawingContext {
|
|
10
|
-
canvas: HTMLCanvasElement | any;
|
|
11
|
-
ctx: CanvasRenderingContext2D | any;
|
|
12
|
-
scale: number;
|
|
13
|
-
offset: { x: number; y: number };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface DrawingConfig {
|
|
17
|
-
color: string;
|
|
18
|
-
size: number;
|
|
19
|
-
opacity: number;
|
|
20
|
-
style: 'normal' | 'marker' | 'spray';
|
|
21
|
-
smoothing: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class DrawingEngine {
|
|
25
|
-
private static smoothPath(path: DrawingPath): DrawingPath {
|
|
26
|
-
if (path.length < 3) return path;
|
|
27
|
-
|
|
28
|
-
const smoothed: DrawingPath = [path[0]];
|
|
29
|
-
|
|
30
|
-
for (let i = 1; i < path.length - 1; i++) {
|
|
31
|
-
const prev = path[i - 1];
|
|
32
|
-
const curr = path[i];
|
|
33
|
-
const next = path[i + 1];
|
|
34
|
-
|
|
35
|
-
smoothed.push({
|
|
36
|
-
x: curr.x * 0.5 + (prev.x + next.x) * 0.25,
|
|
37
|
-
y: curr.y * 0.5 + (prev.y + next.y) * 0.25,
|
|
38
|
-
pressure: curr.pressure,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
smoothed.push(path[path.length - 1]);
|
|
43
|
-
return smoothed;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private static drawMarker(
|
|
47
|
-
ctx: CanvasRenderingContext2D,
|
|
48
|
-
from: { x: number; y: number },
|
|
49
|
-
to: { x: number; y: number },
|
|
50
|
-
config: DrawingConfig
|
|
51
|
-
): void {
|
|
52
|
-
ctx.globalAlpha = config.opacity * 0.3;
|
|
53
|
-
const strokeWidth = (config as any).strokeWidth || config.size * 2;
|
|
54
|
-
ctx.lineWidth = strokeWidth;
|
|
55
|
-
|
|
56
|
-
ctx.beginPath();
|
|
57
|
-
ctx.moveTo(from.x, from.y);
|
|
58
|
-
ctx.lineTo(to.x, to.y);
|
|
59
|
-
ctx.stroke();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private static drawSpray(
|
|
63
|
-
ctx: CanvasRenderingContext2D,
|
|
64
|
-
point: { x: number; y: number },
|
|
65
|
-
config: DrawingConfig
|
|
66
|
-
): void {
|
|
67
|
-
const density = config.size * 2;
|
|
68
|
-
const radius = config.size;
|
|
69
|
-
|
|
70
|
-
for (let i = 0; i < density; i++) {
|
|
71
|
-
const angle = Math.random() * Math.PI * 2;
|
|
72
|
-
const distance = Math.random() * radius;
|
|
73
|
-
const x = point.x + Math.cos(angle) * distance;
|
|
74
|
-
const y = point.y + Math.sin(angle) * distance;
|
|
75
|
-
|
|
76
|
-
ctx.globalAlpha = config.opacity * Math.random() * 0.5;
|
|
77
|
-
ctx.fillRect(x, y, 1, 1);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
static drawStroke(
|
|
82
|
-
ctx: CanvasRenderingContext2D,
|
|
83
|
-
path: DrawingPath,
|
|
84
|
-
config: DrawingConfig
|
|
85
|
-
): void {
|
|
86
|
-
if (path.length < 2) return;
|
|
87
|
-
|
|
88
|
-
const smoothedPath = config.smoothing ? this.smoothPath(path) : path;
|
|
89
|
-
|
|
90
|
-
ctx.lineCap = 'round';
|
|
91
|
-
ctx.lineJoin = 'round';
|
|
92
|
-
ctx.strokeStyle = config.color;
|
|
93
|
-
ctx.globalAlpha = config.opacity;
|
|
94
|
-
|
|
95
|
-
switch (config.style) {
|
|
96
|
-
case 'marker':
|
|
97
|
-
this.drawMarker(ctx, smoothedPath[0], smoothedPath[smoothedPath.length - 1], config);
|
|
98
|
-
break;
|
|
99
|
-
|
|
100
|
-
case 'spray':
|
|
101
|
-
smoothedPath.forEach(point => this.drawSpray(ctx, point, config));
|
|
102
|
-
break;
|
|
103
|
-
|
|
104
|
-
default:
|
|
105
|
-
ctx.lineWidth = config.size;
|
|
106
|
-
ctx.beginPath();
|
|
107
|
-
ctx.moveTo(smoothedPath[0].x, smoothedPath[0].y);
|
|
108
|
-
|
|
109
|
-
for (let i = 1; i < smoothedPath.length; i++) {
|
|
110
|
-
ctx.lineTo(smoothedPath[i].x, smoothedPath[i].y);
|
|
111
|
-
}
|
|
112
|
-
ctx.stroke();
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
static drawShape(
|
|
117
|
-
ctx: CanvasRenderingContext2D,
|
|
118
|
-
type: 'rectangle' | 'circle' | 'line' | 'arrow',
|
|
119
|
-
start: { x: number; y: number },
|
|
120
|
-
end: { x: number; y: number },
|
|
121
|
-
config: Partial<DrawingConfig>
|
|
122
|
-
): void {
|
|
123
|
-
const cfg = config as any;
|
|
124
|
-
const color = cfg.color || '#000000';
|
|
125
|
-
const size = cfg.size || 2;
|
|
126
|
-
const opacity = cfg.opacity || 1;
|
|
127
|
-
const strokeWidth = cfg.strokeWidth || 2;
|
|
128
|
-
|
|
129
|
-
ctx.strokeStyle = color;
|
|
130
|
-
ctx.fillStyle = color;
|
|
131
|
-
ctx.lineWidth = strokeWidth;
|
|
132
|
-
ctx.globalAlpha = opacity;
|
|
133
|
-
ctx.lineCap = 'round';
|
|
134
|
-
|
|
135
|
-
switch (type) {
|
|
136
|
-
case 'rectangle':
|
|
137
|
-
const width = end.x - start.x;
|
|
138
|
-
const height = end.y - start.y;
|
|
139
|
-
ctx.strokeRect(start.x, start.y, width, height);
|
|
140
|
-
break;
|
|
141
|
-
|
|
142
|
-
case 'circle':
|
|
143
|
-
const radius = Math.sqrt(
|
|
144
|
-
Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2)
|
|
145
|
-
);
|
|
146
|
-
ctx.beginPath();
|
|
147
|
-
ctx.arc(start.x, start.y, radius, 0, 2 * Math.PI);
|
|
148
|
-
ctx.stroke();
|
|
149
|
-
break;
|
|
150
|
-
|
|
151
|
-
case 'line':
|
|
152
|
-
ctx.beginPath();
|
|
153
|
-
ctx.moveTo(start.x, start.y);
|
|
154
|
-
ctx.lineTo(end.x, end.y);
|
|
155
|
-
ctx.stroke();
|
|
156
|
-
break;
|
|
157
|
-
|
|
158
|
-
case 'arrow':
|
|
159
|
-
const angle = Math.atan2(end.y - start.y, end.x - start.x);
|
|
160
|
-
const arrowLength = 15;
|
|
161
|
-
const arrowAngle = Math.PI / 6;
|
|
162
|
-
|
|
163
|
-
// Draw line
|
|
164
|
-
ctx.beginPath();
|
|
165
|
-
ctx.moveTo(start.x, start.y);
|
|
166
|
-
ctx.lineTo(end.x, end.y);
|
|
167
|
-
ctx.stroke();
|
|
168
|
-
|
|
169
|
-
// Draw arrowhead
|
|
170
|
-
ctx.beginPath();
|
|
171
|
-
ctx.moveTo(end.x, end.y);
|
|
172
|
-
ctx.lineTo(
|
|
173
|
-
end.x - arrowLength * Math.cos(angle - arrowAngle),
|
|
174
|
-
end.y - arrowLength * Math.sin(angle - arrowAngle)
|
|
175
|
-
);
|
|
176
|
-
ctx.moveTo(end.x, end.y);
|
|
177
|
-
ctx.lineTo(
|
|
178
|
-
end.x - arrowLength * Math.cos(angle + arrowAngle),
|
|
179
|
-
end.y - arrowLength * Math.sin(angle + arrowAngle)
|
|
180
|
-
);
|
|
181
|
-
ctx.stroke();
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
static erase(
|
|
187
|
-
ctx: CanvasRenderingContext2D,
|
|
188
|
-
position: { x: number; y: number },
|
|
189
|
-
size: number
|
|
190
|
-
): void {
|
|
191
|
-
ctx.globalCompositeOperation = 'destination-out';
|
|
192
|
-
ctx.beginPath();
|
|
193
|
-
ctx.arc(position.x, position.y, size, 0, 2 * Math.PI);
|
|
194
|
-
ctx.fill();
|
|
195
|
-
ctx.globalCompositeOperation = 'source-over';
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
static createDrawingContext(
|
|
199
|
-
canvas: HTMLCanvasElement | any,
|
|
200
|
-
scale: number = 1,
|
|
201
|
-
offset: { x: number; y: number } = { x: 0, y: 0 }
|
|
202
|
-
): DrawingContext {
|
|
203
|
-
return {
|
|
204
|
-
canvas,
|
|
205
|
-
ctx: canvas.getContext('2d'),
|
|
206
|
-
scale,
|
|
207
|
-
offset,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,168 +0,0 @@
|
|
|
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
|
-
}
|