2026-03-17 12:19:15 +08:00
|
|
|
import { ImageTracer, Options } from "@image-tracer-ts/core";
|
2026-03-16 09:29:32 +08:00
|
|
|
//@ts-ignore
|
|
|
|
|
import {SVGLoader, SVGResult} from "three/examples/jsm/loaders/SVGLoader";
|
|
|
|
|
import {Color, ShapePath, Shape} from "three";
|
2026-03-15 18:45:20 +08:00
|
|
|
|
|
|
|
|
export interface TracedLayer {
|
|
|
|
|
id: string;
|
|
|
|
|
name: string;
|
2026-03-16 09:29:32 +08:00
|
|
|
color: Color;
|
|
|
|
|
paths: Shape[];
|
2026-03-15 18:45:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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> = {
|
2026-03-16 09:34:15 +08:00
|
|
|
...Options.Presets.default,
|
2026-03-15 18:45:20 +08:00
|
|
|
numberOfColors: 8, // 限制颜色数量以控制图层数
|
2026-03-16 09:02:35 +08:00
|
|
|
minColorQuota: 0.01, // 降低最小颜色占比阈值
|
2026-03-15 18:45:20 +08:00
|
|
|
strokeWidth: 0, // 不需要描边
|
2026-03-16 09:02:35 +08:00
|
|
|
lineFilter: true,
|
2026-03-15 18:45:20 +08:00
|
|
|
...options,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 创建追踪器
|
|
|
|
|
const tracer = new ImageTracer(defaultOptions);
|
|
|
|
|
|
|
|
|
|
// 执行追踪,返回 SVG 字符串
|
|
|
|
|
const svgString = tracer.traceImage(imageData);
|
|
|
|
|
|
|
|
|
|
// 解析 SVG 字符串
|
2026-03-16 09:29:32 +08:00
|
|
|
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),
|
|
|
|
|
};
|
|
|
|
|
});
|
2026-03-15 18:45:20 +08:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
width: canvas.width,
|
|
|
|
|
height: canvas.height,
|
|
|
|
|
layers,
|
|
|
|
|
};
|
2026-03-16 09:29:32 +08:00
|
|
|
}
|