]> git.draconx.ca Git - liblbx.git/blob - src/image.c
Trivial manual fixes.
[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 © 2006-2011, 2013-2014 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 #include <config.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <assert.h>
23 #include <stdbool.h>
24 #include <inttypes.h>
25 #include <errno.h>
26
27 #include "pack.h"
28 #include "misc.h"
29 #include "lbx.h"
30 #include "error.h"
31 #include "image.h"
32
33 #define FLAG_RAW       0x0100 /* Image is stored as a flat array of bytes. */
34 #define FLAG_OVERWRITE 0x0400 /* Draw each frame on a clean slate (unsure). */
35 #define FLAG_BUILDING  0x0800 /* Buildings have this, related to shadow? */
36 #define FLAG_PALETTE   0x1000 /* Image contains embedded palette. */
37 #define FLAG_LOOPING   0x2000 /* Loop over all frames in the image (unsure). */
38
39 #define FLAG_ALL (FLAG_RAW|FLAG_OVERWRITE|FLAG_BUILDING|FLAG_PALETTE|FLAG_LOOPING)
40
41 #define HDR_LEN 12
42
43 /* States for image readout */
44 enum {
45         READ_STATE_INIT,
46         READ_STATE_HEADER,
47         READ_STATE_DATA,
48         READ_STATE_DONE,
49 };
50
51 struct lbx_image_priv {
52         struct lbx_image pub;
53
54         unsigned short wtf, flags;
55         unsigned char  wtf2;
56
57         const struct lbx_file_ops *fops;
58         int (*dtor)(void *handle);
59         void *f;
60
61         /* State of frame readout */
62         unsigned currentx, currenty, currentn;
63         int read_state;
64
65         unsigned long offsets[FLEXIBLE_ARRAY_MEMBER];
66 };
67
68 static struct lbx_image_priv *lbx_img_init(unsigned char *hdr)
69 {
70         unsigned short nframes = unpack_16_le(hdr+6);
71         struct lbx_image_priv *img;
72
73         img = malloc(sizeof *img + sizeof img->offsets[0] * (nframes+1));
74         if (!img) {
75                 lbx_error_raise(LBX_ENOMEM);
76                 return NULL;
77         }
78
79         *img = (struct lbx_image_priv) {
80                 .pub.width  = unpack_16_le(hdr+0),
81                 .pub.height = unpack_16_le(hdr+2),
82                 .wtf        = unpack_16_le(hdr+4),
83                 .pub.frames = hdr[6],
84                 .wtf2       = hdr[7],
85                 .pub.leadin = hdr[8],
86                 .pub.chunk  = hdr[9],
87                 .flags      = unpack_16_le(hdr+10),
88         };
89
90         if (img->flags & FLAG_OVERWRITE)
91                 img->pub.chunk = 1;
92
93         if (img->flags & FLAG_LOOPING)
94                 img->pub.leadin = 0;
95
96         if (img->pub.leadin >= img->pub.frames) {
97                 lbx_error_raise(LBX_EFORMAT);
98                 free(img);
99                 return NULL;
100         }
101
102         return img;
103 }
104
105 struct lbx_image *lbx_img_open(void *f, const struct lbx_file_ops *fops,
106                                int (*destructor)(void *))
107 {
108         unsigned char hdr_buf[HDR_LEN];
109         struct lbx_image_priv *img;
110
111         if (fops->read(hdr_buf, sizeof hdr_buf, f) != sizeof hdr_buf) {
112                 if (fops->eof(f))
113                         lbx_error_raise(LBX_EEOF);
114                 return NULL;
115         }
116
117         img = lbx_img_init(hdr_buf);
118         if (!img)
119                 return NULL;
120
121         img->f    = f;
122         img->fops = fops;
123         img->dtor = destructor;
124
125         /*
126          * DEBUG ONLY.  These assertions exist to catch otherwise valid image
127          * files which differ from what I believe to be true of all LBX images.
128          * When we can decode every image, then these assertions should be
129          * replaced with constraints.
130          */
131         _lbx_assert(img->wtf  == 0); /* version? */
132         _lbx_assert(img->wtf2 == 0); /* very likely is simply reserved. */
133         _lbx_assert(!(img->flags & ~FLAG_ALL));
134
135         /* Read all offsets.  Should be merged with identical code in lbx.c */
136         for (unsigned i = 0; i <= img->pub.frames; i++) {
137                 unsigned char buf[4];
138
139                 if (fops->read(buf, sizeof buf, f) != sizeof buf) {
140                         if (fops->eof(f))
141                                 lbx_error_raise(LBX_EEOF);
142                         free(img);
143                         return NULL;
144                 }
145
146                 img->offsets[i] = unpack_32_le(buf);
147         }
148
149         return &img->pub;
150 }
151
152 static int pipe_close(void *f)
153 {
154         struct lbx_pipe_state *p = f;
155         int rc;
156
157         rc = fclose(p->f);
158         free(p);
159         return rc;
160 }
161
162 static int file_close(void *f)
163 {
164         return fclose((FILE *)f);
165 }
166
167 struct lbx_image *lbx_img_fopen(const char *file)
168 {
169         struct lbx_pipe_state *p;
170         FILE *f;
171
172         f = fopen(file, "rb");
173         if (!f) {
174                 lbx_error_raise(-errno);
175                 return NULL;
176         }
177
178         if (fseek(f, 0, SEEK_CUR) == 0)
179                 return lbx_img_open(f, &lbx_default_fops, file_close);
180
181         p = malloc(sizeof *p);
182         if (!p) {
183                 lbx_error_raise(LBX_ENOMEM);
184                 fclose(f);
185                 return NULL;
186         }
187
188         *p = (struct lbx_pipe_state) { .f = f };
189         return lbx_img_open(p, &lbx_pipe_fops, pipe_close);
190 }
191
192 int lbx_img_seek(struct lbx_image *pub, unsigned frame)
193 {
194         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
195
196         if (frame >= pub->frames) {
197                 lbx_error_raise(LBX_EINVAL);
198                 return -1;
199         }
200
201         if (img->fops->seek(img->f, img->offsets[frame], SEEK_SET)) {
202                 return -1;
203         }
204
205         if (!(img->flags & FLAG_RAW)) {
206                 unsigned char buf[4];
207
208                 /* Read frame header */
209                 if (img->fops->read(buf, 4, img->f) != 4) {
210                         if (img->fops->eof(img->f))
211                                 lbx_error_raise(LBX_EEOF);
212                         return -1;
213                 }
214
215                 if (unpack_16_le(buf) != 1) {
216                         lbx_error_raise(LBX_EFORMAT);
217                         return -1;
218                 }
219
220                 img->currentx = 0;
221                 img->currenty = unpack_16_le(buf+2);
222                 if (img->currenty > img->pub.height) {
223                         lbx_error_raise(LBX_EFORMAT);
224                         return -1;
225                 }
226         }
227
228         img->read_state = READ_STATE_HEADER;
229         return 0;
230 }
231
232 long lbx_img_read_row_header(struct lbx_image *pub, unsigned *x, unsigned *y)
233 {
234         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
235         unsigned short length, offset;
236         unsigned char buf[4];
237
238         if (img->read_state != READ_STATE_HEADER) {
239                 lbx_error_raise(LBX_EINVAL);
240                 return -1;
241         }
242
243         /* Raw images have no row headers */
244         if (img->flags & FLAG_RAW) {
245                 img->currentn = img->pub.width;
246                 *y = img->currenty++;
247                 *x = 0;
248
249                 if (*y < img->pub.height) {
250                         img->read_state = READ_STATE_DATA;
251                         return img->currentn;
252                 } else {
253                         img->read_state = READ_STATE_DONE;
254                         return 0;
255                 }
256         }
257
258         do {
259                 if (img->fops->read(buf, sizeof buf, img->f) != sizeof buf) {
260                         if (img->fops->eof(img->f))
261                                 lbx_error_raise(LBX_EEOF);
262                         return -1;
263                 }
264
265                 length = unpack_16_le(buf+0);
266                 offset = unpack_16_le(buf+2);
267
268                 if (length == 0) {
269                         if (offset == 1000) {
270                                 img->read_state = READ_STATE_DONE;
271                                 return 0;
272                         } else if (offset > img->pub.height - img->currenty) {
273                                 lbx_error_raise(LBX_EFORMAT);
274                                 return -1;
275                         }
276
277                         img->currenty += offset;
278                         img->currentx  = 0;
279                 }
280         } while (length == 0);
281
282         if (offset > img->pub.width - img->currentx) {
283                 lbx_error_raise(LBX_EFORMAT);
284                 return -1;
285         }
286         img->currentx += offset;
287
288         if (length > img->pub.width - img->currentx) {
289                 lbx_error_raise(LBX_EFORMAT);
290                 return -1;
291         }
292         img->currentn = length;
293
294         img->read_state = READ_STATE_DATA;
295         *x = img->currentx;
296         *y = img->currenty;
297
298         return img->currentn;
299 }
300
301 long lbx_img_read_row_data(struct lbx_image *pub, void *buf)
302 {
303         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
304
305         if (img->read_state != READ_STATE_DATA) {
306                 lbx_error_raise(LBX_EINVAL);
307                 return -1;
308         }
309
310         if (img->fops->read(buf, img->currentn, img->f) != img->currentn) {
311                 if (img->fops->eof(img->f))
312                         lbx_error_raise(LBX_EEOF);
313                 return -1;
314         }
315
316         if (!(img->flags & FLAG_RAW) && img->currentn % 2) {
317                 unsigned char c;
318
319                 /* Skip padding byte */
320                 if (img->fops->read(&c, 1, img->f) != 1) {
321                         if (img->fops->eof(img->f))
322                                 lbx_error_raise(LBX_EEOF);
323                         return -1;
324                 }
325         }
326
327         img->read_state = READ_STATE_HEADER;
328         img->currentx += img->currentn;
329
330         return img->currentn;
331 }
332
333 static int read_palette(void *f, const struct lbx_file_ops *fops,
334                         struct lbx_colour *palette, unsigned count,
335                         bool external)
336 {
337         assert(count <= 256);
338         for (unsigned i = 0; i < count; i++) {
339                 unsigned char buf[4];
340
341                 if (fops->read(buf, 4, f) != 4) {
342                         if (fops->eof(f))
343                                 lbx_error_raise(LBX_EEOF);
344                         return -1;
345                 }
346
347                 if (buf[0] != external) {
348                         lbx_error_raise(LBX_EFORMAT);
349                         return -1;
350                 }
351
352                 palette[i] = (struct lbx_colour) {
353                         .red    = buf[1] & 0x3f,
354                         .green  = buf[2] & 0x3f,
355                         .blue   = buf[3] & 0x3f,
356                         .active = 1,
357                 };
358         }
359
360         return 0;
361 }
362
363 int lbx_img_loadpalette(void *f, const struct lbx_file_ops *fops,
364                         struct lbx_colour *palette)
365 {
366         return read_palette(f, fops, palette, 256, true);
367 }
368
369 int lbx_img_getpalette(struct lbx_image *pub, struct lbx_colour *out)
370 {
371         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
372         unsigned long palette_start, palette_count, palette_offset;
373         unsigned char buf[4];
374         int rc;
375
376         /* Do nothing if the image doesn't have embedded palette data. */
377         if (!(img->flags & FLAG_PALETTE))
378                 return 0;
379
380         palette_offset = 16 + 4ul * img->pub.frames;
381         if (img->fops->seek(img->f, palette_offset, SEEK_SET)) {
382                 return -1;
383         }
384
385         /* Read embedded palette header */
386         if (img->fops->read(buf, 4, img->f) < 4)
387                 goto readerr;
388
389         palette_start = unpack_16_le(buf+0);
390         palette_count = unpack_16_le(buf+2);
391         if (palette_start + palette_count > 256) {
392                 lbx_error_raise(LBX_EFORMAT);
393                 return -1;
394         }
395
396         if (out) {
397                 rc = read_palette(img->f, img->fops,
398                                   out+palette_start, palette_count,
399                                   false);
400                 if (rc < 0)
401                         return -1;
402         }
403
404         return palette_count;
405 readerr:
406         if (img->fops->eof(img->f))
407                 lbx_error_raise(LBX_EEOF);
408         return -1;
409 }
410
411 int lbx_img_close(struct lbx_image *pub)
412 {
413         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
414         int rc = 0;
415
416         if (img && img->dtor) {
417                 rc = img->dtor(img->f);
418         }
419         free(img);
420
421         return rc;
422 }