human/src/draw/primitives.ts

129 lines
5.0 KiB
TypeScript

import { log } from '../util/util';
import type { AnyCanvas } from '../exports';
import type { Point } from '../result';
import type { DrawOptions } from './options';
export const getCanvasContext = (input: AnyCanvas) => {
if (!input) log('draw error: invalid canvas');
else if (!input.getContext) log('draw error: canvas context not defined');
else {
const ctx = input.getContext('2d', { willReadFrequently: true });
if (!ctx) log('draw error: cannot get canvas context');
else return ctx;
}
return null;
};
export const rad2deg = (theta: number) => Math.round((theta * 180) / Math.PI);
export const replace = (str: string, source: string, target: string | number) => str.replace(source, typeof target === 'number' ? target.toFixed(1) : target);
export const colorDepth = (z: number | undefined, opt: DrawOptions): string => { // performance optimization needed
if (!opt.useDepth || typeof z === 'undefined') return opt.color;
const rgb = Uint8ClampedArray.from([127 + (2 * z), 127 - (2 * z), 255]);
return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${opt.alpha})`;
};
export function labels(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, str: string, startX: number, startY: number, localOptions: DrawOptions) {
const line: string[] = str.replace(/\[.*\]/g, '').split('\n').map((l) => l.trim()); // remove unmatched templates and split into array
const x = Math.max(0, startX);
for (let i = line.length - 1; i >= 0; i--) {
const y = i * localOptions.lineHeight + startY;
if (localOptions.shadowColor && localOptions.shadowColor !== '') {
ctx.fillStyle = localOptions.shadowColor;
ctx.fillText(line[i], x + 5, y + 16);
}
ctx.fillStyle = localOptions.labelColor;
ctx.fillText(line[i], x + 4, y + 15);
}
}
export function point(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, x: number, y: number, z: number | undefined, localOptions: DrawOptions) {
ctx.fillStyle = colorDepth(z, localOptions);
ctx.beginPath();
ctx.arc(x, y, localOptions.pointSize, 0, 2 * Math.PI);
ctx.fill();
}
export function rect(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, x: number, y: number, width: number, height: number, localOptions: DrawOptions) {
ctx.beginPath();
ctx.lineWidth = localOptions.lineWidth;
if (localOptions.useCurves) {
const cx = (x + x + width) / 2;
const cy = (y + y + height) / 2;
ctx.ellipse(cx, cy, width / 2, height / 2, 0, 0, 2 * Math.PI);
} else {
ctx.moveTo(x + localOptions.roundRect, y);
ctx.lineTo(x + width - localOptions.roundRect, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + localOptions.roundRect);
ctx.lineTo(x + width, y + height - localOptions.roundRect);
ctx.quadraticCurveTo(x + width, y + height, x + width - localOptions.roundRect, y + height);
ctx.lineTo(x + localOptions.roundRect, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - localOptions.roundRect);
ctx.lineTo(x, y + localOptions.roundRect);
ctx.quadraticCurveTo(x, y, x + localOptions.roundRect, y);
ctx.closePath();
}
ctx.stroke();
}
export function lines(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, points: Point[], localOptions: DrawOptions) {
if (points.length < 2) return;
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (const pt of points) {
ctx.strokeStyle = colorDepth(pt[2] || 0, localOptions);
ctx.lineTo(Math.trunc(pt[0]), Math.trunc(pt[1]));
}
ctx.stroke();
if (localOptions.fillPolygons) {
ctx.closePath();
ctx.fill();
}
}
export function curves(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, points: Point[], localOptions: DrawOptions) {
if (points.length < 2) return;
ctx.lineWidth = localOptions.lineWidth;
if (!localOptions.useCurves || points.length <= 2) {
lines(ctx, points, localOptions);
return;
}
ctx.moveTo(points[0][0], points[0][1]);
for (let i = 0; i < points.length - 2; i++) {
const xc = (points[i][0] + points[i + 1][0]) / 2;
const yc = (points[i][1] + points[i + 1][1]) / 2;
ctx.quadraticCurveTo(points[i][0], points[i][1], xc, yc);
}
ctx.quadraticCurveTo(points[points.length - 2][0], points[points.length - 2][1], points[points.length - 1][0], points[points.length - 1][1]);
ctx.stroke();
if (localOptions.fillPolygons) {
ctx.closePath();
ctx.fill();
}
}
export function arrow(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, from: Point, to: Point, radius = 5) {
let angle;
let x;
let y;
ctx.beginPath();
ctx.moveTo(from[0], from[1]);
ctx.lineTo(to[0], to[1]);
angle = Math.atan2(to[1] - from[1], to[0] - from[0]);
x = radius * Math.cos(angle) + to[0];
y = radius * Math.sin(angle) + to[1];
ctx.moveTo(x, y);
angle += (1.0 / 3.0) * (2 * Math.PI);
x = radius * Math.cos(angle) + to[0];
y = radius * Math.sin(angle) + to[1];
ctx.lineTo(x, y);
angle += (1.0 / 3.0) * (2 * Math.PI);
x = radius * Math.cos(angle) + to[0];
y = radius * Math.sin(angle) + to[1];
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke();
ctx.fill();
}