Files
Epicnabbo-Catalogus-Updated…/src/lib/services/clone-icon.ts
T
2026-07-12 19:12:25 +02:00

65 lines
2.0 KiB
TypeScript

import { cropRgba, decodePng } from '@/lib/services/imager/png-decode'
import { encodePng, parseNitroBundle } from '@/lib/services/swf/nitro-builder'
interface FurniAsset {
x?: number
y?: number
source?: string
}
interface FurniFrame {
frame: { x: number; y: number; w: number; h: number }
rotated?: boolean
}
interface FurniNitroJson {
name?: string
assets?: Record<string, FurniAsset>
spritesheet?: { frames?: Record<string, FurniFrame> }
}
/**
* Extract the furni catalog icon — the `{name}_icon_a` sprite — from a Nitro
* bundle and return it as a standalone PNG, or null if the bundle has no icon
* asset / usable frame. Used for sources that embed furni icons inside the
* `.nitro` instead of serving a separate `{classname}_icon.png`.
*/
export function extractFurniIconPng(nitro: Buffer): Buffer | null {
let json: FurniNitroJson
let png: Buffer
try {
const parsed = parseNitroBundle(nitro)
json = parsed.json as FurniNitroJson
png = parsed.png
} catch {
return null
}
const name = json.name
if (!name || !json.assets) return null
// The icon asset is conventionally `${name}_icon_a`; fall back to any
// `${name}_icon*` the bundle exposes.
const iconKey = json.assets[`${name}_icon_a`]
? `${name}_icon_a`
: Object.keys(json.assets).find((k) => k.startsWith(`${name}_icon`))
if (!iconKey) return null
const asset = json.assets[iconKey]
// An asset may alias another's pixels via `source`; the frame key is the
// bundle name prefixed onto the asset name. Different converters pack the
// key with or without a trailing `.png`, so try both.
const pixelAsset = asset.source ?? iconKey
const frames = json.spritesheet?.frames ?? {}
const frame = frames[`${name}_${pixelAsset}`] ?? frames[`${name}_${pixelAsset}.png`]
if (!frame || frame.rotated) return null
const { x, y, w, h } = frame.frame
if (w <= 0 || h <= 0) return null
try {
const sheet = decodePng(png)
const px = cropRgba(sheet.rgba, sheet.width, x, y, w, h)
return encodePng(w, h, px)
} catch {
return null
}
}