]> git.draconx.ca Git - liblbx.git/blob - src/image.c
liblbx: Kill byteorder.h.
[liblbx.git] / src / image.c
1 /*
2  *  2ooM: The Master of Orion II Reverse Engineering Project
3  *  Library for working with LBX image files.
4  *  Copyright (C) 2006-2008 Nick Bowler
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 #ifdef HAVE_CONFIG_H
20 #       include "config.h"
21 #endif
22
23 #include <stdlib.h>
24 #include <stdint.h>
25 #include <string.h>
26 #include <assert.h>
27 #include <errno.h>
28
29 #include "pack.h"
30 #include "misc.h"
31 #include "lbx.h"
32 #include "image.h"
33
34 #define FLAG_OVERWRITE 0x0400 /* Draw each frame on a clean slate (unsure). */
35 #define FLAG_PALETTE   0x1000 /* Image contains embedded palette. */
36 #define FLAG_LOOPING   0x2000 /* Loop over all frames in the image (unsure). */
37 #define FLAG_ALL (FLAG_OVERWRITE|FLAG_PALETTE|FLAG_LOOPING)
38
39 #define HDR_LEN 12
40
41 struct lbx_image {
42         unsigned short width, height;
43         unsigned short wtf, flags;
44         unsigned short frames, leadin;
45         unsigned short palstart, palcount;
46
47         FILE *f;
48         long foff, paloff;
49
50         int currentframe;
51         int currentx, currenty;
52         unsigned char **framedata;
53         unsigned char **mask;
54
55         unsigned long offsets[];
56 };
57
58 static struct lbx_image *lbximg_init(unsigned char hdr[static HDR_LEN])
59 {
60         unsigned short nframes = unpack_16_le(hdr+6);
61         struct lbx_image *img;
62
63         img = malloc(sizeof *img + sizeof img->offsets[0] * (nframes+1));
64         if (!img) {
65                 lbx_errno = -errno;
66                 return NULL;
67         }
68
69         *img = (struct lbx_image) {
70                 .width  = unpack_16_le(hdr+0),
71                 .height = unpack_16_le(hdr+2),
72                 .wtf    = unpack_16_le(hdr+4),
73                 .frames = unpack_16_le(hdr+6),
74                 .leadin = unpack_16_le(hdr+8),
75                 .flags  = unpack_16_le(hdr+10),
76
77                 .currentframe = -1,
78         };
79
80         return img;
81 }
82
83 struct lbx_image *lbximg_fopen(FILE *f)
84 {
85         unsigned char hdr_buf[HDR_LEN];
86         struct lbx_image *img;
87         size_t rc;
88
89         if (fread(hdr_buf, 1, sizeof hdr_buf, f) != sizeof hdr_buf) {
90                 lbx_errno = -errno;
91                 if (feof(f))
92                         lbx_errno = LBX_EEOF;
93                 return NULL;
94         }
95
96         img = lbximg_init(hdr_buf);
97         if (!img)
98                 return NULL;
99
100         img->f    = f;
101         img->foff = sizeof hdr_buf;
102
103         /*
104          * DEBUG ONLY.  These assertions exist to catch otherwise valid image
105          * files which differ from what I believe to be true of all LBX images.
106          * If we never find any exceptions, we can replace the assertions with
107          * assumptions.
108          */
109         _lbx_assert(img->wtf == 0); /* version? */
110         _lbx_assert(img->frames > img->leadin); /* cmbtshp.lbx breaks this. */
111         _lbx_assert(!(img->flags & ~FLAG_ALL));
112
113         /* Read all offsets.  Should be merged with identical code in lbx.c */
114         for (unsigned i = 0; i <= img->frames; i++) {
115                 unsigned char buf[4];
116
117                 if (fread(buf, 1, sizeof buf, f) != sizeof buf) {
118                         lbx_errno = -errno;
119                         if (feof(f))
120                                 lbx_errno = LBX_EEOF;
121                         free(img);
122                         return NULL;
123                 }
124
125                 img->offsets[i] = unpack_32_le(buf);
126                 img->foff += 4;
127         }
128
129         if (img->flags & FLAG_PALETTE) {
130                 unsigned char buf[4];
131
132                 if (fread(buf, 1, sizeof buf, f) != sizeof buf) {
133                         lbx_errno = -errno;
134                         if (feof(f))
135                                 lbx_errno = LBX_EEOF;
136                         free(img);
137                         return NULL;
138                 }
139
140                 img->palstart = unpack_16_le(buf+0);
141                 img->palcount = unpack_16_le(buf+2);
142                 img->foff    += sizeof buf;
143                 img->paloff   = img->foff;
144
145                 if (img->palstart + img->palcount > 256) {
146                         lbx_errno = LBX_EFORMAT;
147                         free(img);
148                         return NULL;
149                 }
150         }
151
152         return img;
153 }
154
155 static int _lbx_drawrow(int first, struct lbx_image *img)
156 {
157         unsigned short type, count, yval, xval;
158         unsigned char buf[4];
159         unsigned char *pos;
160         size_t rc;
161
162         assert(img->framedata);
163         assert(img->mask);
164
165         if (fread(buf, 1, sizeof buf, img->f) != sizeof buf)
166                 goto readerr;
167         img->foff += 4;
168         type = unpack_16_le(buf+0);
169
170         if (first) {
171                 img->currentx = 0;
172                 img->currenty = 0;
173                 type = 0;
174         }
175
176         if (type == 0) {
177                 yval = unpack_16_le(buf+2);
178                 if (yval == 1000)
179                         return 1;
180
181                 if (fread(buf, 1, sizeof buf, img->f) != sizeof buf)
182                         goto readerr;
183                 img->foff += 4;
184                 count = unpack_16_le(buf+0);
185
186                 xval = unpack_16_le(buf+2);
187                 if (xval == 1000)
188                         return 1;
189
190                 /* Ensure that the row fits in the image. */
191                 if (img->height - img->currenty <= yval || xval >= img->width) {
192                         lbx_errno = LBX_EFORMAT;
193                         return -1;
194                 }
195
196                 img->currenty += yval;
197                 img->currentx  = xval;
198         } else {
199                 xval = unpack_16_le(buf+2);
200
201                 if (img->width - img->currentx <= xval) {
202                         lbx_errno = LBX_EFORMAT;
203                         return -1;
204                 }
205                 img->currentx += xval;
206
207                 count = type;
208         }
209
210         if (count > img->width - img->currentx) {
211                 lbx_errno = LBX_EFORMAT;
212                 return -1;
213         }
214
215         memset(&img->mask[img->currenty][img->currentx], 1, count);
216
217         pos = &img->framedata[img->currenty][img->currentx];
218         rc  = fread(pos, 1, count, img->f);
219         img->currentx += rc;
220         img->foff     += rc;
221
222         if (rc < count)
223                 goto readerr;
224
225         if (count % 2) {
226                 if (fread(buf, 1, 1, img->f) != 1)
227                         goto readerr;
228                 img->foff += 1;
229         }
230
231         return 0;
232 readerr:
233         if (feof(img->f)) {
234                 lbx_errno = LBX_EEOF;
235         } else {
236                 lbx_errno = -errno;
237         }
238         return -1;
239 }
240
241 static unsigned char **allocframebuffer(size_t width, size_t height)
242 {
243         unsigned char **new, *tmp;
244         size_t i;
245
246         tmp = calloc(height, width);
247         if (!tmp) {
248                 lbx_errno = -errno;
249                 return NULL;
250         }
251
252         new = malloc(height * sizeof *new);
253         if (!new) {
254                 lbx_errno = -errno;
255                 free(tmp);
256                 return NULL;
257         }
258
259         for (i = 0; i < height; i++) {
260                 new[i] = tmp + i * width;
261         }
262
263         return new;
264 }
265
266 unsigned char **lbximg_getframe(struct lbx_image *img, int frame)
267 {
268         if (frame >= img->frames || frame < 0) {
269                 lbx_errno = LBX_ERANGE;
270                 return NULL;
271         }
272
273         if (!img->framedata) {
274                 img->framedata = allocframebuffer(img->width, img->height);
275                 if (!img->framedata)
276                         return NULL;
277         }
278
279         if (!img->mask) {
280                 img->mask = allocframebuffer(img->width, img->height);
281                 if (!img->mask)
282                         return NULL;
283         }
284
285         /* Start over if we are backtracking. */
286         if (img->currentframe > frame)
287                 img->currentframe == -1;
288
289         if (img->flags & FLAG_OVERWRITE) {
290                 /* Clear the slate. */
291                 memset(img->framedata[0], 0, img->width * img->height);
292                 memset(img->mask[0],      0, img->width * img->height);
293         } else {
294                 /* We must have previous frame decoded to continue. */
295                 if (frame > img->currentframe + 1) {
296                         if (!lbximg_getframe(img, frame-1))
297                                 return NULL;
298                 }
299         }
300
301         if (img->currentframe != frame) {
302                 int rc, first = 1;
303
304                 if (_lbx_fseek(img->f, &img->foff, img->offsets[frame]) == -1)
305                         return NULL;
306
307                 do {
308                         rc = _lbx_drawrow(first, img);
309                         if (rc == -1)
310                                 return NULL;
311                         first = 0;
312
313                         if (!rc && img->foff > img->offsets[frame+1]) {
314                                 lbx_errno = LBX_EFORMAT;
315                                 return NULL;
316                         }
317                 } while (!rc);
318         }
319
320         img->currentframe = frame;
321         return img->framedata;
322 }
323
324 int lbximg_loadpalette(FILE *f, struct lbx_colour palette[static 256])
325 {
326         uint8_t entry[4];
327         int i;
328
329         for (i = 0; i < 256; i++) {
330                 if (fread(entry, sizeof entry, 1, f) != 1) {
331                         lbx_errno = (feof(f)) ? LBX_EEOF : -errno;
332                         return -1;
333                 }
334
335                 if (entry[0] != 1) {
336                         lbx_errno = LBX_EFORMAT;
337                         return -1;
338                 }
339
340                 palette[i] = (struct lbx_colour){
341                         .red   = entry[1] << 2,
342                         .green = entry[2] << 2,
343                         .blue  = entry[3] << 2,
344                 };
345         }
346
347         return 0;
348 }
349
350 int
351 lbximg_getpalette(struct lbx_image *img, struct lbx_colour palette[static 256])
352 {
353         unsigned int i;
354         size_t rc;
355
356         uint8_t  entry[4];
357
358         /* Do nothing if the image doesn't have embedded palette data. */
359         if (!(img->flags & FLAG_PALETTE))
360                 return 0;
361
362         if (_lbx_fseek(img->f, &img->foff, img->paloff) == -1)
363                 return -1;
364
365         for (i = 0; i < img->palcount; i++) {
366                 rc = fread(entry, 1, sizeof entry, img->f);
367                 img->foff += rc;
368
369                 if (rc < sizeof entry) {
370                         goto readerr;
371                 }
372
373                 if (entry[0] != 0) {
374                         lbx_errno = LBX_EFORMAT;
375                         return -1;
376                 }
377
378                 palette[img->palstart + i] = (struct lbx_colour){
379                         .red   = entry[1] << 2,
380                         .green = entry[2] << 2,
381                         .blue  = entry[3] << 2,
382                 };
383         }
384
385         return 0;
386 readerr:
387         lbx_errno = feof(img->f) ? LBX_EEOF : -errno;
388         return -1;
389 }
390
391 void lbximg_getinfo(struct lbx_image *img, struct lbx_imginfo *info)
392 {
393         *info = (struct lbx_imginfo) {
394                 .width      = img->width,
395                 .height     = img->height,
396                 .nframes    = img->frames,
397                 .palettesz  = (img->flags & FLAG_PALETTE) ? img->palcount : 0,
398         };
399
400         /* There seems to be two ways of specifying that an image loops. */
401         if (img->flags & FLAG_LOOPING) {
402                 info->loopstart = 0;
403                 info->looping   = 1;
404         } else if (img->leadin != img->frames - 1) {
405                 info->loopstart = img->leadin;
406                 info->looping   = 1;
407         }
408 }
409
410 unsigned char **lbximg_getmask(struct lbx_image *img)
411 {
412         return img->mask;
413 }
414
415 void lbximg_close(struct lbx_image *img)
416 {
417         if (!img) return;
418
419         if (img->framedata) {
420                 free(img->framedata[0]);
421                 free(img->framedata);
422         }
423
424         if (img->mask) {
425                 free(img->mask[0]);
426                 free(img->mask);
427         }
428
429         if (img->f) {
430                 fclose(img->f);
431         }
432
433         free(img);
434 }