import fs from 'fs' import path from 'path' import { trace } from 'potrace' import sharp from 'sharp' const assetsDir = path.resolve(process.cwd(), 'src', 'assets') const outJson = path.resolve(process.cwd(), 'src', 'data', 'slotClipPaths.json') // theme slot definitions to inspect (matching stripThemes.js layout) const themesToTrace = { pink: { file: '1.png', slots: [ { x:80,y:160,width:440,height:290 }, { x:80,y:615,width:440,height:290 }, { x:80,y:1070,width:440,height:290 } ] }, ticket: { file: '8.png', slots: [ { x:80,y:160,width:440,height:290 }, { x:80,y:615,width:440,height:290 }, { x:80,y:1070,width:440,height:290 } ] }, music: { file: '9.png', slots: [ { x:78,y:50,width:444,height:333 }, { x:78,y:545,width:444,height:333 }, { x:78,y:1040,width:444,height:333 } ] }, bowtie: { file: '12.png', slots: [ { x:77,y:160,width:380,height:271 }, { x:77,y:549,width:380,height:271 }, { x:77,y:939,width:380,height:271 } ] }, } async function traceSlot(themeId, fileName, slot, index, options) { const src = path.join(assetsDir, fileName) const tmp = path.join(process.cwd(), 'tmp', `${themeId}-slot-${index}.png`) await fs.promises.mkdir(path.dirname(tmp), { recursive: true }) // crop the slot area with a small padding to capture edges const pad = 2 await sharp(src) .extract({ left: Math.max(0, slot.x - pad), top: Math.max(0, slot.y - pad), width: slot.width + pad*2, height: slot.height + pad*2 }) .resize(slot.width, slot.height) .toFile(tmp) const traceOptions = Object.assign({ background: 'transparent' }, options || {}) return new Promise((resolve, reject) => { trace(tmp, traceOptions, (err, svg) => { if (err) return reject(err) // extract path d attribute const m = svg.match(/]*d="([^"]+)"/) const d = m ? m[1] : null resolve(d) }) }) } async function main() { const out = {} // parse CLI args for tuning options: --turdSize=20 --threshold=128 --optTolerance=0.4 const argv = process.argv.slice(2) const cli = {} for (const a of argv) { const parts = a.replace(/^--/, '').split('=') cli[parts[0]] = parts[1] === undefined ? true : parts[1] } const options = {} if (cli.turdSize) options.turdSize = parseInt(cli.turdSize, 10) if (cli.threshold) options.threshold = parseInt(cli.threshold, 10) if (cli.optTolerance) options.optTolerance = parseFloat(cli.optTolerance) if (cli.turnPolicy) options.turnPolicy = cli.turnPolicy if (cli.background) options.background = cli.background console.log('Tracing with options:', options) for (const [themeId, info] of Object.entries(themesToTrace)) { const paths = [] for (let i = 0; i < info.slots.length; i++) { try { process.stdout.write(`Tracing ${themeId} slot ${i}... `) const d = await traceSlot(themeId, info.file, info.slots[i], i, options) paths.push(d) console.log('ok') } catch (err) { console.error('failed', err.message) paths.push(null) } } out[themeId] = paths } await fs.promises.writeFile(outJson, JSON.stringify(out, null, 2), 'utf8') console.log('Wrote', outJson) } main().catch(err => { console.error(err); process.exit(1) })