mirror of https://github.com/vladmandic/human
129 lines
5.0 KiB
TypeScript
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();
|
|
}
|