Script.c: remove obsolete defines
[rofl0r-agsutils.git] / agsprite.c
blobbc9029742c6677feb144fbfd28ac4f1b0e26d593
1 #include "File.h"
2 #include "SpriteFile.h"
3 #include <unistd.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <sys/stat.h>
7 #include <errno.h>
8 #include "endianness.h"
9 #include "BitmapFuncs.h"
10 #include "Targa.h"
11 #include "rle.h"
12 #include <assert.h>
13 #include "version.h"
14 #define ADS ":::AGSprite " VERSION " by rofl0r:::"
16 #ifdef __x86_64__
17 #define breakpoint() __asm__("int3")
18 #else
19 #define breakpoint() do{}while(0)
20 #endif
22 #define FL_EXTRACT 1<<0
23 #define FL_PACK 1<<1
24 #define FL_VERBOSE 1<<2
25 #define FL_UNCOMPRESSED 1<<3
26 #define FL_HICOLOR 1<<4
27 #define FL_HICOLOR_SIMPLE (1<<5)
28 #define FL_SPRINDEX (1<<6)
30 static int debug_pic = -1, flags, filenr;
32 static int lookup_palette(unsigned color, unsigned *palette, int ncols)
34 int i;
35 for(i=0; i<ncols; ++i)
36 if(palette[i] == color) return i;
37 return -1;
40 static void rgb565_to_888(unsigned lo, unsigned hi, unsigned *r, unsigned *g, unsigned *b)
42 *r = (hi & ~7) | (hi >> 5);
43 *g = ((hi & 7) << 5) | ((lo & 224) >> 3) | ((hi & 7) >> 1);
44 *b = ((lo & 31) << 3) | ((lo & 28) >> 2);
47 static int convert_16_to_24(ImageData *d) {
48 size_t outsz = d->width*d->height*3;
49 unsigned char *out = malloc(outsz),
50 *p = out, *pe = out + outsz,
51 *q = d->data;
52 if(!out) return 0;
53 while(p < pe) {
54 unsigned r,g,b,lo,hi;
55 lo = *(q++);
56 hi = *(q++);
57 rgb565_to_888(lo, hi, &r, &g, &b);
58 *(p++) = b;
59 *(p++) = g;
60 *(p++) = r;
62 free(d->data);
63 d->data = out;
64 d->data_size = outsz;
65 d->bytesperpixel = 3;
66 return 1;
69 static int create_palette_pic(const ImageData* d, unsigned *palette, unsigned char **data)
71 int ret = 0;
72 unsigned char *p = d->data, *q = p + d->data_size;
73 *data = malloc(d->width * d->height);
74 unsigned char *o = *data, *e = o + (d->width * d->height);
75 unsigned i, a = 0;
76 while(p < q && o < e) {
77 unsigned col, r, g, b;
78 switch(d->bytesperpixel) {
79 case 2: {
80 unsigned lo = *(p++);
81 unsigned hi = *(p++);
82 rgb565_to_888(lo, hi, &r, &g, &b);
83 break; }
84 case 3:
85 case 4:
86 b = *(p++);
87 g = *(p++);
88 r = *(p++);
89 if(d->bytesperpixel == 4) a = *(p++);
90 break;
91 default: assert(0);
93 col = a << 24 | r << 16 | g << 8 | b;
94 int n = lookup_palette(col, palette, ret);
95 if(n < 0) {
96 if(ret == 256) {
97 free(*data);
98 *data = 0;
99 return -1;
101 n = ret;
102 palette[ret++] = col;
104 *(o++) = n;
106 return ret;
109 static void write_tga(char *name, ImageData* d, unsigned char *palette)
111 unsigned pal[256];
112 unsigned char *paldata = 0, *data = d->data;
113 unsigned bpp = d->bytesperpixel;
114 unsigned data_size = d->data_size;
115 int palcount = 256;
116 int i;
118 FILE *f = fopen(name, "w");
119 if(!f) {
120 fprintf(stderr, "error opening %s\n", name);
121 return;
124 if(bpp == 1) {
125 if(!palette) for(i=0; i<256; ++i) pal[i] = rand();
126 else for(i=0; i<256; ++i) {
127 unsigned r = *(palette++);
128 unsigned g = *(palette++);
129 unsigned b = *(palette++);
130 pal[i] = r << 16 | g << 8 | b ;
132 } else if( /* bpp != 2 && */
133 (palcount = create_palette_pic(d, pal, &paldata)) > 0) {
134 /* can be saved as 8 bit palettized image */
135 bpp = 1;
136 data = paldata;
137 data_size = d->width*d->height;
138 } else if(bpp == 2 && convert_16_to_24(d)) {
139 bpp = 3;
140 data = d->data;
141 data_size = d->data_size;
143 unsigned char *rle_data = 0;
144 unsigned rle_data_size = rle_encode(data, data_size, bpp, &rle_data);
145 int use_rle = 0;
146 if(rle_data && rle_data_size < data_size) {
147 data_size = rle_data_size;
148 data = rle_data;
149 use_rle = 1;
151 struct TargaHeader hdr = {
152 .idlength = 0,
153 .colourmaptype = bpp == 1 ? 1 : 0,
154 .datatypecode = bpp == 1 ?
155 (use_rle ? TIT_RLE_COLOR_MAPPED : TIT_COLOR_MAPPED) :
156 (use_rle ? TIT_RLE_TRUE_COLOR : TIT_TRUE_COLOR),
157 .colourmaporigin = 0,
158 .colourmaplength = bpp == 1 ? le16(palcount) : 0,
159 .colourmapdepth = bpp == 1 ? 24 : 0,
160 .x_origin = 0,
161 .y_origin = le16(d->height), /* image starts at the top */
162 .width = le16(d->width),
163 .height = le16(d->height),
164 .bitsperpixel = bpp*8,
165 .imagedescriptor = 0x20, /* image starts at the top */
167 fwrite(&hdr, 1, sizeof hdr, f);
168 unsigned tmp;
169 if(bpp == 1) for(i=0; i<palcount; ++i) {
170 tmp = le32(pal[i]);
171 fwrite(&tmp, 1, 3, f);
173 fwrite(data, 1, data_size, f);
174 fclose(f);
175 free(paldata);
176 free(rle_data);
179 static int extract(char* file, char* dir) {
180 if(access(dir, R_OK) == -1 && errno == ENOENT) {
181 mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
183 AF f;
184 SpriteFile sf;
185 int ret;
186 ret = AF_open(&f, file);
187 if(!ret) {
188 fprintf(stderr, "error opening %s\n", file);
189 return 1;
191 if(flags & FL_VERBOSE) printf("processing spritefile TOC...\n");
192 ret = SpriteFile_read(&f, &sf);
193 if(!ret) {
194 fprintf(stderr, "error reading spritefile %s\n", file);
195 return 1;
197 FILE *info = 0;
199 char buf[1024];
200 if(sf.palette) {
201 snprintf(buf, sizeof buf, "%s/agsprite.pal", dir);
202 FILE *pal = fopen(buf, "w");
203 if(!pal) goto ferr;
204 fwrite(sf.palette, 1, 256*3, pal);
205 fclose(pal);
207 snprintf(buf, sizeof buf, "%s/agsprite.info", dir);
208 info = fopen(buf, "w");
209 if(!info) {
210 ferr:
211 fprintf(stderr, "error opening %s\n", buf);
212 return 1;
214 fprintf(info,
215 "info=infofile created by " ADS "\n"
216 "info=this file is needed to reconstruct acsprset.spr\n"
217 "spritecacheversion=%d\n"
218 "spritecount=%d\n"
219 "id=%d\n"
220 "palette=%s\n"
221 , sf.version, sf.num_sprites, sf.id, sf.palette ? "agsprite.pal" : ""
224 int i;
225 for(i=0; i<sf.num_sprites; i++) {
226 if(debug_pic == i) breakpoint();
227 ImageData d;
228 if(SpriteFile_extract(&f, &sf, i, &d)) {
229 char namebuf[64];
230 snprintf(namebuf, sizeof namebuf, "sprite%06d_%02d_%dx%d.tga", i, d.bytesperpixel*8, d.width, d.height);
231 fprintf(info, "%d=%s\n", i, namebuf);
232 if(flags & FL_VERBOSE) printf("extracting sprite %d (%s)\n", i, namebuf);
233 char filename[1024];
234 snprintf(filename, sizeof filename, "%s/%s", dir, namebuf);
235 write_tga(filename, &d, sf.palette);
236 free(d.data);
239 fclose(info);
240 return 0;
243 static void convert_bottom_left_tga(ImageData *d) {
244 size_t y, w = d->width*d->bytesperpixel;
245 unsigned char *swp = malloc(w);
246 if(!swp) return;
247 for(y = 0; y < d->height/2; ++y) {
248 size_t to = w*y, bo = (d->height-1-y)*w;
249 memcpy(swp, d->data + to, w);
250 memcpy(d->data + to, d->data + bo, w);
251 memcpy(d->data + bo, swp, w);
253 free(swp);
256 static int read_tga(FILE *f, ImageData *idata, int skip_palette) {
257 struct TargaHeader hdr;
258 struct TargaFooter ftr;
259 fread(&hdr, 1, sizeof hdr, f);
260 fseek(f, 0, SEEK_END);
261 off_t fs = ftello(f);
262 if(fs > sizeof ftr) {
263 fseek(f, 0-sizeof ftr, SEEK_END);
264 fread(&ftr, 1, sizeof ftr, f);
265 if(!memcmp(ftr.signature, TARGA_FOOTER_SIGNATURE, sizeof ftr.signature))
266 fs -= sizeof ftr;
268 hdr.colourmaplength = le16(hdr.colourmaplength);
269 hdr.colourmapdepth = le16(hdr.colourmapdepth);
270 hdr.x_origin = le16(hdr.x_origin);
271 hdr.y_origin = le16(hdr.y_origin);
272 hdr.width = le16(hdr.width);
273 hdr.height = le16(hdr.height);
275 fseek(f, sizeof hdr + hdr.idlength, SEEK_SET);
276 fs -= ftello(f);
277 unsigned char *data = malloc(fs), *palette = 0;
278 unsigned palsz = 0;
279 fread(data, 1, fs, f);
280 if(hdr.colourmaptype) {
281 palette = data;
282 palsz = hdr.colourmaplength * hdr.colourmapdepth/8;
283 if(fs <= palsz) return 0;
284 data += palsz;
285 fs -= palsz;
287 unsigned char *workdata = 0;
288 unsigned tmp;
289 switch(hdr.datatypecode) {
290 case TIT_RLE_COLOR_MAPPED:
291 case TIT_RLE_TRUE_COLOR:
292 tmp = hdr.width*hdr.height*hdr.bitsperpixel/8;
293 workdata = malloc(tmp);
294 rle_decode(data, fs, hdr.bitsperpixel/8, workdata, tmp);
295 break;
296 case TIT_COLOR_MAPPED:
297 case TIT_TRUE_COLOR:
298 workdata = data;
299 break;
300 default:
301 return 0;
303 idata->width = hdr.width;
304 idata->height = hdr.height;
305 if(skip_palette)
306 idata->bytesperpixel = hdr.bitsperpixel/8;
307 else
308 idata->bytesperpixel = hdr.colourmapdepth? hdr.colourmapdepth/8 : hdr.bitsperpixel/8;
310 tmp = idata->width*idata->height*idata->bytesperpixel;
311 idata->data_size = tmp;
312 idata->data = malloc(tmp);
313 if(palette && !skip_palette) {
314 unsigned i, j, bpp = hdr.colourmapdepth/8;
315 unsigned char *p = idata->data, *q = workdata;
316 for(i=0; i < idata->width*idata->height; ++i) {
317 unsigned idx = *(q++);
318 if(idx >= hdr.colourmaplength) return 0;
319 for(j=0; j < bpp; ++j)
320 *(p++) = palette[idx*bpp+j];
322 } else {
323 memcpy(idata->data, workdata, tmp);
325 if(workdata != data) free(workdata);
326 if(palette) free(palette);
327 else free(data);
328 if(hdr.y_origin == 0) convert_bottom_left_tga(idata);
329 return 1;
332 static int is_upscaled_16bit(ImageData *d) {
333 int i;
334 for(i=0; i<d->data_size; i++) {
335 if(i%3 != 1) { /* topmost 3 bits appended */
336 if((d->data[i] & 7) != (d->data[i] >> 5))
337 return 0;
338 } else { /* topmost 2 bits appended */
339 if((d->data[i] & 3) != (d->data[i] >> 6))
340 return 0;
343 return 1;
346 static int rawx_to_ags16(ImageData *d, int bpp) {
347 int i, imax = d->data_size/bpp;
348 unsigned char *data = malloc(d->width*d->height*2), *p = data;
349 if(!data) return 0;
350 for(i=0; i<imax; i++) {
351 unsigned b = d->data[i*bpp+0];
352 unsigned g = d->data[i*bpp+1];
353 unsigned r = d->data[i*bpp+2];
354 unsigned hi = (r & ~7) | (g >> 5);
355 unsigned lo = ((g & 28) << 3) | (b >> 3);
356 *(p++) = lo;
357 *(p++) = hi;
359 free(d->data);
360 d->data = data;
361 d->bytesperpixel = 2;
362 d->data_size = d->width*d->height*2;
363 return 1;
366 static int raw24_to_ags16(ImageData *d) {
367 return rawx_to_ags16(d, 3);
369 static int raw32_to_ags16(ImageData *d) {
370 return rawx_to_ags16(d, 4);
373 static int raw24_to_32(ImageData *d) {
374 unsigned char* data = malloc(d->width*d->height*4), *p = data, *q = d->data;
375 if(!data) return 0;
376 int i;
377 for(i=0;i<d->width*d->height;++i) {
378 unsigned b = *(q++);
379 unsigned g = *(q++);
380 unsigned r = *(q++);
381 *(p++) = b;
382 *(p++) = g;
383 *(p++) = r;
384 /* restore transparency for "magic magenta" pixels */
385 *(p++) = "\xff\0"[!!(r == 0xff && g == 0 && b == 0xff)];
387 free(d->data);
388 d->data = data;
389 d->bytesperpixel = 4;
390 d->data_size = d->width*d->height*4;
391 return 1;
393 static int raw32_swap_alpha(ImageData *d) {
394 #if 0
395 unsigned char *p = d->data, *pe = d->data+d->data_size;
396 while(p < pe) {
397 unsigned char *q = p;
398 unsigned r = *(q++);
399 unsigned g = *(q++);
400 unsigned b = *(q++);
401 unsigned a = *(q++);
402 *(p++) = a;
403 *(p++) = r;
404 *(p++) = g;
405 *(p++) = b;
407 #endif
408 return 1;
410 /* return true if alpha uses only values 0 (fully transparent)
411 or 0xff (not transparent), and transparency is only used
412 for "magic magenta". */
413 static int is_hicolor_candidate(ImageData *d) {
414 unsigned char *p = d->data, *pe = d->data+d->data_size;
415 while(p < pe) {
416 unsigned b = *(p++);
417 unsigned g = *(p++);
418 unsigned r = *(p++);
419 unsigned a = *(p++);
420 switch(a) {
421 case 0xff: break;
422 case 0:
423 if(!(r == 0xff && g == 0 && b == 0xff))
424 return 0;
425 break;
426 default: return 0;
429 return 1;
431 static int tga_to_ags(ImageData *d, int org_bpp) {
432 /* convert raw image data to something acceptable for ags */
433 switch(d->bytesperpixel) {
434 case 4:
435 if(flags & FL_HICOLOR) return raw32_to_ags16(d);
436 else if(flags & FL_HICOLOR_SIMPLE && is_hicolor_candidate(d)) {
437 if(flags & FL_VERBOSE) printf("converting %d to 16bpp\n", filenr);
438 return raw32_to_ags16(d);
439 } else return raw32_swap_alpha(d);
440 case 3:
441 if(flags & FL_HICOLOR) return raw24_to_ags16(d);
442 if(org_bpp == 2 && is_upscaled_16bit(d)) return raw24_to_ags16(d);
443 else return raw24_to_32(d);
445 return 1;
448 static int pack(char* file, char* dir) {
449 if(access(dir, R_OK) == -1 && errno == ENOENT) {
450 fprintf(stderr, "error opening dir %s\n", dir);
451 return 1;
453 FILE *info = 0, *out = 0;
454 char buf[1024];
455 snprintf(buf, sizeof buf, "%s/agsprite.info", dir);
456 info = fopen(buf, "r");
457 if(!info) {
458 fprintf(stderr, "error opening %s\n", buf);
459 return 1;
461 SpriteFile sf = {0};
462 int line = 0;
463 while(fgets(buf, sizeof buf, info)) {
464 ++line;
465 if(buf[0] == '#') continue; /* comment */
466 char *p;
467 p = strrchr(buf, '\n');
468 if(p) {
469 p[0] = 0;
470 if(p > buf && p[-1] == '\r') p[-1] = 0;
472 p = strchr(buf, '=');
473 if(!p) {
474 fprintf(stderr, "syntax error on line %d of agsprite.info\n", line);
475 return 1;
477 *(p++) = 0;
478 if(!out) {
479 if(0) {
480 } else if(!strcmp("info", buf)) {
481 } else if(!strcmp("spritecacheversion", buf)) {
482 sf.version = atoi(p);
483 } else if(!strcmp("spritecount", buf)) {
484 sf.num_sprites = atoi(p);
485 } else if(!strcmp("id", buf)) {
486 sf.id = atoi(p);
487 } else if(!strcmp("palette", buf)) {
488 if (*p) {
489 char buf2[1024];
490 snprintf(buf2, sizeof buf2, "%s/%s", dir, p);
491 FILE *pal = fopen(buf2, "r");
492 if(!pal) {
493 fprintf(stderr, "error opening %s\n", buf2);
494 return 1;
496 sf.palette = malloc(256*3);
497 fread(sf.palette, 1, 256*3, pal);
498 fclose(pal);
500 } else {
501 if(strcmp(buf, "0")) {
502 fprintf(stderr, "unexpected keyword %s\n", buf);
503 return 1;
505 out = fopen(file, "w");
506 if(!out) {
507 fprintf(stderr, "error opening %s\n", file);
508 return 1;
510 /* default to compressed, if possible, and unless overridden */
511 sf.compressed = !(flags&FL_UNCOMPRESSED);
512 SpriteFile_write_header(out, &sf);
515 if(out) {
516 int n = filenr = atoi(buf);
517 int org_bpp = 4;
518 /* FIXME: use sscanf */
519 if(strstr(p, "_08_")) org_bpp = 1;
520 else if(strstr(p, "_16_")) org_bpp = 2;
521 if(flags & FL_VERBOSE) printf("adding %d (%s)\n", n, p);
522 if(debug_pic == n) breakpoint();
524 while(sf.num_sprites < n) SpriteFile_add(out, &sf, &(ImageData){0});
525 char fnbuf[1024];
526 snprintf(fnbuf, sizeof fnbuf, "%s/%s", dir, p);
527 FILE *spr = fopen(fnbuf, "r");
528 if(!spr) {
529 fprintf(stderr, "error opening %s\n", fnbuf);
530 return 1;
532 ImageData data;
533 int skip_palette =
534 (sf.version == 4 && org_bpp != 1) ||
535 (sf.version >= 4 && org_bpp == 1);
536 if(!read_tga(spr, &data, skip_palette)) {
537 fprintf(stderr, "error reading tga file %s\n", p);
538 return 1;
540 tga_to_ags(&data, org_bpp);
541 SpriteFile_add(out, &sf, &data);
542 free(data.data);
543 fclose(spr);
546 SpriteFile_finalize(out, &sf);
547 fclose(out);
548 fclose(info);
549 return 0;
552 static int sprindex(char* infile, char* outfile) {
553 AF f;
554 SpriteFile sf;
555 int ret;
556 ret = AF_open(&f, infile);
557 if(!ret) {
558 fprintf(stderr, "error opening %s\n", infile);
559 return 1;
561 if(flags & FL_VERBOSE) printf("processing spritefile TOC...\n");
562 ret = SpriteFile_read(&f, &sf);
563 if(!ret) {
564 fprintf(stderr, "error reading spritefile %s\n", infile);
565 return 1;
567 FILE *out = fopen(outfile, "w");
568 if(!out) {
569 fprintf(stderr, "error opening outfile %s\n", outfile);
570 return 1;
572 ret = SpriteFile_write_sprindex(&f, &sf, out);
573 AF_close(&f);
574 fclose(out);
575 return !ret;
578 static int parse_argstr(char *arg)
580 const struct flagmap {
581 char chr;
582 int flag;
583 } map[] = {
584 { 'x', FL_EXTRACT},
585 { 'c', FL_PACK},
586 { 'i', FL_SPRINDEX},
587 { 'v', FL_VERBOSE},
588 { 'u', FL_UNCOMPRESSED},
589 { 'h', FL_HICOLOR},
590 { 'H', FL_HICOLOR_SIMPLE},
591 {0, 0},
593 int flags = 0, i;
594 while(*arg) {
595 int found = 0;
596 for(i=0;map[i].chr;++i)
597 if(map[i].chr == *arg) {
598 flags |= map[i].flag;
599 found = 1;
600 break;
602 if(!found) return 0;
603 ++arg;
605 return flags;
608 static int usage(char *a) {
609 printf( "%s ACTIONSTR acsprset.spr DIR\n"
610 "ACTIONSTR can be:\n"
611 "x - extract\n"
612 "c - pack\n"
613 "i - create sprindex.dat from .spr\n"
614 "optionally followed by option characters.\n\n"
615 "option characters:\n"
616 "v - be verbose (both)\n"
617 "u - don't use RLE compression if v >= 6 (pack)\n"
618 "h - store all 32bit sprites as 16bit (pack)\n"
619 "H - same, but only when alpha unused (pack)\n"
620 "\n"
621 "extract mode:\n"
622 "extracts all sprites from acsprset.spr to DIR\n"
623 "due to the way sprite packs work, for some versions\n"
624 "8 bit images are stored without palette (dynamically\n"
625 "assigned during game). in such a case a random palette\n"
626 "is generated.\n\n"
627 "pack mode:\n"
628 "packs files in DIR to acsprset.spr\n"
629 "image files need to be in tga format\n\n"
630 "sprite index mode:\n"
631 "here DIR parameter is repurposed to actually mean output file.\n"
632 "a sprindex.dat file corresponding to acsprset.spr param is created.\n\n"
633 "examples:\n"
634 "%s xv acsprset.spr IMAGES/\n"
635 "%s cu test.spr IMAGES/\n"
636 "%s i repack.spr FILES/sprindex.dat\n"
637 , a, a, a, a);
638 return 1;
641 int main(int argc, char **argv) {
643 if(argc != 4 || !(flags = parse_argstr(argv[1]))
644 || !((flags & FL_EXTRACT) || (flags & FL_PACK) || (flags & FL_SPRINDEX))
645 || ((flags&FL_EXTRACT)&&(flags&FL_PACK)) )
646 return usage(argv[0]);
648 char* file = argv[2];
649 char *dir = argv[3];
650 if(getenv("DEBUG")) debug_pic = atoi(getenv("DEBUG"));
652 if(flags & FL_EXTRACT) return extract(file, dir);
653 else if(flags & FL_SPRINDEX) return sprindex(file, dir);
654 else return pack(file, dir);