]> git.draconx.ca Git - liblbx.git/blob - src/lbximg.c
Update palette handling in the lbximg tool.
[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 int outpng(unsigned int frameno, unsigned char **framedata,
80            unsigned int width,   unsigned int height,
81            struct lbx_colour palette[static 256])
82 {
83         char name[strlen(outname) + sizeof ".65535.png"];
84         FILE *of;
85
86         png_structp png;
87         png_infop   info;
88
89         assert(frameno < 65536);
90         snprintf(name, sizeof name, "%s.%03d.png", outname, frameno);
91         
92         of = fopen(name, "wb");
93         if (!of) {
94                 errmsg("failed to open %s: %s.\n", name, strerror(errno));
95                 return -1;
96         }
97
98         png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
99         if (!png) {
100                 errmsg("failed to init libpng.\n", 0);
101                 goto err;
102         }
103
104         info = png_create_info_struct(png);
105         if (!info) {
106                 errmsg("failed to init libpng.\n", 0);
107                 png_destroy_write_struct(&png, NULL);
108                 goto err;
109         }
110
111         if (setjmp(png_jmpbuf(png))) {
112                 png_destroy_write_struct(&png, &info);
113                 goto err;
114         }
115
116                 
117         png_set_IHDR(png, info, width, height, 8,
118                      usepalette ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_GRAY,
119                      PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
120                      PNG_FILTER_TYPE_DEFAULT);
121
122         if (usepalette)
123                 png_set_PLTE(png, info, (png_colorp)palette, 256);
124         png_set_rows(png, info, framedata);
125
126         png_init_io(png, of);
127         png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
128
129         png_destroy_write_struct(&png, &info);
130         fclose(of);
131
132         if (verbose)
133                 printf("wrote %s\n", name);
134
135         return 0;
136 err:
137         fclose(of);
138         remove(name);
139         return -1;
140 }
141
142 static int loadpalette(LBX_IMG *img, struct lbx_imginfo *info,
143                        FILE *palf, struct lbx_colour palette[static 256])
144 {
145         int i;
146
147         for (i = 0; i < 256; i++)
148                 palette[i] = (struct lbx_colour){0xff, 0x00, 0xff};
149
150         if (palf) {
151                 if (lbximg_loadpalette(palf, palette) == -1) {
152                         errmsg("error reading palette: %s\n", lbx_strerror());
153                         return -1;
154                 }
155         } else if (!info->haspalette) {
156                 errmsg("no palette available.\n", 0);
157                 return -1;
158         }
159
160         if (lbximg_getpalette(img, palette) == -1) {
161                 errmsg("error reading embedded palette: %s\n", lbx_strerror());
162                 return -1;
163         }
164
165         return 0;
166 }
167
168 int decode(LBX_IMG *img, FILE *palf, FILE *override, char **argv)
169 {
170         unsigned char *framebits;
171         struct lbx_colour palette[256];
172         struct lbx_imginfo info;
173         int extracted = 0;
174         unsigned int i;
175
176         lbximg_getinfo(img, &info);
177
178         framebits = malloc(info.nframes / CHAR_BIT + 1);
179         if (!framebits) {
180                 return EXIT_FAILURE;
181         }
182
183         /* Figure out what images we're extracting. */
184         if (!argv[0]) {
185                 /* extract all images by default. */
186                 memset(framebits, -1, info.nframes / CHAR_BIT + 1);
187         } else {
188                 for (i = 0; argv[i]; i++) {
189                         parserange(&info, argv[i], framebits);
190                 }
191         }
192
193         if (usepalette && loadpalette(img, &info, palf, palette) == -1) {
194                 goto err;
195         }
196
197         if (override) {
198                 LBX_IMG *overimg = lbximg_fopen(override);
199                 struct lbx_imginfo info;
200
201                 if (!overimg) {
202                         errmsg("failed to open override image: %s\n",
203                                lbx_strerror());
204                         goto err;
205                 }
206                 lbximg_getinfo(overimg, &info);
207
208                 if (!info.haspalette) {
209                         errmsg("override image has no palette.\n", 0);
210                         lbximg_close(overimg);
211                         goto err;
212                 }
213
214                 if (lbximg_getpalette(overimg, palette) == -1) {
215                         errmsg("error reading override palette: %s\n",
216                                lbx_strerror());
217                         lbximg_close(overimg);
218                         goto err;
219                 }
220
221                 lbximg_close(overimg);
222         }
223
224         /* Extract the images, in order. */
225         for (i = 0; i < info.nframes; i++) {
226                 unsigned char **framedata;
227
228                 if (!(framebits[i / CHAR_BIT] & (1 << (i % CHAR_BIT))))
229                         continue;
230
231                 framedata = lbximg_getframe(img, i);
232                 if (!framedata) {
233                         errmsg("error in frame %u: %s\n", i, lbx_strerror());
234                         continue;
235                 }
236
237                 if (!outpng(i, framedata, info.width, info.height, palette)) {
238                         extracted = 1;
239                 }
240         }
241
242         if (!extracted) {
243                 errmsg("no frames extracted.\n", 0);
244                 goto err;
245         }
246         
247         free(framebits);
248         return EXIT_SUCCESS;
249 err:
250         free(framebits);
251         return EXIT_FAILURE;
252 }
253
254 int main(int argc, char **argv)
255 {
256         int mode = MODE_NONE;
257         FILE *inf = stdin, *palf = NULL, *overf = NULL;
258         const char *name = "stdin";
259         LBX_IMG *img;
260         int opt;
261
262         static const char *sopts = "idvf:p:O:";
263         static const struct option lopts[] = {
264                 { "info",     0, NULL, 'i' },
265                 { "decode",   0, NULL, 'd' },
266                 { "verbose",  0, NULL, 'v' },
267                 { "file",     1, NULL, 'f' },
268                 { "palette",  1, NULL, 'p' },
269                 { "override", 1, NULL, 'p' },
270
271                 { "nopalette", 0, &usepalette, 0 },
272
273                 { 0 }
274         };
275
276         progname = "lbximg"; /* argv[0]; */
277         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
278                 switch(opt) {
279                 case 'i':
280                         mode = MODE_IDENT;
281                         break;
282                 case 'd':
283                         mode = MODE_DECODE;
284                         break;
285                 case 'v':
286                         verbose = 1;
287                         break;
288                 case 'f':
289                         if (strcmp(optarg, "-") == 0)
290                                 break;
291
292                         name = strrchr(optarg, '/');
293                         name = name ? name+1 : optarg;
294
295                         inf = fopen(optarg, "rb");
296                         if (!inf) {
297                                 errmsg("failed to open %s: %m\n", optarg);
298                                 return EXIT_FAILURE;
299                         }
300                         break;
301                 case 'p':
302                         palf = fopen(optarg, "rb");
303                         if (!palf) {
304                                 errmsg("failed to open %s: %m\n", optarg);
305                                 return EXIT_FAILURE;
306                         }
307
308                         break;
309                 case 'O':
310                         overf = fopen(optarg, "rb");
311                         if (!overf) {
312                                 errmsg("failed to open %s: %m\n", optarg);
313                                 return EXIT_FAILURE;
314                         }
315                         break;
316                 case '?':
317                 case ':':
318                         return EXIT_FAILURE;
319                 }
320         }
321
322         if (mode == MODE_NONE) {
323                 errmsg("you must specify a mode.\n", 0);
324                 return EXIT_FAILURE;
325         }
326
327         img = lbximg_fopen(inf);
328         if (!img) {
329                 errmsg("failed to open image: %s.\n", lbx_strerror());
330                 return EXIT_FAILURE;
331         }
332
333         if (verbose || mode == MODE_IDENT) {
334                 struct lbx_imginfo info;
335                 lbximg_getinfo(img, &info);
336
337                 printf("%s is %ux%u LBX image, %u frame(s)%s%s\n",
338                        name, info.width, info.height, info.nframes,
339                        info.haspalette ? ", embedded palette" : "",
340                        info.looping    ? ", loops" : "");
341         }
342
343         switch (mode) {
344         case MODE_DECODE:
345                 if (decode(img, palf, overf, &argv[optind])) {
346                         lbximg_close(img);
347                         return EXIT_FAILURE;
348                 }
349                 break;
350         }
351
352         lbximg_close(img);
353         return EXIT_SUCCESS;
354 }