23bb58101813371312781c5666efd3c04185e2fd
[eradicate] / tools / rlesprite / src / main.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <stdint.h>
6 #include <assert.h>
7 #include <alloca.h>
8 #include "image.h"
9
10 #define MAGIC   "RLESPRIT"
11
12 struct file_header {
13         char magic[8];
14         uint16_t width, height, bpp;
15         uint16_t count;
16 } __attribute__((packed));
17
18 enum {
19         CSOP_END,
20         CSOP_ENDL,
21         CSOP_SKIP,
22         CSOP_FILL,
23         CSOP_COPY
24 };
25
26 struct csop {
27         unsigned char op;
28         unsigned char padding;
29         uint16_t len;
30 } __attribute__((packed));
31
32
33 struct rect {
34         int x, y, w, h;
35 };
36
37 int rlesprite(struct image *img, int x, int y, int xsz, int ysz);
38 int proc_sheet(const char *fname);
39 void print_usage(const char *argv0);
40
41 int tile_xsz, tile_ysz;
42 struct rect rect;
43 int cmap_offs;
44 int ckey;
45 int conv565;
46 int padding;
47 FILE *outfp;
48
49 int main(int argc, char **argv)
50 {
51         int i;
52         char *endp;
53         const char *name = 0;
54
55         for(i=1; i<argc; i++) {
56                 if(argv[i][0] == '-') {
57                         if(strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "-size") == 0) {
58                                 if(sscanf(argv[++i], "%dx%d", &tile_xsz, &tile_ysz) != 2 ||
59                                                 tile_xsz <= 0 || tile_ysz <= 0) {
60                                         fprintf(stderr, "%s must be followed by WIDTHxHEIGHT\n", argv[i - 1]);
61                                         return 1;
62                                 }
63
64                         } else if(strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "-rect") == 0) {
65                                 rect.x = rect.y = 0;
66                                 if(sscanf(argv[++i], "%dx%d+%d+%d", &rect.w, &rect.h, &rect.x, &rect.y) < 2 || rect.w <= 0 || rect.h <= 0) {
67                                         fprintf(stderr, "%s must be followed by WIDTHxHEIGHT+X+Y\n", argv[i - 1]);
68                                         return 1;
69                                 }
70
71                         } else if(strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "-pad") == 0) {
72                                 padding = strtol(argv[++i], &endp, 10);
73                                 if(endp == argv[i] || padding < 0) {
74                                         fprintf(stderr, "%s must be followed by a positive number\n", argv[i - 1]);
75                                         return 1;
76                                 }
77
78                         } else if(strcmp(argv[i], "-coffset") == 0) {
79                                 cmap_offs = strtol(argv[++i], &endp, 10);
80                                 if(endp == argv[i] || cmap_offs < 0 || cmap_offs >= 256) {
81                                         fprintf(stderr, "-coffset must be followed by a valid colormap offset\n");
82                                         return 1;
83                                 }
84
85                         } else if(strcmp(argv[i], "-o") == 0) {
86                                 if(!argv[++i]) {
87                                         fprintf(stderr, "%s must be followed by an output filename\n", argv[i - 1]);
88                                         return 1;
89                                 }
90                                 if(name && strcmp(name, argv[i]) != 0) {
91                                         if(outfp) {
92                                                 fclose(outfp);
93                                                 outfp = 0;
94                                         }
95                                 }
96                                 name = argv[i];
97
98                         } else if(strcmp(argv[i], "-k") == 0 || strcmp(argv[i], "-key") == 0) {
99                                 ckey = strtol(argv[++i], &endp, 10);
100                                 if(endp == argv[i] || ckey < 0) {
101                                         fprintf(stderr, "%s must be followed by a valid color key\n", argv[i - 1]);
102                                         return 1;
103                                 }
104
105                         } else if(strcmp(argv[i], "-conv565") == 0) {
106                                 conv565 = 1;
107
108                         } else if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-help") == 0) {
109                                 print_usage(argv[0]);
110                                 return 0;
111
112                         } else {
113                                 fprintf(stderr, "invalid option: %s\n", argv[i]);
114                                 print_usage(argv[0]);
115                                 return 1;
116                         }
117
118                 } else {
119                         if(!outfp) {
120                                 if(name) {
121                                         if(!(outfp = fopen(name, "wb"))) {
122                                                 fprintf(stderr, "failed to open output file: %s: %s\n", name, strerror(errno));
123                                                 return 1;
124                                         }
125                                 } else {
126                                         outfp = stdout;
127                                 }
128                         }
129
130                         if(proc_sheet(argv[i]) == -1) {
131                                 return 1;
132                         }
133                 }
134         }
135
136         return 0;
137 }
138
139 int proc_sheet(const char *fname)
140 {
141         int i, j, x, y, num_xtiles, num_ytiles, xsz, ysz;
142         struct image img;
143         struct file_header hdr;
144
145         if(load_image(&img, fname) == -1) {
146                 fprintf(stderr, "failed to load image: %s\n", fname);
147                 return -1;
148         }
149         if(conv565) {
150                 struct image tmp;
151                 if(conv_image_rgb565(&tmp, &img) == -1) {
152                         fprintf(stderr, "failed to convert image to 16bpp 565: %s\n", fname);
153                         free(img.pixels);
154                         return -1;
155                 }
156                 free(img.pixels);
157                 img = tmp;
158         }
159
160         if(rect.w <= 0) {
161                 rect.w = img.width;
162                 rect.h = img.height;
163         }
164
165         if(tile_xsz <= 0) {
166                 num_xtiles = num_ytiles = 1;
167                 xsz = rect.w - padding;
168                 ysz = rect.h - padding;
169         } else {
170                 if(padding) {
171                         num_xtiles = num_ytiles = 0;
172                         i = 0;
173                         while(i < rect.w) {
174                                 num_xtiles++;
175                                 i += tile_xsz + padding;
176                         }
177                         i = 0;
178                         while(i < rect.h) {
179                                 num_ytiles++;
180                                 i += tile_ysz + padding;
181                         }
182                 } else {
183                         num_xtiles = rect.w / tile_xsz;
184                         num_ytiles = rect.h / tile_ysz;
185                 }
186                 xsz = tile_xsz;
187                 ysz = tile_ysz;
188         }
189
190         memcpy(hdr.magic, MAGIC, sizeof hdr.magic);
191         hdr.width = xsz;
192         hdr.height = ysz;
193         hdr.bpp = img.bpp;
194         hdr.count = num_xtiles * num_ytiles;
195         fwrite(&hdr, sizeof hdr, 1, outfp);
196
197         y = rect.y;
198         for(i=0; i<num_ytiles; i++) {
199                 x = rect.x;
200                 for(j=0; j<num_xtiles; j++) {
201                         rlesprite(&img, x, y, xsz, ysz);
202                         x += xsz + padding;
203                 }
204                 y += ysz + padding;
205         }
206
207         free(img.pixels);
208         return 0;
209 }
210
211 int rlesprite(struct image *img, int x, int y, int xsz, int ysz)
212 {
213         int i, j, numops, mode, new_mode, start, skip_acc, pixsz = img->bpp / 8;
214         unsigned char *scanptr, *pptr;
215         struct csop *ops, *optr, endop = {0};
216
217         pptr = img->pixels + y * img->scansz + x * pixsz;
218
219         ops = optr = alloca((xsz + 1) * ysz * sizeof *ops);
220
221         for(i=0; i<ysz; i++) {
222                 mode = -1;
223                 start = -1;
224
225                 if(i > 0) {
226                         optr++->op = CSOP_ENDL;
227                 }
228
229                 for(j=0; j<xsz; j++) {
230                         if(memcmp(pptr, &ckey, pixsz) == 0) {
231                                 new_mode = CSOP_SKIP;
232                         } else {
233                                 new_mode = CSOP_COPY;
234                         }
235
236                         if(new_mode != mode) {
237                                 if(mode != -1) {
238                                         assert(start >= 0);
239                                         optr->op = mode;
240                                         optr->len = j - start;
241                                         optr++;
242                                 }
243                                 mode = new_mode;
244                                 start = j;
245                         }
246                         pptr += pixsz;
247                 }
248                 pptr += img->scansz - xsz * pixsz;
249
250                 if(mode != -1) {
251                         assert(start >= 0);
252                         optr->op = mode;
253                         optr->len = xsz - start;
254                         optr++;
255                 }
256         }
257         numops = optr - ops;
258
259         scanptr = pptr = img->pixels + y * img->scansz + x * img->bpp / 8;
260         optr = ops;
261         skip_acc = 0;
262
263         for(i=0; i<numops; i++) {
264                 switch(optr->op) {
265                 case CSOP_SKIP:
266                         skip_acc += optr->len;
267                         pptr += optr->len * pixsz;
268                         break;
269
270                 case CSOP_ENDL:
271                         /* maybe at some point combine multiple endl into yskips? meh */
272                         fwrite(optr, sizeof *optr, 1, outfp);
273                         skip_acc = 0;
274                         scanptr += img->scansz;
275                         pptr = scanptr;
276                         break;
277
278                 case CSOP_COPY:
279                         if(skip_acc) {
280                                 struct csop skip = {0};
281                                 skip.op = CSOP_SKIP;
282                                 skip.len = skip_acc;
283                                 fwrite(&skip, sizeof skip, 1, outfp);
284                                 skip_acc = 0;
285                         }
286
287                         fwrite(optr, sizeof *optr, 1, outfp);
288                         fwrite(pptr, pixsz, optr->len, outfp);
289
290                         pptr += optr->len * pixsz;
291                         break;
292
293                 default:
294                         fprintf(stderr, "invalid op\n");
295                 }
296                 optr++;
297         }
298
299         endop.op = CSOP_END;
300         fwrite(&endop, sizeof endop, 1, outfp);
301         return 0;
302 }
303
304 void print_usage(const char *argv0)
305 {
306         printf("Usage: %s [options] <spritesheet>\n", argv0);
307         printf("Options:\n");
308         printf(" -o <filename>: output filename (default: stdout)\n");
309         printf(" -s,-size <WxH>: tile size (default: whole image)\n");
310         printf(" -r,-rect <WxH+X+Y>: use rectangle of the input image (default: whole image)\n");
311         printf(" -p,-pad <N>: how many pixels to skip between tiles in source image (default: 0)\n");
312         printf(" -coffset <offs>: colormap offset [0, 255] (default: 0)\n");
313         printf(" -k,-key <color>: color-key for transparency (default: 0)\n");
314         printf(" -conv565: convert image to 16bpp 565 before processing\n");
315         printf(" -h: print usage and exit\n");
316 }