/* * Descent 3 * Copyright (C) 2024 Parallax Software * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . --- HISTORICAL COMMENTS FOLLOW --- * $Logfile: /DescentIII/Main/bitmap/iff.cpp $ * $Revision: 8 $ * $Date: 10/21/99 9:28p $ * $Author: Jeff $ * * Code to read IFF files * * $Log: /DescentIII/Main/bitmap/iff.cpp $ * * 8 10/21/99 9:28p Jeff * B.A. Macintosh code merge * * 7 4/14/99 1:07a Jeff * fixed case mismatched #includes * * 6 10/08/98 4:23p Kevin * Changed code to comply with memory library usage. Always use mem_malloc * , mem_free and mem_strdup * * 5 4/23/98 6:38p Jason * made bitmaps use 1555 format * * 4 3/19/98 3:18p Samir * enforce constant char* arguments when needed. done in CFILE and bitmap * libraries as well as ddio. * * 3 12/22/97 7:34p Samir * Removed instances of gr.h include. Replaced with grdefs.h * * 2 10/15/97 5:20p Jason * did a HUGE overhaul of the bitmap system * * 11 3/07/97 4:02p Samir * Took out conio.h and malloc.h for ANSI compliance. * * 10 3/07/97 1:02p Jason * decreased size of bitmap headers array to compensate for stupid mac * problem * * 9 3/03/97 6:20p Matt * Changed cfile functions to use D3 naming convention * * $NoKeywords: $ */ #define COMPRESS 1 // do the RLE or not? (for debugging mostly) #define WRITE_TINY 0 // should we write a TINY chunk? #define MIN_COMPRESS_WIDTH 65 // don't compress if less than this wide #include #include #include #include "mem.h" #include "iff.h" #include "byteswap.h" #include "cfile.h" #include "pserror.h" #include "pstypes.h" #include "bitmap.h" #include "log.h" #include "grdefs.h" // Internal constants and structures for this library // Compression types #define cmpNone 0 #define cmpByteRun1 1 // Masking types #define mskNone 0 #define mskHasMask 1 #define mskHasTransparentColor 2 // Palette entry structure struct pal_entry { uint8_t r, g, b; }; // structure of the header in the file struct iff_bitmap_header { int16_t w, h; // width and height of this bitmap int16_t x, y; // generally unused int16_t type; // see types above int16_t transparentcolor; // which color is transparent (if any) int16_t pagewidth, pageheight; // width & height of source screen uint8_t nplanes; // number of planes (8 for 256 color image) uint8_t masking, compression; // see constants above uint8_t xaspect, yaspect; // aspect ratio (usually 5/6) pal_entry palette[256]; // the palette for this bitmap uint8_t *raw_data; // ptr to array of data int16_t row_size; // offset to next row }; int16_t iff_transparent_color; int16_t iff_has_transparency; // 0=no transparency, 1=iff_transparent_color is valid #define MIN(a, b) ((a < b) ? a : b) #define MAKE_SIG(a, b, c, d) (((int32_t)(a) << 24) + ((int32_t)(b) << 16) + ((c) << 8) + (d)) #define IFF_SIG_FORM 1 #define IFF_SIG_ILBM 2 #define IFF_SIG_BODY 3 #define IFF_SIG_BMHD 4 #define IFF_SIG_CMAP 5 #define IFF_SIG_UNKNOWN 6 #define IFF_SIG_PBM 7 #define IFF_SIG_ANIM 8 #define IFF_SIG_DELTA 9 #define IFF_SIG_ANHD 10 static int bm_iff_get_sig(CFILE *f); static int bm_iff_parse_bmhd(CFILE *ifile, uint32_t len, iff_bitmap_header *bmheader); /// the buffer pointed to by raw_data is stuffed with a pointer to decompressed pixel data static int bm_iff_parse_body(CFILE *ifile, int len, iff_bitmap_header *bmheader); /// the buffer pointed to by raw_data is stuffed with a pointer to bitplane pixel data static void bm_iff_skip_chunk(CFILE *ifile, uint32_t len); /// modify passed bitmap static int bm_iff_parse_delta(CFILE *ifile, int len, iff_bitmap_header *bmheader); static int bm_iff_parse_file(CFILE *ifile, iff_bitmap_header *bmheader, iff_bitmap_header *prev_bm); static void bm_iff_convert_8_to_16(int dest_bm, iff_bitmap_header *iffbm); int bm_iff_get_sig(CFILE *f) { char s[4]; int i; for (i = 0; i < 4; i++) s[i] = cf_ReadByte(f); if (!strncmp("ILBM", s, 4)) return IFF_SIG_ILBM; if (!strncmp("BODY", s, 4)) return IFF_SIG_BODY; if (!strncmp("CMAP", s, 4)) return IFF_SIG_CMAP; if (!strncmp("BMHD", s, 4)) return IFF_SIG_BMHD; if (!strncmp("FORM", s, 4)) return IFF_SIG_FORM; if (!strncmp("PBM ", s, 4)) return IFF_SIG_PBM; if (!strncmp("ANIM", s, 4)) return IFF_SIG_ANIM; if (!strncmp("DLTA", s, 4)) return IFF_SIG_DELTA; if (!strncmp("ANHD", s, 4)) return IFF_SIG_ANHD; return (IFF_SIG_UNKNOWN); } int bm_iff_parse_bmhd(CFILE *ifile, uint32_t len, iff_bitmap_header *bmheader) { len = len; bmheader->w = cf_ReadShort(ifile, false); bmheader->h = cf_ReadShort(ifile, false); bmheader->x = cf_ReadShort(ifile, false); bmheader->y = cf_ReadShort(ifile, false); bmheader->nplanes = cf_ReadByte(ifile); bmheader->masking = cf_ReadByte(ifile); bmheader->compression = cf_ReadByte(ifile); cf_ReadByte(ifile); /* skip pad */ bmheader->transparentcolor = cf_ReadShort(ifile, false); bmheader->xaspect = cf_ReadByte(ifile); bmheader->yaspect = cf_ReadByte(ifile); bmheader->pagewidth = cf_ReadShort(ifile, false); bmheader->pageheight = cf_ReadShort(ifile, false); iff_transparent_color = bmheader->transparentcolor; iff_has_transparency = 0; if (bmheader->masking == mskHasTransparentColor) iff_has_transparency = 1; else if (bmheader->masking != mskNone && bmheader->masking != mskHasMask) return IFF_UNKNOWN_MASK; return IFF_NO_ERROR; } // the buffer pointed to by raw_data is stuffed with a pointer to decompressed pixel data int bm_iff_parse_body(CFILE *ifile, int len, iff_bitmap_header *bmheader) { uint8_t *p = bmheader->raw_data; int width = 0, depth = 0, done = 0; if (bmheader->type == TYPE_PBM) { width = bmheader->w; depth = 1; } else if (bmheader->type == TYPE_ILBM) { width = (bmheader->w + 7) / 8; depth = bmheader->nplanes; } if (bmheader->compression == cmpNone) // no compression { for (int y = 0; y < bmheader->h; y++) { int x; for (x = 0; x < width * depth; x++) *p++ = cf_ReadByte(ifile); if (bmheader->masking == mskHasMask) { for (int i = 0; i < width; i++) cf_ReadByte(ifile); // skip mask! } if (bmheader->w & 1) cf_ReadByte(ifile); } } else if (bmheader->compression == cmpByteRun1) // compression { uint8_t *data_end = p + (bmheader->h * depth * width); uint8_t mask = (bmheader->masking == mskHasMask); int cur_width = 0, skip_mask = 0; int command; int plane = 0; while (!done) { if (p >= data_end) { done = 1; continue; } if (cur_width == width) { plane++; if ((plane == depth && !mask) || (plane == depth + 1 && mask)) { skip_mask = 0; plane = 0; } if (mask && plane == depth) skip_mask = 1; cur_width = 0; } command = cf_ReadByte(ifile); if (command >= 0 && command <= 127) { if (!skip_mask) { for (int i = 0; i < command + 1; i++) *p++ = cf_ReadByte(ifile); } else { for (int i = 0; i < (command + 1); i++) cf_ReadByte(ifile); } cur_width += (command + 1); } else if (command >= -127 && command < 0) { int run = (-command) + 1; int repeat_byte = cf_ReadByte(ifile); if (!skip_mask) { for (int i = 0; i < run; i++) *p++ = repeat_byte; } cur_width += run; } } } return IFF_NO_ERROR; } // the buffer pointed to by raw_data is stuffed with a pointer to bitplane pixel data void bm_iff_skip_chunk(CFILE *ifile, uint32_t len) { uint32_t i; for (i = 0; i < len; i++) cf_ReadByte(ifile); } // modify passed bitmap int bm_iff_parse_delta(CFILE *ifile, int len, iff_bitmap_header *bmheader) { uint8_t *p = bmheader->raw_data; int y; int32_t chunk_end = cftell(ifile) + len; cf_ReadInt(ifile); // longword, seems to be equal to 4. Don't know what it is for (y = 0; y < bmheader->h; y++) { uint8_t n_items; int cnt = bmheader->w; uint8_t code; n_items = cf_ReadByte(ifile); while (n_items--) { code = cf_ReadByte(ifile); if (code == 0) { uint8_t rep, val; rep = cf_ReadByte(ifile); val = cf_ReadByte(ifile); cnt -= rep; if (cnt == -1) rep--; while (rep--) *p++ = val; } else if (code > 0x80) { // skip cnt -= (code - 0x80); p += (code - 0x80); if (cnt == -1) p--; } else { // literal cnt -= code; if (cnt == -1) code--; while (code--) *p++ = cf_ReadByte(ifile); if (cnt == -1) cf_ReadByte(ifile); } } if (cnt == -1) { if (!bmheader->w & 1) return IFF_CORRUPT; } else if (cnt) return IFF_CORRUPT; } if (cftell(ifile) == chunk_end - 1) // pad cf_ReadByte(ifile); if (cftell(ifile) != chunk_end) { Int3(); return IFF_CORRUPT; } else return IFF_NO_ERROR; } // read an PBM // Pass pointer to opened file, and to empty bitmap_header structure, and form length int bm_iff_parse_file(CFILE *ifile, iff_bitmap_header *bmheader, iff_bitmap_header *prev_bm) { uint32_t sig, len; int done = 0; while (!done) { if (cfeof(ifile)) { done = 1; continue; } sig = bm_iff_get_sig(ifile); len = cf_ReadInt(ifile, false); switch (sig) { case IFF_SIG_FORM: { bm_iff_get_sig(ifile); bmheader->type = TYPE_PBM; break; } case IFF_SIG_BMHD: { int ret; ret = bm_iff_parse_bmhd(ifile, len, bmheader); if (ret != IFF_NO_ERROR) return ret; else { bmheader->raw_data = (uint8_t *)mem_malloc(bmheader->w * bmheader->h); if (!bmheader->raw_data) return IFF_NO_MEM; } } break; case IFF_SIG_ANHD: { if (!prev_bm) { Int3(); return IFF_CORRUPT; } bmheader->w = prev_bm->w; bmheader->h = prev_bm->h; bmheader->type = prev_bm->type; bmheader->raw_data = (uint8_t *)mem_malloc(bmheader->w * bmheader->h); if (!bmheader->raw_data) return IFF_NO_MEM; memcpy(bmheader->raw_data, prev_bm->raw_data, bmheader->w * bmheader->h); if (len & 1) len++; bm_iff_skip_chunk(ifile, len); break; } case IFF_SIG_CMAP: { int ncolors = (int)(len / 3), cnum; uint8_t r, g, b; for (cnum = 0; cnum < ncolors; cnum++) { r = cf_ReadByte(ifile); g = cf_ReadByte(ifile); b = cf_ReadByte(ifile); r >>= 2; bmheader->palette[cnum].r = r; g >>= 2; bmheader->palette[cnum].g = g; b >>= 2; bmheader->palette[cnum].b = b; } if (len & 1) cf_ReadByte(ifile); } break; case IFF_SIG_BODY: { int r; if ((r = bm_iff_parse_body(ifile, len, bmheader)) != IFF_NO_ERROR) return r; done = 1; break; } case IFF_SIG_DELTA: { int r; if ((r = bm_iff_parse_delta(ifile, len, bmheader)) != IFF_NO_ERROR) return r; done = 1; break; } default: // Don't know this chunk if (len & 1) len++; bm_iff_skip_chunk(ifile, len); break; } } return IFF_NO_ERROR; /* ok! */ } void bm_iff_convert_8_to_16(int dest_bm, iff_bitmap_header *iffbm) { ASSERT(bm_w(dest_bm, 0) == iffbm->w); ASSERT(bm_h(dest_bm, 0) == iffbm->h); uint16_t *data; data = (uint16_t *)bm_data(dest_bm, 0); for (int i = 0; i < iffbm->h; i++) for (int t = 0; t < iffbm->w; t++) { uint16_t pixel; uint8_t c = iffbm->raw_data[i * iffbm->w + t]; int r = iffbm->palette[c].r >> 1; int g = iffbm->palette[c].g >> 1; int b = iffbm->palette[c].b >> 1; pixel = OPAQUE_FLAG | (r << 10) | (g << 5) | b; if (c == iffbm->transparentcolor) pixel = NEW_TRANSPARENT_COLOR; data[i * bm_w(dest_bm, 0) + t] = pixel; } } // Loads an iff into a structure, allocs bitmap memory and converts 8 bit iff file into // 16bit bitmap // Returns bitmap handle on success, or -1 if failed int bm_iff_alloc_file(CFILE *ifile) { int ret; // return code iff_bitmap_header bmheader; int src_bm; char cur_sig[4]; // Ignore FORM and form length cf_ReadInt(ifile); cf_ReadInt(ifile); // check if this an ILBM for (int i = 0; i < 4; i++) cur_sig[i] = cf_ReadByte(ifile); if (strncmp("PBM ", cur_sig, 4)) { LOG_ERROR << "IFF file isn't a PBM...aborting."; return -1; } if (!strncmp("PBM ", cur_sig, 4)) bmheader.type = TYPE_PBM; else Int3(); // Huh? Get Jason! ret = bm_iff_parse_file(ifile, &bmheader, NULL); if (ret != IFF_NO_ERROR) { LOG_ERROR << "Couldn't load IFF file."; return -1; } // Alloc our bitmap src_bm = bm_AllocBitmap(bmheader.w, bmheader.h, 0); if (src_bm < 0) { mem_free(bmheader.raw_data); return -1; } // Convert our 8 bit bitmap to 16bit bm_iff_convert_8_to_16(src_bm, &bmheader); free(bmheader.raw_data); return src_bm; } // returns number of bitmaps or -1 on error int bm_iff_read_animbrush(const char *ifilename, int *bm_list) { CFILE *ifile; iff_bitmap_header bm_headers[40]; iff_bitmap_header *temp_bm_head; int32_t sig, form_len; int32_t form_type; int num_bitmaps = 0; int ret, i; for (int t = 0; t < 40; t++) bm_headers[t].raw_data = 0; ifile = cfopen(ifilename, "rb"); if (!ifile) return -1; sig = bm_iff_get_sig(ifile); form_len = cf_ReadInt(ifile, false); if (sig != IFF_SIG_FORM) { LOG_ERROR << "Not a valid IFF file."; cfclose(ifile); return -1; } form_type = bm_iff_get_sig(ifile); if (form_type == IFF_SIG_ANIM) { while (!cfeof(ifile) && num_bitmaps < 40) { ret = bm_iff_parse_file(ifile, &bm_headers[num_bitmaps], num_bitmaps > 0 ? &bm_headers[num_bitmaps - 1] : NULL); if (ret != IFF_NO_ERROR) goto done; num_bitmaps++; } } else { cfclose(ifile); return -1; } done: cfclose(ifile); for (i = 0; i < num_bitmaps; i++) { int src_bm; temp_bm_head = &bm_headers[i]; memcpy(temp_bm_head->palette, bm_headers[0].palette, sizeof(pal_entry) * 256); temp_bm_head->transparentcolor = bm_headers[0].transparentcolor; src_bm = bm_AllocBitmap(temp_bm_head->w, temp_bm_head->h, 0); ASSERT(src_bm > 0); bm_iff_convert_8_to_16(src_bm, temp_bm_head); bm_list[i] = src_bm; } for (i = 0; i < num_bitmaps; i++) mem_free(bm_headers[i].raw_data); return num_bitmaps; }