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 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
;
235 fwrite(&blk
, sizeof(blk
), 1, fout
);
240 int map_cachetex(const char *name
) {
241 static char path
[PATHSIZE
];
243 if (!name
|| !name
[0])
246 if (!strncmp(name
, "_water_", 7))
247 snprintf(path
, PATHSIZE
, name
);
248 else if (strchr(name
, '\\'))
249 snprintf(path
, PATHSIZE
, ":\\%s.PNG", name
);
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))
257 int i
= map_numtex
++;
258 map_tex
= realloc(map_tex
, map_numtex
* sizeof(map_texture_t
));
259 if (!map_tex
) return -1;
262 strncpy(map_tex
[i
].resname
, path
, 64);
263 map_tex
[i
].resname
[63] = 0;
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);
282 map_pan
[i
].texid
= texid
;
284 map_pan
[i
].flags
= PFLAG_HIDE
;
285 if (type
>= PANEL_WATER
&& type
<= PANEL_ACID2
)
286 map_pan
[i
].flags
|= PFLAG_WATERTEXTURES
;
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);
304 void img_free(img_t
*img
) {
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
;
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
) {
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
;
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
);
350 snprintf(path
, sizeof(path
), fname
);
351 return !stbi_write_png(path
, src
->w
, src
->h
, 4, src
->data
, 4 * src
->w
);
356 void die(const char *fmt
, ...) {
357 fprintf(stderr
, "FATAL ERROR: ");
360 vfprintf(stderr
, fmt
, 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
);
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 };
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");
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
];
401 void scronch_layer(int layer
, img_t
*img
, int cellsize
, const char *secname
) {
404 img_t
*cells
= cellsize
? img_segment(img
, cellsize
, &numx
, &numy
) : img
;
406 if (!numx
|| !numy
|| !cells
)
407 die("scronching failure");
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
);
429 img_free(cells
+ idx
);
430 if (map_addpanel(layer
, path
, x
, y
, pw
, ph
) < 0)
431 die("could not add layer panel");
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
)
447 static inline int spawn_panel(uint32_t type
, int x
, int y
, int w
, int h
) {
448 const char *tex
= tex_name
[type
];
454 if (g_liquidstexture
== 1 || g_liquidstexture
== 2) {
455 map_addpanel(PANEL_BACK
+ g_liquidstexture
- 1, tex
, x
, y
, w
, h
);
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
);
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
);
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
) {
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
)
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
);
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
) {
528 fill_panel_single(img
, done
, x1
, y1
, scale
);
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
) {
551 printf("usage: %s <input> <output> [options...]\n", argv
[0]);
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
);
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
);
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
);
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
);