DEADSOFTWARE

thanks git, very cool
[d2df-sdl.git] / src / tools / png2map / png2map.c
1 /* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 */
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <stdint.h>
19 #include <string.h>
20 #include <unistd.h>
22 #include "mapdef.h"
24 #define STB_IMAGE_IMPLEMENTATION
25 #include "stb_image.h"
26 #define STB_IMAGE_WRITE_IMPLEMENTATION
27 #include "stb_image_write.h"
29 #define PATHSIZE 1024
30 #define PANEL_ALLOC_STEP 128
32 #ifdef _WIN32
33 # define f_mkdir(name) mkdir(name)
34 #else
35 # define f_mkdir(name) mkdir(name, 0777)
36 #endif
38 typedef struct {
39 int w, h, bpp;
40 uint32_t *data;
41 } img_t;
43 int g_scale = 1;
44 int g_bgcell = 512;
45 int g_fgcell = 512;
46 int g_nooptimize = 0;
47 int g_streamstexture = 1; // 0=no, 1=bg, 2=fg
48 int g_liquidstexture = 3; // 0=no, 1=bg, 2=fg, 3=yes, 4=default
50 char g_texsec[PATHSIZE] = "TEXTURES";
51 char g_bgsec[PATHSIZE] = "MEGA_BG";
52 char g_fgsec[PATHSIZE] = "MEGA_FG";
53 char g_resdir[PATHSIZE] = "RES";
54 char g_bgname[PATHSIZE] = "_BACKGROUND";
55 char g_fgname[PATHSIZE] = "_FOREGROUND";
57 // indexed with PANEL_ enum
58 const char *tex_name[] = {
59 NULL,
60 "WALL",
61 NULL,
62 NULL,
63 "WATER",
64 "ACID1",
65 "ACID2",
66 "STEP",
67 "STREAMUP",
68 "STREAMDN",
69 "DOOR",
70 "DOOR",
71 NULL,
72 "STREAMLT",
73 "STREAMRT",
74 };
76 const uint32_t map_palette[] = {
77 0x00000000,
78 0xFFD0D0D0, // WALL
79 0x00000000,
80 0x00000000,
81 0xFFC00000, // WATER
82 0xFF00B000, // ACID1
83 0xFF0000B0, // ACID2
84 0xFF808080, // STEP
85 0xFF244874, // STREAMUP
86 0xFF607C74, // STREAMDOWN
87 0xFF5CDC64, // OPENDOOR
88 0xFF40B8D4, // DOOR
89 0xFFC000C0, // BLOCKMON
90 0xFF0450C8, // STREAMLT
91 0xFF388CFC, // STREAMRT
92 };
94 map_panel_t *map_pan = NULL;
95 int map_numpan = 0;
96 int map_cappan = 0;
97 map_texture_t *map_tex = NULL;
98 int map_numtex = 0;
100 uint16_t map_width = 1600;
101 uint16_t map_height = 1600;
103 img_t img_bg = { 0 };
104 img_t img_fg = { 0 };
105 img_t img_map = { 0 };
107 /* option parsing */
109 #define OPT_NUMSEL 8
111 typedef enum {
112 OPT_BOOL,
113 OPT_INT,
114 OPT_STR,
115 OPT_SEL,
116 } opt_type_t;
118 typedef struct {
119 opt_type_t type;
120 const char *name;
121 const char *sel[OPT_NUMSEL];
122 union {
123 char *val_str;
124 int *val_int;
125 };
126 } opt_t;
128 #define DEF_OPT_BOOL(oname) \
129 { .type = OPT_BOOL, .name = #oname, .val_int = &g_##oname }
130 #define DEF_OPT_INT(oname) \
131 { .type = OPT_INT, .name = #oname, .val_int = &g_##oname }
132 #define DEF_OPT_STR(oname) \
133 { .type = OPT_STR, .name = #oname, .val_str = g_##oname }
134 #define DEF_OPT_SEL(oname, ...) \
135 { .type = OPT_SEL, .name = #oname, .sel = { __VA_ARGS__ }, .val_int = &g_##oname }
137 const opt_t opt_defs[] = {
138 DEF_OPT_INT(scale),
139 DEF_OPT_INT(bgcell),
140 DEF_OPT_INT(fgcell),
141 DEF_OPT_BOOL(nooptimize),
142 DEF_OPT_STR(texsec),
143 DEF_OPT_STR(bgsec),
144 DEF_OPT_STR(fgsec),
145 DEF_OPT_STR(resdir),
146 DEF_OPT_SEL(streamstexture, "no", "bg", "fg"),
147 DEF_OPT_SEL(liquidstexture, "no", "bg", "fg", "yes", "xtra"),
148 };
150 const size_t opt_numdefs = sizeof(opt_defs) / sizeof(opt_t);
152 const char *opt_parse(const char *str) {
153 if (!str || str[0] != '-' || str[1] != '-' || !str[2])
154 return "invalid option string";
156 str += 2;
158 char *eq = strrchr(str, '=');
159 if (eq) *eq = 0;
160 const char *opts = str;
161 const char *vals = eq ? eq + 1 : NULL;
163 for (int i = 0; i < opt_numdefs; ++i) {
164 if (strcmp(opts, opt_defs[i].name))
165 continue;
167 switch (opt_defs[i].type) {
168 case OPT_BOOL:
169 if (eq) return "don't need an explicit value";
170 *opt_defs[i].val_int = 1;
171 return NULL;
172 case OPT_INT:
173 if (!eq) return "need numeric value";
174 *opt_defs[i].val_int = atoi(vals);
175 return NULL;
176 case OPT_STR:
177 if (!eq) return "need string value";
178 strncpy(opt_defs[i].val_str, vals, PATHSIZE);
179 opt_defs[i].val_str[PATHSIZE-1] = 0;
180 return NULL;
181 case OPT_SEL:
182 if (!eq) return "need string value";
183 for (int j = 0; j < OPT_NUMSEL && opt_defs[i].sel[j]; ++j) {
184 if (!strcmp(opt_defs[i].sel[j], vals)) {
185 *opt_defs[i].val_int = j;
186 return NULL;
189 return "invalid variant value";
190 default:
191 break;
195 return "unknown option";
198 /* -------------- */
200 /* map writing */
202 int map_save(const char *fname) {
203 FILE *fout = fopen(fname, "wb");
204 if (!fout) return -1;
206 map_header_t hdr = { { 0 } };
207 strcpy(hdr.name, "png2map map");
208 strcpy(hdr.author, "png2map");
209 strcpy(hdr.desc, "generated by png2map");
210 hdr.music[0] = 0;
211 hdr.sky[0] = 0;
212 hdr.width = map_width;
213 hdr.height = map_height;
215 map_block_t blk = { MBLK_HEADER, 0, sizeof(hdr) };
217 fwrite(MAP_MAGIC, 4, 1, fout);
218 fwrite(&blk, sizeof(blk), 1, fout);
219 fwrite(&hdr, sizeof(hdr), 1, fout);
221 blk.type = MBLK_TEXTURES;
222 blk.size = sizeof(map_texture_t) * map_numtex;
223 fwrite(&blk, sizeof(blk), 1, fout);
224 for (int i = 0; i < map_numtex; ++i)
225 fwrite(map_tex + i, sizeof(map_texture_t), 1, fout);
227 blk.type = MBLK_PANELS;
228 blk.size = sizeof(map_panel_t) * map_numpan;
229 fwrite(&blk, sizeof(blk), 1, fout);
230 for (int i = 0; i < map_numpan; ++i)
231 fwrite(map_pan + i, sizeof(map_panel_t), 1, fout);
233 blk.type = MBLK_NONE;
234 blk.size = 0;
235 fwrite(&blk, sizeof(blk), 1, fout);
237 return 0;
240 int map_cachetex(const char *name) {
241 static char path[PATHSIZE];
243 if (!name || !name[0])
244 return -1;
246 if (!strncmp(name, "_water_", 7))
247 snprintf(path, PATHSIZE, name);
248 else if (strchr(name, '\\'))
249 snprintf(path, PATHSIZE, ":\\%s.PNG", name);
250 else
251 snprintf(path, PATHSIZE, ":\\%s\\%s.PNG", g_texsec, name);
253 for (int i = 0; i < map_numtex; ++i)
254 if (!strncmp(path, map_tex[i].resname, 64))
255 return i;
257 int i = map_numtex++;
258 map_tex = realloc(map_tex, map_numtex * sizeof(map_texture_t));
259 if (!map_tex) return -1;
261 map_tex[i].anim = 0;
262 strncpy(map_tex[i].resname, path, 64);
263 map_tex[i].resname[63] = 0;
265 return i;
268 int map_addpanel(int type, const char *tex, int x, int y, int w, int h) {
269 if (!type) return -2;
271 if (map_numpan >= map_cappan) {
272 map_cappan += PANEL_ALLOC_STEP;
273 map_pan = realloc(map_pan, map_cappan * sizeof(map_panel_t));
274 if (!map_pan) return -1;
277 int texid = map_cachetex(tex);
278 int i = map_numpan++;
279 memset(map_pan + i, 0, sizeof(map_panel_t));
280 map_pan[i].type = 1 << (type - 1);
281 if (texid >= 0)
282 map_pan[i].texid = texid;
283 else
284 map_pan[i].flags = PFLAG_HIDE;
285 if (type >= PANEL_WATER && type <= PANEL_ACID2)
286 map_pan[i].flags |= PFLAG_WATERTEXTURES;
287 map_pan[i].x = x;
288 map_pan[i].y = y;
289 map_pan[i].w = w;
290 map_pan[i].h = h;
292 return i;
295 /* ----------- */
297 /* image shit */
299 int img_load(const char *fname, img_t *out) {
300 out->data = (uint32_t *)stbi_load(fname, &out->w, &out->h, &out->bpp, 4);
301 return !out->data;
304 void img_free(img_t *img) {
305 free(img->data);
306 img->data = NULL;
309 img_t *img_segment(img_t *src, int cell, int *out_cx, int *out_cy) {
310 if (!src || !src->data || cell <= 0) return NULL;
312 int cx = (src->w + cell - 1) / cell;
313 int cy = (src->h + cell - 1) / cell;
314 int cn = cx * cy;
316 img_t *cells = calloc(cn, sizeof(img_t));
317 if (!cells) return NULL;
318 for (int i = 0; i < cn; ++i) {
319 cells[i].w = cells[i].h = cell;
320 cells[i].data = calloc(cell * cell, 4);
321 if (!cells[i].data) {
322 free(cells);
323 return NULL;
327 // this is awfully retarded, but who cares
328 uint32_t *inp = src->data;
329 for (int y = 0; y < src->h; ++y) {
330 int cell_y = y / cell;
331 for (int x = 0; x < src->w; ++x, ++inp) {
332 int pos_y = y % cell;
333 int pos_x = x % cell;
334 int pos = pos_y * cell + pos_x;
335 cells[cell_y * cx + x / cell].data[pos] = *inp;
339 *out_cx = cx;
340 *out_cy = cy;
341 return cells;
344 int img_save(const char *fname, img_t *src) {
345 static char path[PATHSIZE];
346 if (!src || !src->data) return -1;
347 if (!strstr(fname, ".png"))
348 snprintf(path, sizeof(path), "%s.png", fname);
349 else
350 snprintf(path, sizeof(path), fname);
351 return !stbi_write_png(path, src->w, src->h, 4, src->data, 4 * src->w);
354 /* ---------- */
356 void die(const char *fmt, ...) {
357 fprintf(stderr, "FATAL ERROR: ");
358 va_list args;
359 va_start(args, fmt);
360 vfprintf(stderr, fmt, args);
361 va_end(args);
362 fprintf(stderr, "\n");
364 if (img_bg.data) img_free(&img_bg);
365 if (img_fg.data) img_free(&img_fg);
366 if (img_map.data) img_free(&img_map);
367 if (map_pan) free(map_pan);
368 if (map_tex) free(map_tex);
370 exit(1);
373 void tex_check(const char *resdir, int liquidsmode) {
374 static const char *liquids[] = { "_water_0", "_water_1", "_water_2" };
375 char path[PATHSIZE] = { 0 };
377 if (!liquidsmode) {
378 tex_name[PANEL_WATER] = tex_name[PANEL_ACID1] = tex_name[PANEL_ACID2] = NULL;
379 } else if (liquidsmode == 4) {
380 tex_name[PANEL_WATER] = liquids[0];
381 tex_name[PANEL_ACID1] = liquids[1];
382 tex_name[PANEL_ACID2] = liquids[2];
385 for (int i = 0; i < PANEL_NUMTYPES; ++i) {
386 if (!tex_name[i]) continue;
387 snprintf(path, PATHSIZE, "%s/%s.png", resdir, tex_name[i]);
388 FILE *f = fopen(path, "rb");
389 if (f) {
390 fclose(f);
391 } else {
392 printf("texture `%s` missing from `%s`, using default\n", tex_name[i], resdir);
393 if (i >= PANEL_WATER && i <= PANEL_ACID2 && liquidsmode)
394 tex_name[i] = liquids[i - PANEL_WATER];
395 else
396 tex_name[i] = NULL;
401 void scronch_layer(int layer, img_t *img, int cellsize, const char *secname) {
402 int numx = 1;
403 int numy = 1;
404 img_t *cells = cellsize ? img_segment(img, cellsize, &numx, &numy) : img;
406 if (!numx || !numy || !cells)
407 die("scronching failure");
409 f_mkdir(secname);
411 int pw, ph;
412 if (cellsize) {
413 pw = ph = cellsize;
414 } else {
415 pw = img->w;
416 ph = img->h;
419 char path[PATHSIZE] = { 0 };
420 for (int yc = 0; yc < numy; ++yc) {
421 int y = yc * cellsize;
422 for (int xc = 0; xc < numx; ++xc) {
423 int x = xc * cellsize;
424 int idx = yc * numx + xc;
425 snprintf(path, PATHSIZE, "%s\\BG%03X%03X", secname, xc, yc);
426 if (img_save(path, cells + idx))
427 die("could not save layer cell `%s`", path);
428 if (cells != img)
429 img_free(cells + idx);
430 if (map_addpanel(layer, path, x, y, pw, ph) < 0)
431 die("could not add layer panel");
435 if (cells != img)
436 free(cells);
439 static inline uint32_t type_for_color(const uint32_t c) {
440 if (!c) return 0; // empty
441 for (int i = 0; i < PANEL_NUMTYPES; ++i)
442 if (map_palette[i] == c)
443 return i;
444 return 0;
447 static inline int spawn_panel(uint32_t type, int x, int y, int w, int h) {
448 const char *tex = tex_name[type];
450 switch (type) {
451 case PANEL_WATER:
452 case PANEL_ACID1:
453 case PANEL_ACID2:
454 if (g_liquidstexture == 1 || g_liquidstexture == 2) {
455 map_addpanel(PANEL_BACK + g_liquidstexture - 1, tex, x, y, w, h);
456 tex = NULL;
458 break;
459 case PANEL_LIFTUP:
460 case PANEL_LIFTDOWN:
461 case PANEL_LIFTLEFT:
462 case PANEL_LIFTRIGHT:
463 if (g_streamstexture == 1 || g_streamstexture == 2) {
464 map_addpanel(PANEL_BACK + g_streamstexture - 1, tex, x, y, w, h);
465 tex = NULL;
467 break;
468 default:
469 break;
472 map_addpanel(type, tex, x, y, w, h);
475 static inline void fill_panel_opt(const img_t *img, uint8_t *done, int x1, int y1, int scale) {
476 #define PIXEQ(xx, yy) (img->data[(yy) * w + (xx)] == c1)
477 #define DONE(xx, yy) (done[(yy) * w + (xx)])
478 const uint32_t c1 = img->data[y1 * img->w + x1];
479 const uint32_t type = type_for_color(c1);
480 if (!type) {
481 done[y1 * img->w + x1] = 1;
482 return; // unknown panel or emptiness
485 const int w = img->w;
486 const int h = img->h;
488 // this ain't optimal, but it'll do
490 // find horizontal bounds
491 int ml = x1, mr = x1;
492 for (; ml > 0 && !DONE(ml-1, y1) && PIXEQ(ml-1, y1); --ml);
493 for (; mr < w-1 && !DONE(mr+1, y1) && PIXEQ(mr+1, y1); ++mr);
495 // find min vertical bounds
496 int mu = 0, md = h - 1;
497 for (int x = ml; x <= mr; ++x) {
498 int tu, td;
499 for (tu = y1; tu > 0 && !DONE(x, tu-1) && PIXEQ(x, tu-1); --tu);
500 for (td = y1; td < h-1 && !DONE(x, td+1) && PIXEQ(x, td+1); ++td);
501 if (tu > mu) mu = tu;
502 if (td < md) md = td;
505 // don't overlap this later
506 for (int y = mu; y <= md; ++y)
507 for (int x = ml; x <= mr; ++x)
508 DONE(x, y) = 1;
510 int pw = (mr - ml + 1);
511 int ph = (md - mu + 1);
512 if (spawn_panel(type, ml * scale, mu * scale, scale * pw, scale * ph) < 0)
513 die("could not add panel at (%d, %d) x (%d, %d)", ml, mu, pw, ph);
514 #undef PIX
515 #undef DONE
518 static inline void fill_panel_single(const img_t *img, uint8_t *done, int x1, int y1, int scale) {
519 const uint32_t c1 = img->data[y1 * img->w + x1];
520 const uint32_t type = type_for_color(c1);
521 done[y1 * img->w + x1] = 1;
522 if (!type) return; // unknown panel or emptiness
523 spawn_panel(type, x1 * scale, y1 * scale, scale, scale);
526 static inline void fill_panel(const img_t *img, uint8_t *done, int x1, int y1, int scale) {
527 if (g_nooptimize)
528 fill_panel_single(img, done, x1, y1, scale);
529 else
530 fill_panel_opt(img, done, x1, y1, scale);
533 void convert_map(img_t *img, int scale) {
534 if (!img || !img->data) return;
536 uint8_t *done = calloc(1, img->w * img->h);
538 for (int x = 0; x < img->w; ++x) {
539 for (int y = 0; y < img->h; ++y) {
540 if (done[y * img->w + x]) continue;
541 fill_panel(img, done, x, y, scale);
545 map_width = img->w * scale;
546 map_height = img->h * scale;
549 int main(int argc, const char **argv) {
550 if (argc < 3) {
551 printf("usage: %s <input> <output> [options...]\n", argv[0]);
552 return -1;
555 for (int i = 3; i < argc; ++i) {
556 const char *err = opt_parse(argv[i]);
557 if (err) printf("error parsing option `%s`: %s\n", argv[i], err);
560 char path[PATHSIZE];
562 tex_check(g_resdir, !g_liquidstexture);
564 if (img_load(argv[1], &img_map))
565 die("could not load map image `%s`", argv[1]);
567 snprintf(path, sizeof(path), "%s/%s.png", g_resdir, g_bgname);
568 if (!img_load(path, &img_bg)) {
569 printf("scronching background: %s\n", path);
570 scronch_layer(PANEL_BACK, &img_bg, g_bgcell, g_bgsec);
571 img_free(&img_bg);
574 snprintf(path, sizeof(path), "%s/%s.png", g_resdir, g_fgname);
575 if (!img_load(path, &img_fg)) {
576 printf("scronching foreground: %s\n", path);
577 scronch_layer(PANEL_FORE, &img_fg, g_fgcell, g_fgsec);
578 img_free(&img_fg);
581 printf("converting map from image `%s` (%dx%d)\n", argv[1], img_map.w, img_map.h);
582 convert_map(&img_map, g_scale);
583 map_save(argv[2]);
584 img_free(&img_map);
586 return 0;