]> git.draconx.ca Git - liblbx.git/blob - src/lbximg.c
liblbx: Remove now-redundant fields from lbx_imginfo.
[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 a set of PNGs.
4  *  Copyright (C) 2006-2010 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 <string.h>
23 #include <limits.h>
24 #include <assert.h>
25 #include <getopt.h>
26 #include <errno.h>
27
28 #include <png.h>
29
30 #include "tools.h"
31 #include "image.h"
32 #include "error.h"
33 #include "lbx.h"
34
35 /* Global flags */
36 static int verbose = 0;
37 static char *outname = "out";
38 static int usepalette = 1;
39
40 static void printusage(void)
41 {
42         puts("usage: lbximg [-i|-d] [-v] [-p palette_file] [-O override_file]"
43                           " [-f path]");
44         puts("              [frameno ...]");
45 }
46
47 static void printhelp(void)
48 {
49         printusage();
50         puts("For now, see the man page for detailed help.");
51 }
52
53 static const char *progname;
54 #define errmsg(fmt, ...) (\
55         fprintf(stderr, "%s: " fmt, progname, __VA_ARGS__)\
56 )
57
58 enum {
59         MODE_NONE,
60         MODE_DECODE,
61         MODE_IDENT,
62 };
63
64 int parserange(unsigned frames, char *str, unsigned char *bits)
65 {
66         unsigned long start, end;
67         unsigned int i;
68         char *endptr;
69
70         start = strtoul(str, &endptr, 0);
71         if (start >= frames) {
72                 errmsg("frame %lu out of range.\n", start);
73                 return -1;
74         }
75
76         if (endptr == str) {
77                 errmsg("invalid frame range: %s.\n", str);
78                 return -1;
79         }
80
81         switch (*endptr) {
82         case '\0':
83                 end = start;
84                 break;
85         case '-':
86                 end = strtoul(endptr+1, &endptr, 0);
87                 if (end >= frames) {
88                         errmsg("frame %lu out of range.\n", end);
89                         return -1;
90                 }
91
92                 if (endptr == str)
93                         end = frames - 1;
94                 break;
95         default:
96                 errmsg("invalid frame range: %s.\n", str);
97                 return -1;
98         }
99
100         if (end < start) {
101                 errmsg("invalid frame range: %s.\n", str);
102                 return -1;
103         }
104
105         for (i = start; i <= end; i++) {
106                 bits[i / CHAR_BIT] |= 1 << (i % CHAR_BIT);
107         }
108
109         return 0;
110 }
111
112 static int ismasked(unsigned char **mask, unsigned width, unsigned height)
113 {
114         unsigned y, x;
115         for (y = 0; y < height; y++) {
116                 for (x = 0; x < width; x++) {
117                         if (mask[y][x] == 0) return 1;
118                 }
119         }
120
121         return 0;
122 }
123
124 int outpng(unsigned int frameno,
125            unsigned char **framedata, unsigned char **mask,
126            unsigned int width, unsigned int height,
127            struct lbx_colour palette[static 256])
128 {
129         char name[strlen(outname) + sizeof ".65535.png"];
130         unsigned char *row;
131         unsigned int x, y;
132         FILE *of;
133
134         png_structp png;
135         png_infop   info;
136
137         assert(frameno < 65536);
138         snprintf(name, sizeof name, "%s.%03d.png", outname, frameno);
139
140         row = malloc(4 * width);
141         if (!row) {
142                 errmsg("failed to allocate row buffer: %s\n", strerror(errno));
143                 return -1;
144         }
145
146         of = fopen(name, "wb");
147         if (!of) {
148                 errmsg("failed to open %s: %s.\n", name, strerror(errno));
149                 free(row);
150                 return -1;
151         }
152
153         png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
154         if (!png) {
155                 errmsg("failed to init libpng.\n", 0);
156                 goto err;
157         }
158
159         info = png_create_info_struct(png);
160         if (!info) {
161                 errmsg("failed to init libpng.\n", 0);
162                 png_destroy_write_struct(&png, NULL);
163                 goto err;
164         }
165
166         if (setjmp(png_jmpbuf(png))) {
167                 png_destroy_write_struct(&png, &info);
168                 goto err;
169         }
170
171         png_init_io(png, of);
172
173         if (!ismasked(mask, width, height)) {
174                 /*
175                  * This case is easy; we can just feed the palette and pixel
176                  * data to libpng and let it do its magic.
177                  */
178
179                 png_color png_palette[256];
180                 for (unsigned i = 0; i < 256; i++) {
181                         png_palette[i].red   = palette[i].red;
182                         png_palette[i].green = palette[i].green;
183                         png_palette[i].blue  = palette[i].blue;
184                 }
185
186                 png_set_IHDR(png, info, width, height, 8,
187                              PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
188                              PNG_COMPRESSION_TYPE_DEFAULT,
189                              PNG_FILTER_TYPE_DEFAULT);
190                 
191                 png_set_PLTE(png, info, png_palette, 256);
192                 png_set_rows(png, info, framedata);
193                 png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
194         } else {
195                 /*
196                  * Unfortunately, LBX doesn't translate nicely to PNG here.
197                  * LBX has a 256 colour palette _plus_ transparency.
198                  * We'll form an RGBA PNG to deal with this.
199                  */
200
201                 png_set_IHDR(png, info, width, height, 8,
202                              PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
203                              PNG_COMPRESSION_TYPE_DEFAULT,
204                              PNG_FILTER_TYPE_DEFAULT);
205         
206                 png_write_info(png, info);
207         
208                 for (y = 0; y < height; y++) {
209                         for (x = 0; x < width; x++) {
210                                 row[4*x+0] = palette[framedata[y][x]].red;
211                                 row[4*x+1] = palette[framedata[y][x]].green;
212                                 row[4*x+2] = palette[framedata[y][x]].blue;
213                                 row[4*x+3] = (mask[y][x]) ? -1 : 0;
214                         }
215         
216                         png_write_row(png, row);
217                 }
218         
219                 png_write_end(png, NULL);
220         }
221
222         png_destroy_write_struct(&png, &info);
223         fclose(of);
224         free(row);
225
226         if (verbose)
227                 printf("wrote %s\n", name);
228         return 0;
229 err:
230         fclose(of);
231         remove(name);
232         free(row);
233         return -1;
234 }
235
236 static int loadoverride(FILE *f, struct lbx_colour palette[static 256])
237 {
238         struct lbx_image *overimg = lbx_img_open(f, &lbx_default_fops, NULL);
239         struct lbx_imginfo info;
240
241         if (!overimg) {
242                 errmsg("failed to open override image: %s\n", lbx_errmsg());
243                 return -1;
244         }
245         lbx_img_getinfo(overimg, &info);
246
247         if (!info.palettesz) {
248                 errmsg("override image has no palette.\n", 0);
249                 lbx_img_close(overimg);
250                 return -1;
251         }
252
253         if (lbx_img_getpalette(overimg, palette) == -1) {
254                 errmsg("error reading override palette: %s\n", lbx_errmsg());
255                 lbx_img_close(overimg);
256                 return -1;
257         }
258
259         lbx_img_close(overimg);
260         return 0;
261 }
262
263 static int loadpalette(struct lbx_image *img, struct lbx_imginfo *info,
264                        FILE *palf, FILE *override,
265                        struct lbx_colour palette[static 256])
266 {
267         int i;
268
269         /* In no-palette mode, use palette indices for colour. */
270         if (!usepalette) {
271                 for (i = 0; i < 256; i++) {
272                         palette[i] = (struct lbx_colour){i,i,i};
273                 }
274
275                 return 0;
276         }
277
278         /* For sanity. */
279         if (!palf && !info->palettesz && !override) {
280                 errmsg("no palette available.\n", 0);
281                 return -1;
282         }
283
284         /* Default the palette to a wonderful pink. */
285         for (i = 0; i < 256; i++) {
286                 palette[i] = (struct lbx_colour){0xff, 0x00, 0xff};
287         }
288
289         /* Read the external palette, if any. */
290         if (palf && lbx_img_loadpalette(palf, &lbx_default_fops, palette) != 0) {
291                 errmsg("error reading external palette: %s\n", lbx_errmsg());
292                 return -1;
293         }
294
295         /* Read the embedded palette, if any. */
296         if (info->palettesz && lbx_img_getpalette(img, palette) == -1) {
297                 errmsg("error reading embedded palette: %s\n", lbx_errmsg());
298                 return -1;
299         }
300
301         /* Read the override palette, if any. */
302         if (override && loadoverride(override, palette) == -1) {
303                 return -1;
304         }
305
306         return 0;
307 }
308
309 int decode(struct lbx_image *img, FILE *palf, FILE *override, char **argv)
310 {
311         unsigned char *framebits;
312         struct lbx_colour palette[256];
313         struct lbx_imginfo info;
314         int extracted = 0;
315         unsigned int i;
316
317         lbx_img_getinfo(img, &info);
318
319         framebits = calloc(1, img->frames / CHAR_BIT + 1);
320         if (!framebits) {
321                 return EXIT_FAILURE;
322         }
323
324         /* Figure out what images we're extracting. */
325         if (!argv[0]) {
326                 /* extract all images by default. */
327                 memset(framebits, -1, img->frames / CHAR_BIT + 1);
328         } else {
329                 for (i = 0; argv[i]; i++) {
330                         parserange(img->frames, argv[i], framebits);
331                 }
332         }
333
334         if (loadpalette(img, &info, palf, override, palette) == -1) {
335                 goto err;
336         }
337
338         /* Extract the images, in order. */
339         for (i = 0; i < img->frames; i++) {
340                 unsigned char **data;
341                 unsigned char **mask;
342
343                 if (!(framebits[i / CHAR_BIT] & (1 << (i % CHAR_BIT))))
344                         continue;
345
346                 data = lbx_img_getframe(img, i);
347                 if (!data) {
348                         errmsg("error in frame %u: %s\n", i, lbx_errmsg());
349                         continue;
350                 }
351
352                 mask = lbx_img_getmask(img);
353
354                 if (!outpng(i, data, mask, img->width, img->height, palette)) {
355                         extracted = 1;
356                 }
357         }
358
359         if (!extracted) {
360                 errmsg("no frames extracted.\n", 0);
361                 goto err;
362         }
363
364         free(framebits);
365         return EXIT_SUCCESS;
366 err:
367         free(framebits);
368         return EXIT_FAILURE;
369 }
370
371 int main(int argc, char **argv)
372 {
373         int mode = MODE_NONE, opt, rc = EXIT_FAILURE;
374         struct lbx_pipe_state stdin_handle = { .f = stdin };
375         FILE *palf = NULL, *overf = NULL;
376         const char *file = NULL;
377         struct lbx_image *img;
378
379         static const char *sopts = "idnvf:p:O:V";
380         static const struct option lopts[] = {
381                 { "ident",      0, NULL, 'i' },
382                 { "decode",     0, NULL, 'd' },
383                 { "verbose",    0, NULL, 'v' },
384                 { "file",       1, NULL, 'f' },
385                 { "palette",    1, NULL, 'p' },
386                 { "override",   1, NULL, 'p' },
387
388                 { "version",    0, NULL, 'V' },
389                 { "usage",      0, NULL, 'U' },
390                 { "help",       0, NULL, 'H' },
391
392                 { "no-palette", 0, NULL, 'n' },
393
394                 { 0 }
395         };
396
397         progname = "lbximg"; /* argv[0]; */
398         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
399                 switch(opt) {
400                 case 'i':
401                         mode = MODE_IDENT;
402                         break;
403                 case 'd':
404                         mode = MODE_DECODE;
405                         break;
406                 case 'v':
407                         verbose = 1;
408                         break;
409                 case 'f':
410                         file = optarg;
411                         break;
412                 case 'n':
413                         usepalette = 0;
414                         break;
415                 case 'p':
416                         palf = fopen(optarg, "rb");
417                         if (!palf) {
418                                 errmsg("failed to open %s: %m\n", optarg);
419                                 return EXIT_FAILURE;
420                         }
421
422                         break;
423                 case 'O':
424                         overf = fopen(optarg, "rb");
425                         if (!overf) {
426                                 errmsg("failed to open %s: %m\n", optarg);
427                                 return EXIT_FAILURE;
428                         }
429                         break;
430                 case 'V':
431                         puts(VERSION_BOILERPLATE("lbximg"));
432                         return EXIT_SUCCESS;
433                 case 'U':
434                         printusage();
435                         return EXIT_SUCCESS;
436                 case 'H':
437                         printhelp();
438                         return EXIT_SUCCESS;
439                 case '?':
440                 case ':':
441                         return EXIT_FAILURE;
442                 }
443         }
444
445         if (mode == MODE_NONE) {
446                 errmsg("you must specify a mode.\n", 0);
447                 return EXIT_FAILURE;
448         }
449
450         if (file)
451                 img = lbx_img_fopen(file);
452         else
453                 img = lbx_img_open(&stdin_handle, &lbx_pipe_fops, NULL);
454
455         if (!img) {
456                 errmsg("failed to open image: %s.\n", lbx_errmsg());
457                 return EXIT_FAILURE;
458         }
459
460         if (verbose || mode == MODE_IDENT) {
461                 struct lbx_imginfo info;
462
463                 if (!file)
464                         file = "stdin";
465
466                 lbx_img_getinfo(img, &info);
467                 printf("%s is %ux%u LBX image, %u frame(s)%s%s\n",
468                        file, img->width, img->height, img->frames,
469                        info.palettesz ? ", embedded palette" : "",
470                        img->chunk     ? ", chunked" : "",
471                        info.looping   ? ", loops" : "");
472         }
473
474         switch (mode) {
475         case MODE_DECODE:
476                 rc = decode(img, palf, overf, &argv[optind]);
477                 break;
478         }
479
480         lbx_img_close(img);
481         return rc;
482 }