]> git.draconx.ca Git - liblbx.git/blob - src/image.c
Implement new handling of the LBX image header data.
[liblbx.git] / src / image.c
1 #ifdef HAVE_CONFIG_H
2 #       include "config.h"
3 #endif
4
5 #include <stdlib.h>
6 #include <stdint.h>
7 #include <string.h>
8 #include <assert.h>
9 #include <errno.h>
10
11 #include "byteorder.h"
12 #include "misc.h"
13 #include "lbx.h"
14 #include "image.h"
15
16 #define FLAG_OVERWRITE 0x0400 /* Draw each frame on a clean slate (unsure). */
17 #define FLAG_PALETTE   0x1000 /* Image contains embedded palette. */
18 #define FLAG_LOOPING   0x2000 /* Loop over all frames in the image (unsure). */
19
20 struct lbx_image {
21         uint16_t width, height;
22         uint16_t wtf1;
23         uint16_t frames, leadin;
24         uint16_t flags;
25
26         FILE *f;
27         long foff;
28
29         int currentframe;
30         int currentx, currenty;
31         unsigned char **framedata;
32
33         uint32_t offsets[];
34 };
35
36 struct lbx_image *lbximg_fopen(FILE *f)
37 {
38         struct lbx_image tmp = {.f = f}, *new = NULL;
39         size_t rc;
40
41         if (fread(&tmp.width,  sizeof tmp.width,   1, f) != 1) goto readerr;
42         if (fread(&tmp.height, sizeof tmp.height,  1, f) != 1) goto readerr;
43         if (fread(&tmp.wtf1,   sizeof tmp.wtf1,    1, f) != 1) goto readerr;
44         if (fread(&tmp.frames, sizeof tmp.frames,  1, f) != 1) goto readerr;
45         if (fread(&tmp.leadin, sizeof tmp.leadin,  1, f) != 1) goto readerr;
46         if (fread(&tmp.flags,  sizeof tmp.flags,   1, f) != 1) goto readerr;
47
48         tmp.width  = letohs(tmp.width);  tmp.foff += sizeof tmp.width;
49         tmp.height = letohs(tmp.height); tmp.foff += sizeof tmp.height;
50         tmp.wtf1   = letohs(tmp.wtf1);   tmp.foff += sizeof tmp.wtf1;
51         tmp.frames = letohs(tmp.frames); tmp.foff += sizeof tmp.frames;
52         tmp.leadin = letohs(tmp.leadin); tmp.foff += sizeof tmp.leadin;
53         tmp.flags  = letohs(tmp.flags);  tmp.foff += sizeof tmp.flags;
54
55         /* Format constraints. */
56         if (tmp.frames <= tmp.leadin) {
57                 lbx_errno = LBX_EFORMAT;
58                 return NULL;
59         }
60
61         /*
62          * DEBUG ONLY.  These assertions exist to catch otherwise valid image
63          * files which differ from what I believe to be true of all LBX images.
64          * If we never find any exceptions, we can replace the assertions with
65          * assumptions.
66          */
67         _lbx_assert(tmp.wtf1 == 0);
68         _lbx_assert(!(tmp.flags & ~(FLAG_PALETTE|FLAG_OVERWRITE|FLAG_LOOPING)));
69
70         new = malloc(sizeof *new + (tmp.frames+1) * sizeof *new->offsets);
71         if (!new) {
72                 lbx_errno = -errno;
73                 return NULL;
74         }
75
76         *new = tmp;
77         new->currentframe = -1;
78
79         rc = fread(new->offsets, sizeof *new->offsets, new->frames+1, f);
80         if (rc < new->frames+1)
81                 goto readerr;
82         new->foff += sizeof *new->offsets * (new->frames+1);
83
84         return new;
85 readerr:
86         if (feof(f)) {
87                 lbx_errno = LBX_EEOF;
88         } else {
89                 lbx_errno = -errno;
90         }
91
92         free(new);
93         return NULL;
94 }
95
96 static int _lbx_drawrow(int first, struct lbx_image *img)
97 {
98         uint16_t type, yval, count, xval;
99         unsigned char *pos;
100         unsigned char abyss;
101         size_t rc;
102
103         assert(img->framedata);
104
105         if (fread(&type, sizeof type, 1, img->f) != 1) goto readerr;
106         type = letohs(type); img->foff += sizeof type;
107
108         if (first) {
109                 img->currentx = 0;
110                 img->currenty = 0;
111                 type = 0;
112         }
113
114         if (type == 0) {
115                 if (fread(&yval,  sizeof yval,  1, img->f) != 1) goto readerr;
116                 yval = letohs(yval); img->foff += sizeof yval;
117                 if (yval == 1000)
118                         return 1;
119                 if (fread(&count, sizeof count, 1, img->f) != 1) goto readerr;
120                 count = letohs(count); img->foff += sizeof count;
121                 if (fread(&xval,  sizeof xval,  1, img->f) != 1) goto readerr;
122                 xval = letohs(xval); img->foff += sizeof xval;
123                 if (xval == 1000)
124                         return 1;
125
126                 /* Ensure that the row fits in the image. */
127                 if (img->height - img->currenty <= yval || xval >= img->width) {
128                         lbx_errno = LBX_EFORMAT;
129                         return -1;
130                 }
131
132                 img->currenty += yval;
133                 img->currentx  = xval;
134         } else {
135                 if (fread(&xval,  sizeof xval,  1, img->f) != 1) goto readerr;
136                 xval = letohs(xval); img->foff += sizeof xval;
137
138                 if (img->width - img->currentx <= xval) {
139                         lbx_errno = LBX_EFORMAT;
140                         return -1;
141                 }
142                 img->currentx += xval;
143
144                 count = type;
145
146         }
147
148         if (count > img->width - img->currentx) {
149                 lbx_errno = LBX_EFORMAT;
150                 return -1;
151         }
152
153         pos = &img->framedata[img->currenty][img->currentx];
154         rc  = fread(pos, 1, count, img->f);
155         img->currentx += rc;
156         img->foff     += rc;
157
158         if (rc < count)
159                 goto readerr;
160
161         if (count % 2) {
162                 if (fread(&abyss, 1, 1, img->f) != 1)
163                         goto readerr;
164                 img->foff += 1;
165         }
166
167         return 0;
168 readerr:
169         if (feof(img->f)) {
170                 lbx_errno = LBX_EEOF;
171         } else {
172                 lbx_errno = -errno;
173         }
174         return -1;
175 }
176
177 unsigned char **lbximg_getframe(struct lbx_image *img, int frame)
178 {
179         if (frame >= img->frames || frame < 0) {
180                 lbx_errno = LBX_ERANGE;
181                 return NULL;
182         }
183
184         if (!img->framedata) {
185                 unsigned char *tmp;
186                 int i;
187
188                 tmp = malloc(img->width * img->height);
189                 if (!tmp) {
190                         lbx_errno = -errno;
191                         return NULL;
192                 }
193
194                 img->framedata = malloc(img->height * sizeof *img->framedata);
195                 if (!img->framedata) {
196                         lbx_errno = -errno;
197                         free(tmp);
198                         return NULL;
199                 }
200
201                 for (i = 0; i < img->height; i++) {
202                         img->framedata[i] = tmp + i * img->width;
203                 }
204         }
205
206         /* Start over if we are backtracking. */
207         if (img->currentframe > frame)
208                 img->currentframe == -1;
209
210         if (img->flags & FLAG_OVERWRITE) {
211                 /* Clear the slate. */
212                 memset(img->framedata[0], 0, img->width * img->height);
213         } else {
214                 /* We must have previous frame decoded to continue. */
215                 if (frame > img->currentframe + 1) {
216                         if (!lbximg_getframe(img, frame-1))
217                                 return NULL;
218                 }
219         }
220
221         if (img->currentframe != frame) {
222                 int rc, first = 1;
223
224                 if (_lbx_fseek(img->f, &img->foff, img->offsets[frame]) == -1)
225                         return NULL;
226
227                 do {
228                         rc = _lbx_drawrow(first, img);
229                         if (rc == -1)
230                                 return NULL;
231                         first = 0;
232
233                         if (!rc && img->foff > img->offsets[frame+1]) {
234                                 lbx_errno = LBX_EFORMAT;
235                                 return NULL;
236                         }
237                 } while (!rc);
238         }
239
240         img->currentframe = frame;
241         return img->framedata;
242 }
243
244 int lbximg_loadpalette(FILE *f, struct lbx_colour palette[static 256])
245 {
246         uint8_t entry[4];
247         int i;
248
249         for (i = 0; i < 256; i++) {
250                 if (fread(entry, sizeof entry, 1, f) != 1) {
251                         lbx_errno = (feof(f)) ? LBX_EEOF : -errno;
252                         return -1;
253                 }
254
255                 if (entry[0] != 1) {
256                         lbx_errno = LBX_EFORMAT;
257                         return -1;
258                 }
259
260                 palette[i] = (struct lbx_colour){
261                         .red   = entry[1] << 2,
262                         .green = entry[2] << 2,
263                         .blue  = entry[3] << 2,
264                 };
265         }
266
267         return 0;
268 }
269
270 int
271 lbximg_getpalette(struct lbx_image *img, struct lbx_colour palette[static 256])
272 {
273         size_t hdrlen = 6*(sizeof img->wtf1)+(img->frames+1)*(sizeof *img->offsets);
274         unsigned int i;
275         size_t rc;
276
277         uint16_t start, count;
278         uint8_t  entry[4];
279
280         /* Do nothing if the image doesn't have embedded palette data. */
281         if (!(img->flags & FLAG_PALETTE))
282                 return 0;
283
284         /* Palette data is located right after the header. */
285         if (_lbx_fseek(img->f, &img->foff, hdrlen) == -1)
286                 return -1;
287         
288         /* Palette header */
289         if (fread(&start, sizeof start, 1, img->f) != 1) goto readerr;
290         if (fread(&count, sizeof count, 1, img->f) != 1) goto readerr;
291         start = letohs(start); img->foff += sizeof start;
292         count = letohs(count); img->foff += sizeof count;
293
294         if (start + count > 256) {
295                 lbx_errno = LBX_EFORMAT;
296                 return -1;
297         }
298
299         if (hdrlen + 2*sizeof start + count*sizeof entry > img->offsets[0]) {
300                 lbx_errno = LBX_EFORMAT;
301                 return -1;
302         }
303
304         for (i = 0; i < count; i++) {
305                 rc = fread(entry, 1, sizeof entry, img->f);
306                 img->foff += rc;
307
308                 if (rc < sizeof entry) {
309                         goto readerr;
310                 }
311
312                 if (entry[0] != 0) {
313                         lbx_errno = LBX_EFORMAT;
314                         return -1;
315                 }
316
317                 palette[start + i] = (struct lbx_colour){
318                         .red   = entry[1] << 2,
319                         .green = entry[2] << 2,
320                         .blue  = entry[3] << 2,
321                 };
322         }
323
324         return 0;
325 readerr:
326         lbx_errno = feof(img->f) ? LBX_EEOF : -errno;
327         return -1;
328 }
329
330 void lbximg_getinfo(struct lbx_image *img, struct lbx_imginfo *info)
331 {
332         *info = (struct lbx_imginfo) {
333                 .width      = img->width,
334                 .height     = img->height,
335                 .nframes    = img->frames,
336                 .haspalette = (_Bool)(img->flags & FLAG_PALETTE),
337         };
338
339         /* There seems to be two ways of specifying that an image loops. */
340         if (img->flags & FLAG_LOOPING) {
341                 info->loopstart = 0;
342                 info->looping   = 1;
343         } else if (img->leadin != img->frames - 1) {
344                 info->loopstart = img->leadin;
345                 info->looping   = 1;
346         }
347 }
348
349 void lbximg_close(struct lbx_image *img)
350 {
351         if (!img) return;
352
353         if (img->framedata) {
354                 free(img->framedata[0]);
355                 free(img->framedata);
356         }
357
358         if (img->f) {
359                 fclose(img->f);
360         }
361
362         free(img);
363 }