]> git.draconx.ca Git - liblbx.git/blob - src/lbximg.c
ada16929a32cf400797b089e849c4bdb371eabf0
[liblbx.git] / src / lbximg.c
1 /*
2  *  2ooM: The Master of Orion II Reverse Engineering Project
3  *  Simple command-line tool to convert an LBX image to other formats.
4  *  Copyright © 2006-2011, 2013 Nick Bowler
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 #include <config.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <stdbool.h>
23 #include <string.h>
24 #include <limits.h>
25 #include <assert.h>
26 #include <getopt.h>
27 #include <errno.h>
28
29 #include "tools.h"
30 #include "image.h"
31 #include "error.h"
32 #include "lbx.h"
33
34 #include "imgoutput.h"
35
36 /* Global flags */
37 static int verbose = 0;
38 static char *outname = "out";
39 static int usepalette = 1;
40
41 static void printusage(void)
42 {
43         puts("usage: lbximg [-i|-d] [-v] [-p palette_file] [-O override_file]"
44                           " [-f path]");
45         puts("              [-F format] [frameno ...]");
46 }
47
48 static void printhelp(void)
49 {
50         printusage();
51         puts("For now, see the man page for detailed help.");
52 }
53
54 enum {
55         MODE_NONE,
56         MODE_DECODE,
57         MODE_IDENT,
58 };
59
60 static const struct img_format {
61         img_output_func *output;
62         char name[4];
63         bool enabled;
64 } formats[] = {
65         { img_output_png, "png", 1 },
66         { img_output_pam, "pam", 1 },
67         { img_output_ppm, "ppm", 1 },
68         { img_output_pbm, "pbm", 1 },
69 };
70
71 static int lookup_format(const char *fmt)
72 {
73         for (size_t i = 0; i < sizeof formats / sizeof formats[0]; i++) {
74                 assert(!formats[i].name[sizeof formats[i].name - 1]);
75
76                 if (!fmt && formats[i].enabled)
77                         return i;
78
79                 if (strcmp(formats[i].name, fmt))
80                         continue;
81
82                 if (!formats[i].enabled) {
83                         tool_err(-1, "%s support disabled at build time", fmt);
84                         return -1;
85                 }
86
87                 return i;
88         }
89
90         tool_err(-1, "unknown format %s", fmt);
91         return -1;
92 }
93
94 bool img_is_masked(unsigned char **mask, unsigned width, unsigned height)
95 {
96         unsigned x, y;
97
98         for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
99                 if (mask[y][x] == 0)
100                         return true;
101         }
102
103         return false;
104 }
105
106 int parserange(unsigned frames, char *str, unsigned char *bits)
107 {
108         unsigned long start, end;
109         unsigned int i;
110         char *endptr;
111
112         start = strtoul(str, &endptr, 0);
113         if (start >= frames) {
114                 tool_err(-1, "frame %lu out of range.", start);
115                 return -1;
116         }
117
118         if (endptr == str) {
119                 tool_err(-1, "invalid frame range: %s.", str);
120                 return -1;
121         }
122
123         switch (*endptr) {
124         case '\0':
125                 end = start;
126                 break;
127         case '-':
128                 end = strtoul(endptr+1, &endptr, 0);
129                 if (end >= frames) {
130                         tool_err(-1, "frame %lu out of range.", end);
131                         return -1;
132                 }
133
134                 if (endptr == str)
135                         end = frames - 1;
136                 break;
137         default:
138                 tool_err(-1, "invalid frame range: %s.", str);
139                 return -1;
140         }
141
142         if (end < start) {
143                 tool_err(-1, "invalid frame range: %s.", str);
144                 return -1;
145         }
146
147         for (i = start; i <= end; i++) {
148                 bits[i / CHAR_BIT] |= 1 << (i % CHAR_BIT);
149         }
150
151         return 0;
152 }
153
154 int output(unsigned int frameno, const struct img_format *fmt,
155            unsigned char **framedata, unsigned char **mask,
156            unsigned int width, unsigned int height,
157            struct lbx_colour palette[static 256])
158 {
159         char name[strlen(outname) + sizeof ".65535.png"];
160         FILE *of;
161         int rc;
162
163         assert(fmt->output != NULL);
164         assert(frameno < 65536);
165         snprintf(name, sizeof name, "%s.%03d.%s", outname, frameno, fmt->name);
166
167         of = fopen(name, "wb");
168         if (!of) {
169                 tool_err(0, "failed to open %s", name);
170                 return -1;
171         }
172
173         rc = fmt->output(of, name, width, height, framedata, mask, palette);
174         if (rc < 0) {
175                 fclose(of);
176                 return -1;
177         }
178
179         if (fclose(of) == EOF) {
180                 tool_err(0, "error writing %s", name);
181                 return -1;
182         }
183                 
184         if (verbose)
185                 printf("wrote %s\n", name);
186
187         return 0;
188 }
189
190 static int loadoverride(FILE *f, struct lbx_colour palette[static 256])
191 {
192         struct lbx_image *overimg = lbx_img_open(f, &lbx_default_fops, NULL);
193         struct lbx_imginfo info;
194
195         if (!overimg) {
196                 tool_err(-1, "failed to open override image: %s", lbx_errmsg());
197                 return -1;
198         }
199         lbx_img_getinfo(overimg, &info);
200
201         if (!info.palettesz) {
202                 tool_err(-1, "override image has no palette.");
203                 lbx_img_close(overimg);
204                 return -1;
205         }
206
207         if (lbx_img_getpalette(overimg, palette) == -1) {
208                 tool_err(-1, "error reading override palette: %s", lbx_errmsg());
209                 lbx_img_close(overimg);
210                 return -1;
211         }
212
213         lbx_img_close(overimg);
214         return 0;
215 }
216
217 static int loadpalette(struct lbx_image *img, struct lbx_imginfo *info,
218                        FILE *palf, FILE *override,
219                        struct lbx_colour palette[static 256])
220 {
221         int i;
222
223         /* For sanity. */
224         if (!palf && !info->palettesz && !override) {
225                 tool_err(-1, "no palette available.");
226                 return -1;
227         }
228
229         /* Default the palette to a wonderful pink. */
230         for (i = 0; i < 256; i++) {
231                 palette[i] = (struct lbx_colour){0x3f, 0x00, 0x3f};
232         }
233
234         /* Read the external palette, if any. */
235         if (palf && lbx_img_loadpalette(palf, &lbx_default_fops, palette) != 0) {
236                 tool_err(-1, "error reading external palette: %s", lbx_errmsg());
237                 return -1;
238         }
239
240         /* Read the embedded palette, if any. */
241         if (info->palettesz && lbx_img_getpalette(img, palette) == -1) {
242                 tool_err(-1, "error reading embedded palette: %s", lbx_errmsg());
243                 return -1;
244         }
245
246         /* Read the override palette, if any. */
247         if (override && loadoverride(override, palette) == -1) {
248                 return -1;
249         }
250
251         return 0;
252 }
253
254 static int
255 decode(struct lbx_image *img, FILE *palf, FILE *override, int fmt, char **argv)
256 {
257         unsigned char *framebits;
258         struct lbx_colour palette[256];
259         struct lbx_imginfo info;
260         int extracted = 0;
261         unsigned int i;
262
263         assert(fmt >= 0 && fmt < sizeof formats / sizeof formats[0]);
264
265         lbx_img_getinfo(img, &info);
266
267         framebits = calloc(1, img->frames / CHAR_BIT + 1);
268         if (!framebits) {
269                 return EXIT_FAILURE;
270         }
271
272         /* Figure out what images we're extracting. */
273         if (!argv[0]) {
274                 /* extract all images by default. */
275                 memset(framebits, -1, img->frames / CHAR_BIT + 1);
276         } else {
277                 for (i = 0; argv[i]; i++) {
278                         parserange(img->frames, argv[i], framebits);
279                 }
280         }
281
282         if (usepalette) {
283                 if (loadpalette(img, &info, palf, override, palette) == -1) {
284                         goto err;
285                 }
286         }
287
288         /* Extract the images, in order. */
289         for (i = 0; i < img->frames; i++) {
290                 unsigned char **data;
291                 unsigned char **mask;
292
293                 if (!(framebits[i / CHAR_BIT] & (1 << (i % CHAR_BIT))))
294                         continue;
295
296                 data = lbx_img_getframe(img, i);
297                 if (!data) {
298                         tool_err(-1, "error in frame %u: %s", i, lbx_errmsg());
299                         continue;
300                 }
301
302                 mask = lbx_img_getmask(img);
303
304                 if (!output(i, &formats[fmt], data, mask,
305                             img->width, img->height,
306                             usepalette ? palette : NULL)) {
307                         extracted = 1;
308                 }
309         }
310
311         if (!extracted) {
312                 tool_err(-1, "no frames extracted.");
313                 goto err;
314         }
315
316         free(framebits);
317         return EXIT_SUCCESS;
318 err:
319         free(framebits);
320         return EXIT_FAILURE;
321 }
322
323 int main(int argc, char **argv)
324 {
325         int mode = MODE_NONE, fmt, opt, rc = EXIT_FAILURE;
326         struct lbx_pipe_state stdin_handle = { .f = stdin };
327         const char *file = NULL, *fmtstring = NULL;
328         FILE *palf = NULL, *overf = NULL;
329         struct lbx_image *img;
330
331         static const char sopts[] = "idnvF:f:p:O:VH";
332         static const struct option lopts[] = {
333                 { "ident",      0, NULL, 'i' },
334                 { "decode",     0, NULL, 'd' },
335                 { "verbose",    0, NULL, 'v' },
336                 { "file",       1, NULL, 'f' },
337                 { "format",     1, NULL, 'F' },
338                 { "palette",    1, NULL, 'p' },
339                 { "override",   1, NULL, 'p' },
340
341                 { "version",    0, NULL, 'V' },
342                 { "usage",      0, NULL, 'U' },
343                 { "help",       0, NULL, 'H' },
344
345                 { "no-palette", 0, NULL, 'n' },
346
347                 { 0 }
348         };
349
350         tool_init("lbximg", argc, argv);
351         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
352                 switch(opt) {
353                 case 'i':
354                         mode = MODE_IDENT;
355                         break;
356                 case 'd':
357                         mode = MODE_DECODE;
358                         break;
359                 case 'v':
360                         verbose = 1;
361                         break;
362                 case 'F':
363                         fmtstring = optarg;
364                         break;
365                 case 'f':
366                         file = optarg;
367                         break;
368                 case 'n':
369                         usepalette = 0;
370                         break;
371                 case 'p':
372                         palf = fopen(optarg, "rb");
373                         if (!palf) {
374                                 tool_err(0, "failed to open %s", optarg);
375                                 return EXIT_FAILURE;
376                         }
377
378                         break;
379                 case 'O':
380                         overf = fopen(optarg, "rb");
381                         if (!overf) {
382                                 tool_err(0, "failed to open %s", optarg);
383                                 return EXIT_FAILURE;
384                         }
385                         break;
386                 case 'V':
387                         tool_version();
388                         return EXIT_SUCCESS;
389                 case 'U':
390                         printusage();
391                         return EXIT_SUCCESS;
392                 case 'H':
393                         printhelp();
394                         return EXIT_SUCCESS;
395                 default:
396                         return EXIT_FAILURE;
397                 }
398         }
399
400         if (mode == MODE_NONE) {
401                 tool_err(-1, "you must specify a mode.");
402                 return EXIT_FAILURE;
403         }
404
405         fmt = lookup_format(fmtstring);
406         if (fmt < 0)
407                 return EXIT_FAILURE;
408
409         if (file)
410                 img = lbx_img_fopen(file);
411         else
412                 img = lbx_img_open(&stdin_handle, &lbx_pipe_fops, NULL);
413
414         if (!img) {
415                 tool_err(-1, "failed to open image: %s.", lbx_errmsg());
416                 return EXIT_FAILURE;
417         }
418
419         if (verbose || mode == MODE_IDENT) {
420                 struct lbx_imginfo info;
421
422                 if (!file)
423                         file = "stdin";
424
425                 lbx_img_getinfo(img, &info);
426                 printf("%s is %hux%hu LBX image, %hhu frame(s)%s%s%s\n",
427                        file, img->width, img->height, img->frames,
428                        info.palettesz ? ", embedded palette" : "",
429                        img->chunk     ? ", chunked" : "",
430                        info.looping   ? ", loops" : "");
431         }
432
433         switch (mode) {
434         case MODE_DECODE:
435                 rc = decode(img, palf, overf, fmt, &argv[optind]);
436                 break;
437         }
438
439         lbx_img_close(img);
440         return rc;
441 }