import UPNG from "upng-js";
export interface GplEntry {
name: string;
r: number;
g: number;
b: number;
a: number;
}
export interface TileGrid {
cols: number;
rows: number;
tiles: Uint8Array;
}
export function parseGpl(text: string): GplEntry[] {
const entries: GplEntry[] = [];
for (const line of text.split("\n")) {
const trimmed = line.trim();
if (
!trimmed ||
trimmed.startsWith("GIMP") ||
trimmed.startsWith("Channels") ||
trimmed.startsWith("#")
)
continue;
const match = trimmed.match(/^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/);
if (!match) continue;
entries.push({
r: parseInt(match[1]),
g: parseInt(match[2]),
b: parseInt(match[3]),
a: parseInt(match[4]),
name: match[5].trim(),
});
}
return entries;
}
export function decodeWorldMap(
palette: GplEntry[],
pngBuffer: ArrayBuffer,
tileEnum: Record<string, number>,
): TileGrid {
const enumNames = new Set(
Object.keys(tileEnum).filter((k) => isNaN(Number(k))),
);
const paletteNames = new Set(palette.map((e) => e.name));
for (const name of enumNames) {
if (!paletteNames.has(name))
throw new Error(`Enum member "${name}" has no matching palette entry`);
}
for (const name of paletteNames) {
if (!enumNames.has(name))
throw new Error(`Palette entry "${name}" has no matching enum member`);
}
const colorToTile = new Map<number, number>();
for (const entry of palette) {
const key = (entry.r << 24) | (entry.g << 16) | (entry.b << 8) | entry.a;
colorToTile.set(key, tileEnum[entry.name]);
}
const img = UPNG.decode(pngBuffer);
const rgba = new Uint8Array(UPNG.toRGBA8(img)[0]);
const area = img.width * img.height;
const tiles = new Uint8Array(area);
for (let i = 0; i < area; i++) {
const key =
(rgba[i * 4] << 24) |
(rgba[i * 4 + 1] << 16) |
(rgba[i * 4 + 2] << 8) |
rgba[i * 4 + 3];
const tile = colorToTile.get(key);
if (tile === undefined)
throw new Error(
`Pixel at index ${i} has no matching palette entry (RGBA ${rgba[i * 4]},${rgba[i * 4 + 1]},${rgba[i * 4 + 2]},${rgba[i * 4 + 3]})`,
);
tiles[i] = tile;
}
return { cols: img.width, rows: img.height, tiles };
}