1 /* Copyright (C) Doom 2D: Forever Developers
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.
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.
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/>.
24 #define STB_IMAGE_IMPLEMENTATION
25 #include "stb_image.h"
26 #define STB_IMAGE_WRITE_IMPLEMENTATION
27 #include "stb_image_write.h"
30 #define PANEL_ALLOC_STEP 128
33 # define f_mkdir(name) mkdir(name)
35 # define f_mkdir(name) mkdir(name, 0777)
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
[] = {
76 const uint32_t map_palette
[] = {
85 0xFF244874, // STREAMUP
86 0xFF607C74, // STREAMDOWN
87 0xFF5CDC64, // OPENDOOR
89 0xFFC000C0, // BLOCKMON
90 0xFF0450C8, // STREAMLT
91 0xFF388CFC, // STREAMRT
94 map_panel_t
*map_pan
= NULL
;
97 map_texture_t
*map_tex
= NULL
;
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 };
121 const char *sel
[OPT_NUMSEL
];
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
[] = {
141 DEF_OPT_BOOL(nooptimize
),
146 DEF_OPT_SEL(streamstexture
, "no", "bg", "fg"),
147 DEF_OPT_SEL(liquidstexture
, "no", "bg", "fg", "yes", "xtra"),
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";
158 char *eq
= strrchr(str
, '=');
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
))
167 switch (opt_defs
[i
].type
) {
169 if (eq
) return "don't need an explicit value";
170 *opt_defs
[i
].val_int
= 1;
173 if (!eq
) return "need numeric value";
174 *opt_defs
[i
].val_int
= atoi(vals
);
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;
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
;
189 return "invalid variant value";
195 return "unknown option";
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");
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
;
239 fwrite(&blk
, sizeof(blk
), 1, fout
);
244 int map_cachetex(const char *name
) {
245 static char path
[PATHSIZE
];
247 if (!name
|| !name
[0])
250 if (!strncmp(name
, "_water_", 7))
251 snprintf(path
, PATHSIZE
, name
);
252 else if (strchr(name
, '\\'))
253 snprintf(path
, PATHSIZE
, ":\\%s.PNG", name
);
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))
261 int i
= map_numtex
++;
262 map_tex
= realloc(map_tex
, map_numtex
* sizeof(map_texture_t
));
263 if (!map_tex
) return -1;
266 strncpy(map_tex
[i
].resname
, path
, 64);
267 map_tex
[i
].resname
[63] = 0;
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);
286 map_pan
[i
].texid
= texid
;
288 map_pan
[i
].flags
= PFLAG_HIDE
;
289 if (type
>= PANEL_WATER
&& type
<= PANEL_ACID2
)
290 map_pan
[i
].flags
|= PFLAG_WATERTEXTURES
;
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);
308 void img_free(img_t
*img
) {
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
;
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
) {
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
;
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
);
354 snprintf(path
, sizeof(path
), fname
);
355 return !stbi_write_png(path
, src
->w
, src
->h
, 4, src
->data
, 4 * src
->w
);
360 void die(const char *fmt
, ...) {
361 fprintf(stderr
, "FATAL ERROR: ");
364 vfprintf(stderr
, fmt
, 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
);
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 };
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");
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
];
405 void scronch_layer(int layer
, img_t
*img
, int cellsize
, const char *secname
) {
408 img_t
*cells
= cellsize
? img_segment(img
, cellsize
, &numx
, &numy
) : img
;
410 if (!numx
|| !numy
|| !cells
)
411 die("scronching failure");
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
);
433 img_free(cells
+ idx
);
434 if (map_addpanel(layer
, path
, x
, y
, pw
, ph
) < 0)
435 die("could not add layer panel");
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
)
451 static inline int spawn_panel(uint32_t type
, int x
, int y
, int w
, int h
) {
452 const char *tex
= tex_name
[type
];
458 if (g_liquidstexture
== 1 || g_liquidstexture
== 2) {
459 map_addpanel(PANEL_BACK
+ g_liquidstexture
- 1, tex
, x
, y
, w
, h
);
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
);
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
);
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
) {
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
)
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
);
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
) {
532 fill_panel_single(img
, done
, x1
, y1
, scale
);
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
) {
555 printf("usage: %s <input> <output> [options...]\n", argv
[0]);
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
);
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
);
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
);
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
);