Files
Taylor Richards 552f9990d9 remove support for memory mapped files (#6748)
FSO doesn't appear to use this any longer. And even when it was enabled the
requirements made it largely useless anyway. Removing to clean up cfile a
bit and so we don't have to keep maintaining it.
2025-06-05 17:53:13 -04:00

776 lines
20 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include <cstring>
#include "globalincs/pstypes.h"
#include "tgautils/tgautils.h"
#include "cfile/cfile.h"
#include "bmpman/bmpman.h"
#include "graphics/2d.h"
#include "cmdline/cmdline.h"
// -----------------
//
// Defines
//
// -----------------
const char *Xfile_ID = "TRUEVISION-XFILE.";
#define TARGA_FOOTER_SIZE 26 // using sizeof(targa_footer) doesn't work unless we
// pack the struct and I don't want to do that :)
#define MAX_TARGA_RUN_LENGTH_PACKET 128
#define TARGA_HEADER_LENGTH 18
#define ULORIGIN (header.image_descriptor & 0x20)
// -----------------
//
// Structures
//
// -----------------
typedef struct targa_header {
short xoffset;
short yoffset;
short width;
short height;
short cmap_start;
short cmap_length;
ubyte id_length;
ubyte color_map_type;
ubyte image_type;
ubyte cmap_depth;
ubyte pixel_depth;
ubyte image_descriptor;
} targa_header;
typedef struct targa_footer {
uint ext_offset; // file offset to extension area (we ignore it)
uint dev_offset; // file offset to developer area (we also ignore this)
char sig_string[18]; // check to see if this is new TGA format, string should be value of Xfile_ID
} targa_footer;
// -----------------
//
// Internal Functions
//
// -----------------
// copy from one pixel buffer to another
//
// to - pointer to dest. buffet
// from - pointer to source buffer
// pixels - number of pixels to copy
// fromsize - source pixel size
// tosize - dest. pixel size
//
// returns - number of pixels copied to destination
//
static int targa_copy_data(char *to, char *from, int pixels, int fromsize, int tosize)
{
if ( (fromsize == 2) && (tosize==2) ) {
// Flip the alpha bit on 1555 format
ushort *src, *dst;
src = (ushort *)from;
dst = (ushort *)from;
for (int i=0; i<pixels; i++ ) {
*dst++ = (ushort)((*src++) ^ 0x8000); // Flip the transparency bit
}
return tosize*pixels;
} else if ( (fromsize == 2) && (tosize == 3) ) {
ushort *src;
src = (ushort *)from;
for (int i=0; i<pixels; i++ ) {
ushort pixel = *src++;
*to++ = (ubyte)((pixel & 0x1f) * 8);
*to++ = (ubyte)(((pixel >> 5) & 63) * 4);
*to++ = (ubyte)(((pixel >> 11) & 0x1f) * 8);
}
return tosize*pixels;
} else {
Assert(fromsize == tosize);
memcpy(to, from, pixels * fromsize);
return tosize*pixels;
}
}
// targa_pixels_equal -- Test if two pixels are identical
//
// pix1 - first pixel data
// pix2 - second pixel data
// pixbytes - number of bytes per pixel
//
// returns - 0 if No Match, else 1 if Match
static int targa_pixels_equal(char *pix1, char *pix2, int pixbytes)
{
do {
if ( *pix1++ != *pix2++ ) {
return 0;
}
} while ( --pixbytes > 0 );
return 1;
}
// Perform targa RLE on the input data
//
// out - Buffer to write it out to
// in - Buffer to compress
// outsize - Number of bytes in output buffer
// pixsize - Number of bytes in input pixel
// bytecount - Number of bytes input
//
// returns - size of compressed data
//
int targa_compress(char *out, char *in, int outsize, int pixsize, int bytecount)
{
int pixcount; // number of pixels in the current packet
char *inputpixel=NULL; // current input pixel position
char *matchpixel=NULL; // pixel value to match for a run
char *flagbyte=NULL; // location of last flag byte to set
int rlcount; // current count in r.l. string
int rlthresh; // minimum valid run length
char *copyloc; // location to begin copying at
// set the threshold -- the minimum valid run length
if (outsize == 1) {
rlthresh = 2; // for 8bpp, require a 2 pixel span before rle'ing
} else {
rlthresh = 1;
}
// set the first pixel up
flagbyte = out; // place to put next flag if run
inputpixel = in;
pixcount = 1;
rlcount = 0;
copyloc = (char *)0;
// loop till data processing complete
do {
// if we have accumulated a 128-byte packet, process it
if ( pixcount == 129 ) {
*flagbyte = 127;
// set the run flag if this is a run
if ( rlcount >= rlthresh ) {
*flagbyte |= 0x80;
pixcount = 2;
}
// copy the data into place
++flagbyte;
flagbyte += targa_copy_data(flagbyte, copyloc, pixcount-1, pixsize, outsize);
pixcount = 1;
// set up for next packet
continue;
}
// if zeroth byte, handle as special case
if ( pixcount == 1 ) {
rlcount = 0;
copyloc = inputpixel; /* point to 1st guy in packet */
matchpixel = inputpixel; /* set pointer to pix to match */
pixcount = 2;
inputpixel += pixsize;
continue;
}
// assembling a packet -- look at next pixel
// current pixel == match pixel?
if ( targa_pixels_equal(inputpixel, matchpixel, outsize) ) {
// establishing a run of enough length to
// save space by doing it
// -- write the non-run length packet
// -- start run-length packet
if ( ++rlcount == rlthresh ) {
// close a non-run packet
if ( pixcount > (rlcount+1) ) {
// write out length and do not set run flag
*flagbyte++ = (char)(pixcount - 2 - rlthresh);
flagbyte += targa_copy_data(flagbyte, copyloc, (pixcount-1-rlcount), pixsize, outsize);
copyloc = inputpixel;
pixcount = rlcount + 1;
}
}
} else {
// no match -- either break a run or continue without one
// if a run exists break it:
// write the bytes in the string (outsize+1)
// start the next string
if ( rlcount >= rlthresh ) {
*flagbyte++ = (char)(0x80 | rlcount);
flagbyte += targa_copy_data(flagbyte, copyloc, 1, pixsize, outsize);
pixcount = 1;
continue;
} else {
// not a match and currently not a run
// - save the current pixel
// - reset the run-length flag
rlcount = 0;
matchpixel = inputpixel;
}
}
pixcount++;
inputpixel += pixsize;
} while ( inputpixel < (in + bytecount));
// quit this buffer without loosing any data
if ( --pixcount >= 1 ) {
*flagbyte = (char)(pixcount - 1);
if ( rlcount >= rlthresh ) {
*flagbyte |= 0x80;
pixcount = 1;
}
// copy the data into place
++flagbyte;
flagbyte += targa_copy_data(flagbyte, copyloc, pixcount, pixsize, outsize);
}
return (int)(flagbyte-out);
}
// Reads a pixel of the specified bytes_per_pixel into memory and
// returns the number of bytes read into memory.
// NOTE : for FreeSpace2, this also swizzles data into the proper screen (NOT texture) format - just like
// the pcxutils do.
//
// dst - A pointer to the destination. Must be at least 4 bytes long.
// targa_file - The file to read from.
// bytes_per_pixel - The bytes per pixel of the file.
// dest_size - bytes per pixel in destination (1 or 2. 1 == 8 bit paletted)
//
// returns - Number of byte read into memory
//
static void targa_read_pixel( int num_pixels, ubyte **dst, ubyte **src, int bytes_per_pixel, int dest_size )
{
int idx;
ushort pixel;
int pixel32;
ubyte r, g, b;
ubyte al = 0;
for(idx=0; idx<num_pixels; idx++){
// 24 or 32 bit
if ( (bytes_per_pixel == 3) || (bytes_per_pixel == 4) ) {
memset( &pixel32, 0xff, sizeof(int) );
memcpy( &pixel32, *src, bytes_per_pixel );
#if BYTE_ORDER == BIG_ENDIAN
// on big-endian it will be used as ARGB so switch it back to BGRA
if ( dest_size == 4 ) {
pixel32 = INTEL_INT(pixel32);
}
#endif
// should have it's own alpha settings so just copy it out as is
memcpy( *dst, &pixel32, dest_size );
}
// 8 or 16 bit
else {
// stuff the 16 bit pixel
memcpy(&pixel, *src, bytes_per_pixel);
pixel = INTEL_SHORT(pixel);
// if the pixel is transparent, make it so...
if(((pixel & 0x7c00) == 0) && ((pixel & 0x03e0) == 0x03e0) && ((pixel & 0x001f) == 0)){
r = b = 0;
g = 255;
al = 0;
bm_set_components((ubyte*)&pixel, &r, &g, &b, &al);
} else {
// get the 8 bit r, g, and b values
r = (ubyte)(((pixel & 0x7c00) >> 10) * 8);
g = (ubyte)(((pixel & 0x03e0) >> 5) * 8);
b = (ubyte)((pixel & 0x001f) * 8);
al = 1;
// now stuff these back, swizzling properly
pixel = 0;
bm_set_components((ubyte*)&pixel, &r, &g, &b, &al);
}
// 16 bit destination
if(dest_size == 2){
// stuff the final pixel
memcpy( *dst, &pixel, bytes_per_pixel );
}
}
// next pixel
(*dst) += dest_size;
(*src) += bytes_per_pixel;
}
}
// -----------------
//
// External Functions
//
// -----------------
// Reads header information from the targa file into the bitmap pointer
//
// filename - name of the targa bitmap file
// w - (output) width of the bitmap
// h - (output) height of the bitmap
// bpp - (output) bits per pixel of the bitmap
//
// returns - TARGA_ERROR_NONE if successful, otherwise error code
//
int targa_read_header(const char *real_filename, CFILE *img_cfp, int *w, int *h, int *bpp, ubyte * /*palette*/ )
{
targa_header header;
CFILE *targa_file = NULL;
char filename[MAX_FILENAME_LEN];
if (img_cfp == NULL) {
strcpy_s( filename, real_filename );
char *p = strchr( filename, '.' );
if ( p )
*p = 0;
strcat_s( filename, ".tga" );
targa_file = cfopen( filename , "rb" );
if ( !targa_file ) {
return TARGA_ERROR_READING;
}
} else {
targa_file = img_cfp;
}
header.id_length = cfread_ubyte(targa_file);
// header.id_length=targa_file.read_char();
header.color_map_type = cfread_ubyte(targa_file);
// header.color_map_type=targa_file.read_char();
header.image_type = cfread_ubyte(targa_file);
// header.image_type=targa_file.read_char();
header.cmap_start = cfread_short(targa_file);
// header.cmap_start=targa_file.read_short();
header.cmap_length = cfread_short(targa_file);
// header.cmap_length=targa_file.read_short();
header.cmap_depth = cfread_ubyte(targa_file);
// header.cmap_depth=targa_file.read_char();
header.xoffset = cfread_short(targa_file);
// header.xoffset=targa_file.read_short();
header.yoffset = cfread_short(targa_file);
// header.yoffset=targa_file.read_short();
header.width = cfread_short(targa_file);
// header.width=targa_file.read_short();
header.height = cfread_short(targa_file);
// header.height=targa_file.read_short();
header.pixel_depth = cfread_ubyte(targa_file);
// header.pixel_depth=targa_file.read_char();
header.image_descriptor = cfread_ubyte(targa_file);
// header.image_descriptor=targa_file.read_char();
if (img_cfp == NULL) {
cfclose(targa_file);
targa_file = NULL;
}
if ( (header.pixel_depth != 16) && (header.pixel_depth != 24) && (header.pixel_depth != 32) ) {
Assertion( (header.pixel_depth != 16) && (header.pixel_depth != 24) && (header.pixel_depth != 32), "Invalid colour depth (%d) in header of tga file %s\n", header.pixel_depth, real_filename );
return TARGA_ERROR_READING;
}
if (w) *w = header.width;
if (h) *h = header.height;
if (bpp) *bpp = header.pixel_depth;
return TARGA_ERROR_NONE;
}
// Uncompresses some RLE'd TGA data
//
// dst: pointer uncompressed destination.
// src: pointer to source rle'd data.
// bitmap_width: how many pixels to uncompress.
// bytes_per_pixel: bytes per pixel of the data.
//
// returns: number of input bytes processed.
//
int targa_uncompress( ubyte *dst, ubyte *src, int bitmap_width, int bytes_per_pixel, int dest_size )
{
ubyte *pixdata = dst;
ubyte *src_pixels = src;
int pixel_count = 0; // Initialize pixel counter
// Main decoding loop
while (pixel_count < bitmap_width ) {
// Get the pixel count
int run_count = *src_pixels++;
// Make sure writing this next run will not overflow the buffer
Assert(pixel_count + (run_count & 0x7f) + 1 <= bitmap_width );
// If the run is encoded...
if ( run_count & 0x80 ) {
run_count &= ~0x80; // Mask off the upper bit
// Update total pixel count
pixel_count += (run_count + 1);
ubyte pixel_value[4]; // temporary
ubyte *tmp = pixel_value;
targa_read_pixel( 1, &tmp, &src_pixels, bytes_per_pixel, dest_size );
// Write remainder of pixel run to buffer 'run_count' times
do {
memcpy( pixdata, pixel_value, dest_size );
pixdata += dest_size;
} while (run_count--);
} else { // ...the run is unencoded (raw)
// Update total pixel count
pixel_count += (run_count + 1);
// Read run_count pixels
targa_read_pixel(run_count+1, &pixdata, &src_pixels, bytes_per_pixel, dest_size );
}
}
Assert( pixel_count == bitmap_width );
return (int)(src_pixels - src);
}
// Loads a Targa bitmap
//
// filename - name of the targa file to load
// image_data - allocated storage for the bitmap
//
// returns - true if succesful, false otherwise
//
int targa_read_bitmap(const char *real_filename, ubyte *image_data, ubyte *palette, int dest_size, int cf_type)
{
Assert(real_filename);
targa_header header;
targa_footer footer;
CFILE *targa_file;
char filename[MAX_FILENAME_LEN];
ubyte r, g, b;
int xfile_offset = 0;
// open the file
strcpy_s( filename, real_filename );
char *p = strchr( filename, '.' );
if ( p ) *p = 0;
strcat_s( filename, ".tga" );
targa_file = cfopen( filename , "rb", cf_type );
if ( !targa_file ){
return TARGA_ERROR_READING;
}
// read the footer info first
cfseek( targa_file, cfilelength(targa_file) - TARGA_FOOTER_SIZE, CF_SEEK_SET );
memset( &footer, 0, sizeof(targa_footer) );
footer.ext_offset = cfread_uint(targa_file);
footer.dev_offset = cfread_uint(targa_file);
cfread_string(footer.sig_string, sizeof(footer.sig_string), targa_file);
if ( !strcmp(footer.sig_string, Xfile_ID) ) {
// it's an extended file to lets be sure to skip the extra crap which comes after the
// image data section, dev section comes first in the file and we only need one offset
if (footer.dev_offset || footer.ext_offset) {
xfile_offset = cfilelength(targa_file) - ((footer.dev_offset) ? footer.dev_offset : footer.ext_offset);
}
}
// done with the footer so jump back and do normal reading
cfseek( targa_file, 0, CF_SEEK_SET );
header.id_length = cfread_ubyte(targa_file);
// header.id_length=targa_file.read_char();
header.color_map_type = cfread_ubyte(targa_file);
// header.color_map_type=targa_file.read_char();
header.image_type = cfread_ubyte(targa_file);
// header.image_type=targa_file.read_char();
header.cmap_start = cfread_short(targa_file);
// header.cmap_start=targa_file.read_short();
header.cmap_length = cfread_short(targa_file);
// header.cmap_length=targa_file.read_short();
header.cmap_depth = cfread_ubyte(targa_file);
// header.cmap_depth=targa_file.read_char();
header.xoffset = cfread_short(targa_file);
// header.xoffset=targa_file.read_short();
header.yoffset = cfread_short(targa_file);
// header.yoffset=targa_file.read_short();
header.width = cfread_short(targa_file);
// header.width=targa_file.read_short();
header.height = cfread_short(targa_file);
// header.height=targa_file.read_short();
header.pixel_depth = cfread_ubyte(targa_file);
// header.pixel_depth=targa_file.read_char();
header.image_descriptor = cfread_ubyte(targa_file);
// header.image_descriptor=targa_file.read_char();
int bytes_per_pixel = (header.pixel_depth>>3);
// we're only allowing 2 bytes per pixel (16 bit compressed), unless Cmdline_jpgtga is used
Assert( (bytes_per_pixel == 2) || (bytes_per_pixel == 3) || (bytes_per_pixel == 4) );
if ( (bytes_per_pixel < 2) || (bytes_per_pixel > 4) ) {
cfclose(targa_file);
Int3();
return TARGA_ERROR_READING;
}
if((header.image_type!=1)&&(header.image_type!=2)&&(header.image_type!=9)&&(header.image_type!=10)) {
cfclose(targa_file);
return TARGA_ERROR_READING;
}
// skip the Image ID field -- should not be needed
if(header.id_length>0) {
if ( cfseek(targa_file, header.id_length, CF_SEEK_CUR) ) {
cfclose(targa_file);
return TARGA_ERROR_READING;
}
}
// read palette if one present.
if (header.color_map_type) { // non-zero indicates palette in the file
Int3();
// Determine the size of the color map
Assert(header.cmap_depth==24);
Assert(header.cmap_length<=256);
Assert(palette);
// Read the color map data
int i;
for (i = 0; i < header.cmap_length; i++) {
r = cfread_ubyte(targa_file);
g = cfread_ubyte(targa_file);
b = cfread_ubyte(targa_file);
if(palette != NULL){
palette[i*3+2] = r;
palette[i*3+1] = g;
palette[i*3+0] = b;
}
}
// Fill out with black.
if(palette != NULL){
for (; i < 256; i++) {
palette[i*3+2] = 0;
palette[i*3+1] = 0;
palette[i*3+0] = 0;
}
}
}
int bytes_remaining = cfilelength(targa_file) - cftell(targa_file) - xfile_offset;
Assert(bytes_remaining > 0);
ubyte *fileptr = (ubyte*)vm_malloc(bytes_remaining);
Assert(fileptr);
if(fileptr == NULL){
return TARGA_ERROR_READING;
}
ubyte *src_pixels = fileptr;
cfread(fileptr, bytes_remaining, 1, targa_file);
int rowsize = header.width * dest_size;
if ( (header.image_type == 1) || (header.image_type == 2) || (header.image_type == 3) ) {
// Uncompressed read
for (int i = 0; i < header.height; i++) {
ubyte * pixptr;
if ( ULORIGIN ) {
pixptr = image_data + i * rowsize;
} else {
pixptr = image_data + ((header.height - i - 1) * rowsize);
}
targa_read_pixel(header.width, &pixptr, &src_pixels, bytes_per_pixel, dest_size );
}
} else if (header.image_type == 9 || header.image_type == 10 || header.image_type == 11) {
// the following handles RLE'ed targa data.
// targas encoded by the scanline -- loop on the height
for (int i = 0; i < header.height; i++) {
ubyte *pixdata;
if (ULORIGIN) {
pixdata = image_data + i * rowsize;
} else {
pixdata = image_data + ((header.height - i - 1) * rowsize);
}
src_pixels += targa_uncompress( pixdata, src_pixels, header.width, bytes_per_pixel, dest_size );
}
}
vm_free(fileptr);
cfclose(targa_file);
targa_file = NULL;
return TARGA_ERROR_NONE;
}
// Write out a Targa format bitmap. Always writes out a top-up bitmap.
// JAS: DOESN'T WORK WITH 8-BPP PALETTES
//
// filename: name of the Targa file, .tga extension added if not passed in
// data: raw image data
// w: width of the bitmap in pixels
// h: height of the bitmap in pixels
// bpp: bits per pixel of the bitmap
//
// returns: 0 if successful, otherwise -1
//
int targa_write_bitmap(char *real_filename, ubyte *data, ubyte * /*palette*/, int w, int h, int bpp)
{
Assert(bpp == 24);
char filename[MAX_FILENAME_LEN];
CFILE *f;
int bytes_per_pixel = (bpp >> 3);
// open the file
strcpy_s( filename, real_filename );
char *p = strchr( filename, '.' );
if ( p ) *p = 0;
strcat_s( filename, ".tga" );
f = cfopen( filename , "wb" );
if ( !f ){
return TARGA_ERROR_READING;
}
// Write the TGA header
cfwrite_ubyte(0, f);
// f.write_ubyte(0); // IDLength
cfwrite_ubyte(0, f);
//f.write_ubyte(0); // ColorMapType
cfwrite_ubyte(10, f);
// f.write_ubyte(10); // image_type: 2 = 24bpp, uncompressed, 10=24bpp rle compressed
cfwrite_ushort(0, f);
// f.write_ushort(0); // CMapStart
cfwrite_ushort(0, f);
// f.write_ushort(0); // CMapLength
cfwrite_ubyte(0, f);
// f.write_ubyte(0); // CMapDepth
cfwrite_ushort(0, f);
// f.write_ushort(0); // XOffset
cfwrite_ushort(0, f);
// f.write_ushort(0); // YOffset
cfwrite_ushort((ushort)w, f);
// f.write_ushort((ushort)w); // Width
cfwrite_ushort((ushort)h, f);
// f.write_ushort((ushort)h); // Height
cfwrite_ubyte(24, f);
// f.write_ubyte(24); // pixel_depth
cfwrite_ubyte(0x20, f);
// f.write_ubyte(0x20); // ImageDesc ( 0x20 = Origin at upper left )
ubyte *compressed_data;
compressed_data = (ubyte*)vm_malloc(w * h * bytes_per_pixel);
Assert(compressed_data);
if(compressed_data == NULL){
cfclose(f);
return -1;
}
int compressed_data_len;
compressed_data_len = targa_compress((char*)compressed_data, (char*)data, 3, bytes_per_pixel, w * h * bytes_per_pixel);
if (compressed_data_len < 0) {
vm_free(compressed_data);
cfclose(f);
return -1;
}
cfwrite(compressed_data, compressed_data_len, 1, f);
cfclose(f);
vm_free(compressed_data);
return 0;
}