Files
EpicNext-Cms/src/lib/services/clone-icon.ts
T
openhands df38dccbf1
Local Build and Deploy / deploy (push) Failing after 46s
style: format code biome
2026-07-13 21:57:41 +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;
}
}