Use c99types.h from dxcommon.
[liblbx.git] / src / lbximg.c
blobd7b5f37ca46eef049aba7987cd838c04623ed633
1 /*
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/>.
21 #include <config.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <limits.h>
26 #include <assert.h>
27 #include <errno.h>
29 #include "help.h"
30 #include "xtra.h"
31 #include "gnu_getopt.h"
33 #include "tools.h"
34 #include "image.h"
35 #include "error.h"
36 #include "lbx.h"
37 #include "imgoutput.h"
38 #include "imgopts.h"
40 #define MIN(a, b) ((a) < (b) ? (a) : (b))
42 /* Global options */
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);
56 if (f != stdout)
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;
65 print_usage(stdout);
67 putchar('\n');
68 puts("Options:");
69 for (opt = lopts; opt->name; opt++) {
70 if (!lopt_get_help(opt, &help))
71 continue;
73 help_print_option(opt, help.arg, help.desc, 20);
75 putchar('\n');
77 puts("For more information, see the lbximg(1) man page.");
78 putchar('\n');
80 printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
83 enum {
84 FMT_PAM,
85 FMT_PBM,
86 FMT_PNG,
87 FMT_PPM
90 static const char format_ext[][4] = {
91 "pam",
92 "pbm",
93 "png",
94 "ppm"
97 static int lookup_format(const char *fmt)
99 unsigned i;
101 if (!fmt)
102 return FMT_PAM;
104 for (i = 0; i < sizeof format_ext / sizeof format_ext[0]; i++) {
105 if (!strcmp(fmt, format_ext[i]))
106 goto found;
109 tool_error("unknown format %s", fmt);
110 return -1;
111 found:
112 #if !HAVE_LIBPNG
113 if (i == FMT_PNG) {
114 tool_error("%s support unavailable", fmt);
115 return -1;
117 #endif
118 return i;
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)
128 return true;
131 if (mask[i] != (BITMAP_MAX >> (-sz & BITMAP_MASK)))
132 return true;
134 return false;
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;
142 FILE *of;
143 int rc;
145 switch (fmt) {
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;
149 #if HAVE_LIBPNG
150 case FMT_PNG: fmt_output = img_output_png; break;
151 #endif
152 default: assert(0);
155 if (!(of = fopen(name, "wb"))) {
156 tool_error("%s: failed to open: %s", name, strerror(errno));
157 return -1;
160 rc = fmt_output(of, name, img->width, img->height,
161 pixels, mask, palette);
162 if (rc < 0) {
163 fclose(of);
164 return -1;
167 if (fclose(of) == EOF) {
168 tool_error("%s: write failed: %s", name, strerror(errno));
169 return -1;
172 if (verbose)
173 printf("wrote %s\n", name);
175 return 0;
178 /* Return true iff a divides b. */
179 static bool divides(unsigned a, unsigned b)
181 if (b == 0)
182 return true;
183 if (a == 0)
184 return false;
186 return b % a == 0;
189 static int decode_frame(struct lbx_image *img, unsigned n,
190 unsigned char *pixels, bitmap_slice *mask)
192 unsigned x, y;
193 long rc;
195 rc = lbx_img_seek(img, n);
196 if (rc < 0) {
197 tool_error("frame %u: invalid frame: %s", n, lbx_errmsg());
198 return -1;
201 while ((rc = lbx_img_read_row_header(img, &x, &y)) != 0) {
202 unsigned long offset;
204 if (rc < 0) {
205 tool_error("frame %u: invalid row: %s", n, lbx_errmsg());
206 return -1;
209 offset = (unsigned long) y * img->width + x;
210 rc = lbx_img_read_row_data(img, pixels+offset);
211 if (rc < 0) {
212 tool_error("frame %u: error reading row: %s", n, lbx_errmsg());
213 return -1;
216 if (rc)
217 bitmap_set_range(mask, offset, offset+rc-1);
220 return 0;
223 static int parse_range(struct bitmap256 *dst, char *spec, unsigned nframes)
225 unsigned char range[3] = { 0 };
226 unsigned state = 0, cur = 0;
227 unsigned char c;
228 char *s;
230 range[1] = nframes-1;
231 for (s = spec; (c = *s); s++) {
232 if (state == 0 && c == '-') {
233 state++;
234 cur = 0;
235 } else if ((c -= '0') < 10 && (cur = 10 * cur + c) < 256) {
236 range[state] = cur;
237 } else {
238 goto invalid;
242 if ((range[state+1] = range[state]) < range[0])
243 goto invalid;
245 if (range[state] >= nframes)
246 tool_warn("%s: frame out of range", spec);
248 bitmap256_set_range(dst, range[0], range[1]);
249 return 0;
250 invalid:
251 tool_error("%s: invalid frame specification", spec);
252 return -1;
255 static int parse_ranges(struct bitmap256 *dst, char **argv, struct lbx_image *img)
257 unsigned i;
258 int rc;
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)
265 return -1;
268 return 0;
271 static bitmap_slice *alloc_scratch(struct lbx_image *img, uint_least32_t *sz)
273 uint_least32_t img_sz, mask_sz;
274 bitmap_slice *ret;
276 img_sz = (uint_least32_t) img->width * img->height;
277 if (!img_sz) {
278 img_sz = 1;
279 *sz = mask_sz = 0;
280 } else {
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))) {
286 return ret;
290 tool_error("failed to allocate memory");
291 return NULL;
294 /* Format n into three decimal digits, writing to dst. */
295 static void update_name(char *dst, unsigned char n)
297 unsigned char q;
299 q = ((n*205)>>8)>>3;
300 dst[2] = '0' + (n - 10*q);
301 dst[0] = (q*26)>>8;
302 dst[1] = '0' + (q - 10*dst[0]);
303 dst[0] += '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;
311 bitmap_slice *mask;
312 char *name, *ext;
313 size_t name_sz;
315 bool extracted = false;
316 int i, ret;
318 if (!no_palette && !have_palette && fmt != FMT_PBM)
319 tool_warn("empty palette");
321 if (parse_ranges(&selected, argv, img))
322 return EXIT_FAILURE;
324 if (!(mask = alloc_scratch(img, &mask_sz)))
325 return EXIT_FAILURE;
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);
334 ext[3] = '.';
336 /* Extract the images, in order. */
337 ret = EXIT_SUCCESS;
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) {
343 ret = EXIT_FAILURE;
344 break;
347 if (!bitmap256_test_bit(&selected, i))
348 continue;
350 update_name(ext, i);
351 if (output(name, fmt, pixels, mask, img) < 0)
352 ret = EXIT_FAILURE;
353 else
354 extracted = true;
357 if (!extracted)
358 tool_warn("no frames extracted");
360 free(name);
361 free(mask);
362 return ret;
365 static int load_base_palette(const char *file, struct lbx_colour *out)
367 FILE *f;
368 int n;
370 if (!(f = fopen(file, "rb"))) {
371 tool_error("%s: failed to open: %s", file, strerror(errno));
372 return -1;
375 if ((n = lbx_img_loadpalette(f, NULL, out)) < 0)
376 tool_error("%s: read failed: %s", file, lbx_errmsg());
378 fclose(f);
379 return n;
382 static int load_over_palette(const char *file, struct lbx_colour *out)
384 struct lbx_image *img;
385 int n;
387 if (!(img = lbx_img_fopen(file))) {
388 tool_error("%s: failed to open: %s", file, lbx_errmsg());
389 return -1;
392 if ((n = lbx_img_getpalette(img, out)) < 0)
393 tool_error("%s: read failed: %s", file, lbx_errmsg());
394 else if (!n)
395 tool_warn("%s: no embedded palette", file);
397 lbx_img_close(img);
398 return n;
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;
406 int n;
408 if (!f || !strcmp(f, "-")) {
409 if (!(img = lbx_img_open(stdin, NULL, NULL)))
410 goto err_open;
411 *filename = "standard input";
412 } else if (!(img = lbx_img_fopen(f))) {
413 goto err_open;
416 if ((n = lbx_img_getpalette(img, palette)) < 0) {
417 tool_error("%s: read failed: %s", infile, lbx_errmsg());
418 lbx_img_close(img);
419 return -1;
422 *out_img = img;
423 return n;
424 err_open:
425 tool_error("%s: failed to open: %s", infile, lbx_errmsg());
426 return -1;
429 enum {
430 MODE_EXIT_SUCCESS = -1,
431 MODE_EXIT_FAILURE = 0,
432 MODE_NONE = MODE_EXIT_FAILURE,
433 MODE_DECODE,
434 MODE_IDENT
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);
441 if (palcount)
442 printf(", %d palette entries", palcount);
443 if (img->frames > 1)
444 printf(", %u frames", (unsigned)img->frames);
445 if (img->chunk)
446 printf(", chunked");
447 if (img->leadin+1 < img->frames)
448 printf(", loops");
450 putchar('\n');
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;
457 int i, rc, opt;
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) {
466 switch (opt) {
467 case 'i':
468 mode = MODE_IDENT;
469 break;
470 case 'd':
471 mode = MODE_DECODE;
472 break;
473 case 'v':
474 verbose = true;
475 break;
476 case 'F':
477 if ((format_id = lookup_format(optarg)) < 0)
478 return MODE_EXIT_FAILURE;
479 break;
480 case 'f':
481 infile = optarg;
482 break;
483 case 'n':
484 no_palette = true;
485 break;
486 case 'p':
487 if (load_base_palette(optarg, tmp_palette) < 0)
488 return MODE_EXIT_FAILURE;
489 have_palette = true;
490 break;
491 case 'O':
492 if ((rc = load_over_palette(optarg, lbx_palette)) < 0)
493 return MODE_EXIT_FAILURE;
494 else if (rc)
495 have_palette = true;
496 break;
497 case LOPT_OUTPUT_PREFIX:
498 outname = optarg;
499 break;
500 case 'V':
501 tool_version();
502 return MODE_EXIT_SUCCESS;
503 case 'H':
504 print_help(lopts);
505 return MODE_EXIT_SUCCESS;
506 default:
507 print_usage(stderr);
508 return MODE_EXIT_FAILURE;
512 if (mode == MODE_NONE) {
513 tool_error("no mode specified");
514 print_usage(stderr);
517 if ((rc = open_image(&infile, tmp_palette, out_img)) < 0)
518 return MODE_EXIT_FAILURE;
519 else if (rc)
520 have_palette = true;
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];
530 return mode;
533 int main(int argc, char **argv)
535 struct lbx_image *img;
536 int ret = 0;
538 switch (initialize(argc, argv, &img)) {
539 case MODE_EXIT_SUCCESS: return EXIT_SUCCESS;
540 case MODE_EXIT_FAILURE: return EXIT_FAILURE;
541 case MODE_DECODE:
542 ret = decode(img, format_id, &argv[optind]);
545 lbx_img_close(img);
546 return ret;