textures, overlay images, libimago
[demo_prior] / libs / imago2 / src / file_lbm.c
diff --git a/libs/imago2/src/file_lbm.c b/libs/imago2/src/file_lbm.c
new file mode 100644 (file)
index 0000000..de18831
--- /dev/null
@@ -0,0 +1,452 @@
+/*
+libimago - a multi-format image file input/output library.
+Copyright (C) 2017 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* -- LBM (PNM/ILBM) module -- */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#if defined(__WATCOMC__) || defined(WIN32)
+#include <malloc.h>
+#else
+#include <alloca.h>
+#endif
+#include "imago2.h"
+#include "ftype_module.h"
+#include "byteord.h"
+
+#ifdef __GNUC__
+#define PACKED __attribute__((packed))
+#endif
+
+#define MKID(a, b, c, d)       (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
+
+#define IS_IFF_CONTAINER(id)   ((id) == IFF_FORM || (id) == IFF_CAT || (id) == IFF_LIST)
+
+enum {
+       IFF_FORM = MKID('F', 'O', 'R', 'M'),
+       IFF_CAT = MKID('C', 'A', 'T', ' '),
+       IFF_LIST = MKID('L', 'I', 'S', 'T'),
+       IFF_ILBM = MKID('I', 'L', 'B', 'M'),
+       IFF_PBM = MKID('P', 'B', 'M', ' '),
+       IFF_BMHD = MKID('B', 'M', 'H', 'D'),
+       IFF_CMAP = MKID('C', 'M', 'A', 'P'),
+       IFF_BODY = MKID('B', 'O', 'D', 'Y'),
+       IFF_CRNG = MKID('C', 'R', 'N', 'G')
+};
+
+struct chdr {
+       uint32_t id;
+       uint32_t size;
+};
+
+#if defined(__WATCOMC__) || defined(_MSC_VER)
+#pragma push(pack, 1)
+#endif
+struct bitmap_header {
+       uint16_t width, height;
+       int16_t xoffs, yoffs;
+       uint8_t nplanes;
+       uint8_t masking;
+       uint8_t compression;
+       uint8_t padding;
+       uint16_t colorkey;
+       uint8_t aspect_num, aspect_denom;
+       int16_t pgwidth, pgheight;
+} PACKED;
+#if defined(__WATCOMC__) || defined(_MSC_VER)
+#pragma pop(pack)
+#endif
+
+enum {
+       MASK_NONE,
+       MASK_PLANE,
+       MASK_COLORKEY,
+       MASK_LASSO
+};
+
+struct crng {
+       uint16_t padding;
+       uint16_t rate;
+       uint16_t flags;
+       uint8_t low, high;
+};
+
+enum {
+       CRNG_ENABLE = 1,
+       CRNG_REVERSE = 2
+};
+
+
+static int check_file(struct img_io *io);
+static int read_file(struct img_pixmap *img, struct img_io *io);
+static int write_file(struct img_pixmap *img, struct img_io *io);
+
+static int read_header(struct img_io *io, struct chdr *hdr);
+static int read_ilbm_pbm(struct img_io *io, uint32_t type, uint32_t size, struct img_pixmap *img);
+static void convert_rgb(struct img_pixmap *img, unsigned char *pal);
+static int read_bmhd(struct img_io *io, struct bitmap_header *bmhd);
+static int read_crng(struct img_io *io, struct crng *crng);
+static int read_body_ilbm(struct img_io *io, struct bitmap_header *bmhd, struct img_pixmap *img);
+static int read_body_pbm(struct img_io *io, struct bitmap_header *bmhd, struct img_pixmap *img);
+static int read_compressed_scanline(struct img_io *io, unsigned char *scanline, int width);
+
+#ifdef IMAGO_LITTLE_ENDIAN
+static uint16_t swap16(uint16_t x);
+static uint32_t swap32(uint32_t x);
+#endif
+
+
+int img_register_lbm(void)
+{
+       static struct ftype_module mod = {".lbm:.ilbm:.iff", check_file, read_file, write_file };
+       return img_register_module(&mod);
+}
+
+static int check_file(struct img_io *io)
+{
+       uint32_t type;
+       struct chdr hdr;
+       long pos = io->seek(0, SEEK_CUR, io->uptr);
+
+       while(read_header(io, &hdr) != -1) {
+               if(IS_IFF_CONTAINER(hdr.id)) {
+                       type = img_read_uint32_be(io);
+                       if(type == IFF_ILBM || type == IFF_PBM ) {
+                               io->seek(pos, SEEK_SET, io->uptr);
+                               return 0;
+                       }
+                       hdr.size -= sizeof type;        /* so we will seek fwd correctly */
+               }
+               io->seek(hdr.size, SEEK_CUR, io->uptr);
+       }
+
+       io->seek(pos, SEEK_SET, io->uptr);
+       return -1;
+}
+
+static int read_file(struct img_pixmap *img, struct img_io *io)
+{
+       uint32_t type;
+       struct chdr hdr;
+
+       while(read_header(io, &hdr) != -1) {
+               if(IS_IFF_CONTAINER(hdr.id)) {
+                       type = img_read_uint32_be(io);
+                       hdr.size -= sizeof type;        /* to compensate for having advanced 4 more bytes */
+
+                       if(type == IFF_ILBM) {
+                               if(read_ilbm_pbm(io, type, hdr.size, img) == -1) {
+                                       return -1;
+                               }
+                               return 0;
+                       }
+                       if(type == IFF_PBM) {
+                               if(read_ilbm_pbm(io, type, hdr.size, img) == -1) {
+                                       return -1;
+                               }
+                               return 0;
+                       }
+               }
+               io->seek(hdr.size, SEEK_CUR, io->uptr);
+       }
+       return 0;
+}
+
+static int write_file(struct img_pixmap *img, struct img_io *io)
+{
+       return -1;      /* TODO */
+}
+
+static int read_header(struct img_io *io, struct chdr *hdr)
+{
+       if(io->read(hdr, sizeof *hdr, io->uptr) < sizeof *hdr) {
+               return -1;
+       }
+#ifdef IMAGO_LITTLE_ENDIAN
+       hdr->id = swap32(hdr->id);
+       hdr->size = swap32(hdr->size);
+#endif
+       return 0;
+}
+
+static int read_ilbm_pbm(struct img_io *io, uint32_t type, uint32_t size, struct img_pixmap *img)
+{
+       int res = -1;
+       struct chdr hdr;
+       struct bitmap_header bmhd;
+       struct crng crng;
+       /*struct colrange *crnode;*/
+       unsigned char pal[3 * 256];
+       long start = io->seek(0, SEEK_CUR, io->uptr);
+
+       memset(img, 0, sizeof *img);
+
+       while(read_header(io, &hdr) != -1 && io->seek(0, SEEK_CUR, io->uptr) - start < size) {
+               switch(hdr.id) {
+               case IFF_BMHD:
+                       assert(hdr.size == 20);
+                       if(read_bmhd(io, &bmhd) == -1) {
+                               return -1;
+                       }
+                       img->width = bmhd.width;
+                       img->height = bmhd.height;
+                       if(bmhd.nplanes > 8) {
+                               /* TODO */
+                               fprintf(stderr, "libimago: %d planes found, only paletized LBM files supported\n", bmhd.nplanes);
+                               return -1;
+                       }
+                       if(img_set_pixels(img, img->width, img->height, IMG_FMT_RGB24, 0)) {
+                               return -1;
+                       }
+                       break;
+
+               case IFF_CMAP:
+                       assert(hdr.size / 3 <= 256);
+
+                       if(io->read(pal, hdr.size, io->uptr) < hdr.size) {
+                               return -1;
+                       }
+                       break;
+
+               case IFF_CRNG:
+                       assert(hdr.size == sizeof crng);
+
+                       if(read_crng(io, &crng) == -1) {
+                               return -1;
+                       }
+                       if(crng.low != crng.high && crng.rate > 0) {
+                               /* XXX color cycling not currently supported
+                               if(!(crnode = malloc(sizeof *crnode))) {
+                                       return -1;
+                               }
+                               crnode->low = crng.low;
+                               crnode->high = crng.high;
+                               crnode->cmode = (crng.flags & CRNG_REVERSE) ? CYCLE_REVERSE : CYCLE_NORMAL;
+                               crnode->rate = crng.rate;
+                               crnode->next = img->range;
+                               img->range = crnode;
+                               ++img->num_ranges;
+                               */
+                       }
+                       break;
+
+               case IFF_BODY:
+                       if(!img->pixels) {
+                               fprintf(stderr, "libimago: malformed LBM image: encountered BODY chunk before BMHD\n");
+                               return -1;
+                       }
+                       if(type == IFF_ILBM) {
+                               if(read_body_ilbm(io, &bmhd, img) == -1) {
+                                       return -1;
+                               }
+                       } else {
+                               assert(type == IFF_PBM);
+                               if(read_body_pbm(io, &bmhd, img) == -1) {
+                                       return -1;
+                               }
+                       }
+
+                       convert_rgb(img, pal);
+
+                       res = 0;        /* sucessfully read image */
+                       break;
+
+               default:
+                       /* skip unknown chunks */
+                       io->seek(hdr.size, SEEK_CUR, io->uptr);
+                       if(io->seek(0, SEEK_CUR, io->uptr) & 1) {
+                               /* chunks must start at even offsets */
+                               io->seek(1, SEEK_CUR, io->uptr);
+                       }
+               }
+       }
+
+       return res;
+}
+
+static void convert_rgb(struct img_pixmap *img, unsigned char *pal)
+{
+       int i, npixels = img->width * img->height;
+       unsigned char *sptr, *dptr = img->pixels;
+
+       dptr = (unsigned char*)img->pixels + npixels * 3;
+       sptr = (unsigned char*)img->pixels + npixels;
+
+       for(i=0; i<npixels; i++) {
+               int c = *--sptr;
+               *--dptr = pal[c * 3 + 2];
+               *--dptr = pal[c * 3 + 1];
+               *--dptr = pal[c * 3];
+       }
+}
+
+
+static int read_bmhd(struct img_io *io, struct bitmap_header *bmhd)
+{
+       if(io->read(bmhd, sizeof *bmhd, io->uptr) < 1) {
+               return -1;
+       }
+#ifdef IMAGO_LITTLE_ENDIAN
+       bmhd->width = swap16(bmhd->width);
+       bmhd->height = swap16(bmhd->height);
+       bmhd->xoffs = swap16(bmhd->xoffs);
+       bmhd->yoffs = swap16(bmhd->yoffs);
+       bmhd->colorkey = swap16(bmhd->colorkey);
+       bmhd->pgwidth = swap16(bmhd->pgwidth);
+       bmhd->pgheight = swap16(bmhd->pgheight);
+#endif
+       return 0;
+}
+
+static int read_crng(struct img_io *io, struct crng *crng)
+{
+       if(io->read(crng, sizeof *crng, io->uptr) < 1) {
+               return -1;
+       }
+#ifdef IMAGO_LITTLE_ENDIAN
+       crng->rate = swap16(crng->rate);
+       crng->flags = swap16(crng->flags);
+#endif
+       return 0;
+}
+
+/* scanline: [bp0 row][bp1 row]...[bpN-1 row][opt mask row]
+ * each uncompressed row is width / 8 bytes
+ */
+static int read_body_ilbm(struct img_io *io, struct bitmap_header *bmhd, struct img_pixmap *img)
+{
+       int i, j, k, bitidx;
+       int rowsz = img->width / 8;
+       unsigned char *src, *dest = img->pixels;
+       unsigned char *rowbuf = alloca(rowsz);
+
+       assert(bmhd->width == img->width);
+       assert(bmhd->height == img->height);
+       assert(img->pixels);
+
+       for(i=0; i<img->height; i++) {
+
+               memset(dest, 0, img->width);    /* clear the whole scanline to OR bits into place */
+
+               for(j=0; j<bmhd->nplanes; j++) {
+                       /* read a row corresponding to bitplane j */
+                       if(bmhd->compression) {
+                               if(read_compressed_scanline(io, rowbuf, rowsz) == -1) {
+                                       return -1;
+                               }
+                       } else {
+                               if(io->read(rowbuf, rowsz, io->uptr) < rowsz) {
+                                       return -1;
+                               }
+                       }
+
+                       /* distribute all bits across the linear output scanline */
+                       src = rowbuf;
+                       bitidx = 0;
+
+                       for(k=0; k<img->width; k++) {
+                               dest[k] |= ((*src >> (7 - bitidx)) & 1) << j;
+
+                               if(++bitidx >= 8) {
+                                       bitidx = 0;
+                                       ++src;
+                               }
+                       }
+               }
+
+               if(bmhd->masking & MASK_PLANE) {
+                       /* skip the mask (1bpp) */
+                       io->seek(rowsz, SEEK_CUR, io->uptr);
+               }
+
+               dest += img->width;
+       }
+       return 0;
+}
+
+static int read_body_pbm(struct img_io *io, struct bitmap_header *bmhd, struct img_pixmap *img)
+{
+       int i;
+       int npixels = img->width * img->height;
+       unsigned char *dptr = img->pixels;
+
+       assert(bmhd->width == img->width);
+       assert(bmhd->height == img->height);
+       assert(img->pixels);
+
+       if(bmhd->compression) {
+               for(i=0; i<img->height; i++) {
+                       if(read_compressed_scanline(io, dptr, img->width) == -1) {
+                               return -1;
+                       }
+                       dptr += img->width;
+               }
+
+       } else {
+               /* uncompressed */
+               if(io->read(img->pixels, npixels, io->uptr) < npixels) {
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+static int read_compressed_scanline(struct img_io *io, unsigned char *scanline, int width)
+{
+       int i, count, x = 0;
+       signed char ctl;
+
+       while(x < width) {
+               if(io->read(&ctl, 1, io->uptr) < 1) return -1;
+
+               if(ctl == -128) continue;
+
+               if(ctl >= 0) {
+                       count = ctl + 1;
+                       if(io->read(scanline, count, io->uptr) < count) return -1;
+                       scanline += count;
+
+               } else {
+                       unsigned char pixel;
+                       count = 1 - ctl;
+                       if(io->read(&pixel, 1, io->uptr) < 1) return -1;
+
+                       for(i=0; i<count; i++) {
+                               *scanline++ = pixel;
+                       }
+               }
+
+               x += count;
+       }
+
+       return 0;
+}
+
+#ifdef IMAGO_LITTLE_ENDIAN
+static uint16_t swap16(uint16_t x)
+{
+       return (x << 8) | (x >> 8);
+}
+
+static uint32_t swap32(uint32_t x)
+{
+       return (x << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | (x >> 24);
+}
+#endif