feat(proxy): add image tracing endpoint

Add GET /trace which fetches an image, traces it into a vector shape, and returns the result BSON-encoded. Tracing is configurable via mode (alpha, bw, color) and format (shape, svg). The shape format parses the vtracer SVG into an outline/holes polygon matching @tts/mesh's Shape interface.
This commit is contained in:
2026-08-08 14:51:36 +08:00
parent d554d6bde1
commit 445dbc1781
10 changed files with 922 additions and 0 deletions
+53
View File
@@ -5,6 +5,7 @@ import {
itemIdSchema,
searchQuerySchema,
searchResultSchema,
traceRequestSchema,
workshopItemSchema,
} from './schemas.js';
@@ -132,4 +133,56 @@ describe('searchResultSchema', () => {
const result = { items: [], page: 1, hasMore: false };
expect(searchResultSchema.parse(result)).toEqual(result);
});
});
describe('traceRequestSchema', () => {
it('defaults mode, threshold, and format', () => {
expect(traceRequestSchema.parse({ url: 'https://example.com/a.png' })).toEqual({
url: 'https://example.com/a.png',
mode: 'alpha',
threshold: 128,
format: 'shape',
});
});
it('coerces threshold and accepts optional params', () => {
expect(
traceRequestSchema.parse({
url: 'https://example.com/a.png',
mode: 'bw',
threshold: '200',
format: 'svg',
simplify: 1.5,
maxColors: '8',
}),
).toEqual({
url: 'https://example.com/a.png',
mode: 'bw',
threshold: 200,
format: 'svg',
simplify: 1.5,
maxColors: 8,
});
});
it('rejects an invalid url', () => {
expect(traceRequestSchema.safeParse({ url: 'not-a-url' }).success).toBe(
false,
);
});
it('rejects an unknown mode or format', () => {
expect(
traceRequestSchema.safeParse({
url: 'https://example.com/a.png',
mode: 'nope',
}).success,
).toBe(false);
expect(
traceRequestSchema.safeParse({
url: 'https://example.com/a.png',
format: 'nope',
}).success,
).toBe(false);
});
});