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