index 0d2d861b99c1a2cd9db08af0ba119e72c593003f..ee17a6015378494c258e4e8715afdea7c80ae7a8 100644 (file)
-/* Copyright (C) Doom 2D: Forever Developers\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, version 3 of the License ONLY.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- */\r
-\r
-#include <stdlib.h>\r
-#include <stdio.h>\r
-#include <stdint.h>\r
-#include <string.h>\r
-#include <unistd.h>\r
-\r
-#include "mapdef.h"\r
-\r
-#define STB_IMAGE_IMPLEMENTATION\r
-#include "stb_image.h"\r
-#define STB_IMAGE_WRITE_IMPLEMENTATION\r
-#include "stb_image_write.h"\r
-\r
-#define PATHSIZE 1024\r
-#define PANEL_ALLOC_STEP 128\r
-\r
-#ifdef _WIN32\r
-# define f_mkdir(name) mkdir(name)\r
-#else\r
-# define f_mkdir(name) mkdir(name, 0777)\r
-#endif\r
-\r
-typedef struct {\r
- int w, h, bpp;\r
- uint32_t *data;\r
-} img_t;\r
-\r
-int g_scale = 1;\r
-int g_bgcell = 512;\r
-int g_fgcell = 512;\r
-int g_nooptimize = 0;\r
-int g_streamstexture = 1; // 0=no, 1=bg, 2=fg\r
-int g_liquidstexture = 3; // 0=no, 1=bg, 2=fg, 3=yes, 4=default\r
-\r
-char g_texsec[PATHSIZE] = "TEXTURES";\r
-char g_bgsec[PATHSIZE] = "MEGA_BG";\r
-char g_fgsec[PATHSIZE] = "MEGA_FG";\r
-char g_resdir[PATHSIZE] = "RES";\r
-char g_bgname[PATHSIZE] = "_BACKGROUND";\r
-char g_fgname[PATHSIZE] = "_FOREGROUND";\r
-\r
-// indexed with PANEL_ enum\r
-const char *tex_name[] = {\r
- NULL,\r
- "WALL",\r
- NULL,\r
- NULL,\r
- "WATER",\r
- "ACID1",\r
- "ACID2",\r
- "STEP",\r
- "STREAMUP",\r
- "STREAMDN",\r
- "DOOR",\r
- "DOOR",\r
- NULL,\r
- "STREAMLT",\r
- "STREAMRT",\r
-};\r
-\r
-const uint32_t map_palette[] = {\r
- 0x00000000,\r
- 0xFFD0D0D0, // WALL\r
- 0x00000000,\r
- 0x00000000,\r
- 0xFFC00000, // WATER\r
- 0xFF00B000, // ACID1\r
- 0xFF0000B0, // ACID2\r
- 0xFF808080, // STEP\r
- 0xFF244874, // STREAMUP\r
- 0xFF607C74, // STREAMDOWN\r
- 0xFF5CDC64, // OPENDOOR\r
- 0xFF40B8D4, // DOOR\r
- 0xFFC000C0, // BLOCKMON\r
- 0xFF0450C8, // STREAMLT\r
- 0xFF388CFC, // STREAMRT\r
-};\r
-\r
-map_panel_t *map_pan = NULL;\r
-int map_numpan = 0;\r
-int map_cappan = 0;\r
-map_texture_t *map_tex = NULL;\r
-int map_numtex = 0;\r
-\r
-uint16_t map_width = 1600;\r
-uint16_t map_height = 1600;\r
-\r
-img_t img_bg = { 0 };\r
-img_t img_fg = { 0 };\r
-img_t img_map = { 0 };\r
-\r
-/* option parsing */\r
-\r
-#define OPT_NUMSEL 8\r
-\r
-typedef enum {\r
- OPT_BOOL,\r
- OPT_INT,\r
- OPT_STR,\r
- OPT_SEL,\r
-} opt_type_t;\r
-\r
-typedef struct {\r
- opt_type_t type;\r
- const char *name;\r
- const char *sel[OPT_NUMSEL];\r
- union {\r
- char *val_str;\r
- int *val_int;\r
- };\r
-} opt_t;\r
-\r
-#define DEF_OPT_BOOL(oname) \\r
- { .type = OPT_BOOL, .name = #oname, .val_int = &g_##oname }\r
-#define DEF_OPT_INT(oname) \\r
- { .type = OPT_INT, .name = #oname, .val_int = &g_##oname }\r
-#define DEF_OPT_STR(oname) \\r
- { .type = OPT_STR, .name = #oname, .val_str = g_##oname }\r
-#define DEF_OPT_SEL(oname, ...) \\r
- { .type = OPT_SEL, .name = #oname, .sel = { __VA_ARGS__ }, .val_int = &g_##oname }\r
-\r
-const opt_t opt_defs[] = {\r
- DEF_OPT_INT(scale),\r
- DEF_OPT_INT(bgcell),\r
- DEF_OPT_INT(fgcell),\r
- DEF_OPT_BOOL(nooptimize),\r
- DEF_OPT_STR(texsec),\r
- DEF_OPT_STR(bgsec),\r
- DEF_OPT_STR(fgsec),\r
- DEF_OPT_STR(resdir),\r
- DEF_OPT_SEL(streamstexture, "no", "bg", "fg"),\r
- DEF_OPT_SEL(liquidstexture, "no", "bg", "fg", "yes", "xtra"),\r
-};\r
-\r
-const size_t opt_numdefs = sizeof(opt_defs) / sizeof(opt_t);\r
-\r
-const char *opt_parse(const char *str) {\r
- if (!str || str[0] != '-' || str[1] != '-' || !str[2])\r
- return "invalid option string";\r
-\r
- str += 2;\r
-\r
- char *eq = strrchr(str, '=');\r
- if (eq) *eq = 0;\r
- const char *opts = str;\r
- const char *vals = eq ? eq + 1 : NULL;\r
-\r
- for (int i = 0; i < opt_numdefs; ++i) {\r
- if (strcmp(opts, opt_defs[i].name))\r
- continue;\r
-\r
- switch (opt_defs[i].type) {\r
- case OPT_BOOL:\r
- if (eq) return "don't need an explicit value";\r
- *opt_defs[i].val_int = 1;\r
- return NULL;\r
- case OPT_INT:\r
- if (!eq) return "need numeric value";\r
- *opt_defs[i].val_int = atoi(vals);\r
- return NULL;\r
- case OPT_STR:\r
- if (!eq) return "need string value";\r
- strncpy(opt_defs[i].val_str, vals, PATHSIZE);\r
- opt_defs[i].val_str[PATHSIZE-1] = 0;\r
- return NULL;\r
- case OPT_SEL:\r
- if (!eq) return "need string value";\r
- for (int j = 0; j < OPT_NUMSEL && opt_defs[i].sel[j]; ++j) {\r
- if (!strcmp(opt_defs[i].sel[j], vals)) {\r
- *opt_defs[i].val_int = j;\r
- return NULL;\r
- }\r
- }\r
- return "invalid variant value";\r
- default:\r
- break;\r
- }\r
- }\r
-\r
- return "unknown option";\r
-}\r
-\r
-/* -------------- */\r
-\r
-/* map writing */\r
-\r
-int map_save(const char *fname) {\r
- FILE *fout = fopen(fname, "wb");\r
- if (!fout) return -1;\r
-\r
- map_header_t hdr = { { 0 } };\r
- strcpy(hdr.name, "png2map map");\r
- strcpy(hdr.author, "png2map");\r
- strcpy(hdr.desc, "generated by png2map");\r
- hdr.music[0] = 0;\r
- hdr.sky[0] = 0;\r
- hdr.width = map_width;\r
- hdr.height = map_height;\r
-\r
- map_block_t blk = { MBLK_HEADER, 0, sizeof(hdr) };\r
-\r
- fwrite(MAP_MAGIC, 4, 1, fout);\r
- fwrite(&blk, sizeof(blk), 1, fout);\r
- fwrite(&hdr, sizeof(hdr), 1, fout);\r
-\r
- blk.type = MBLK_TEXTURES;\r
- blk.size = sizeof(map_texture_t) * map_numtex;\r
- fwrite(&blk, sizeof(blk), 1, fout);\r
- for (int i = 0; i < map_numtex; ++i)\r
- fwrite(map_tex + i, sizeof(map_texture_t), 1, fout);\r
-\r
- blk.type = MBLK_PANELS;\r
- blk.size = sizeof(map_panel_t) * map_numpan;\r
- fwrite(&blk, sizeof(blk), 1, fout);\r
- for (int i = 0; i < map_numpan; ++i)\r
- fwrite(map_pan + i, sizeof(map_panel_t), 1, fout);\r
-\r
- blk.type = MBLK_NONE;\r
- blk.size = 0;\r
- fwrite(&blk, sizeof(blk), 1, fout);\r
-\r
- return 0;\r
-}\r
-\r
-int map_cachetex(const char *name) {\r
- static char path[PATHSIZE];\r
-\r
- if (!name || !name[0])\r
- return -1;\r
-\r
- if (!strncmp(name, "_water_", 7))\r
- snprintf(path, PATHSIZE, name);\r
- else if (strchr(name, '\\'))\r
- snprintf(path, PATHSIZE, ":\\%s.PNG", name);\r
- else\r
- snprintf(path, PATHSIZE, ":\\%s\\%s.PNG", g_texsec, name);\r
-\r
- for (int i = 0; i < map_numtex; ++i)\r
- if (!strncmp(path, map_tex[i].resname, 64))\r
- return i;\r
-\r
- int i = map_numtex++;\r
- map_tex = realloc(map_tex, map_numtex * sizeof(map_texture_t));\r
- if (!map_tex) return -1;\r
-\r
- map_tex[i].anim = 0;\r
- strncpy(map_tex[i].resname, path, 64);\r
- map_tex[i].resname[63] = 0;\r
-\r
- return i;\r
-}\r
-\r
-int map_addpanel(int type, const char *tex, int x, int y, int w, int h) {\r
- if (!type) return -2;\r
-\r
- if (map_numpan >= map_cappan) {\r
- map_cappan += PANEL_ALLOC_STEP;\r
- map_pan = realloc(map_pan, map_cappan * sizeof(map_panel_t));\r
- if (!map_pan) return -1;\r
- }\r
-\r
- int texid = map_cachetex(tex);\r
- int i = map_numpan++;\r
- memset(map_pan + i, 0, sizeof(map_panel_t));\r
- map_pan[i].type = 1 << (type - 1);\r
- if (texid >= 0)\r
- map_pan[i].texid = texid;\r
- else\r
- map_pan[i].flags = PFLAG_HIDE;\r
- if (type >= PANEL_WATER && type <= PANEL_ACID2)\r
- map_pan[i].flags |= PFLAG_WATERTEXTURES;\r
- map_pan[i].x = x;\r
- map_pan[i].y = y;\r
- map_pan[i].w = w;\r
- map_pan[i].h = h;\r
-\r
- return i;\r
-}\r
-\r
-/* ----------- */\r
-\r
-/* image shit */\r
-\r
-int img_load(const char *fname, img_t *out) {\r
- out->data = (uint32_t *)stbi_load(fname, &out->w, &out->h, &out->bpp, 4);\r
- return !out->data;\r
-}\r
-\r
-void img_free(img_t *img) {\r
- free(img->data);\r
- img->data = NULL;\r
-}\r
-\r
-img_t *img_segment(img_t *src, int cell, int *out_cx, int *out_cy) {\r
- if (!src || !src->data || cell <= 0) return NULL;\r
-\r
- int cx = (src->w + cell - 1) / cell;\r
- int cy = (src->h + cell - 1) / cell;\r
- int cn = cx * cy;\r
-\r
- img_t *cells = calloc(cn, sizeof(img_t));\r
- if (!cells) return NULL;\r
- for (int i = 0; i < cn; ++i) {\r
- cells[i].w = cells[i].h = cell;\r
- cells[i].data = calloc(cell * cell, 4);\r
- if (!cells[i].data) {\r
- free(cells);\r
- return NULL;\r
- }\r
- }\r
-\r
- // this is awfully retarded, but who cares\r
- uint32_t *inp = src->data;\r
- for (int y = 0; y < src->h; ++y) {\r
- int cell_y = y / cell;\r
- for (int x = 0; x < src->w; ++x, ++inp) {\r
- int pos_y = y % cell;\r
- int pos_x = x % cell;\r
- int pos = pos_y * cell + pos_x;\r
- cells[cell_y * cx + x / cell].data[pos] = *inp;\r
- }\r
- }\r
-\r
- *out_cx = cx;\r
- *out_cy = cy;\r
- return cells;\r
-}\r
-\r
-int img_save(const char *fname, img_t *src) {\r
- static char path[PATHSIZE];\r
- if (!src || !src->data) return -1;\r
- if (!strstr(fname, ".png"))\r
- snprintf(path, sizeof(path), "%s.png", fname);\r
- else\r
- snprintf(path, sizeof(path), fname);\r
- return !stbi_write_png(path, src->w, src->h, 4, src->data, 4 * src->w);\r
-}\r
-\r
-/* ---------- */\r
-\r
-void die(const char *fmt, ...) {\r
- fprintf(stderr, "FATAL ERROR: ");\r
- va_list args;\r
- va_start(args, fmt);\r
- vfprintf(stderr, fmt, args);\r
- va_end(args);\r
- fprintf(stderr, "\n");\r
-\r
- if (img_bg.data) img_free(&img_bg);\r
- if (img_fg.data) img_free(&img_fg);\r
- if (img_map.data) img_free(&img_map);\r
- if (map_pan) free(map_pan);\r
- if (map_tex) free(map_tex);\r
-\r
- exit(1);\r
-}\r
-\r
-void tex_check(const char *resdir, int liquidsmode) {\r
- static const char *liquids[] = { "_water_0", "_water_1", "_water_2" };\r
- char path[PATHSIZE] = { 0 };\r
-\r
- if (!liquidsmode) {\r
- tex_name[PANEL_WATER] = tex_name[PANEL_ACID1] = tex_name[PANEL_ACID2] = NULL;\r
- } else if (liquidsmode == 4) {\r
- tex_name[PANEL_WATER] = liquids[0];\r
- tex_name[PANEL_ACID1] = liquids[1];\r
- tex_name[PANEL_ACID2] = liquids[2];\r
- }\r
-\r
- for (int i = 0; i < PANEL_NUMTYPES; ++i) {\r
- if (!tex_name[i]) continue;\r
- snprintf(path, PATHSIZE, "%s/%s.png", resdir, tex_name[i]);\r
- FILE *f = fopen(path, "rb");\r
- if (f) {\r
- fclose(f);\r
- } else {\r
- printf("texture `%s` missing from `%s`, using default\n", tex_name[i], resdir);\r
- if (i >= PANEL_WATER && i <= PANEL_ACID2 && liquidsmode)\r
- tex_name[i] = liquids[i - PANEL_WATER];\r
- else\r
- tex_name[i] = NULL;\r
- }\r
- }\r
-}\r
-\r
-void scronch_layer(int layer, img_t *img, int cellsize, const char *secname) {\r
- int numx = 1;\r
- int numy = 1;\r
- img_t *cells = cellsize ? img_segment(img, cellsize, &numx, &numy) : img;\r
-\r
- if (!numx || !numy || !cells)\r
- die("scronching failure");\r
-\r
- f_mkdir(secname);\r
-\r
- int pw, ph;\r
- if (cellsize) {\r
- pw = ph = cellsize;\r
- } else {\r
- pw = img->w;\r
- ph = img->h;\r
- }\r
-\r
- char path[PATHSIZE] = { 0 };\r
- for (int yc = 0; yc < numy; ++yc) {\r
- int y = yc * cellsize;\r
- for (int xc = 0; xc < numx; ++xc) {\r
- int x = xc * cellsize;\r
- int idx = yc * numx + xc;\r
- snprintf(path, PATHSIZE, "%s\\BG%03X%03X", secname, xc, yc);\r
- if (img_save(path, cells + idx))\r
- die("could not save layer cell `%s`", path);\r
- if (cells != img)\r
- img_free(cells + idx);\r
- if (map_addpanel(layer, path, x, y, pw, ph) < 0)\r
- die("could not add layer panel");\r
- }\r
- }\r
-\r
- if (cells != img)\r
- free(cells);\r
-}\r
-\r
-static inline uint32_t type_for_color(const uint32_t c) {\r
- if (!c) return 0; // empty\r
- for (int i = 0; i < PANEL_NUMTYPES; ++i)\r
- if (map_palette[i] == c)\r
- return i;\r
- return 0;\r
-}\r
-\r
-static inline int spawn_panel(uint32_t type, int x, int y, int w, int h) {\r
- const char *tex = tex_name[type];\r
-\r
- switch (type) {\r
- case PANEL_WATER:\r
- case PANEL_ACID1:\r
- case PANEL_ACID2:\r
- if (g_liquidstexture == 1 || g_liquidstexture == 2) {\r
- map_addpanel(PANEL_BACK + g_liquidstexture - 1, tex, x, y, w, h);\r
- tex = NULL;\r
- }\r
- break;\r
- case PANEL_LIFTUP:\r
- case PANEL_LIFTDOWN:\r
- case PANEL_LIFTLEFT:\r
- case PANEL_LIFTRIGHT:\r
- if (g_streamstexture == 1 || g_streamstexture == 2) {\r
- map_addpanel(PANEL_BACK + g_streamstexture - 1, tex, x, y, w, h);\r
- tex = NULL;\r
- }\r
- break;\r
- default:\r
- break;\r
- }\r
-\r
- map_addpanel(type, tex, x, y, w, h);\r
-}\r
-\r
-static inline void fill_panel_opt(const img_t *img, uint8_t *done, int x1, int y1, int scale) {\r
- #define PIXEQ(xx, yy) (img->data[(yy) * w + (xx)] == c1)\r
- #define DONE(xx, yy) (done[(yy) * w + (xx)])\r
- const uint32_t c1 = img->data[y1 * img->w + x1];\r
- const uint32_t type = type_for_color(c1);\r
- if (!type) {\r
- done[y1 * img->w + x1] = 1;\r
- return; // unknown panel or emptiness\r
- }\r
-\r
- const int w = img->w;\r
- const int h = img->h;\r
-\r
- // this ain't optimal, but it'll do\r
-\r
- // find horizontal bounds\r
- int ml = x1, mr = x1;\r
- for (; ml > 0 && !DONE(ml-1, y1) && PIXEQ(ml-1, y1); --ml);\r
- for (; mr < w-1 && !DONE(mr+1, y1) && PIXEQ(mr+1, y1); ++mr);\r
-\r
- // find min vertical bounds\r
- int mu = 0, md = h - 1;\r
- for (int x = ml; x <= mr; ++x) {\r
- int tu, td;\r
- for (tu = y1; tu > 0 && !DONE(x, tu-1) && PIXEQ(x, tu-1); --tu);\r
- for (td = y1; td < h-1 && !DONE(x, td+1) && PIXEQ(x, td+1); ++td);\r
- if (tu > mu) mu = tu;\r
- if (td < md) md = td;\r
- }\r
-\r
- // don't overlap this later\r
- for (int y = mu; y <= md; ++y)\r
- for (int x = ml; x <= mr; ++x)\r
- DONE(x, y) = 1;\r
-\r
- int pw = (mr - ml + 1);\r
- int ph = (md - mu + 1);\r
- if (spawn_panel(type, ml * scale, mu * scale, scale * pw, scale * ph) < 0)\r
- die("could not add panel at (%d, %d) x (%d, %d)", ml, mu, pw, ph);\r
- #undef PIX\r
- #undef DONE\r
-}\r
-\r
-static inline void fill_panel_single(const img_t *img, uint8_t *done, int x1, int y1, int scale) {\r
- const uint32_t c1 = img->data[y1 * img->w + x1];\r
- const uint32_t type = type_for_color(c1);\r
- done[y1 * img->w + x1] = 1;\r
- if (!type) return; // unknown panel or emptiness\r
- spawn_panel(type, x1 * scale, y1 * scale, scale, scale);\r
-}\r
-\r
-static inline void fill_panel(const img_t *img, uint8_t *done, int x1, int y1, int scale) {\r
- if (g_nooptimize)\r
- fill_panel_single(img, done, x1, y1, scale);\r
- else\r
- fill_panel_opt(img, done, x1, y1, scale);\r
-}\r
-\r
-void convert_map(img_t *img, int scale) {\r
- if (!img || !img->data) return;\r
-\r
- uint8_t *done = calloc(1, img->w * img->h);\r
-\r
- for (int x = 0; x < img->w; ++x) {\r
- for (int y = 0; y < img->h; ++y) {\r
- if (done[y * img->w + x]) continue;\r
- fill_panel(img, done, x, y, scale);\r
- }\r
- }\r
-\r
- map_width = img->w * scale;\r
- map_height = img->h * scale;\r
-}\r
-\r
-int main(int argc, const char **argv) {\r
- if (argc < 3) {\r
- printf("usage: %s <input> <output> [options...]\n", argv[0]);\r
- return -1;\r
- }\r
-\r
- for (int i = 3; i < argc; ++i) {\r
- const char *err = opt_parse(argv[i]);\r
- if (err) printf("error parsing option `%s`: %s\n", argv[i], err);\r
- }\r
-\r
- char path[PATHSIZE];\r
-\r
- tex_check(g_resdir, !g_liquidstexture);\r
-\r
- if (img_load(argv[1], &img_map))\r
- die("could not load map image `%s`", argv[1]);\r
-\r
- snprintf(path, sizeof(path), "%s/%s.png", g_resdir, g_bgname);\r
- if (!img_load(path, &img_bg)) {\r
- printf("scronching background: %s\n", path);\r
- scronch_layer(PANEL_BACK, &img_bg, g_bgcell, g_bgsec);\r
- img_free(&img_bg);\r
- }\r
-\r
- snprintf(path, sizeof(path), "%s/%s.png", g_resdir, g_fgname);\r
- if (!img_load(path, &img_fg)) {\r
- printf("scronching foreground: %s\n", path);\r
- scronch_layer(PANEL_FORE, &img_fg, g_fgcell, g_fgsec);\r
- img_free(&img_fg);\r
- }\r
-\r
- printf("converting map from image `%s` (%dx%d)\n", argv[1], img_map.w, img_map.h);\r
- convert_map(&img_map, g_scale);\r
- map_save(argv[2]);\r
- img_free(&img_map);\r
-\r
- return 0;\r
-}\r
+/* Copyright (C) Doom 2D: Forever Developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License ONLY.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mapdef.h"
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#include "stb_image_write.h"
+
+#define PATHSIZE 1024
+#define PANEL_ALLOC_STEP 128
+
+#ifdef _WIN32
+# define f_mkdir(name) mkdir(name)
+#else
+# define f_mkdir(name) mkdir(name, 0777)
+#endif
+
+typedef struct {
+ int w, h, bpp;
+ uint32_t *data;
+} img_t;
+
+int g_scale = 1;
+int g_bgcell = 512;
+int g_fgcell = 512;
+int g_nooptimize = 0;
+int g_streamstexture = 1; // 0=no, 1=bg, 2=fg
+int g_liquidstexture = 3; // 0=no, 1=bg, 2=fg, 3=yes, 4=default
+
+char g_texsec[PATHSIZE] = "TEXTURES";
+char g_bgsec[PATHSIZE] = "MEGA_BG";
+char g_fgsec[PATHSIZE] = "MEGA_FG";
+char g_resdir[PATHSIZE] = "RES";
+char g_bgname[PATHSIZE] = "_BACKGROUND";
+char g_fgname[PATHSIZE] = "_FOREGROUND";
+
+// indexed with PANEL_ enum
+const char *tex_name[] = {
+ NULL,
+ "WALL",
+ NULL,
+ NULL,
+ "WATER",
+ "ACID1",
+ "ACID2",
+ "STEP",
+ "STREAMUP",
+ "STREAMDN",
+ "DOOR",
+ "DOOR",
+ NULL,
+ "STREAMLT",
+ "STREAMRT",
+};
+
+const uint32_t map_palette[] = {
+ 0x00000000,
+ 0xFFD0D0D0, // WALL
+ 0x00000000,
+ 0x00000000,
+ 0xFFC00000, // WATER
+ 0xFF00B000, // ACID1
+ 0xFF0000B0, // ACID2
+ 0xFF808080, // STEP
+ 0xFF244874, // STREAMUP
+ 0xFF607C74, // STREAMDOWN
+ 0xFF5CDC64, // OPENDOOR
+ 0xFF40B8D4, // DOOR
+ 0xFFC000C0, // BLOCKMON
+ 0xFF0450C8, // STREAMLT
+ 0xFF388CFC, // STREAMRT
+};
+
+map_panel_t *map_pan = NULL;
+int map_numpan = 0;
+int map_cappan = 0;
+map_texture_t *map_tex = NULL;
+int map_numtex = 0;
+
+uint16_t map_width = 1600;
+uint16_t map_height = 1600;
+
+img_t img_bg = { 0 };
+img_t img_fg = { 0 };
+img_t img_map = { 0 };
+
+/* option parsing */
+
+#define OPT_NUMSEL 8
+
+typedef enum {
+ OPT_BOOL,
+ OPT_INT,
+ OPT_STR,
+ OPT_SEL,
+} opt_type_t;
+
+typedef struct {
+ opt_type_t type;
+ const char *name;
+ const char *sel[OPT_NUMSEL];
+ union {
+ char *val_str;
+ int *val_int;
+ };
+} opt_t;
+
+#define DEF_OPT_BOOL(oname) \
+ { .type = OPT_BOOL, .name = #oname, .val_int = &g_##oname }
+#define DEF_OPT_INT(oname) \
+ { .type = OPT_INT, .name = #oname, .val_int = &g_##oname }
+#define DEF_OPT_STR(oname) \
+ { .type = OPT_STR, .name = #oname, .val_str = g_##oname }
+#define DEF_OPT_SEL(oname, ...) \
+ { .type = OPT_SEL, .name = #oname, .sel = { __VA_ARGS__ }, .val_int = &g_##oname }
+
+const opt_t opt_defs[] = {
+ DEF_OPT_INT(scale),
+ DEF_OPT_INT(bgcell),
+ DEF_OPT_INT(fgcell),
+ DEF_OPT_BOOL(nooptimize),
+ DEF_OPT_STR(texsec),
+ DEF_OPT_STR(bgsec),
+ DEF_OPT_STR(fgsec),
+ DEF_OPT_STR(resdir),
+ DEF_OPT_SEL(streamstexture, "no", "bg", "fg"),
+ DEF_OPT_SEL(liquidstexture, "no", "bg", "fg", "yes", "xtra"),
+};
+
+const size_t opt_numdefs = sizeof(opt_defs) / sizeof(opt_t);
+
+const char *opt_parse(const char *str) {
+ if (!str || str[0] != '-' || str[1] != '-' || !str[2])
+ return "invalid option string";
+
+ str += 2;
+
+ char *eq = strrchr(str, '=');
+ if (eq) *eq = 0;
+ const char *opts = str;
+ const char *vals = eq ? eq + 1 : NULL;
+
+ for (int i = 0; i < opt_numdefs; ++i) {
+ if (strcmp(opts, opt_defs[i].name))
+ continue;
+
+ switch (opt_defs[i].type) {
+ case OPT_BOOL:
+ if (eq) return "don't need an explicit value";
+ *opt_defs[i].val_int = 1;
+ return NULL;
+ case OPT_INT:
+ if (!eq) return "need numeric value";
+ *opt_defs[i].val_int = atoi(vals);
+ return NULL;
+ case OPT_STR:
+ if (!eq) return "need string value";
+ strncpy(opt_defs[i].val_str, vals, PATHSIZE);
+ opt_defs[i].val_str[PATHSIZE-1] = 0;
+ return NULL;
+ case OPT_SEL:
+ if (!eq) return "need string value";
+ for (int j = 0; j < OPT_NUMSEL && opt_defs[i].sel[j]; ++j) {
+ if (!strcmp(opt_defs[i].sel[j], vals)) {
+ *opt_defs[i].val_int = j;
+ return NULL;
+ }
+ }
+ return "invalid variant value";
+ default:
+ break;
+ }
+ }
+
+ return "unknown option";
+}
+
+/* -------------- */
+
+/* map writing */
+
+int map_save(const char *fname) {
+ FILE *fout = fopen(fname, "wb");
+ if (!fout) return -1;
+
+ map_header_t hdr = { { 0 } };
+ strcpy(hdr.name, "png2map map");
+ strcpy(hdr.author, "png2map");
+ strcpy(hdr.desc, "generated by png2map");
+ hdr.music[0] = 0;
+ hdr.sky[0] = 0;
+ hdr.width = map_width;
+ hdr.height = map_height;
+
+ map_block_t blk = { MBLK_HEADER, 0, sizeof(hdr) };
+
+ fwrite(MAP_MAGIC, 4, 1, fout);
+ fwrite(&blk, sizeof(blk), 1, fout);
+ fwrite(&hdr, sizeof(hdr), 1, fout);
+
+ blk.type = MBLK_TEXTURES;
+ blk.size = sizeof(map_texture_t) * map_numtex;
+ fwrite(&blk, sizeof(blk), 1, fout);
+ for (int i = 0; i < map_numtex; ++i)
+ fwrite(map_tex + i, sizeof(map_texture_t), 1, fout);
+
+ blk.type = MBLK_PANELS;
+ blk.size = sizeof(map_panel_t) * map_numpan;
+ fwrite(&blk, sizeof(blk), 1, fout);
+ for (int i = 0; i < map_numpan; ++i)
+ fwrite(map_pan + i, sizeof(map_panel_t), 1, fout);
+
+ blk.type = MBLK_NONE;
+ blk.size = 0;
+ fwrite(&blk, sizeof(blk), 1, fout);
+
+ return 0;
+}
+
+int map_cachetex(const char *name) {
+ static char path[PATHSIZE];
+
+ if (!name || !name[0])
+ return -1;
+
+ if (!strncmp(name, "_water_", 7))
+ snprintf(path, PATHSIZE, name);
+ else if (strchr(name, '\\'))
+ snprintf(path, PATHSIZE, ":\\%s.PNG", name);
+ else
+ snprintf(path, PATHSIZE, ":\\%s\\%s.PNG", g_texsec, name);
+
+ for (int i = 0; i < map_numtex; ++i)
+ if (!strncmp(path, map_tex[i].resname, 64))
+ return i;
+
+ int i = map_numtex++;
+ map_tex = realloc(map_tex, map_numtex * sizeof(map_texture_t));
+ if (!map_tex) return -1;
+
+ map_tex[i].anim = 0;
+ strncpy(map_tex[i].resname, path, 64);
+ map_tex[i].resname[63] = 0;
+
+ return i;
+}
+
+int map_addpanel(int type, const char *tex, int x, int y, int w, int h) {
+ if (!type) return -2;
+
+ if (map_numpan >= map_cappan) {
+ map_cappan += PANEL_ALLOC_STEP;
+ map_pan = realloc(map_pan, map_cappan * sizeof(map_panel_t));
+ if (!map_pan) return -1;
+ }
+
+ int texid = map_cachetex(tex);
+ int i = map_numpan++;
+ memset(map_pan + i, 0, sizeof(map_panel_t));
+ map_pan[i].type = 1 << (type - 1);
+ if (texid >= 0)
+ map_pan[i].texid = texid;
+ else
+ map_pan[i].flags = PFLAG_HIDE;
+ if (type >= PANEL_WATER && type <= PANEL_ACID2)
+ map_pan[i].flags |= PFLAG_WATERTEXTURES;
+ map_pan[i].x = x;
+ map_pan[i].y = y;
+ map_pan[i].w = w;
+ map_pan[i].h = h;
+
+ return i;
+}
+
+/* ----------- */
+
+/* image shit */
+
+int img_load(const char *fname, img_t *out) {
+ out->data = (uint32_t *)stbi_load(fname, &out->w, &out->h, &out->bpp, 4);
+ return !out->data;
+}
+
+void img_free(img_t *img) {
+ free(img->data);
+ img->data = NULL;
+}
+
+img_t *img_segment(img_t *src, int cell, int *out_cx, int *out_cy) {
+ if (!src || !src->data || cell <= 0) return NULL;
+
+ int cx = (src->w + cell - 1) / cell;
+ int cy = (src->h + cell - 1) / cell;
+ int cn = cx * cy;
+
+ img_t *cells = calloc(cn, sizeof(img_t));
+ if (!cells) return NULL;
+ for (int i = 0; i < cn; ++i) {
+ cells[i].w = cells[i].h = cell;
+ cells[i].data = calloc(cell * cell, 4);
+ if (!cells[i].data) {
+ free(cells);
+ return NULL;
+ }
+ }
+
+ // this is awfully retarded, but who cares
+ uint32_t *inp = src->data;
+ for (int y = 0; y < src->h; ++y) {
+ int cell_y = y / cell;
+ for (int x = 0; x < src->w; ++x, ++inp) {
+ int pos_y = y % cell;
+ int pos_x = x % cell;
+ int pos = pos_y * cell + pos_x;
+ cells[cell_y * cx + x / cell].data[pos] = *inp;
+ }
+ }
+
+ *out_cx = cx;
+ *out_cy = cy;
+ return cells;
+}
+
+int img_save(const char *fname, img_t *src) {
+ static char path[PATHSIZE];
+ if (!src || !src->data) return -1;
+ if (!strstr(fname, ".png"))
+ snprintf(path, sizeof(path), "%s.png", fname);
+ else
+ snprintf(path, sizeof(path), fname);
+ return !stbi_write_png(path, src->w, src->h, 4, src->data, 4 * src->w);
+}
+
+/* ---------- */
+
+void die(const char *fmt, ...) {
+ fprintf(stderr, "FATAL ERROR: ");
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+
+ if (img_bg.data) img_free(&img_bg);
+ if (img_fg.data) img_free(&img_fg);
+ if (img_map.data) img_free(&img_map);
+ if (map_pan) free(map_pan);
+ if (map_tex) free(map_tex);
+
+ exit(1);
+}
+
+void tex_check(const char *resdir, int liquidsmode) {
+ static const char *liquids[] = { "_water_0", "_water_1", "_water_2" };
+ char path[PATHSIZE] = { 0 };
+
+ if (!liquidsmode) {
+ tex_name[PANEL_WATER] = tex_name[PANEL_ACID1] = tex_name[PANEL_ACID2] = NULL;
+ } else if (liquidsmode == 4) {
+ tex_name[PANEL_WATER] = liquids[0];
+ tex_name[PANEL_ACID1] = liquids[1];
+ tex_name[PANEL_ACID2] = liquids[2];
+ }
+
+ for (int i = 0; i < PANEL_NUMTYPES; ++i) {
+ if (!tex_name[i]) continue;
+ snprintf(path, PATHSIZE, "%s/%s.png", resdir, tex_name[i]);
+ FILE *f = fopen(path, "rb");
+ if (f) {
+ fclose(f);
+ } else {
+ printf("texture `%s` missing from `%s`, using default\n", tex_name[i], resdir);
+ if (i >= PANEL_WATER && i <= PANEL_ACID2 && liquidsmode)
+ tex_name[i] = liquids[i - PANEL_WATER];
+ else
+ tex_name[i] = NULL;
+ }
+ }
+}
+
+void scronch_layer(int layer, img_t *img, int cellsize, const char *secname) {
+ int numx = 1;
+ int numy = 1;
+ img_t *cells = cellsize ? img_segment(img, cellsize, &numx, &numy) : img;
+
+ if (!numx || !numy || !cells)
+ die("scronching failure");
+
+ f_mkdir(secname);
+
+ int pw, ph;
+ if (cellsize) {
+ pw = ph = cellsize;
+ } else {
+ pw = img->w;
+ ph = img->h;
+ }
+
+ char path[PATHSIZE] = { 0 };
+ for (int yc = 0; yc < numy; ++yc) {
+ int y = yc * cellsize;
+ for (int xc = 0; xc < numx; ++xc) {
+ int x = xc * cellsize;
+ int idx = yc * numx + xc;
+ snprintf(path, PATHSIZE, "%s\\BG%03X%03X", secname, xc, yc);
+ if (img_save(path, cells + idx))
+ die("could not save layer cell `%s`", path);
+ if (cells != img)
+ img_free(cells + idx);
+ if (map_addpanel(layer, path, x, y, pw, ph) < 0)
+ die("could not add layer panel");
+ }
+ }
+
+ if (cells != img)
+ free(cells);
+}
+
+static inline uint32_t type_for_color(const uint32_t c) {
+ if (!c) return 0; // empty
+ for (int i = 0; i < PANEL_NUMTYPES; ++i)
+ if (map_palette[i] == c)
+ return i;
+ return 0;
+}
+
+static inline int spawn_panel(uint32_t type, int x, int y, int w, int h) {
+ const char *tex = tex_name[type];
+
+ switch (type) {
+ case PANEL_WATER:
+ case PANEL_ACID1:
+ case PANEL_ACID2:
+ if (g_liquidstexture == 1 || g_liquidstexture == 2) {
+ map_addpanel(PANEL_BACK + g_liquidstexture - 1, tex, x, y, w, h);
+ tex = NULL;
+ }
+ break;
+ case PANEL_LIFTUP:
+ case PANEL_LIFTDOWN:
+ case PANEL_LIFTLEFT:
+ case PANEL_LIFTRIGHT:
+ if (g_streamstexture == 1 || g_streamstexture == 2) {
+ map_addpanel(PANEL_BACK + g_streamstexture - 1, tex, x, y, w, h);
+ tex = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ map_addpanel(type, tex, x, y, w, h);
+}
+
+static inline void fill_panel_opt(const img_t *img, uint8_t *done, int x1, int y1, int scale) {
+ #define PIXEQ(xx, yy) (img->data[(yy) * w + (xx)] == c1)
+ #define DONE(xx, yy) (done[(yy) * w + (xx)])
+ const uint32_t c1 = img->data[y1 * img->w + x1];
+ const uint32_t type = type_for_color(c1);
+ if (!type) {
+ done[y1 * img->w + x1] = 1;
+ return; // unknown panel or emptiness
+ }
+
+ const int w = img->w;
+ const int h = img->h;
+
+ // this ain't optimal, but it'll do
+
+ // find horizontal bounds
+ int ml = x1, mr = x1;
+ for (; ml > 0 && !DONE(ml-1, y1) && PIXEQ(ml-1, y1); --ml);
+ for (; mr < w-1 && !DONE(mr+1, y1) && PIXEQ(mr+1, y1); ++mr);
+
+ // find min vertical bounds
+ int mu = 0, md = h - 1;
+ for (int x = ml; x <= mr; ++x) {
+ int tu, td;
+ for (tu = y1; tu > 0 && !DONE(x, tu-1) && PIXEQ(x, tu-1); --tu);
+ for (td = y1; td < h-1 && !DONE(x, td+1) && PIXEQ(x, td+1); ++td);
+ if (tu > mu) mu = tu;
+ if (td < md) md = td;
+ }
+
+ // don't overlap this later
+ for (int y = mu; y <= md; ++y)
+ for (int x = ml; x <= mr; ++x)
+ DONE(x, y) = 1;
+
+ int pw = (mr - ml + 1);
+ int ph = (md - mu + 1);
+ if (spawn_panel(type, ml * scale, mu * scale, scale * pw, scale * ph) < 0)
+ die("could not add panel at (%d, %d) x (%d, %d)", ml, mu, pw, ph);
+ #undef PIX
+ #undef DONE
+}
+
+static inline void fill_panel_single(const img_t *img, uint8_t *done, int x1, int y1, int scale) {
+ const uint32_t c1 = img->data[y1 * img->w + x1];
+ const uint32_t type = type_for_color(c1);
+ done[y1 * img->w + x1] = 1;
+ if (!type) return; // unknown panel or emptiness
+ spawn_panel(type, x1 * scale, y1 * scale, scale, scale);
+}
+
+static inline void fill_panel(const img_t *img, uint8_t *done, int x1, int y1, int scale) {
+ if (g_nooptimize)
+ fill_panel_single(img, done, x1, y1, scale);
+ else
+ fill_panel_opt(img, done, x1, y1, scale);
+}
+
+void convert_map(img_t *img, int scale) {
+ if (!img || !img->data) return;
+
+ uint8_t *done = calloc(1, img->w * img->h);
+
+ for (int x = 0; x < img->w; ++x) {
+ for (int y = 0; y < img->h; ++y) {
+ if (done[y * img->w + x]) continue;
+ fill_panel(img, done, x, y, scale);
+ }
+ }
+
+ map_width = img->w * scale;
+ map_height = img->h * scale;
+}
+
+int main(int argc, const char **argv) {
+ if (argc < 3) {
+ printf("usage: %s <input> <output> [options...]\n", argv[0]);
+ return -1;
+ }
+
+ for (int i = 3; i < argc; ++i) {
+ const char *err = opt_parse(argv[i]);
+ if (err) printf("error parsing option `%s`: %s\n", argv[i], err);
+ }
+
+ char path[PATHSIZE];
+
+ tex_check(g_resdir, !g_liquidstexture);
+
+ if (img_load(argv[1], &img_map))
+ die("could not load map image `%s`", argv[1]);
+
+ snprintf(path, sizeof(path), "%s/%s.png", g_resdir, g_bgname);
+ if (!img_load(path, &img_bg)) {
+ printf("scronching background: %s\n", path);
+ scronch_layer(PANEL_BACK, &img_bg, g_bgcell, g_bgsec);
+ img_free(&img_bg);
+ }
+
+ snprintf(path, sizeof(path), "%s/%s.png", g_resdir, g_fgname);
+ if (!img_load(path, &img_fg)) {
+ printf("scronching foreground: %s\n", path);
+ scronch_layer(PANEL_FORE, &img_fg, g_fgcell, g_fgsec);
+ img_free(&img_fg);
+ }
+
+ printf("converting map from image `%s` (%dx%d)\n", argv[1], img_map.w, img_map.h);
+ convert_map(&img_map, g_scale);
+ map_save(argv[2]);
+ img_free(&img_map);
+
+ return 0;
+}