Spaces:
Running
Running
// 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; | |
} | |
} | |
}; |