prepare for the DOS port
[retroray] / libs / imago / src / filelbm.c
1 /*
2 libimago - a multi-format image file input/output library.
3 Copyright (C) 2017 John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published
7 by the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /* -- LBM (PNM/ILBM) module -- */
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <assert.h>
24 #if defined(__WATCOMC__) || defined(_WIN32)
25 #include <malloc.h>
26 #else
27 #ifndef __FreeBSD__
28 #include <alloca.h>
29 #endif
30 #endif
31 #include "imago2.h"
32 #include "ftmodule.h"
33 #include "byteord.h"
34
35 #ifdef __GNUC__
36 #define PACKED  __attribute__((packed))
37 #endif
38
39 #define MKID(a, b, c, d)        (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
40
41 #define IS_IFF_CONTAINER(id)    ((id) == IFF_FORM || (id) == IFF_CAT || (id) == IFF_LIST)
42
43 enum {
44         IFF_FORM = MKID('F', 'O', 'R', 'M'),
45         IFF_CAT = MKID('C', 'A', 'T', ' '),
46         IFF_LIST = MKID('L', 'I', 'S', 'T'),
47         IFF_ILBM = MKID('I', 'L', 'B', 'M'),
48         IFF_PBM = MKID('P', 'B', 'M', ' '),
49         IFF_BMHD = MKID('B', 'M', 'H', 'D'),
50         IFF_CMAP = MKID('C', 'M', 'A', 'P'),
51         IFF_BODY = MKID('B', 'O', 'D', 'Y'),
52         IFF_CRNG = MKID('C', 'R', 'N', 'G')
53 };
54
55 struct chdr {
56         uint32_t id;
57         uint32_t size;
58 };
59
60 #if defined(__WATCOMC__) || defined(_MSC_VER)
61 #pragma pack(push, 1)
62 #endif
63 struct bitmap_header {
64         uint16_t width, height;
65         int16_t xoffs, yoffs;
66         uint8_t nplanes;
67         uint8_t masking;
68         uint8_t compression;
69         uint8_t padding;
70         uint16_t colorkey;
71         uint8_t aspect_num, aspect_denom;
72         int16_t pgwidth, pgheight;
73 } PACKED;
74 #if defined(__WATCOMC__) || defined(_MSC_VER)
75 #pragma pack(pop)
76 #endif
77
78 enum {
79         MASK_NONE,
80         MASK_PLANE,
81         MASK_COLORKEY,
82         MASK_LASSO
83 };
84
85 struct crng {
86         uint16_t padding;
87         uint16_t rate;
88         uint16_t flags;
89         uint8_t low, high;
90 };
91
92 enum {
93         CRNG_ENABLE = 1,
94         CRNG_REVERSE = 2
95 };
96
97
98 static int check_file(struct img_io *io);
99 static int read_file(struct img_pixmap *img, struct img_io *io);
100 static int write_file(struct img_pixmap *img, struct img_io *io);
101
102 static int read_header(struct img_io *io, struct chdr *hdr);
103 static int read_ilbm_pbm(struct img_io *io, uint32_t type, uint32_t size, struct img_pixmap *img);
104 static int read_bmhd(struct img_io *io, struct bitmap_header *bmhd);
105 static int read_crng(struct img_io *io, struct crng *crng);
106 static int read_body_ilbm(struct img_io *io, struct bitmap_header *bmhd, struct img_pixmap *img);
107 static int read_body_pbm(struct img_io *io, struct bitmap_header *bmhd, struct img_pixmap *img);
108 static int read_compressed_scanline(struct img_io *io, unsigned char *scanline, int width);
109
110 #ifdef IMAGO_LITTLE_ENDIAN
111 static uint16_t img_swap16(uint16_t x);
112 static uint32_t img_swap32(uint32_t x);
113 #endif
114
115
116 int img_register_lbm(void)
117 {
118         static struct ftype_module mod = {".lbm:.ilbm:.iff", check_file, read_file, write_file };
119         return img_register_module(&mod);
120 }
121
122 static int check_file(struct img_io *io)
123 {
124         uint32_t type;
125         struct chdr hdr;
126         long pos = io->seek(0, SEEK_CUR, io->uptr);
127
128         while(read_header(io, &hdr) != -1) {
129                 if(IS_IFF_CONTAINER(hdr.id)) {
130                         type = img_read_uint32_be(io);
131                         if(type == IFF_ILBM || type == IFF_PBM ) {
132                                 io->seek(pos, SEEK_SET, io->uptr);
133                                 return 0;
134                         }
135                         hdr.size -= sizeof type;        /* so we will seek fwd correctly */
136                 }
137                 io->seek(hdr.size, SEEK_CUR, io->uptr);
138         }
139
140         io->seek(pos, SEEK_SET, io->uptr);
141         return -1;
142 }
143
144 static int read_file(struct img_pixmap *img, struct img_io *io)
145 {
146         uint32_t type;
147         struct chdr hdr;
148
149         while(read_header(io, &hdr) != -1) {
150                 if(IS_IFF_CONTAINER(hdr.id)) {
151                         type = img_read_uint32_be(io);
152                         hdr.size -= sizeof type;        /* to compensate for having advanced 4 more bytes */
153
154                         if(type == IFF_ILBM) {
155                                 if(read_ilbm_pbm(io, type, hdr.size, img) == -1) {
156                                         return -1;
157                                 }
158                                 return 0;
159                         }
160                         if(type == IFF_PBM) {
161                                 if(read_ilbm_pbm(io, type, hdr.size, img) == -1) {
162                                         return -1;
163                                 }
164                                 return 0;
165                         }
166                 }
167                 io->seek(hdr.size, SEEK_CUR, io->uptr);
168         }
169         return 0;
170 }
171
172 static int write_file(struct img_pixmap *img, struct img_io *io)
173 {
174         return -1;      /* TODO */
175 }
176
177 static int read_header(struct img_io *io, struct chdr *hdr)
178 {
179         if(io->read(hdr, sizeof *hdr, io->uptr) < sizeof *hdr) {
180                 return -1;
181         }
182 #ifdef IMAGO_LITTLE_ENDIAN
183         hdr->id = img_swap32(hdr->id);
184         hdr->size = img_swap32(hdr->size);
185 #endif
186         return 0;
187 }
188
189 static int read_ilbm_pbm(struct img_io *io, uint32_t type, uint32_t size, struct img_pixmap *img)
190 {
191         int res = -1;
192         struct chdr hdr;
193         struct bitmap_header bmhd;
194         struct crng crng;
195         /*struct colrange *crnode;*/
196         struct img_colormap cmap;
197         long start = io->seek(0, SEEK_CUR, io->uptr);
198
199         memset(img, 0, sizeof *img);
200
201         while(read_header(io, &hdr) != -1 && io->seek(0, SEEK_CUR, io->uptr) - start < (int)size) {
202                 switch(hdr.id) {
203                 case IFF_BMHD:
204                         assert(hdr.size == 20);
205                         if(read_bmhd(io, &bmhd) == -1) {
206                                 return -1;
207                         }
208                         img->width = bmhd.width;
209                         img->height = bmhd.height;
210                         if(bmhd.nplanes > 8) {
211                                 /* TODO */
212                                 fprintf(stderr, "libimago: %d planes found, only paletized LBM files supported\n", bmhd.nplanes);
213                                 return -1;
214                         }
215                         if(img_set_pixels(img, img->width, img->height, IMG_FMT_IDX8, 0)) {
216                                 return -1;
217                         }
218                         break;
219
220                 case IFF_CMAP:
221                         cmap.ncolors = hdr.size / 3;
222                         assert(cmap.ncolors <= 256);
223                         if(io->read(cmap.color, hdr.size, io->uptr) < hdr.size) {
224                                 return -1;
225                         }
226                         break;
227
228                 case IFF_CRNG:
229                         assert(hdr.size == sizeof crng);
230
231                         if(read_crng(io, &crng) == -1) {
232                                 return -1;
233                         }
234                         if(crng.low != crng.high && crng.rate > 0) {
235                                 /* XXX color cycling not currently supported
236                                 if(!(crnode = malloc(sizeof *crnode))) {
237                                         return -1;
238                                 }
239                                 crnode->low = crng.low;
240                                 crnode->high = crng.high;
241                                 crnode->cmode = (crng.flags & CRNG_REVERSE) ? CYCLE_REVERSE : CYCLE_NORMAL;
242                                 crnode->rate = crng.rate;
243                                 crnode->next = img->range;
244                                 img->range = crnode;
245                                 ++img->num_ranges;
246                                 */
247                         }
248                         break;
249
250                 case IFF_BODY:
251                         if(!img->pixels) {
252                                 fprintf(stderr, "libimago: malformed LBM image: encountered BODY chunk before BMHD\n");
253                                 return -1;
254                         }
255                         if(type == IFF_ILBM) {
256                                 if(read_body_ilbm(io, &bmhd, img) == -1) {
257                                         return -1;
258                                 }
259                         } else {
260                                 assert(type == IFF_PBM);
261                                 if(read_body_pbm(io, &bmhd, img) == -1) {
262                                         return -1;
263                                 }
264                         }
265
266                         *img_colormap(img) = cmap;
267
268                         res = 0;        /* sucessfully read image */
269                         break;
270
271                 default:
272                         /* skip unknown chunks */
273                         io->seek(hdr.size, SEEK_CUR, io->uptr);
274                         if(io->seek(0, SEEK_CUR, io->uptr) & 1) {
275                                 /* chunks must start at even offsets */
276                                 io->seek(1, SEEK_CUR, io->uptr);
277                         }
278                 }
279         }
280
281         return res;
282 }
283
284 static int read_bmhd(struct img_io *io, struct bitmap_header *bmhd)
285 {
286         if(io->read(bmhd, sizeof *bmhd, io->uptr) < 1) {
287                 return -1;
288         }
289 #ifdef IMAGO_LITTLE_ENDIAN
290         bmhd->width = img_swap16(bmhd->width);
291         bmhd->height = img_swap16(bmhd->height);
292         bmhd->xoffs = img_swap16(bmhd->xoffs);
293         bmhd->yoffs = img_swap16(bmhd->yoffs);
294         bmhd->colorkey = img_swap16(bmhd->colorkey);
295         bmhd->pgwidth = img_swap16(bmhd->pgwidth);
296         bmhd->pgheight = img_swap16(bmhd->pgheight);
297 #endif
298         return 0;
299 }
300
301 static int read_crng(struct img_io *io, struct crng *crng)
302 {
303         if(io->read(crng, sizeof *crng, io->uptr) < 1) {
304                 return -1;
305         }
306 #ifdef IMAGO_LITTLE_ENDIAN
307         crng->rate = img_swap16(crng->rate);
308         crng->flags = img_swap16(crng->flags);
309 #endif
310         return 0;
311 }
312
313 /* scanline: [bp0 row][bp1 row]...[bpN-1 row][opt mask row]
314  * each uncompressed row is width / 8 bytes
315  */
316 static int read_body_ilbm(struct img_io *io, struct bitmap_header *bmhd, struct img_pixmap *img)
317 {
318         int i, j, k, bitidx;
319         int rowsz = img->width / 8;
320         unsigned char *src, *dest = img->pixels;
321         unsigned char *rowbuf = alloca(rowsz);
322
323         assert(bmhd->width == img->width);
324         assert(bmhd->height == img->height);
325         assert(img->pixels);
326
327         for(i=0; i<img->height; i++) {
328
329                 memset(dest, 0, img->width);    /* clear the whole scanline to OR bits into place */
330
331                 for(j=0; j<bmhd->nplanes; j++) {
332                         /* read a row corresponding to bitplane j */
333                         if(bmhd->compression) {
334                                 if(read_compressed_scanline(io, rowbuf, rowsz) == -1) {
335                                         return -1;
336                                 }
337                         } else {
338                                 if(io->read(rowbuf, rowsz, io->uptr) < rowsz) {
339                                         return -1;
340                                 }
341                         }
342
343                         /* distribute all bits across the linear output scanline */
344                         src = rowbuf;
345                         bitidx = 0;
346
347                         for(k=0; k<img->width; k++) {
348                                 dest[k] |= ((*src >> (7 - bitidx)) & 1) << j;
349
350                                 if(++bitidx >= 8) {
351                                         bitidx = 0;
352                                         ++src;
353                                 }
354                         }
355                 }
356
357                 if(bmhd->masking & MASK_PLANE) {
358                         /* skip the mask (1bpp) */
359                         io->seek(rowsz, SEEK_CUR, io->uptr);
360                 }
361
362                 dest += img->width;
363         }
364         return 0;
365 }
366
367 static int read_body_pbm(struct img_io *io, struct bitmap_header *bmhd, struct img_pixmap *img)
368 {
369         int i;
370         int npixels = img->width * img->height;
371         unsigned char *dptr = img->pixels;
372
373         assert(bmhd->width == img->width);
374         assert(bmhd->height == img->height);
375         assert(img->pixels);
376
377         if(bmhd->compression) {
378                 for(i=0; i<img->height; i++) {
379                         if(read_compressed_scanline(io, dptr, img->width) == -1) {
380                                 return -1;
381                         }
382                         dptr += img->width;
383                 }
384
385         } else {
386                 /* uncompressed */
387                 if(io->read(img->pixels, npixels, io->uptr) < npixels) {
388                         return -1;
389                 }
390         }
391
392         return 0;
393 }
394
395 static int read_compressed_scanline(struct img_io *io, unsigned char *scanline, int width)
396 {
397         int i, count, x = 0;
398         signed char ctl;
399
400         while(x < width) {
401                 if(io->read(&ctl, 1, io->uptr) < 1) return -1;
402
403                 if(ctl == -128) continue;
404
405                 if(ctl >= 0) {
406                         count = ctl + 1;
407                         if(io->read(scanline, count, io->uptr) < count) return -1;
408                         scanline += count;
409
410                 } else {
411                         unsigned char pixel;
412                         count = 1 - ctl;
413                         if(io->read(&pixel, 1, io->uptr) < 1) return -1;
414
415                         for(i=0; i<count; i++) {
416                                 *scanline++ = pixel;
417                         }
418                 }
419
420                 x += count;
421         }
422
423         return 0;
424 }
425
426 #ifdef IMAGO_LITTLE_ENDIAN
427 static uint16_t img_swap16(uint16_t x)
428 {
429         return (x << 8) | (x >> 8);
430 }
431
432 static uint32_t img_swap32(uint32_t x)
433 {
434         return (x << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | (x >> 24);
435 }
436 #endif