|
|
|
export const getCoordinates = (e, canvas) => { |
|
const rect = canvas.getBoundingClientRect(); |
|
|
|
|
|
const scaleX = canvas.width / rect.width; |
|
const scaleY = canvas.height / rect.height; |
|
|
|
|
|
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 |
|
}; |
|
}; |
|
|
|
|
|
export const initializeCanvas = (canvas) => { |
|
const ctx = canvas.getContext("2d"); |
|
|
|
|
|
ctx.fillStyle = "#FFFFFF"; |
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
}; |
|
|
|
|
|
export const drawImageToCanvas = (canvas, backgroundImage) => { |
|
if (!canvas || !backgroundImage) return; |
|
|
|
const ctx = canvas.getContext("2d"); |
|
|
|
|
|
ctx.fillStyle = "#FFFFFF"; |
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
ctx.drawImage( |
|
backgroundImage, |
|
0, 0, |
|
canvas.width, canvas.height |
|
); |
|
}; |
|
|
|
|
|
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; |
|
|
|
|
|
ctx.moveTo(points[0].x, points[0].y); |
|
|
|
|
|
for (let i = 0; i < points.length - 1; i++) { |
|
const current = points[i]; |
|
const next = points[i + 1]; |
|
|
|
if (current.handleOut && next.handleIn) { |
|
|
|
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 { |
|
|
|
ctx.lineTo(next.x, next.y); |
|
} |
|
} |
|
|
|
ctx.stroke(); |
|
}; |
|
|
|
|
|
export const drawBezierGuides = (ctx, points) => { |
|
if (!points || points.length === 0) return; |
|
|
|
|
|
ctx.save(); |
|
ctx.globalAlpha = 0.3; |
|
ctx.strokeStyle = '#888888'; |
|
ctx.lineWidth = 1.5; |
|
|
|
ctx.beginPath(); |
|
ctx.moveTo(points[0].x, points[0].y); |
|
|
|
|
|
for (let i = 0; i < points.length - 1; i++) { |
|
const current = points[i]; |
|
const next = points[i + 1]; |
|
|
|
if (current.handleOut && next.handleIn) { |
|
|
|
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 { |
|
|
|
ctx.lineTo(next.x, next.y); |
|
} |
|
} |
|
|
|
ctx.stroke(); |
|
ctx.restore(); |
|
|
|
|
|
ctx.strokeStyle = 'rgba(100, 100, 255, 0.5)'; |
|
ctx.lineWidth = 1; |
|
|
|
for (const point of points) { |
|
|
|
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(); |
|
} |
|
|
|
|
|
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(); |
|
} |
|
} |
|
|
|
|
|
for (const point of points) { |
|
|
|
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(); |
|
|
|
|
|
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(); |
|
} |
|
} |
|
}; |
|
|
|
|
|
export const createAnchorPoint = (x, y, prevPoint = null) => { |
|
|
|
const point = { x, y, handleIn: null, handleOut: null }; |
|
|
|
|
|
if (prevPoint) { |
|
|
|
const dx = x - prevPoint.x; |
|
const dy = y - prevPoint.y; |
|
const distance = Math.sqrt(dx * dx + dy * dy); |
|
const handleLength = distance * 0.3; |
|
|
|
|
|
|
|
const angle = Math.atan2(dy, dx); |
|
|
|
|
|
if (!prevPoint.handleOut) { |
|
prevPoint.handleOut = { |
|
x: Math.cos(angle) * -handleLength, |
|
y: Math.sin(angle) * -handleLength |
|
}; |
|
} |
|
|
|
|
|
point.handleIn = { |
|
x: Math.cos(angle) * -handleLength, |
|
y: Math.sin(angle) * -handleLength |
|
}; |
|
} |
|
|
|
return point; |
|
}; |
|
|
|
|
|
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; |
|
}; |
|
|
|
|
|
export const updateHandle = (point, handleType, dx, dy, symmetric = true) => { |
|
if (!point || !point[handleType]) return; |
|
|
|
|
|
point[handleType].x += dx; |
|
point[handleType].y += dy; |
|
|
|
|
|
if (symmetric) { |
|
const otherType = handleType === 'handleIn' ? 'handleOut' : 'handleIn'; |
|
|
|
if (point[otherType]) { |
|
point[otherType].x = -point[handleType].x; |
|
point[otherType].y = -point[handleType].y; |
|
} |
|
} |
|
}; |