nuclear@2: /* nuclear@2: colcycle - color cycling image viewer nuclear@2: Copyright (C) 2016-2017 John Tsiombikas nuclear@2: nuclear@2: This program is free software: you can redistribute it and/or modify nuclear@2: it under the terms of the GNU General Public License as published by nuclear@2: the Free Software Foundation, either version 3 of the License, or nuclear@2: (at your option) any later version. nuclear@2: nuclear@2: This program is distributed in the hope that it will be useful, nuclear@2: but WITHOUT ANY WARRANTY; without even the implied warranty of nuclear@2: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nuclear@2: GNU General Public License for more details. nuclear@2: nuclear@2: You should have received a copy of the GNU General Public License nuclear@2: along with this program. If not, see . nuclear@2: */ nuclear@2: #include nuclear@2: #include nuclear@2: #include nuclear@2: #include nuclear@2: #include nuclear@2: #if defined(__WATCOMC__) || defined(WIN32) nuclear@2: #include nuclear@2: #else nuclear@2: #include nuclear@2: #endif nuclear@2: #include "imagelbm.h" nuclear@2: nuclear@2: #ifdef __GNUC__ nuclear@2: #define PACKED __attribute__((packed)) nuclear@2: #endif nuclear@2: nuclear@2: #ifdef __BYTE_ORDER__ nuclear@2: #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ nuclear@2: #define LENDIAN nuclear@2: #else nuclear@2: #define BENDIAN nuclear@2: #endif nuclear@2: #endif nuclear@2: nuclear@2: #define MKID(a, b, c, d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d)) nuclear@2: nuclear@2: #define IS_IFF_CONTAINER(id) ((id) == IFF_FORM || (id) == IFF_CAT || (id) == IFF_LIST) nuclear@2: nuclear@2: enum { nuclear@2: IFF_FORM = MKID('F', 'O', 'R', 'M'), nuclear@2: IFF_CAT = MKID('C', 'A', 'T', ' '), nuclear@2: IFF_LIST = MKID('L', 'I', 'S', 'T'), nuclear@2: IFF_ILBM = MKID('I', 'L', 'B', 'M'), nuclear@2: IFF_PBM = MKID('P', 'B', 'M', ' '), nuclear@2: IFF_BMHD = MKID('B', 'M', 'H', 'D'), nuclear@2: IFF_CMAP = MKID('C', 'M', 'A', 'P'), nuclear@2: IFF_BODY = MKID('B', 'O', 'D', 'Y'), nuclear@2: IFF_CRNG = MKID('C', 'R', 'N', 'G') nuclear@2: }; nuclear@2: nuclear@2: nuclear@2: struct chdr { nuclear@2: uint32_t id; nuclear@2: uint32_t size; nuclear@2: }; nuclear@2: nuclear@2: #ifdef __WATCOMC__ nuclear@2: #pragma push(pack, 1) nuclear@2: #endif nuclear@2: struct bitmap_header { nuclear@2: uint16_t width, height; nuclear@2: int16_t xoffs, yoffs; nuclear@2: uint8_t nplanes; nuclear@2: uint8_t masking; nuclear@2: uint8_t compression; nuclear@2: uint8_t padding; nuclear@2: uint16_t colorkey; nuclear@2: uint8_t aspect_num, aspect_denom; nuclear@2: int16_t pgwidth, pgheight; nuclear@2: } PACKED; nuclear@2: #ifdef __WATCOMC__ nuclear@2: #pragma pop(pack) nuclear@2: #endif nuclear@2: nuclear@2: enum { nuclear@2: MASK_NONE, nuclear@2: MASK_PLANE, nuclear@2: MASK_COLORKEY, nuclear@2: MASK_LASSO nuclear@2: }; nuclear@2: nuclear@2: struct crng { nuclear@2: uint16_t padding; nuclear@2: uint16_t rate; nuclear@2: uint16_t flags; nuclear@2: uint8_t low, high; nuclear@2: }; nuclear@2: nuclear@2: enum { nuclear@2: CRNG_ENABLE = 1, nuclear@2: CRNG_REVERSE = 2 nuclear@2: }; nuclear@2: nuclear@2: static int read_header(FILE *fp, struct chdr *hdr); nuclear@2: static int read_ilbm_pbm(FILE *fp, uint32_t type, uint32_t size, struct image *img); nuclear@2: static int read_bmhd(FILE *fp, struct bitmap_header *bmhd); nuclear@2: static int read_crng(FILE *fp, struct crng *crng); nuclear@2: static int read_body_ilbm(FILE *fp, struct bitmap_header *bmhd, struct image *img); nuclear@2: static int read_body_pbm(FILE *fp, struct bitmap_header *bmhd, struct image *img); nuclear@2: static int read_compressed_scanline(FILE *fp, unsigned char *scanline, int width); nuclear@2: static int read16(FILE *fp, uint16_t *res); nuclear@2: static int read32(FILE *fp, uint32_t *res); nuclear@2: static uint16_t swap16(uint16_t x); nuclear@2: static uint32_t swap32(uint32_t x); nuclear@2: nuclear@2: int file_is_lbm(FILE *fp) nuclear@2: { nuclear@2: uint32_t type; nuclear@2: struct chdr hdr; nuclear@2: nuclear@2: while(read_header(fp, &hdr) != -1) { nuclear@2: if(IS_IFF_CONTAINER(hdr.id)) { nuclear@2: if(read32(fp, &type) == -1) { nuclear@2: break; nuclear@2: } nuclear@2: nuclear@2: if(type == IFF_ILBM || type == IFF_PBM ) { nuclear@2: rewind(fp); nuclear@2: return 1; nuclear@2: } nuclear@2: hdr.size -= sizeof type; /* so we will seek fwd correctly */ nuclear@2: } nuclear@2: fseek(fp, hdr.size, SEEK_CUR); nuclear@2: } nuclear@2: fseek(fp, 0, SEEK_SET); nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: void print_chunkid(uint32_t id) nuclear@2: { nuclear@2: char str[5] = {0}; nuclear@2: #ifdef LENDIAN nuclear@2: id = swap32(id); nuclear@2: #endif nuclear@2: memcpy(str, &id, 4); nuclear@2: puts(str); nuclear@2: } nuclear@2: nuclear@2: int load_image_lbm(struct image *img, FILE *fp) nuclear@2: { nuclear@2: uint32_t type; nuclear@2: struct chdr hdr; nuclear@2: nuclear@2: while(read_header(fp, &hdr) != -1) { nuclear@2: if(IS_IFF_CONTAINER(hdr.id)) { nuclear@2: if(read32(fp, &type) == -1) { nuclear@2: break; nuclear@2: } nuclear@2: hdr.size -= sizeof type; /* to compensate for having advanced 4 more bytes */ nuclear@2: nuclear@2: if(type == IFF_ILBM) { nuclear@2: if(read_ilbm_pbm(fp, type, hdr.size, img) == -1) { nuclear@2: return -1; nuclear@2: } nuclear@2: return 0; nuclear@2: } nuclear@2: if(type == IFF_PBM) { nuclear@2: if(read_ilbm_pbm(fp, type, hdr.size, img) == -1) { nuclear@2: return -1; nuclear@2: } nuclear@2: return 0; nuclear@2: } nuclear@2: } nuclear@2: fseek(fp, hdr.size, SEEK_CUR); nuclear@2: } nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: static int read_header(FILE *fp, struct chdr *hdr) nuclear@2: { nuclear@2: if(fread(hdr, 1, sizeof *hdr, fp) < sizeof *hdr) { nuclear@2: return -1; nuclear@2: } nuclear@2: #ifdef LENDIAN nuclear@2: hdr->id = swap32(hdr->id); nuclear@2: hdr->size = swap32(hdr->size); nuclear@2: #endif nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: static int read_ilbm_pbm(FILE *fp, uint32_t type, uint32_t size, struct image *img) nuclear@2: { nuclear@2: int i, res = -1; nuclear@2: struct chdr hdr; nuclear@2: struct bitmap_header bmhd; nuclear@2: struct crng crng; nuclear@2: struct colrange *crnode; nuclear@2: unsigned char pal[3 * 256]; nuclear@2: unsigned char *pptr; nuclear@2: long start = ftell(fp); nuclear@2: nuclear@2: memset(img, 0, sizeof *img); nuclear@2: nuclear@2: while(read_header(fp, &hdr) != -1 && ftell(fp) - start < size) { nuclear@2: switch(hdr.id) { nuclear@2: case IFF_BMHD: nuclear@2: assert(hdr.size == 20); nuclear@2: if(read_bmhd(fp, &bmhd) == -1) { nuclear@2: return -1; nuclear@2: } nuclear@2: img->width = bmhd.width; nuclear@2: img->height = bmhd.height; nuclear@2: img->bpp = bmhd.nplanes; nuclear@2: if(bmhd.nplanes > 8) { nuclear@2: fprintf(stderr, "%d planes found, only paletized LBM files supported\n", bmhd.nplanes); nuclear@2: return -1; nuclear@2: } nuclear@2: if(!(img->pixels = malloc(img->width * img->height))) { nuclear@2: fprintf(stderr, "failed to allocate %dx%d image\n", img->width, img->height); nuclear@2: return -1; nuclear@2: } nuclear@2: break; nuclear@2: nuclear@2: case IFF_CMAP: nuclear@2: assert(hdr.size / 3 <= 256); nuclear@2: nuclear@2: if(fread(pal, 1, hdr.size, fp) < hdr.size) { nuclear@2: fprintf(stderr, "failed to read colormap\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: pptr = pal; nuclear@2: for(i=0; i<256; i++) { nuclear@2: img->palette[i].r = *pptr++; nuclear@2: img->palette[i].g = *pptr++; nuclear@2: img->palette[i].b = *pptr++; nuclear@2: } nuclear@2: break; nuclear@2: nuclear@2: case IFF_CRNG: nuclear@2: assert(hdr.size == sizeof crng); nuclear@2: nuclear@2: if(read_crng(fp, &crng) == -1) { nuclear@2: fprintf(stderr, "failed to read color cycling range chunk\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: if(crng.low != crng.high && crng.rate > 0) { nuclear@2: if(!(crnode = malloc(sizeof *crnode))) { nuclear@2: fprintf(stderr, "failed to allocate color range node\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: crnode->low = crng.low; nuclear@2: crnode->high = crng.high; nuclear@2: crnode->cmode = (crng.flags & CRNG_REVERSE) ? CYCLE_REVERSE : CYCLE_NORMAL; nuclear@2: crnode->rate = crng.rate; nuclear@2: crnode->next = img->range; nuclear@2: img->range = crnode; nuclear@2: ++img->num_ranges; nuclear@2: } nuclear@2: break; nuclear@2: nuclear@2: case IFF_BODY: nuclear@2: if(!img->pixels) { nuclear@2: fprintf(stderr, "malformed ILBM image: encountered BODY chunk before BMHD\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: if(type == IFF_ILBM) { nuclear@2: if(read_body_ilbm(fp, &bmhd, img) == -1) { nuclear@2: fprintf(stderr, "failed to read interleaved pixel data\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: } else { nuclear@2: assert(type == IFF_PBM); nuclear@2: if(read_body_pbm(fp, &bmhd, img) == -1) { nuclear@2: fprintf(stderr, "failed to read linear pixel data\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: } nuclear@2: res = 0; /* sucessfully read image */ nuclear@2: break; nuclear@2: nuclear@2: default: nuclear@2: /* skip unknown chunks */ nuclear@2: fseek(fp, hdr.size, SEEK_CUR); nuclear@2: if(ftell(fp) & 1) { nuclear@2: /* chunks must start at even offsets */ nuclear@2: fseek(fp, 1, SEEK_CUR); nuclear@2: } nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: return res; nuclear@2: } nuclear@2: nuclear@2: nuclear@2: static int read_bmhd(FILE *fp, struct bitmap_header *bmhd) nuclear@2: { nuclear@2: if(fread(bmhd, sizeof *bmhd, 1, fp) < 1) { nuclear@2: return -1; nuclear@2: } nuclear@2: #ifdef LENDIAN nuclear@2: bmhd->width = swap16(bmhd->width); nuclear@2: bmhd->height = swap16(bmhd->height); nuclear@2: bmhd->xoffs = swap16(bmhd->xoffs); nuclear@2: bmhd->yoffs = swap16(bmhd->yoffs); nuclear@2: bmhd->colorkey = swap16(bmhd->colorkey); nuclear@2: bmhd->pgwidth = swap16(bmhd->pgwidth); nuclear@2: bmhd->pgheight = swap16(bmhd->pgheight); nuclear@2: #endif nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: static int read_crng(FILE *fp, struct crng *crng) nuclear@2: { nuclear@2: if(fread(crng, sizeof *crng, 1, fp) < 1) { nuclear@2: return -1; nuclear@2: } nuclear@2: #ifdef LENDIAN nuclear@2: crng->rate = swap16(crng->rate); nuclear@2: crng->flags = swap16(crng->flags); nuclear@2: #endif nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: /* scanline: [bp0 row][bp1 row]...[bpN-1 row][opt mask row] nuclear@2: * each uncompressed row is width / 8 bytes nuclear@2: */ nuclear@2: static int read_body_ilbm(FILE *fp, struct bitmap_header *bmhd, struct image *img) nuclear@2: { nuclear@2: int i, j, k, bitidx; nuclear@2: int rowsz = img->width / 8; nuclear@2: unsigned char *src, *dest = img->pixels; nuclear@2: unsigned char *rowbuf = alloca(rowsz); nuclear@2: nuclear@2: assert(bmhd->width == img->width); nuclear@2: assert(bmhd->height == img->height); nuclear@2: assert(img->pixels); nuclear@2: nuclear@2: for(i=0; iheight; i++) { nuclear@2: nuclear@2: memset(dest, 0, img->width); /* clear the whole scanline to OR bits into place */ nuclear@2: nuclear@2: for(j=0; jnplanes; j++) { nuclear@2: // read a row corresponding to bitplane j nuclear@2: if(bmhd->compression) { nuclear@2: if(read_compressed_scanline(fp, rowbuf, rowsz) == -1) { nuclear@2: return -1; nuclear@2: } nuclear@2: } else { nuclear@2: if(fread(rowbuf, 1, rowsz, fp) < rowsz) { nuclear@2: return -1; nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: // distribute all bits across the linear output scanline nuclear@2: src = rowbuf; nuclear@2: bitidx = 0; nuclear@2: nuclear@2: for(k=0; kwidth; k++) { nuclear@2: dest[k] |= ((*src >> (7 - bitidx)) & 1) << j; nuclear@2: nuclear@2: if(++bitidx >= 8) { nuclear@2: bitidx = 0; nuclear@2: ++src; nuclear@2: } nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: if(bmhd->masking & MASK_PLANE) { nuclear@2: /* skip the mask (1bpp) */ nuclear@2: fseek(fp, rowsz, SEEK_CUR); nuclear@2: } nuclear@2: nuclear@2: dest += img->width; nuclear@2: } nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: static int read_body_pbm(FILE *fp, struct bitmap_header *bmhd, struct image *img) nuclear@2: { nuclear@2: int i; nuclear@2: int npixels = img->width * img->height; nuclear@2: unsigned char *dptr = img->pixels; nuclear@2: nuclear@2: assert(bmhd->width == img->width); nuclear@2: assert(bmhd->height == img->height); nuclear@2: assert(img->pixels); nuclear@2: nuclear@2: if(bmhd->compression) { nuclear@2: for(i=0; iheight; i++) { nuclear@2: if(read_compressed_scanline(fp, dptr, img->width) == -1) { nuclear@2: return -1; nuclear@2: } nuclear@2: dptr += img->width; nuclear@2: } nuclear@2: nuclear@2: } else { nuclear@2: /* uncompressed */ nuclear@2: if(fread(img->pixels, 1, npixels, fp) < npixels) { nuclear@2: return -1; nuclear@2: } nuclear@2: } nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: static int read_compressed_scanline(FILE *fp, unsigned char *scanline, int width) nuclear@2: { nuclear@2: int i, count, x = 0; nuclear@2: signed char ctl; nuclear@2: nuclear@2: while(x < width) { nuclear@2: if(fread(&ctl, 1, 1, fp) < 1) return -1; nuclear@2: nuclear@2: if(ctl == -128) continue; nuclear@2: nuclear@2: if(ctl >= 0) { nuclear@2: count = ctl + 1; nuclear@2: if(fread(scanline, 1, count, fp) < count) return -1; nuclear@2: scanline += count; nuclear@2: nuclear@2: } else { nuclear@2: unsigned char pixel; nuclear@2: count = 1 - ctl; nuclear@2: if(fread(&pixel, 1, 1, fp) < 1) return -1; nuclear@2: nuclear@2: for(i=0; i> 8); nuclear@2: } nuclear@2: nuclear@2: static uint32_t swap32(uint32_t x) nuclear@2: { nuclear@2: return (x << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | (x >> 24); nuclear@2: }