Descent3/bitmap/iff.cpp
JeodC a4ab78192c Join license header with historical commentrs
Join the license header with historical comments using a separator so IDEs can correctly parse the initial header.

Also use .gitattributes to ensure all files are LF.
2024-05-08 14:41:19 -04:00

618 lines
15 KiB
C++

/*
* 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 <http://www.gnu.org/licenses/>.
--- 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mem.h"
#include "iff.h"
#include "byteswap.h"
#include "cfile.h"
#include "pserror.h"
#include "pstypes.h"
#include "bitmap.h"
#include "mono.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
typedef struct {
ubyte r, g, b;
} pal_entry;
// structure of the header in the file
typedef struct iff_bitmap_header {
short w, h; // width and height of this bitmap
short x, y; // generally unused
short type; // see types above
short transparentcolor; // which color is transparent (if any)
short pagewidth, pageheight; // width & height of source screen
ubyte nplanes; // number of planes (8 for 256 color image)
ubyte masking, compression; // see constants above
ubyte xaspect, yaspect; // aspect ratio (usually 5/6)
pal_entry palette[256]; // the palette for this bitmap
ubyte *raw_data; // ptr to array of data
short row_size; // offset to next row
} iff_bitmap_header;
short iff_transparent_color;
short 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) (((long)(a) << 24) + ((long)(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, uint 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, uint 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, uint len, iff_bitmap_header *bmheader) {
len = len;
bmheader->w = cf_ReadShort(ifile);
bmheader->w = MOTOROLA_SHORT(bmheader->w);
bmheader->h = cf_ReadShort(ifile);
bmheader->h = MOTOROLA_SHORT(bmheader->h);
bmheader->x = cf_ReadShort(ifile);
bmheader->x = MOTOROLA_SHORT(bmheader->x);
bmheader->y = cf_ReadShort(ifile);
bmheader->y = MOTOROLA_SHORT(bmheader->y);
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);
bmheader->transparentcolor = MOTOROLA_SHORT(bmheader->transparentcolor);
bmheader->xaspect = cf_ReadByte(ifile);
bmheader->yaspect = cf_ReadByte(ifile);
bmheader->pagewidth = cf_ReadShort(ifile);
bmheader->pageheight = cf_ReadShort(ifile);
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) {
ubyte *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
{
ubyte *data_end = p + (bmheader->h * depth * width);
ubyte 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, uint len) {
uint 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) {
unsigned char *p = bmheader->raw_data;
int y;
long 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++) {
ubyte n_items;
int cnt = bmheader->w;
ubyte code;
n_items = cf_ReadByte(ifile);
while (n_items--) {
code = cf_ReadByte(ifile);
if (code == 0) {
ubyte 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) {
uint sig, len;
int done = 0;
while (!done) {
if (cfeof(ifile)) {
done = 1;
continue;
}
sig = bm_iff_get_sig(ifile);
len = cf_ReadInt(ifile);
len = MOTOROLA_INT(len);
switch (sig) {
case IFF_SIG_FORM: {
int newsig = 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 = (ubyte *)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 = (ubyte *)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;
unsigned char 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);
ushort *data;
data = (ushort *)bm_data(dest_bm, 0);
for (int i = 0; i < iffbm->h; i++)
for (int t = 0; t < iffbm->w; t++) {
ushort pixel;
ubyte 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)) {
mprintf((0, "IFF file isn't a PBM...aborting.\n"));
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) {
mprintf((0, "Couldn't load IFF file.\n"));
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;
long sig, form_len;
long 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);
if (sig != IFF_SIG_FORM) {
mprintf((0, "Not a valid IFF file.\n"));
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;
}