DEADSOFTWARE

don't write empty blocks (by deaddoomer)
[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 if (map_numtex > 0) {
222 blk.type = MBLK_TEXTURES;
223 blk.size = sizeof(map_texture_t) * map_numtex;
224 fwrite(&blk, sizeof(blk), 1, fout);
225 for (int i = 0; i < map_numtex; ++i)
226 fwrite(map_tex + i, sizeof(map_texture_t), 1, fout);
229 if (map_numpan > 0) {
230 blk.type = MBLK_PANELS;
231 blk.size = sizeof(map_panel_t) * map_numpan;
232 fwrite(&blk, sizeof(blk), 1, fout);
233 for (int i = 0; i < map_numpan; ++i)
234 fwrite(map_pan + i, sizeof(map_panel_t), 1, fout);
237 blk.type = MBLK_NONE;
238 blk.size = 0;
239 fwrite(&blk, sizeof(blk), 1, fout);
241 return 0;
244 int map_cachetex(const char *name) {
245 static char path[PATHSIZE];
247 if (!name || !name[0])
248 return -1;
250 if (!strncmp(name, "_water_", 7))
251 snprintf(path, PATHSIZE, name);
252 else if (strchr(name, '\\'))
253 snprintf(path, PATHSIZE, ":\\%s.PNG", name);
254 else
255 snprintf(path, PATHSIZE, ":\\%s\\%s.PNG", g_texsec, name);
257 for (int i = 0; i < map_numtex; ++i)
258 if (!strncmp(path, map_tex[i].resname, 64))
259 return i;
261 int i = map_numtex++;
262 map_tex = realloc(map_tex, map_numtex * sizeof(map_texture_t));
263 if (!map_tex) return -1;
265 map_tex[i].anim = 0;
266 strncpy(map_tex[i].resname, path, 64);
267 map_tex[i].resname[63] = 0;
269 return i;
272 int map_addpanel(int type, const char *tex, int x, int y, int w, int h) {
273 if (!type) return -2;
275 if (map_numpan >= map_cappan) {
276 map_cappan += PANEL_ALLOC_STEP;
277 map_pan = realloc(map_pan, map_cappan * sizeof(map_panel_t));
278 if (!map_pan) return -1;
281 int texid = map_cachetex(tex);
282 int i = map_numpan++;
283 memset(map_pan + i, 0, sizeof(map_panel_t));
284 map_pan[i].type = 1 << (type - 1);
285 if (texid >= 0)
286 map_pan[i].texid = texid;
287 else
288 map_pan[i].flags = PFLAG_HIDE;
289 if (type >= PANEL_WATER && type <= PANEL_ACID2)
290 map_pan[i].flags |= PFLAG_WATERTEXTURES;
291 map_pan[i].x = x;
292 map_pan[i].y = y;
293 map_pan[i].w = w;
294 map_pan[i].h = h;
296 return i;
299 /* ----------- */
301 /* image shit */
303 int img_load(const char *fname, img_t *out) {
304 out->data = (uint32_t *)stbi_load(fname, &out->w, &out->h, &out->bpp, 4);
305 return !out->data;
308 void img_free(img_t *img) {
309 free(img->data);
310 img->data = NULL;
313 img_t *img_segment(img_t *src, int cell, int *out_cx, int *out_cy) {
314 if (!src || !src->data || cell <= 0) return NULL;
316 int cx = (src->w + cell - 1) / cell;
317 int cy = (src->h + cell - 1) / cell;
318 int cn = cx * cy;
320 img_t *cells = calloc(cn, sizeof(img_t));
321 if (!cells) return NULL;
322 for (int i = 0; i < cn; ++i) {
323 cells[i].w = cells[i].h = cell;
324 cells[i].data = calloc(cell * cell, 4);
325 if (!cells[i].data) {
326 free(cells);
327 return NULL;
331 // this is awfully retarded, but who cares
332 uint32_t *inp = src->data;
333 for (int y = 0; y < src->h; ++y) {
334 int cell_y = y / cell;
335 for (int x = 0; x < src->w; ++x, ++inp) {
336 int pos_y = y % cell;
337 int pos_x = x % cell;
338 int pos = pos_y * cell + pos_x;
339 cells[cell_y * cx + x / cell].data[pos] = *inp;
343 *out_cx = cx;
344 *out_cy = cy;
345 return cells;
348 int img_save(const char *fname, img_t *src) {
349 static char path[PATHSIZE];
350 if (!src || !src->data) return -1;
351 if (!strstr(fname, ".png"))
352 snprintf(path, sizeof(path), "%s.png", fname);
353 else
354 snprintf(path, sizeof(path), fname);
355 return !stbi_write_png(path, src->w, src->h, 4, src->data, 4 * src->w);
358 /* ---------- */
360 void die(const char *fmt, ...) {
361 fprintf(stderr, "FATAL ERROR: ");
362 va_list args;
363 va_start(args, fmt);
364 vfprintf(stderr, fmt, args);
365 va_end(args);
366 fprintf(stderr, "\n");
368 if (img_bg.data) img_free(&img_bg);
369 if (img_fg.data) img_free(&img_fg);
370 if (img_map.data) img_free(&img_map);
371 if (map_pan) free(map_pan);
372 if (map_tex) free(map_tex);
374 exit(1);
377 void tex_check(const char *resdir, int liquidsmode) {
378 static const char *liquids[] = { "_water_0", "_water_1", "_water_2" };
379 char path[PATHSIZE] = { 0 };
381 if (!liquidsmode) {
382 tex_name[PANEL_WATER] = tex_name[PANEL_ACID1] = tex_name[PANEL_ACID2] = NULL;
383 } else if (liquidsmode == 4) {
384 tex_name[PANEL_WATER] = liquids[0];
385 tex_name[PANEL_ACID1] = liquids[1];
386 tex_name[PANEL_ACID2] = liquids[2];
389 for (int i = 0; i < PANEL_NUMTYPES; ++i) {
390 if (!tex_name[i]) continue;
391 snprintf(path, PATHSIZE, "%s/%s.png", resdir, tex_name[i]);
392 FILE *f = fopen(path, "rb");
393 if (f) {
394 fclose(f);
395 } else {
396 printf("texture `%s` missing from `%s`, using default\n", tex_name[i], resdir);
397 if (i >= PANEL_WATER && i <= PANEL_ACID2 && liquidsmode)
398 tex_name[i] = liquids[i - PANEL_WATER];
399 else
400 tex_name[i] = NULL;
405 void scronch_layer(int layer, img_t *img, int cellsize, const char *secname) {
406 int numx = 1;
407 int numy = 1;
408 img_t *cells = cellsize ? img_segment(img, cellsize, &numx, &numy) : img;
410 if (!numx || !numy || !cells)
411 die("scronching failure");
413 f_mkdir(secname);
415 int pw, ph;
416 if (cellsize) {
417 pw = ph = cellsize;
418 } else {
419 pw = img->w;
420 ph = img->h;
423 char path[PATHSIZE] = { 0 };
424 for (int yc = 0; yc < numy; ++yc) {
425 int y = yc * cellsize;
426 for (int xc = 0; xc < numx; ++xc) {
427 int x = xc * cellsize;
428 int idx = yc * numx + xc;
429 snprintf(path, PATHSIZE, "%s\\BG%03X%03X", secname, xc, yc);
430 if (img_save(path, cells + idx))
431 die("could not save layer cell `%s`", path);
432 if (cells != img)
433 img_free(cells + idx);
434 if (map_addpanel(layer, path, x, y, pw, ph) < 0)
435 die("could not add layer panel");
439 if (cells != img)
440 free(cells);
443 static inline uint32_t type_for_color(const uint32_t c) {
444 if (!c) return 0; // empty
445 for (int i = 0; i < PANEL_NUMTYPES; ++i)
446 if (map_palette[i] == c)
447 return i;
448 return 0;
451 static inline int spawn_panel(uint32_t type, int x, int y, int w, int h) {
452 const char *tex = tex_name[type];
454 switch (type) {
455 case PANEL_WATER:
456 case PANEL_ACID1:
457 case PANEL_ACID2:
458 if (g_liquidstexture == 1 || g_liquidstexture == 2) {
459 map_addpanel(PANEL_BACK + g_liquidstexture - 1, tex, x, y, w, h);
460 tex = NULL;
462 break;
463 case PANEL_LIFTUP:
464 case PANEL_LIFTDOWN:
465 case PANEL_LIFTLEFT:
466 case PANEL_LIFTRIGHT:
467 if (g_streamstexture == 1 || g_streamstexture == 2) {
468 map_addpanel(PANEL_BACK + g_streamstexture - 1, tex, x, y, w, h);
469 tex = NULL;
471 break;
472 default:
473 break;
476 map_addpanel(type, tex, x, y, w, h);
479 static inline void fill_panel_opt(const img_t *img, uint8_t *done, int x1, int y1, int scale) {
480 #define PIXEQ(xx, yy) (img->data[(yy) * w + (xx)] == c1)
481 #define DONE(xx, yy) (done[(yy) * w + (xx)])
482 const uint32_t c1 = img->data[y1 * img->w + x1];
483 const uint32_t type = type_for_color(c1);
484 if (!type) {
485 done[y1 * img->w + x1] = 1;
486 return; // unknown panel or emptiness
489 const int w = img->w;
490 const int h = img->h;
492 // this ain't optimal, but it'll do
494 // find horizontal bounds
495 int ml = x1, mr = x1;
496 for (; ml > 0 && !DONE(ml-1, y1) && PIXEQ(ml-1, y1); --ml);
497 for (; mr < w-1 && !DONE(mr+1, y1) && PIXEQ(mr+1, y1); ++mr);
499 // find min vertical bounds
500 int mu = 0, md = h - 1;
501 for (int x = ml; x <= mr; ++x) {
502 int tu, td;
503 for (tu = y1; tu > 0 && !DONE(x, tu-1) && PIXEQ(x, tu-1); --tu);
504 for (td = y1; td < h-1 && !DONE(x, td+1) && PIXEQ(x, td+1); ++td);
505 if (tu > mu) mu = tu;
506 if (td < md) md = td;
509 // don't overlap this later
510 for (int y = mu; y <= md; ++y)
511 for (int x = ml; x <= mr; ++x)
512 DONE(x, y) = 1;
514 int pw = (mr - ml + 1);
515 int ph = (md - mu + 1);
516 if (spawn_panel(type, ml * scale, mu * scale, scale * pw, scale * ph) < 0)
517 die("could not add panel at (%d, %d) x (%d, %d)", ml, mu, pw, ph);
518 #undef PIX
519 #undef DONE
522 static inline void fill_panel_single(const img_t *img, uint8_t *done, int x1, int y1, int scale) {
523 const uint32_t c1 = img->data[y1 * img->w + x1];
524 const uint32_t type = type_for_color(c1);
525 done[y1 * img->w + x1] = 1;
526 if (!type) return; // unknown panel or emptiness
527 spawn_panel(type, x1 * scale, y1 * scale, scale, scale);
530 static inline void fill_panel(const img_t *img, uint8_t *done, int x1, int y1, int scale) {
531 if (g_nooptimize)
532 fill_panel_single(img, done, x1, y1, scale);
533 else
534 fill_panel_opt(img, done, x1, y1, scale);
537 void convert_map(img_t *img, int scale) {
538 if (!img || !img->data) return;
540 uint8_t *done = calloc(1, img->w * img->h);
542 for (int x = 0; x < img->w; ++x) {
543 for (int y = 0; y < img->h; ++y) {
544 if (done[y * img->w + x]) continue;
545 fill_panel(img, done, x, y, scale);
549 map_width = img->w * scale;
550 map_height = img->h * scale;
553 int main(int argc, const char **argv) {
554 if (argc < 3) {
555 printf("usage: %s <input> <output> [options...]\n", argv[0]);
556 return -1;
559 for (int i = 3; i < argc; ++i) {
560 const char *err = opt_parse(argv[i]);
561 if (err) printf("error parsing option `%s`: %s\n", argv[i], err);
564 char path[PATHSIZE];
566 tex_check(g_resdir, !g_liquidstexture);
568 if (img_load(argv[1], &img_map))
569 die("could not load map image `%s`", argv[1]);
571 snprintf(path, sizeof(path), "%s/%s.png", g_resdir, g_bgname);
572 if (!img_load(path, &img_bg)) {
573 printf("scronching background: %s\n", path);
574 scronch_layer(PANEL_BACK, &img_bg, g_bgcell, g_bgsec);
575 img_free(&img_bg);
578 snprintf(path, sizeof(path), "%s/%s.png", g_resdir, g_fgname);
579 if (!img_load(path, &img_fg)) {
580 printf("scronching foreground: %s\n", path);
581 scronch_layer(PANEL_FORE, &img_fg, g_fgcell, g_fgsec);
582 img_free(&img_fg);
585 printf("converting map from image `%s` (%dx%d)\n", argv[1], img_map.w, img_map.h);
586 convert_map(&img_map, g_scale);
587 map_save(argv[2]);
588 img_free(&img_map);
590 return 0;