// 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; } } };