tools: bmp2tiles clearer error message
[swan-dev] / tools / bmp2tiles / bmp2tiles.c
1 /**
2  * File              : bmp2tiles.c
3  * Author            : Robin Krens <robin@robinkrens.nl>
4  * Date              : 04.06.2022
5  * Last Modified Date: 15.06.2022
6  * Last Modified By  : Robin Krens <robin@robinkrens.nl>
7  */
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <stdint.h>
12 #include <stdbool.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <assert.h>
16 #include <getopt.h>
17 #include <errno.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
20 #include <sys/sysmacros.h>
21 #include <SDL2/SDL_image.h>
22
23 #define TILE_SZ         32
24 #define FOURBBP_ROW     8
25 #define FOURBPP_COL     8
26
27 typedef struct {
28         unsigned width;
29         unsigned height;
30         unsigned row_tiles;
31         unsigned col_tiles;
32         unsigned bpp;
33         unsigned char * data;
34         SDL_Colour * palette;
35         unsigned short outpal[8];
36 } swan_gfx_t;
37
38 int set_info(swan_gfx_t * gfx, SDL_Surface * img)
39 {
40         gfx->width = img->w;
41         gfx->height = img->h;
42         gfx->bpp = img->format->BitsPerPixel;
43
44         if (gfx->bpp != 8) {
45                 fprintf(stderr, "error: %d bpp format detected\n", img->format->BitsPerPixel);
46                 return -1;
47         }
48
49         if (gfx->width % 8 | gfx->height % 8) {
50                 fprintf(stderr, "error: image not multiple of 16x16 tiles\n \
51                                 width: %d\theight: %d\n", gfx->width, gfx->height);
52                 return -1;
53         }
54
55         gfx->row_tiles = gfx->height / 8;
56         gfx->col_tiles = gfx->width / 8;
57
58         /* set userdata to iterate over
59          * pixels */
60         SDL_LockSurface(img);
61         img->userdata = img->pixels;
62         gfx->data = (unsigned char *) img->userdata;
63         gfx->palette = img->format->palette->colors;
64         SDL_UnlockSurface(img);
65         
66         return 0;
67 }
68
69 void generate_4bpp_tile(unsigned char *buf, swan_gfx_t * gfx,
70                 bool packed)
71 {
72         unsigned char * ptr = gfx->data;
73         for (int i = 0; i < TILE_SZ; i+=4) {
74                 for (int j = 0; j < 4; ++j) {
75                         if (packed) { /* packed format */
76                                 //printf("%d %d ", ptr[0], ptr[1]);
77                                 if (ptr[0] >= 8 || ptr[1] >= 8) 
78                                         fprintf(stdout, "warning: too many colors detected\n");
79                                 gfx->outpal[ptr[0]] = (gfx->palette[ptr[0]].r >> 4) << 8 |
80                                                       (gfx->palette[ptr[0]].g >> 4) << 4 |
81                                                       gfx->palette[ptr[0]].b >> 4;
82                                 buf[i+j] = ptr[0] << 4;
83                                 buf[i+j] |= ptr[1];
84                                 ptr += 2;
85                         } else { /* TODO: planar format */
86                                 for (int x = 0; x < 8; ++x) {
87                                         if (ptr[x] >= 8) 
88                                                 fprintf(stdout, "warning: too many colors detected\n");
89                                         buf[i + j] = ptr[x] << j; 
90                                 }
91                         }
92                 }
93                 if (!packed)
94                         ptr += 8;
95                 else {
96                         ptr += (8 * (gfx->col_tiles-1));
97                 }
98         }
99 }
100
101 FILE * openstream(const char * name, const char * prefix,
102                 unsigned tile_nr, bool append)
103 {
104         assert(strlen(name) < 200);
105         
106         FILE * stream;
107
108         unsigned buf_len = 256;
109         char buf[buf_len];
110         snprintf(buf, buf_len, "%s/%s%d.%s", prefix, name, tile_nr, prefix);
111
112         //printf("%s\n", buf);
113         if (!append) { 
114                 stream = fopen(buf, "w");
115         } else {
116                 stream = fopen(buf, "a");
117         }
118
119         return stream;
120 }
121
122 int create_dir(const char *name)
123 {
124         struct stat statbuf;
125
126         if (stat(name, &statbuf) == -1) 
127                 return mkdir(name, 0755);
128         
129         if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
130                 return -1;
131
132         return 0;
133 }
134
135
136 int main(int argc, char *argv[])
137 {
138         SDL_Surface * rawbmp;
139         FILE * gfxstream;
140         FILE * palstream;
141         int opt;
142         char * infile;
143         char outfile[256];
144         bool bout = false;
145         const short fill = 0xFFFF;
146         bool seperate_gfx = false;
147         bool seperate_pal = false;
148
149         while ((opt = getopt(argc, argv, "po:sv")) != -1) {
150                 switch (opt) {
151                         case 'p':
152                                 seperate_pal = true;
153                                 printf("generate individual .pal for each tile");
154                                 break;
155                         case 'o':
156                                 printf("output file %s\n", optarg);
157                                 strcpy(outfile, optarg);
158                                 bout = true;
159                                 break;
160                         case 's':
161                                 seperate_gfx = true;
162                                 printf("generate .gfx for each tile\n");
163                                 break;
164                         case 'v':
165                                 printf("verbose");
166                                 break;
167                         default: /* '?' */
168                                 fprintf(stderr, "Usage: %s [-s] [-o output] file \n", argv[0]);
169                                 exit(EXIT_FAILURE);
170                }
171         }
172
173         if (optind >= argc) {
174                 fprintf(stderr, "Expected input .bmp file!\n");
175                 fprintf(stderr, "Usage: %s [-s] [-o output] [file]\n", argv[0]);
176                 exit(EXIT_FAILURE);
177         }
178
179         infile = argv[optind];
180
181         rawbmp = SDL_LoadBMP(infile);
182         if (!rawbmp) {
183                 fprintf(stderr, "can not load %s file\n", infile);
184                 exit(EXIT_FAILURE);
185         }
186
187         swan_gfx_t * gfx = calloc(1, sizeof(swan_gfx_t));
188         int ret = set_info(gfx, rawbmp);
189
190         if (ret != 0)
191                 goto cleanup;
192
193         unsigned char *tile_buf =  calloc(TILE_SZ, sizeof(unsigned char));
194
195         if (!bout) {
196                 sprintf(outfile, "out");
197         }
198         
199         /* check for existing gfx/ and pal/ directories */
200         ret = create_dir("gfx/");
201         ret += create_dir("pal/");
202
203         if (ret) {
204                 fprintf(stderr, "error: can not create output gfx/ or pal/ location\n");
205                 goto cleanup;
206         }
207
208         fprintf(stdout, "generating %d tile(s) \
209                         in %d gfx file(s) \
210                         with %d pal file(s)\n",
211                         gfx->row_tiles * gfx->col_tiles,
212                         seperate_gfx ? gfx->row_tiles * gfx->col_tiles : 1,
213                         seperate_pal ? gfx->row_tiles * gfx->col_tiles : 1);
214
215         for (int i = 0, cnt = 0; i < gfx->row_tiles; ++i) {
216                 for (int j = 0; j < gfx->col_tiles; ++j, ++cnt) {
217
218                         if (i == 0 && j == 0) {
219                                 gfxstream = openstream(outfile, "gfx", 0, false);
220                                 palstream = openstream(outfile, "pal", 0, false);
221                         } else {
222                                 if (seperate_gfx) {
223                                         gfxstream = openstream(outfile, "gfx", cnt, false);
224                                 }
225                                 if (seperate_pal) {
226                                         palstream = openstream(outfile, "pal", cnt, false);
227                                 }
228                         }
229
230                         generate_4bpp_tile(tile_buf, gfx, true);
231                         int ret = fwrite(tile_buf, sizeof(unsigned char), TILE_SZ, gfxstream);
232                         if (ret != TILE_SZ) {
233                                 fprintf(stderr, "failed to convert, only write %d instead of %d\n", ret, TILE_SZ);
234                                 fclose(gfxstream);
235                                 goto cleanup;
236                         }
237
238                         fwrite(gfx->outpal, sizeof(unsigned short), 8, palstream);
239                         for (int i = 0; i < 8; ++i) 
240                                 ret += fwrite(&fill, sizeof(unsigned short), 1, palstream);
241                         
242                         gfx->data += 8; /* next column */
243                         
244                         if (gfx->row_tiles == (i-1) && gfx->col_tiles == (j-1)) {
245                                 fclose(gfxstream);
246                                 fclose(palstream);
247                         } else {
248                                 if (seperate_gfx) 
249                                         fclose(gfxstream);
250                                 if (seperate_pal)
251                                         fclose(palstream);
252                         }
253                 }
254                 gfx->data += (gfx->col_tiles * 64) - (8 * gfx->col_tiles);
255         }
256         
257 cleanup:
258         free(gfx);
259         free(tile_buf);
260         SDL_FreeSurface(rawbmp);
261 }