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