ttrpg-tools/src/components/utils/image-tracer.ts

92 lines
2.2 KiB
TypeScript

import { ImageTracer, Options } from "@image-tracer-ts/core";
//@ts-ignore
import {SVGLoader, SVGResult} from "three/examples/jsm/loaders/SVGLoader";
import {Color, ShapePath, Shape} from "three";
export interface TracedLayer {
id: string;
name: string;
color: Color;
paths: Shape[];
}
export interface PathData {
points: Point[];
isClosed: boolean;
}
export interface Point {
x: number;
y: number;
}
export interface TraceResult {
width: number;
height: number;
layers: TracedLayer[];
}
/**
* 将图像转换为矢量路径
* @param image - 要追踪的图片元素
* @param options - 追踪选项
* @returns 追踪结果
*/
export async function traceImage(
image: HTMLImageElement,
options?: Partial<Options>
): Promise<TraceResult> {
// 创建 canvas 来获取图片数据
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) {
throw new Error("无法创建 canvas 上下文");
}
// 设置 canvas 尺寸为图片原始尺寸
canvas.width = image.naturalWidth || image.width;
canvas.height = image.naturalHeight || image.height;
// 绘制图片到 canvas
ctx.drawImage(image, 0, 0);
// 获取图片数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 默认配置 - 使用 detailed preset 作为基础
const defaultOptions: Partial<Options> = {
...Options.Presets.default,
numberOfColors: 8, // 限制颜色数量以控制图层数
minColorQuota: 0.01, // 降低最小颜色占比阈值
strokeWidth: 0, // 不需要描边
lineFilter: true,
...options,
};
// 创建追踪器
const tracer = new ImageTracer(defaultOptions);
// 执行追踪,返回 SVG 字符串
const svgString = tracer.traceImage(imageData);
// 解析 SVG 字符串
const loader = new SVGLoader();
const result = loader.parse(svgString) as SVGResult;
const paths: ShapePath[] = result.paths;
const layers: TracedLayer[] = paths.map((path, i,) => {
return {
id: `layer-${i}`,
name: `颜色层 ${i + 1}`,
color: path.color,
paths: SVGLoader.createShapes(path),
};
});
return {
width: canvas.width,
height: canvas.height,
layers,
};
}