2 * 2ooM: The Master of Orion II Reverse Engineering Project
3 * Simple command-line tool to convert an LBX image to other formats.
5 * Copyright © 2006-2011, 2013-2014, 2021, 2024 Nick Bowler
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 #include "gnu_getopt.h"
37 #include "imgoutput.h"
40 #define MIN(a, b) ((a) < (b) ? (a) : (b))
43 static bool no_palette
, have_palette
, verbose
;
44 static signed char format_id
;
46 static const char *outname
= "out_";
47 static const char *infile
= NULL
;
49 static struct lbx_colour lbx_palette
[256];
51 static void print_usage(FILE *f
)
53 const char *progname
= tool_invocation();
55 fprintf(f
, "Usage: %s [options] [-i|-d] [frame ...]\n", progname
);
57 fprintf(f
, "Try %s --help for more information.\n", progname
);
60 static void print_help(const struct option
*lopts
)
62 const struct option
*opt
;
63 struct lopt_help help
;
69 for (opt
= lopts
; opt
->name
; opt
++) {
70 if (!lopt_get_help(opt
, &help
))
73 help_print_option(opt
, help
.arg
, help
.desc
, 20);
77 puts("For more information, see the lbximg(1) man page.");
80 printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT
);
90 static const char format_ext
[][4] = {
97 static int lookup_format(const char *fmt
)
104 for (i
= 0; i
< sizeof format_ext
/ sizeof format_ext
[0]; i
++) {
105 if (!strcmp(fmt
, format_ext
[i
]))
109 tool_error("unknown format %s", fmt
);
114 tool_error("%s support unavailable", fmt
);
121 bool img_is_masked(bitmap_slice
*mask
, unsigned width
, unsigned height
)
123 uint_least32_t sz
= (uint_least32_t) width
* height
;
124 long i
, n
= BITMAP_SIZE(sz
);
126 for (i
= 0; i
< n
-1; i
++) {
127 if (mask
[i
] != BITMAP_MAX
)
131 if (mask
[i
] != (BITMAP_MAX
>> (-sz
& BITMAP_MASK
)))
137 static int output(const char *name
, int fmt
, unsigned char *pixels
,
138 bitmap_slice
*mask
, struct lbx_image
*img
)
140 struct lbx_colour
*palette
= no_palette
? NULL
: lbx_palette
;
141 img_output_func
*fmt_output
;
146 case FMT_PAM
: fmt_output
= img_output_pam
; break;
147 case FMT_PBM
: fmt_output
= img_output_pbm
; break;
148 case FMT_PPM
: fmt_output
= img_output_ppm
; break;
150 case FMT_PNG
: fmt_output
= img_output_png
; break;
155 if (!(of
= fopen(name
, "wb"))) {
156 tool_error("%s: failed to open: %s", name
, strerror(errno
));
160 rc
= fmt_output(of
, name
, img
->width
, img
->height
,
161 pixels
, mask
, palette
);
167 if (fclose(of
) == EOF
) {
168 tool_error("%s: write failed: %s", name
, strerror(errno
));
173 printf("wrote %s\n", name
);
178 /* Return true iff a divides b. */
179 static bool divides(unsigned a
, unsigned b
)
189 static int decode_frame(struct lbx_image
*img
, unsigned n
,
190 unsigned char *pixels
, bitmap_slice
*mask
)
195 rc
= lbx_img_seek(img
, n
);
197 tool_error("frame %u: invalid frame: %s", n
, lbx_errmsg());
201 while ((rc
= lbx_img_read_row_header(img
, &x
, &y
)) != 0) {
202 unsigned long offset
;
205 tool_error("frame %u: invalid row: %s", n
, lbx_errmsg());
209 offset
= (unsigned long) y
* img
->width
+ x
;
210 rc
= lbx_img_read_row_data(img
, pixels
+offset
);
212 tool_error("frame %u: error reading row: %s", n
, lbx_errmsg());
217 bitmap_set_range(mask
, offset
, offset
+rc
-1);
223 static int parse_range(struct bitmap256
*dst
, char *spec
, unsigned nframes
)
225 unsigned char range
[3] = { 0 };
226 unsigned state
= 0, cur
= 0;
230 range
[1] = nframes
-1;
231 for (s
= spec
; (c
= *s
); s
++) {
232 if (state
== 0 && c
== '-') {
235 } else if ((c
-= '0') < 10 && (cur
= 10 * cur
+ c
) < 256) {
242 if ((range
[state
+1] = range
[state
]) < range
[0])
245 if (range
[state
] >= nframes
)
246 tool_warn("%s: frame out of range", spec
);
248 bitmap256_set_range(dst
, range
[0], range
[1]);
251 tool_error("%s: invalid frame specification", spec
);
255 static int parse_ranges(struct bitmap256
*dst
, char **argv
, struct lbx_image
*img
)
260 for (i
= 0; i
< BITMAP256_COUNT
; i
++)
261 dst
->map
[i
] = argv
[0] ? 0 : -1;
263 for (i
= 0; argv
[i
]; i
++) {
264 if ((rc
= parse_range(dst
, argv
[i
], img
->frames
)) < 0)
271 static bitmap_slice
*alloc_scratch(struct lbx_image
*img
, uint_least32_t *sz
)
273 uint_least32_t img_sz
, mask_sz
;
276 img_sz
= (uint_least32_t) img
->width
* img
->height
;
281 *sz
= mask_sz
= BITMAP_SIZE(img_sz
) * sizeof (bitmap_slice
);
284 if (img_sz
<= (size_t)-((size_t)1 + mask_sz
)) {
285 if ((ret
= calloc((size_t) img_sz
+ mask_sz
, 1))) {
290 tool_error("failed to allocate memory");
294 /* Format n into three decimal digits, writing to dst. */
295 static void update_name(char *dst
, unsigned char n
)
300 dst
[2] = '0' + (n
- 10*q
);
302 dst
[1] = '0' + (q
- 10*dst
[0]);
306 static int decode(struct lbx_image
*img
, int fmt
, char **argv
)
308 struct bitmap256 selected
;
309 uint_least32_t mask_sz
;
310 unsigned char *pixels
;
315 bool extracted
= false;
318 if (!no_palette
&& !have_palette
&& fmt
!= FMT_PBM
)
319 tool_warn("empty palette");
321 if (parse_ranges(&selected
, argv
, img
))
324 if (!(mask
= alloc_scratch(img
, &mask_sz
)))
326 pixels
= (unsigned char *)mask
+ mask_sz
;
328 name_sz
= strlen(outname
);
329 if (!(name
= malloc(name_sz
+ sizeof ".000.xyz")))
330 return free(mask
), EXIT_FAILURE
;
332 ext
= (char *)memcpy(name
, outname
, name_sz
) + name_sz
;
333 memcpy(ext
+4, format_ext
[fmt
], 4);
336 /* Extract the images, in order. */
338 for (i
= 0; i
< img
->frames
; i
++) {
339 if (divides(img
->chunk
, i
))
340 memset(mask
, 0, mask_sz
);
342 if (decode_frame(img
, i
, pixels
, mask
) < 0) {
347 if (!bitmap256_test_bit(&selected
, i
))
351 if (output(name
, fmt
, pixels
, mask
, img
) < 0)
358 tool_warn("no frames extracted");
365 static int load_base_palette(const char *file
, struct lbx_colour
*out
)
370 if (!(f
= fopen(file
, "rb"))) {
371 tool_error("%s: failed to open: %s", file
, strerror(errno
));
375 if ((n
= lbx_img_loadpalette(f
, NULL
, out
)) < 0)
376 tool_error("%s: read failed: %s", file
, lbx_errmsg());
382 static int load_over_palette(const char *file
, struct lbx_colour
*out
)
384 struct lbx_image
*img
;
387 if (!(img
= lbx_img_fopen(file
))) {
388 tool_error("%s: failed to open: %s", file
, lbx_errmsg());
392 if ((n
= lbx_img_getpalette(img
, out
)) < 0)
393 tool_error("%s: read failed: %s", file
, lbx_errmsg());
395 tool_warn("%s: no embedded palette", file
);
401 static int open_image(const char **filename
, struct lbx_colour
*palette
,
402 struct lbx_image
**out_img
)
404 const char *f
= *filename
;
405 struct lbx_image
*img
;
408 if (!f
|| !strcmp(f
, "-")) {
409 if (!(img
= lbx_img_open(stdin
, NULL
, NULL
)))
411 *filename
= "standard input";
412 } else if (!(img
= lbx_img_fopen(f
))) {
416 if ((n
= lbx_img_getpalette(img
, palette
)) < 0) {
417 tool_error("%s: read failed: %s", infile
, lbx_errmsg());
425 tool_error("%s: failed to open: %s", infile
, lbx_errmsg());
430 MODE_EXIT_SUCCESS
= -1,
431 MODE_EXIT_FAILURE
= 0,
432 MODE_NONE
= MODE_EXIT_FAILURE
,
437 static void identify(const char *filename
, int palcount
, struct lbx_image
*img
)
439 printf("%s: %ux%u LBX image", filename
, (unsigned)img
->width
,
440 (unsigned)img
->height
);
442 printf(", %d palette entries", palcount
);
444 printf(", %u frames", (unsigned)img
->frames
);
447 if (img
->leadin
+1 < img
->frames
)
453 static int initialize(int argc
, char **argv
, struct lbx_image
**out_img
)
455 struct lbx_colour tmp_palette
[256], pink
= { 63, 0, 63, 0 };
456 int mode
= MODE_NONE
;
459 XTRA_PACKED_LOPTS2(lopts
, packed_lopts
);
460 tool_init("lbximg", argc
, argv
);
462 for (i
= 0; i
< 256; i
++)
463 tmp_palette
[i
] = pink
;
465 while ((opt
= getopt_long(argc
, argv
, SOPT_STRING
, lopts
, 0)) != -1) {
477 if ((format_id
= lookup_format(optarg
)) < 0)
478 return MODE_EXIT_FAILURE
;
487 if (load_base_palette(optarg
, tmp_palette
) < 0)
488 return MODE_EXIT_FAILURE
;
492 if ((rc
= load_over_palette(optarg
, lbx_palette
)) < 0)
493 return MODE_EXIT_FAILURE
;
497 case LOPT_OUTPUT_PREFIX
:
502 return MODE_EXIT_SUCCESS
;
505 return MODE_EXIT_SUCCESS
;
508 return MODE_EXIT_FAILURE
;
512 if (mode
== MODE_NONE
) {
513 tool_error("no mode specified");
517 if ((rc
= open_image(&infile
, tmp_palette
, out_img
)) < 0)
518 return MODE_EXIT_FAILURE
;
522 if (verbose
|| mode
== MODE_IDENT
)
523 identify(infile
, rc
, *out_img
);
525 for (i
= 0; i
< 256; i
++) {
526 if (!lbx_palette
[i
].active
)
527 lbx_palette
[i
] = tmp_palette
[i
];
533 int main(int argc
, char **argv
)
535 struct lbx_image
*img
;
538 switch (initialize(argc
, argv
, &img
)) {
539 case MODE_EXIT_SUCCESS
: return EXIT_SUCCESS
;
540 case MODE_EXIT_FAILURE
: return EXIT_FAILURE
;
542 ret
= decode(img
, format_id
, &argv
[optind
]);