]> git.draconx.ca Git - liblbx.git/blob - src/lbximg.c
Update license information.
[liblbx.git] / src / lbximg.c
1 /* 2ooM: The Master of Orion II Reverse Engineering Project
2  * Simple command-line tool to convert an LBX image to a set of PNGs.
3  * Copyright (C) 2006-2008 Nick Bowler
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 #define _GNU_SOURCE
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 "image.h"
31 #include "lbx.h"
32
33 /* Global flags */
34 static int verbose = 0;
35 static char *outname = "out";
36 static int usepalette = 1;
37
38 static const char *progname;
39 #define errmsg(fmt, ...) (\
40         fprintf(stderr, "%s: " fmt, progname, __VA_ARGS__)\
41 )
42
43 enum {
44         MODE_NONE,
45         MODE_DECODE,
46         MODE_IDENT,
47 };
48
49 int parserange(struct lbx_imginfo *info, char *str, unsigned char *bits)
50 {
51         unsigned long start, end;
52         unsigned int i;
53         char *endptr;
54
55         start = strtoul(str, &endptr, 0);
56         if (start >= info->nframes) {
57                 errmsg("frame %lu out of range.\n", start);
58                 return -1;
59         }
60
61         if (endptr == str) {
62                 errmsg("invalid frame range: %s.\n", str);
63                 return -1;
64         }
65
66         switch (*endptr) {
67         case '\0':
68                 end = start;
69                 break;
70         case '-':
71                 end = strtoul(endptr+1, &endptr, 0);
72                 if (end >= info->nframes) {
73                         errmsg("frame %lu out of range.\n", end);
74                         return -1;
75                 }
76
77                 if (endptr == str)
78                         end = info->nframes - 1;
79                 break;
80         default:
81                 errmsg("invalid frame range: %s.\n", str);
82                 return -1;
83         }
84
85         if (end < start) {
86                 errmsg("invalid frame range: %s.\n", str);
87                 return -1;
88         }
89
90         for (i = start; i <= end; i++) {
91                 bits[i / CHAR_BIT] |= 1 << (i % CHAR_BIT);
92         }
93
94         return 0;
95 }
96
97 static int ismasked(unsigned char **mask, unsigned width, unsigned height)
98 {
99         unsigned y, x;
100         for (y = 0; y < height; y++) {
101                 for (x = 0; x < width; x++) {
102                         if (mask[y][x] == 0) return 1;
103                 }
104         }
105
106         return 0;
107 }
108
109 int outpng(unsigned int frameno,
110            unsigned char **framedata, unsigned char **mask,
111            unsigned int width, unsigned int height,
112            struct lbx_colour palette[static 256])
113 {
114         char name[strlen(outname) + sizeof ".65535.png"];
115         unsigned char *row;
116         unsigned int x, y;
117         FILE *of;
118
119         png_structp png;
120         png_infop   info;
121
122         assert(frameno < 65536);
123         snprintf(name, sizeof name, "%s.%03d.png", outname, frameno);
124
125         row = malloc(4 * width);
126         if (!row) {
127                 errmsg("failed to allocate row buffer: %s\n", strerror(errno));
128                 return -1;
129         }
130
131         of = fopen(name, "wb");
132         if (!of) {
133                 errmsg("failed to open %s: %s.\n", name, strerror(errno));
134                 free(row);
135                 return -1;
136         }
137
138         png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
139         if (!png) {
140                 errmsg("failed to init libpng.\n", 0);
141                 goto err;
142         }
143
144         info = png_create_info_struct(png);
145         if (!info) {
146                 errmsg("failed to init libpng.\n", 0);
147                 png_destroy_write_struct(&png, NULL);
148                 goto err;
149         }
150
151         if (setjmp(png_jmpbuf(png))) {
152                 free(row);
153                 png_destroy_write_struct(&png, &info);
154                 goto err;
155         }
156
157         png_init_io(png, of);
158
159         if (!ismasked(mask, width, height)) {
160                 /*
161                  * This case is easy; we can just feed the palette and pixel
162                  * data to libpng and let it do its magic.
163                  */
164
165                 png_set_IHDR(png, info, width, height, 8,
166                              PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
167                              PNG_COMPRESSION_TYPE_DEFAULT,
168                              PNG_FILTER_TYPE_DEFAULT);
169                 
170                 png_set_PLTE(png, info, (png_colorp)palette, 256);
171                 png_set_rows(png, info, framedata);
172                 png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
173         } else {
174                 /*
175                  * Unfortunately, LBX doesn't translate nicely to PNG here.
176                  * LBX has a 256 colour palette _plus_ transparency.
177                  * We'll form an RGBA PNG to deal with this.
178                  */
179
180                 png_set_IHDR(png, info, width, height, 8,
181                              PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
182                              PNG_COMPRESSION_TYPE_DEFAULT,
183                              PNG_FILTER_TYPE_DEFAULT);
184         
185                 png_write_info(png, info);
186         
187                 for (y = 0; y < height; y++) {
188                         for (x = 0; x < width; x++) {
189                                 row[4*x+0] = palette[framedata[y][x]].red;
190                                 row[4*x+1] = palette[framedata[y][x]].green;
191                                 row[4*x+2] = palette[framedata[y][x]].blue;
192                                 row[4*x+3] = (mask[y][x]) ? -1 : 0;
193                         }
194         
195                         png_write_row(png, row);
196                 }
197         
198                 png_write_end(png, NULL);
199         }
200
201         png_destroy_write_struct(&png, &info);
202         fclose(of);
203
204         if (verbose)
205                 printf("wrote %s\n", name);
206
207         return 0;
208
209 err:
210         fclose(of);
211         remove(name);
212         free(row);
213         return -1;
214 }
215
216 static int loadoverride(FILE *f, struct lbx_colour palette[static 256])
217 {
218         LBX_IMG *overimg = lbximg_fopen(f);
219         struct lbx_imginfo info;
220
221         if (!overimg) {
222                 errmsg("failed to open override image: %s\n", lbx_strerror());
223                 return -1;
224         }
225         lbximg_getinfo(overimg, &info);
226
227         if (!info.haspalette) {
228                 errmsg("override image has no palette.\n", 0);
229                 lbximg_close(overimg);
230                 return -1;
231         }
232
233         if (lbximg_getpalette(overimg, palette) == -1) {
234                 errmsg("error reading override palette: %s\n", lbx_strerror());
235                 lbximg_close(overimg);
236                 return -1;
237         }
238
239         lbximg_close(overimg);
240         return 0;
241 }
242
243 static int loadpalette(LBX_IMG *img, struct lbx_imginfo *info,
244                        FILE *palf, FILE *override,
245                        struct lbx_colour palette[static 256])
246 {
247         int i;
248
249         /* In no-palette mode, use palette indices for colour. */
250         if (!usepalette) {
251                 for (i = 0; i < 256; i++) {
252                         palette[i] = (struct lbx_colour){i,i,i};
253                 }
254
255                 return 0;
256         }
257
258         /* For sanity. */
259         if (!palf && !info->haspalette && !override) {
260                 errmsg("no palette available.\n", 0);
261                 return -1;
262         }
263
264         /* Default the palette to a wonderful pink. */
265         for (i = 0; i < 256; i++) {
266                 palette[i] = (struct lbx_colour){0xff, 0x00, 0xff};
267         }
268
269         /* Read the external palette, if any. */
270         if (palf && lbximg_loadpalette(palf, palette) == -1) {
271                 errmsg("error reading external palette: %s\n", lbx_strerror());
272                 return -1;
273         }
274
275         /* Read the embedded palette, if any. */
276         if (info->haspalette && lbximg_getpalette(img, palette) == -1) {
277                 errmsg("error reading embedded palette: %s\n", lbx_strerror());
278                 return -1;
279         }
280
281         /* Read the override palette, if any. */
282         if (override && loadoverride(override, palette) == -1) {
283                 return -1;
284         }
285
286         return 0;
287 }
288
289 int decode(LBX_IMG *img, FILE *palf, FILE *override, char **argv)
290 {
291         unsigned char *framebits;
292         struct lbx_colour palette[256];
293         struct lbx_imginfo info;
294         int extracted = 0;
295         unsigned int i;
296
297         lbximg_getinfo(img, &info);
298
299         framebits = malloc(info.nframes / CHAR_BIT + 1);
300         if (!framebits) {
301                 return EXIT_FAILURE;
302         }
303
304         /* Figure out what images we're extracting. */
305         if (!argv[0]) {
306                 /* extract all images by default. */
307                 memset(framebits, -1, info.nframes / CHAR_BIT + 1);
308         } else {
309                 for (i = 0; argv[i]; i++) {
310                         parserange(&info, argv[i], framebits);
311                 }
312         }
313
314         if (loadpalette(img, &info, palf, override, palette) == -1) {
315                 goto err;
316         }
317
318         /* Extract the images, in order. */
319         for (i = 0; i < info.nframes; i++) {
320                 unsigned char **data;
321                 unsigned char **mask;
322
323                 if (!(framebits[i / CHAR_BIT] & (1 << (i % CHAR_BIT))))
324                         continue;
325
326                 data = lbximg_getframe(img, i);
327                 if (!data) {
328                         errmsg("error in frame %u: %s\n", i, lbx_strerror());
329                         continue;
330                 }
331
332                 mask = lbximg_getmask(img);
333
334                 if (!outpng(i, data, mask, info.width, info.height, palette)) {
335                         extracted = 1;
336                 }
337         }
338
339         if (!extracted) {
340                 errmsg("no frames extracted.\n", 0);
341                 goto err;
342         }
343
344         free(framebits);
345         return EXIT_SUCCESS;
346 err:
347         free(framebits);
348         return EXIT_FAILURE;
349 }
350
351 int main(int argc, char **argv)
352 {
353         int mode = MODE_NONE;
354         FILE *inf = stdin, *palf = NULL, *overf = NULL;
355         const char *name = "stdin";
356         LBX_IMG *img;
357         int opt;
358
359         static const char *sopts = "idvf:p:O:";
360         static const struct option lopts[] = {
361                 { "info",     0, NULL, 'i' },
362                 { "decode",   0, NULL, 'd' },
363                 { "verbose",  0, NULL, 'v' },
364                 { "file",     1, NULL, 'f' },
365                 { "palette",  1, NULL, 'p' },
366                 { "override", 1, NULL, 'p' },
367
368                 { "nopalette", 0, &usepalette, 0 },
369
370                 { 0 }
371         };
372
373         progname = "lbximg"; /* argv[0]; */
374         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
375                 switch(opt) {
376                 case 'i':
377                         mode = MODE_IDENT;
378                         break;
379                 case 'd':
380                         mode = MODE_DECODE;
381                         break;
382                 case 'v':
383                         verbose = 1;
384                         break;
385                 case 'f':
386                         if (strcmp(optarg, "-") == 0)
387                                 break;
388
389                         name = strrchr(optarg, '/');
390                         name = name ? name+1 : optarg;
391
392                         inf = fopen(optarg, "rb");
393                         if (!inf) {
394                                 errmsg("failed to open %s: %m\n", optarg);
395                                 return EXIT_FAILURE;
396                         }
397                         break;
398                 case 'p':
399                         palf = fopen(optarg, "rb");
400                         if (!palf) {
401                                 errmsg("failed to open %s: %m\n", optarg);
402                                 return EXIT_FAILURE;
403                         }
404
405                         break;
406                 case 'O':
407                         overf = fopen(optarg, "rb");
408                         if (!overf) {
409                                 errmsg("failed to open %s: %m\n", optarg);
410                                 return EXIT_FAILURE;
411                         }
412                         break;
413                 case '?':
414                 case ':':
415                         return EXIT_FAILURE;
416                 }
417         }
418
419         if (mode == MODE_NONE) {
420                 errmsg("you must specify a mode.\n", 0);
421                 return EXIT_FAILURE;
422         }
423
424         img = lbximg_fopen(inf);
425         if (!img) {
426                 errmsg("failed to open image: %s.\n", lbx_strerror());
427                 return EXIT_FAILURE;
428         }
429
430         if (verbose || mode == MODE_IDENT) {
431                 struct lbx_imginfo info;
432                 lbximg_getinfo(img, &info);
433
434                 printf("%s is %ux%u LBX image, %u frame(s)%s%s\n",
435                        name, info.width, info.height, info.nframes,
436                        info.haspalette ? ", embedded palette" : "",
437                        info.looping    ? ", loops" : "");
438         }
439
440         switch (mode) {
441         case MODE_DECODE:
442                 if (decode(img, palf, overf, &argv[optind])) {
443                         lbximg_close(img);
444                         return EXIT_FAILURE;
445                 }
446                 break;
447         }
448
449         lbximg_close(img);
450         return EXIT_SUCCESS;
451 }