104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
import { Point, WasmBezierInstance } from "@/utils/types";
|
|
|
|
const HANDLE_RADIUS_FACTOR = 2 / 3;
|
|
const DEFAULT_ENDPOINT_RADIUS = 5;
|
|
|
|
export const COLORS = {
|
|
CANVAS: "white",
|
|
INTERACTIVE: {
|
|
STROKE_1: "black",
|
|
STROKE_2: "grey",
|
|
SELECTED: "blue",
|
|
},
|
|
NON_INTERACTIVE: {
|
|
STROKE_1: "red",
|
|
STROKE_2: "orange",
|
|
},
|
|
};
|
|
|
|
export const getPointSizeByIndex = (index: number, numPoints: number, radius = DEFAULT_ENDPOINT_RADIUS): number => (index === 0 || index === numPoints - 1 ? radius : radius * HANDLE_RADIUS_FACTOR);
|
|
|
|
export const getContextFromCanvas = (canvas: HTMLCanvasElement): CanvasRenderingContext2D => {
|
|
const ctx = canvas.getContext("2d");
|
|
if (ctx === null) {
|
|
throw Error("Failed to fetch context");
|
|
}
|
|
return ctx;
|
|
};
|
|
|
|
export const drawLine = (ctx: CanvasRenderingContext2D, point1: Point, point2: Point, strokeColor = COLORS.INTERACTIVE.STROKE_2): void => {
|
|
ctx.strokeStyle = strokeColor;
|
|
ctx.lineWidth = 1;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(point1.x, point1.y);
|
|
ctx.lineTo(point2.x, point2.y);
|
|
ctx.stroke();
|
|
};
|
|
|
|
export const drawPoint = (ctx: CanvasRenderingContext2D, point: Point, radius: number, strokeColor = COLORS.INTERACTIVE.STROKE_1): void => {
|
|
// Outline the point
|
|
ctx.strokeStyle = strokeColor;
|
|
ctx.lineWidth = radius / 3;
|
|
ctx.beginPath();
|
|
ctx.arc(point.x, point.y, radius, 0, 2 * Math.PI, false);
|
|
ctx.stroke();
|
|
|
|
// Fill the point (hiding any overlapping lines)
|
|
ctx.fillStyle = COLORS.CANVAS;
|
|
ctx.beginPath();
|
|
ctx.arc(point.x, point.y, radius * HANDLE_RADIUS_FACTOR, 0, 2 * Math.PI, false);
|
|
ctx.fill();
|
|
};
|
|
|
|
export const drawText = (ctx: CanvasRenderingContext2D, text: string, x: number, y: number, textColor = COLORS.INTERACTIVE.STROKE_1): void => {
|
|
ctx.fillStyle = textColor;
|
|
ctx.font = "16px Arial";
|
|
ctx.fillText(text, x, y);
|
|
};
|
|
|
|
export const drawBezierHelper = (ctx: CanvasRenderingContext2D, bezier: WasmBezierInstance, strokeColor = COLORS.INTERACTIVE.STROKE_1, radius = DEFAULT_ENDPOINT_RADIUS): void => {
|
|
const points = bezier.get_points().map((p: string) => JSON.parse(p));
|
|
drawBezier(ctx, points, null, strokeColor, radius);
|
|
};
|
|
|
|
export const drawBezier = (ctx: CanvasRenderingContext2D, points: Point[], dragIndex: number | null = null, strokeColor = COLORS.INTERACTIVE.STROKE_1, radius = DEFAULT_ENDPOINT_RADIUS): void => {
|
|
// Points passed to drawBezier are interpreted as follows
|
|
// points[0] = start point
|
|
// points[1] = handle start
|
|
// points[2] = (optional) handle end
|
|
// points[3] = end point
|
|
const start = points[0];
|
|
let end = null;
|
|
let handleStart = null;
|
|
let handleEnd = null;
|
|
if (points.length === 4) {
|
|
handleStart = points[1];
|
|
handleEnd = points[2];
|
|
end = points[3];
|
|
} else {
|
|
handleStart = points[1];
|
|
handleEnd = handleStart;
|
|
end = points[2];
|
|
}
|
|
|
|
ctx.strokeStyle = strokeColor;
|
|
ctx.lineWidth = 2;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(points[0].x, points[0].y);
|
|
if (points.length === 3) {
|
|
ctx.quadraticCurveTo(handleStart.x, handleStart.y, end.x, end.y);
|
|
} else {
|
|
ctx.bezierCurveTo(handleStart.x, handleStart.y, handleEnd.x, handleEnd.y, end.x, end.y);
|
|
}
|
|
ctx.stroke();
|
|
|
|
drawLine(ctx, start, handleStart, strokeColor);
|
|
drawLine(ctx, end, handleEnd, strokeColor);
|
|
|
|
points.forEach((point, index) => {
|
|
drawPoint(ctx, point, getPointSizeByIndex(index, points.length, radius), index === dragIndex ? COLORS.INTERACTIVE.SELECTED : strokeColor);
|
|
});
|
|
};
|