]> git.draconx.ca Git - liblbx.git/blob - src/image.c
liblbx: Clean up embedded palette handling
[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[];
66 };
67
68 static struct lbx_image_priv *lbx_img_init(unsigned char hdr[static HDR_LEN])
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         return img;
97 }
98
99 struct lbx_image *lbx_img_open(void *f, const struct lbx_file_ops *fops,
100                                int (*destructor)(void *))
101 {
102         unsigned char hdr_buf[HDR_LEN];
103         struct lbx_image_priv *img;
104
105         if (fops->read(hdr_buf, sizeof hdr_buf, f) != sizeof hdr_buf) {
106                 if (fops->eof(f))
107                         lbx_error_raise(LBX_EEOF);
108                 return NULL;
109         }
110
111         img = lbx_img_init(hdr_buf);
112         if (!img)
113                 return NULL;
114
115         img->f    = f;
116         img->fops = fops;
117         img->dtor = destructor;
118
119         /*
120          * DEBUG ONLY.  These assertions exist to catch otherwise valid image
121          * files which differ from what I believe to be true of all LBX images.
122          * When we can decode every image, then these assertions should be
123          * replaced with constraints.
124          */
125         _lbx_assert(img->wtf  == 0); /* version? */
126         _lbx_assert(img->wtf2 == 0); /* very likely is simply reserved. */
127         _lbx_assert(img->pub.frames > img->pub.leadin);
128         _lbx_assert(!(img->flags & ~FLAG_ALL));
129
130         /* Read all offsets.  Should be merged with identical code in lbx.c */
131         for (unsigned i = 0; i <= img->pub.frames; i++) {
132                 unsigned char buf[4];
133
134                 if (fops->read(buf, sizeof buf, f) != sizeof buf) {
135                         if (fops->eof(f))
136                                 lbx_error_raise(LBX_EEOF);
137                         free(img);
138                         return NULL;
139                 }
140
141                 img->offsets[i] = unpack_32_le(buf);
142         }
143
144         return &img->pub;
145 }
146
147 static int pipe_close(void *f)
148 {
149         struct lbx_pipe_state *p = f;
150         int rc;
151
152         rc = fclose(p->f);
153         free(p);
154         return rc;
155 }
156
157 static int file_close(void *f)
158 {
159         return fclose((FILE *)f);
160 }
161
162 struct lbx_image *lbx_img_fopen(const char *file)
163 {
164         struct lbx_pipe_state *p;
165         FILE *f;
166
167         f = fopen(file, "rb");
168         if (!f) {
169                 lbx_error_raise(-errno);
170                 return NULL;
171         }
172
173         if (fseek(f, 0, SEEK_CUR) == 0)
174                 return lbx_img_open(f, &lbx_default_fops, file_close);
175
176         p = malloc(sizeof *p);
177         if (!p) {
178                 lbx_error_raise(LBX_ENOMEM);
179                 fclose(f);
180                 return NULL;
181         }
182
183         *p = (struct lbx_pipe_state) { .f = f };
184         return lbx_img_open(p, &lbx_pipe_fops, pipe_close);
185 }
186
187 int lbx_img_seek(struct lbx_image *pub, unsigned frame)
188 {
189         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
190
191         if (frame >= pub->frames) {
192                 lbx_error_raise(LBX_EINVAL);
193                 return -1;
194         }
195
196         if (img->fops->seek(img->f, img->offsets[frame], SEEK_SET)) {
197                 return -1;
198         }
199
200         if (!(img->flags & FLAG_RAW)) {
201                 unsigned char buf[4];
202
203                 /* Read frame header */
204                 if (img->fops->read(buf, 4, img->f) != 4) {
205                         if (img->fops->eof(img->f))
206                                 lbx_error_raise(LBX_EEOF);
207                         return -1;
208                 }
209
210                 if (unpack_16_le(buf) != 1) {
211                         lbx_error_raise(LBX_EFORMAT);
212                         return -1;
213                 }
214
215                 img->currentx = 0;
216                 img->currenty = unpack_16_le(buf+2);
217                 if (img->currenty > img->pub.height) {
218                         lbx_error_raise(LBX_EFORMAT);
219                         return -1;
220                 }
221         }
222
223         img->read_state = READ_STATE_HEADER;
224         return 0;
225 }
226
227 long lbx_img_read_row_header(struct lbx_image *pub, unsigned *x, unsigned *y)
228 {
229         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
230         unsigned short length, offset;
231         unsigned char buf[4];
232
233         if (img->read_state != READ_STATE_HEADER) {
234                 lbx_error_raise(LBX_EINVAL);
235                 return -1;
236         }
237
238         /* Raw images have no row headers */
239         if (img->flags & FLAG_RAW) {
240                 img->currentn = img->pub.width;
241                 *y = img->currenty++;
242                 *x = 0;
243
244                 if (*y < img->pub.height) {
245                         img->read_state = READ_STATE_DATA;
246                         return img->currentn;
247                 } else {
248                         img->read_state = READ_STATE_DONE;
249                         return 0;
250                 }
251         }
252
253         do {
254                 if (img->fops->read(buf, sizeof buf, img->f) != sizeof buf) {
255                         if (img->fops->eof(img->f))
256                                 lbx_error_raise(LBX_EEOF);
257                         return -1;
258                 }
259
260                 length = unpack_16_le(buf+0);
261                 offset = unpack_16_le(buf+2);
262
263                 if (length == 0) {
264                         if (offset == 1000) {
265                                 img->read_state = READ_STATE_DONE;
266                                 return 0;
267                         } else if (offset > img->pub.height - img->currenty) {
268                                 lbx_error_raise(LBX_EFORMAT);
269                                 return -1;
270                         }
271
272                         img->currenty += offset;
273                         img->currentx  = 0;
274                 }
275         } while (length == 0);
276
277         if (offset > img->pub.width - img->currentx) {
278                 lbx_error_raise(LBX_EFORMAT);
279                 return -1;
280         }
281         img->currentx += offset;
282
283         if (length > img->pub.width - img->currentx) {
284                 lbx_error_raise(LBX_EFORMAT);
285                 return -1;
286         }
287         img->currentn = length;
288
289         img->read_state = READ_STATE_DATA;
290         *x = img->currentx;
291         *y = img->currenty;
292
293         return img->currentn;
294 }
295
296 long lbx_img_read_row_data(struct lbx_image *pub, void *buf)
297 {
298         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
299
300         if (img->read_state != READ_STATE_DATA) {
301                 lbx_error_raise(LBX_EINVAL);
302                 return -1;
303         }
304
305         if (img->fops->read(buf, img->currentn, img->f) != img->currentn) {
306                 if (img->fops->eof(img->f))
307                         lbx_error_raise(LBX_EEOF);
308                 return -1;
309         }
310
311         if (!(img->flags & FLAG_RAW)) {
312                 /* Skip padding byte, if any */
313                 if (img->currentn % 2) {
314                         if (img->fops->seek(img->f, 1, SEEK_CUR))
315                                 return -1;
316                 }
317         }
318
319         img->read_state = READ_STATE_HEADER;
320         img->currentx += img->currentn;
321
322         return img->currentn;
323 }
324
325 static int read_palette(void *f, const struct lbx_file_ops *fops,
326                         struct lbx_colour *palette, unsigned count,
327                         bool external)
328 {
329         assert(count <= 256);
330         for (unsigned i = 0; i < count; i++) {
331                 unsigned char buf[4];
332
333                 if (fops->read(buf, 4, f) != 4) {
334                         if (fops->eof(f))
335                                 lbx_error_raise(LBX_EEOF);
336                         return -1;
337                 }
338
339                 if (buf[0] != external) {
340                         lbx_error_raise(LBX_EFORMAT);
341                         return -1;
342                 }
343
344                 palette[i] = (struct lbx_colour) {
345                         .red    = buf[1] & 0x3f,
346                         .green  = buf[2] & 0x3f,
347                         .blue   = buf[3] & 0x3f,
348                         .active = 1,
349                 };
350         }
351
352         return 0;
353 }
354
355 int lbx_img_loadpalette(void *f, const struct lbx_file_ops *fops,
356                         struct lbx_colour *palette)
357 {
358         return read_palette(f, fops, palette, 256, true);
359 }
360
361 int lbx_img_getpalette(struct lbx_image *pub, struct lbx_colour *out)
362 {
363         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
364         unsigned long palette_start, palette_count, palette_offset;
365         unsigned char buf[4];
366         int rc;
367
368         /* Do nothing if the image doesn't have embedded palette data. */
369         if (!(img->flags & FLAG_PALETTE))
370                 return 0;
371
372         palette_offset = 16 + 4ul * img->pub.frames;
373         if (img->fops->seek(img->f, palette_offset, SEEK_SET)) {
374                 return -1;
375         }
376
377         /* Read embedded palette header */
378         if (img->fops->read(buf, 4, img->f) < 4)
379                 goto readerr;
380
381         palette_start = unpack_16_le(buf+0);
382         palette_count = unpack_16_le(buf+2);
383         if (palette_start + palette_count > 256) {
384                 lbx_error_raise(LBX_EFORMAT);
385                 return -1;
386         }
387
388         if (out) {
389                 rc = read_palette(img->f, img->fops,
390                                   out+palette_start, palette_count,
391                                   false);
392                 if (rc < 0)
393                         return -1;
394         }
395
396         return palette_count;
397 readerr:
398         if (img->fops->eof(img->f))
399                 lbx_error_raise(LBX_EEOF);
400         return -1;
401 }
402
403 void lbx_img_getinfo(struct lbx_image *pub, struct lbx_imginfo *info)
404 {
405         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
406
407         *info = (struct lbx_imginfo) { 0 };
408
409         /* There seems to be two ways of specifying that an image loops. */
410         if (img->flags & FLAG_LOOPING) {
411                 info->loopstart = 0;
412                 info->looping   = 1;
413         } else if (img->pub.leadin != pub->frames - 1) {
414                 info->loopstart = img->pub.leadin;
415                 info->looping   = 1;
416         }
417 }
418
419 int lbx_img_close(struct lbx_image *pub)
420 {
421         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
422         int rc = 0;
423
424         if (img && img->dtor) {
425                 rc = img->dtor(img->f);
426         }
427         free(img);
428
429         return rc;
430 }