79 lines
3.1 KiB
JavaScript
79 lines
3.1 KiB
JavaScript
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(/<path[^>]*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) })
|