Trudy's picture
Initial commit with proper LFS tracking
5f07a23
// 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;
}
}
};