world's saddest console
[dosdemo] / tools / csprite / src / main.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <stdint.h>
5 #include <assert.h>
6 #include <alloca.h>
7 #include "image.h"
8
9 struct rect {
10         int x, y, w, h;
11 };
12
13 enum { AS_GNU, AS_NASM };
14
15 int csprite(struct image *img, int x, int y, int xsz, int ysz);
16 int proc_sheet(const char *fname);
17 void print_usage(const char *argv0);
18
19 int tile_xsz, tile_ysz;
20 struct rect rect;
21 int cmap_offs;
22 int ckey;
23 int fbpitch, fbwidth = 320;
24 const char *name = "sprite";
25 int asyntax = AS_GNU;
26 int conv565;
27 int padding;
28 const char *wrop = "mov";
29
30 int main(int argc, char **argv)
31 {
32         int i;
33         char *endp;
34
35         for(i=1; i<argc; i++) {
36                 if(argv[i][0] == '-') {
37                         if(strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "-size") == 0) {
38                                 if(sscanf(argv[++i], "%dx%d", &tile_xsz, &tile_ysz) != 2 ||
39                                                 tile_xsz <= 0 || tile_ysz <= 0) {
40                                         fprintf(stderr, "%s must be followed by WIDTHxHEIGHT\n", argv[i - 1]);
41                                         return 1;
42                                 }
43
44                         } else if(strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "-rect") == 0) {
45                                 rect.x = rect.y = 0;
46                                 if(sscanf(argv[++i], "%dx%d+%d+%d", &rect.w, &rect.h, &rect.x, &rect.y) < 2 || rect.w <= 0 || rect.h <= 0) {
47                                         fprintf(stderr, "%s must be followed by WIDTHxHEIGHT+X+Y\n", argv[i - 1]);
48                                         return 1;
49                                 }
50
51                         } else if(strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "-pad") == 0) {
52                                 padding = strtol(argv[++i], &endp, 10);
53                                 if(endp == argv[i] || padding < 0) {
54                                         fprintf(stderr, "%s must be followed by a positive number\n", argv[i - 1]);
55                                         return 1;
56                                 }
57
58                         } else if(strcmp(argv[i], "-coffset") == 0) {
59                                 cmap_offs = strtol(argv[++i], &endp, 10);
60                                 if(endp == argv[i] || cmap_offs < 0 || cmap_offs >= 256) {
61                                         fprintf(stderr, "-coffset must be followed by a valid colormap offset\n");
62                                         return 1;
63                                 }
64
65                         } else if(strcmp(argv[i], "-fbwidth") == 0) {
66                                 fbwidth = atoi(argv[++i]);
67                                 if(fbwidth <= 0) {
68                                         fprintf(stderr, "-fbwidth must be followed by a positive number\n");
69                                         return 1;
70                                 }
71
72                         } else if(strcmp(argv[i], "-fbpitch") == 0) {
73                                 fbpitch = atoi(argv[++i]);
74                                 if(fbpitch <= 0) {
75                                         fprintf(stderr, "-fbpitch must be followed by a positive number\n");
76                                         return 1;
77                                 }
78
79                         } else if(strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "-name") == 0) {
80                                 name = argv[++i];
81                                 if(!name) {
82                                         fprintf(stderr, "%s must be followed by a name\n", argv[i - 1]);
83                                         return 1;
84                                 }
85
86                         } else if(strcmp(argv[i], "-k") == 0 || strcmp(argv[i], "-key") == 0) {
87                                 ckey = strtol(argv[++i], &endp, 10);
88                                 if(endp == argv[i] || ckey < 0) {
89                                         fprintf(stderr, "%s must be followed by a valid color key\n", argv[i - 1]);
90                                         return 1;
91                                 }
92
93                         } else if(strcmp(argv[i], "-conv565") == 0) {
94                                 conv565 = 1;
95
96                         } else if(strcmp(argv[i], "-x") == 0 || strcmp(argv[i], "-xor") == 0) {
97                                 wrop = "xor";
98
99                         } else if(strcmp(argv[i], "-gas") == 0) {
100                                 asyntax = AS_GNU;
101
102                         } else if(strcmp(argv[i], "-nasm") == 0) {
103                                 asyntax = AS_NASM;
104
105                         } else if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-help") == 0) {
106                                 print_usage(argv[0]);
107                                 return 0;
108
109                         } else {
110                                 fprintf(stderr, "invalid option: %s\n", argv[i]);
111                                 print_usage(argv[0]);
112                                 return 1;
113                         }
114
115                 } else {
116                         if(proc_sheet(argv[i]) == -1) {
117                                 return 1;
118                         }
119                 }
120         }
121
122         return 0;
123 }
124
125 /* prototype of generated function is (void *fb, int x, int y, int idx) */
126 const char *prefixfmt[] = {
127         /* GNU assembler template */
128         "\t.text\n"
129         "\t.global %s\n"
130         "\t.global _%s\n"
131         "%s:\n"
132         "_%s:\n"
133         "\tmov 12(%%esp), %%eax\n"
134         "\tmov $%d, %%ecx\n"
135         "\tmul %%ecx\n"
136         "\tadd 8(%%esp), %%eax\n"
137         "\tadd 4(%%esp), %%eax\n"
138         "\tmov %%eax, %%edx\n"
139         "\tmov 16(%%esp), %%eax\n"
140         "\tjmp *tiletab(,%%eax,4)\n\n"
141         "tiletab:\n",
142
143         /* NASM template */
144         /* TODO hardcoding the 16bpp changes for now, generalize later
145          *      and while we're at it, let's get rid of the mul too ...
146          */
147         "\tsection .text\n"
148         "\tglobal %s\n"
149         "\tglobal _%s\n"
150         "%s:\n"
151         "_%s:\n"
152         "\tmov eax, [esp + 12]\n"
153         ";\tmov ecx, %d\n"
154         ";\tmul ecx\n"
155         "\tmov ecx, eax\n"
156         "\tshl eax, 9\n"
157         "\tshl ecx, 7\n"
158         "\tadd eax, ecx\n"
159         "\tadd eax, [esp + 8]\n"
160         "\tadd eax, [esp + 8]\n"
161         "\tadd eax, [esp + 4]\n"
162         "\tmov edx, eax\n"
163         "\tmov eax, [esp + 16]\n"
164         "\tjmp [titletab + eax * 4]\n\n"
165         "titletab:\n"
166 };
167
168 int proc_sheet(const char *fname)
169 {
170         int i, j, x, y, num_xtiles, num_ytiles, xsz, ysz, tidx;
171         struct image img;
172
173         if(load_image(&img, fname) == -1) {
174                 fprintf(stderr, "failed to load image: %s\n", fname);
175                 return -1;
176         }
177         if(conv565) {
178                 struct image tmp;
179                 if(conv_image_rgb565(&tmp, &img) == -1) {
180                         fprintf(stderr, "failed to convert image to 16bpp 565: %s\n", fname);
181                         free(img.pixels);
182                         return -1;
183                 }
184                 free(img.pixels);
185                 img = tmp;
186         }
187
188         if(!fbpitch) fbpitch = fbwidth * img.bpp / 8;
189
190         if(rect.w <= 0) {
191                 rect.w = img.width;
192                 rect.h = img.height;
193         }
194
195         if(tile_xsz <= 0) {
196                 num_xtiles = num_ytiles = 1;
197                 xsz = rect.w - padding;
198                 ysz = rect.h - padding;
199         } else {
200                 if(padding) {
201                         num_xtiles = num_ytiles = 0;
202                         i = 0;
203                         while(i < rect.w) {
204                                 num_xtiles++;
205                                 i += tile_xsz + padding;
206                         }
207                         i = 0;
208                         while(i < rect.h) {
209                                 num_ytiles++;
210                                 i += tile_ysz + padding;
211                         }
212                 } else {
213                         num_xtiles = rect.w / tile_xsz;
214                         num_ytiles = rect.h / tile_ysz;
215                 }
216                 xsz = tile_xsz;
217                 ysz = tile_ysz;
218         }
219
220         printf(prefixfmt[asyntax], name, name, name, name, fbpitch);
221         for(i=0; i<num_ytiles*num_xtiles; i++) {
222                 if(asyntax == AS_GNU) {
223                         printf("\t.long tile%d\n", i);
224                 } else {
225                         printf("\tdd tile%d\n", i);
226                 }
227         }
228         putchar('\n');
229
230         tidx = 0;
231         y = rect.y;
232         for(i=0; i<num_ytiles; i++) {
233                 x = rect.x;
234                 for(j=0; j<num_xtiles; j++) {
235                         printf("tile%d:\n", tidx++);
236                         csprite(&img, x, y, xsz, ysz);
237                         x += xsz + padding;
238                 }
239                 y += ysz + padding;
240         }
241
242         free(img.pixels);
243         return 0;
244 }
245
246 enum {
247         CSOP_SKIP,
248         CSOP_FILL,
249         CSOP_COPY,
250         CSOP_ENDL
251 };
252
253 struct csop {
254         unsigned char op, val;
255         int len;
256 };
257
258 int csprite(struct image *img, int x, int y, int xsz, int ysz)
259 {
260         int i, j, numops, mode, new_mode, start, skip_acc, lenbytes, pixsz = img->bpp / 8;
261         unsigned char *pptr = img->pixels + y * img->scansz + x * pixsz;
262         struct csop *ops, *optr;
263
264         ops = optr = alloca((xsz + 1) * ysz * sizeof *ops);
265
266         for(i=0; i<ysz; i++) {
267                 mode = -1;
268                 start = -1;
269
270                 if(i > 0) {
271                         optr++->op = CSOP_ENDL;
272                 }
273
274                 for(j=0; j<xsz; j++) {
275                         if(memcmp(pptr, &ckey, pixsz) == 0) {
276                                 new_mode = CSOP_SKIP;
277                         } else {
278                                 new_mode = CSOP_COPY;
279                         }
280
281                         if(new_mode != mode) {
282                                 if(mode != -1) {
283                                         assert(start >= 0);
284                                         optr->op = mode;
285                                         optr->len = j - start;
286                                         optr++;
287                                 }
288                                 mode = new_mode;
289                                 start = j;
290                         }
291                         pptr += pixsz;
292                 }
293                 pptr += img->scansz - xsz * pixsz;
294
295                 if(mode != -1) {
296                         assert(start >= 0);
297                         optr->op = mode;
298                         optr->len = xsz - start;
299                         optr++;
300                 }
301         }
302         numops = optr - ops;
303
304         pptr = img->pixels + y * img->scansz + x * img->bpp / 8;
305         optr = ops;
306         skip_acc = 0;
307         /* edx points to dest */
308         for(i=0; i<numops; i++) {
309                 switch(optr->op) {
310                 case CSOP_SKIP:
311                         skip_acc += optr->len;
312                         pptr += optr->len * pixsz;
313                         break;
314
315                 case CSOP_ENDL:
316                         skip_acc += fbwidth - xsz;
317                         pptr += img->scansz - xsz * pixsz;
318                         break;
319
320                 case CSOP_COPY:
321                         if(skip_acc) {
322                                 if(asyntax == AS_GNU) {
323                                         printf("\tadd $%d, %%edx\n", skip_acc * pixsz);
324                                 } else {
325                                         printf("\tadd edx, %d\n", skip_acc * pixsz);
326                                 }
327                                 skip_acc = 0;
328                         }
329
330                         lenbytes = optr->len * pixsz;
331                         for(j=0; j<lenbytes / 4; j++) {
332                                 if(asyntax == AS_GNU) {
333                                         printf("\t%sl $0x%x, %d(%%edx)\n", wrop, *(uint32_t*)pptr, j * 4);
334                                 } else {
335                                         printf("\t%s dword [edx + %d], 0x%x\n", wrop, j * 4, *(uint32_t*)pptr);
336                                 }
337                                 pptr += 4;
338                         }
339                         j *= 4;
340                         switch(lenbytes % 4) {
341                         case 3:
342                                 if(asyntax == AS_GNU) {
343                                         printf("\t%sb $0x%x, %d(%%edx)\n", wrop, (unsigned int)*pptr++, j++);
344                                 } else {
345                                         printf("\t%s byte [edx + %d], 0x%x\n", wrop, j++, (unsigned int)*pptr++);
346                                 }
347                         case 2:
348                                 if(asyntax == AS_GNU) {
349                                         printf("\t%sw $0x%x, %d(%%edx)\n", wrop, (unsigned int)*(uint16_t*)pptr, j);
350                                 } else {
351                                         printf("\t%s word [edx + %d], 0x%x\n", wrop, j, (unsigned int)*(uint16_t*)pptr);
352                                 }
353                                 pptr += 2;
354                                 j += 2;
355                                 break;
356                         case 1:
357                                 if(asyntax == AS_GNU) {
358                                         printf("\t%sb $0x%x, %d(%%edx)\n", wrop, (unsigned int)*pptr++, j++);
359                                 } else {
360                                         printf("\t%s byte [edx + %d], 0x%x\n", wrop, j++, (unsigned int)*pptr++);
361                                 }
362                                 break;
363                         }
364
365                         skip_acc = optr->len;
366                         break;
367
368                 default:
369                         printf("\t%c invalid op: %d\n", asyntax == AS_GNU ? '#' : ';', optr->op);
370                 }
371                 optr++;
372         }
373         printf("\tret\n");
374
375         return 0;
376 }
377
378 void print_usage(const char *argv0)
379 {
380         printf("Usage: %s [options] <spritesheet>\n", argv0);
381         printf("Options:\n");
382         printf(" -s,-size <WxH>: tile size (default: whole image)\n");
383         printf(" -r,-rect <WxH+X+Y>: use rectangle of the input image (default: whole image)\n");
384         printf(" -p,-pad <N>: how many pixels to skip between tiles in source image (default: 0)\n");
385         printf(" -coffset <offs>: colormap offset [0, 255] (default: 0)\n");
386         printf(" -fbpitch <pitch>: target framebuffer pitch (scanline size in bytes)\n");
387         printf(" -k,-key <color>: color-key for transparency (default: 0)\n");
388         printf(" -conv565: convert image to 16bpp 565 before processing\n");
389         printf(" -x,-xor: use XOR for writing pixels instead of MOV\n");
390         printf(" -gas: output GNU assembler code (default)\n");
391         printf(" -nasm: output NASM-compatible code\n");
392         printf(" -h: print usage and exit\n");
393 }