File size: 6,986 Bytes
5f07a23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// Get the correct coordinates based on canvas scaling
export const getCoordinates = (e, canvas) => {
  const rect = canvas.getBoundingClientRect();
  
  // Calculate the scaling factor between the internal canvas size and displayed size
  const scaleX = canvas.width / rect.width;
  const scaleY = canvas.height / rect.height;
  
  // Apply the scaling to get accurate coordinates
  return {
    x: (e.nativeEvent.offsetX || (e.nativeEvent.touches?.[0]?.clientX - rect.left)) * scaleX,
    y: (e.nativeEvent.offsetY || (e.nativeEvent.touches?.[0]?.clientY - rect.top)) * scaleY
  };
};

// Initialize canvas with white background
export const initializeCanvas = (canvas) => {
  const ctx = canvas.getContext("2d");
  
  // Fill canvas with white background
  ctx.fillStyle = "#FFFFFF";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
};

// Draw the background image to the canvas
export const drawImageToCanvas = (canvas, backgroundImage) => {
  if (!canvas || !backgroundImage) return;
  
  const ctx = canvas.getContext("2d");
  
  // Fill with white background first
  ctx.fillStyle = "#FFFFFF";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  
  // Draw the background image
  ctx.drawImage(
    backgroundImage,
    0, 0,
    canvas.width, canvas.height
  );
};

// Draw bezier curve
export const drawBezierCurve = (canvas, points) => {
  const ctx = canvas.getContext('2d');
  
  if (!points || points.length < 2) {
    console.error('Need at least 2 points to draw a path');
    return;
  }
  
  ctx.beginPath();
  ctx.strokeStyle = '#000000';
  ctx.lineWidth = 4;
  
  // Start at the first anchor point
  ctx.moveTo(points[0].x, points[0].y);
  
  // For each pair of anchor points (and their control points)
  for (let i = 0; i < points.length - 1; i++) {
    const current = points[i];
    const next = points[i + 1];
    
    if (current.handleOut && next.handleIn) {
      // If both points have handles, draw a cubic bezier
      ctx.bezierCurveTo(
        current.x + (current.handleOut?.x || 0), current.y + (current.handleOut?.y || 0),
        next.x + (next.handleIn?.x || 0), next.y + (next.handleIn?.y || 0),
        next.x, next.y
      );
    } else {
      // If no handles, draw a straight line
      ctx.lineTo(next.x, next.y);
    }
  }
  
  ctx.stroke();
};

// Draw bezier guides (control points and lines)
export const drawBezierGuides = (ctx, points) => {
  if (!points || points.length === 0) return;
  
  // Draw the path itself first (as a light preview)
  ctx.save();
  ctx.globalAlpha = 0.3;
  ctx.strokeStyle = '#888888';
  ctx.lineWidth = 1.5;
  
  ctx.beginPath();
  ctx.moveTo(points[0].x, points[0].y);
  
  // For each pair of anchor points (and their control points)
  for (let i = 0; i < points.length - 1; i++) {
    const current = points[i];
    const next = points[i + 1];
    
    if (current.handleOut && next.handleIn) {
      // If both points have handles, draw a cubic bezier
      ctx.bezierCurveTo(
        current.x + (current.handleOut?.x || 0), current.y + (current.handleOut?.y || 0),
        next.x + (next.handleIn?.x || 0), next.y + (next.handleIn?.y || 0),
        next.x, next.y
      );
    } else {
      // If no handles, draw a straight line
      ctx.lineTo(next.x, next.y);
    }
  }
  
  ctx.stroke();
  ctx.restore();
  
  // Draw guide lines between anchor points and their handles
  ctx.strokeStyle = 'rgba(100, 100, 255, 0.5)';
  ctx.lineWidth = 1;
  
  for (const point of points) {
    // Draw line from anchor to in-handle if it exists
    if (point.handleIn) {
      ctx.beginPath();
      ctx.moveTo(point.x, point.y);
      ctx.lineTo(point.x + point.handleIn.x, point.y + point.handleIn.y);
      ctx.stroke();
    }
    
    // Draw line from anchor to out-handle if it exists
    if (point.handleOut) {
      ctx.beginPath();
      ctx.moveTo(point.x, point.y);
      ctx.lineTo(point.x + point.handleOut.x, point.y + point.handleOut.y);
      ctx.stroke();
    }
  }
  
  // Draw anchor points (main points of the path)
  for (const point of points) {
    // Draw the main anchor point
    ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
    ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
    ctx.lineWidth = 1;
    
    ctx.beginPath();
    ctx.arc(point.x, point.y, 5, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();
    
    // Draw the handle points if they exist
    if (point.handleIn) {
      ctx.fillStyle = 'rgba(100, 100, 255, 0.8)';
      ctx.beginPath();
      ctx.arc(point.x + point.handleIn.x, point.y + point.handleIn.y, 4, 0, Math.PI * 2);
      ctx.fill();
    }
    
    if (point.handleOut) {
      ctx.fillStyle = 'rgba(100, 100, 255, 0.8)';
      ctx.beginPath();
      ctx.arc(point.x + point.handleOut.x, point.y + point.handleOut.y, 4, 0, Math.PI * 2);
      ctx.fill();
    }
  }
};

// Helper to create a new anchor point with handles
export const createAnchorPoint = (x, y, prevPoint = null) => {
  // By default, create a point with no handles
  const point = { x, y, handleIn: null, handleOut: null };
  
  // If there's a previous point, automatically add symmetric handles
  if (prevPoint) {
    // Calculate the default handle length (as a percentage of distance to previous point)
    const dx = x - prevPoint.x;
    const dy = y - prevPoint.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    const handleLength = distance * 0.3; // 30% of distance between points
    
    // Create handles perpendicular to the line between points
    // For a smooth curve, make the previous point's out handle opposite to this point's in handle
    const angle = Math.atan2(dy, dx);
    
    // Add an out handle to the previous point (if it doesn't already have one)
    if (!prevPoint.handleOut) {
      prevPoint.handleOut = {
        x: Math.cos(angle) * -handleLength,
        y: Math.sin(angle) * -handleLength
      };
    }
    
    // Add an in handle to the current point
    point.handleIn = {
      x: Math.cos(angle) * -handleLength,
      y: Math.sin(angle) * -handleLength
    };
  }
  
  return point;
};

// Helper to check if a point is near a handle
export const isNearHandle = (point, handleType, x, y, radius = 10) => {
  if (!point || !point[handleType]) return false;
  
  const handleX = point.x + point[handleType].x;
  const handleY = point.y + point[handleType].y;
  
  const dx = handleX - x;
  const dy = handleY - y;
  
  return (dx * dx + dy * dy) <= radius * radius;
};

// Helper to update a handle position
export const updateHandle = (point, handleType, dx, dy, symmetric = true) => {
  if (!point || !point[handleType]) return;
  
  // Update the target handle
  point[handleType].x += dx;
  point[handleType].y += dy;
  
  // If symmetric and the other handle exists, update it to be symmetrical
  if (symmetric) {
    const otherType = handleType === 'handleIn' ? 'handleOut' : 'handleIn';
    
    if (point[otherType]) {
      point[otherType].x = -point[handleType].x;
      point[otherType].y = -point[handleType].y;
    }
  }
};