123 lines
4.2 KiB
TypeScript
123 lines
4.2 KiB
TypeScript
import { BezierStyleConfig, 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 isIndexFirstOrLast = (index: number, arrayLength: number): boolean => index === 0 || index === arrayLength - 1;
|
|
|
|
export const getPointSizeByIndex = (index: number, numPoints: number, radius = DEFAULT_ENDPOINT_RADIUS): number => (isIndexFirstOrLast(index, numPoints) ? 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, bezierStyleConfig: Partial<BezierStyleConfig> = {}): void => {
|
|
const points = bezier.get_points().map((p: string) => JSON.parse(p));
|
|
drawBezier(ctx, points, null, bezierStyleConfig);
|
|
};
|
|
|
|
export const drawBezier = (ctx: CanvasRenderingContext2D, points: Point[], dragIndex: number | null = null, bezierStyleConfig: Partial<BezierStyleConfig> = {}): void => {
|
|
const styleConfig: BezierStyleConfig = {
|
|
curveStrokeColor: COLORS.INTERACTIVE.STROKE_1,
|
|
handleStrokeColor: COLORS.INTERACTIVE.STROKE_1,
|
|
handleLineStrokeColor: COLORS.INTERACTIVE.STROKE_1,
|
|
radius: DEFAULT_ENDPOINT_RADIUS,
|
|
...bezierStyleConfig,
|
|
};
|
|
// if the handle or handle line colors are not specified, use the same colour as the rest of the curve
|
|
if (bezierStyleConfig.curveStrokeColor) {
|
|
if (!bezierStyleConfig.handleStrokeColor) {
|
|
styleConfig.handleStrokeColor = bezierStyleConfig.curveStrokeColor;
|
|
}
|
|
if (!bezierStyleConfig.handleLineStrokeColor) {
|
|
styleConfig.handleLineStrokeColor = bezierStyleConfig.curveStrokeColor;
|
|
}
|
|
}
|
|
// 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 = styleConfig.curveStrokeColor;
|
|
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, styleConfig.handleLineStrokeColor);
|
|
drawLine(ctx, end, handleEnd, styleConfig.handleLineStrokeColor);
|
|
|
|
points.forEach((point, index) => {
|
|
const strokeColor = isIndexFirstOrLast(index, points.length) ? styleConfig.curveStrokeColor : styleConfig.handleStrokeColor;
|
|
drawPoint(ctx, point, getPointSizeByIndex(index, points.length, styleConfig.radius), index === dragIndex ? COLORS.INTERACTIVE.SELECTED : strokeColor);
|
|
});
|
|
};
|