/*
* 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/tga.cpp $
* $Revision: 28 $
* $Date: 9/06/01 10:25a $
* $Author: Matt $
*
* Functions to read TGA files
*
* $Log: /DescentIII/Main/bitmap/tga.cpp $
*
* 28 9/06/01 10:25a Matt
* Took out annoying Int3
*
* 27 2/01/00 2:37a Jason
* Fixed problem with bad tga files
*
* 26 10/21/99 9:28p Jeff
* B.A. Macintosh code merge
*
* 25 4/14/99 1:07a Jeff
* fixed case mismatched #includes
*
* 24 2/16/99 12:42p Kevin
* Improvements to the paging data progress indicator
*
* 23 2/09/99 7:01p Kevin
* First work for new and improved progress screen while loading a level.
* Note that this is a hack at this point, while I get the details worked
* out, then I'll make it cleaner.
*
* 22 1/14/99 2:26p Jason
* made data tracking more reliable
*
* 21 12/18/98 3:16p Jason
* added specularity to mine walls
*
* 20 10/20/98 1:04p Jason
* fixed import problem
*
* 19 10/19/98 4:22p Jason
* more fixes for Beta5
*
* 18 10/09/98 3:32p Kevin
* New memory library
*
* 17 10/08/98 4:23p Kevin
* Changed code to comply with memory library usage. Always use mem_malloc
* , mem_free and mem_strdup
*
* 16 8/14/98 2:55p Jason
* fixed reading of 24 bit TGA files
*
* 15 5/24/98 1:52a Jeff
* no changes, checked out and checked back in so people would recompile
* it, because it is now to be compiled optimized
*
* 14 4/23/98 6:38p Jason
* made bitmaps use 1555 format
*
* 13 3/31/98 3:48p Jason
* added memory lib
*
* 12 2/12/98 1:32p Jason
* got mipmapping working
*
* 11 1/12/98 3:00p Keneta
* Changed assert to Int3 (Jason on MattL's machine)
*
* 10 12/31/97 11:11a Samir
* FROM JASON: Added the ability to read NEW_COMPRESS_MIPPED
*
* 9 12/22/97 7:34p Samir
* Removed instances of gr.h include. Replaced with grdefs.h
*
* 8 12/22/97 3:25p Keneta
* fixed bug with new texture paging
*
* 7 12/19/97 3:36p Jason
* bug fixes for bitmap paging stuff
*
* 6 12/19/97 2:46p Jason
* implemented bitmap paging routines
*
* 5 11/03/97 6:21p Jason
* fixed some problems with animating textures being remapped improperly
*
* 4 10/16/97 4:55p Jason
* fixed stupid off-by-one bug
*
* 3 10/15/97 5:20p Jason
* did a HUGE overhaul of the bitmap system
*
* 2 8/29/97 1:22p Jason
* added tga screenshots
*
* 13 5/29/97 6:21p Jason
* made tga's better handle pixmap info
*
* 12 5/19/97 5:10p Jason
* changes for our new abstracted renderer code
*
* 11 4/30/97 3:15p Jason
* changes to support both 8bit and 16bit rendering in software
*
* 10 3/07/97 4:03p Samir
* Took out malloc.h for ANSI compliance.
*
* 9 3/07/97 1:02p Jason
* added the ability to read 24 bit tgas as well
*
* 8 3/03/97 6:20p Matt
* Changed cfile functions to use D3 naming convention
*
* $NoKeywords: $
*/
#include
#include
#include "bitmap.h"
#include "byteswap.h"
#include "cfile.h"
#include "gamesequence.h"
#include "grdefs.h"
#include "log.h"
#include "mem.h"
#include "pserror.h"
static char *Tga_file_data = NULL;
static int Fake_pos = 0;
static int Bad_tga = 0;
static int Fake_file_size = 0;
static inline char tga_read_byte();
static inline int tga_read_int();
static inline int16_t tga_read_short();
static uint16_t bm_tga_translate_pixel(int pixel, int format);
static int bm_tga_read_outrage_compressed16(CFILE *infile, int n, int num_mips, int type);
inline char tga_read_byte() {
// Check for bad file
if (Fake_pos + 1 > Fake_file_size) {
Bad_tga = 1;
return 0;
}
return Tga_file_data[Fake_pos++];
}
inline int tga_read_int() {
int i;
// Check for bad file
if (Fake_pos + 4 > Fake_file_size) {
Bad_tga = 1;
return 0;
}
i = *(int *)(Tga_file_data + Fake_pos);
Fake_pos += 4;
return INTEL_INT(i);
}
inline int16_t tga_read_short() {
int16_t i;
// Check for bad file
if (Fake_pos + 2 > Fake_file_size) {
Bad_tga = 1;
return 0;
}
memcpy(&i, Tga_file_data + Fake_pos, sizeof(i));
Fake_pos += 2;
return INTEL_SHORT(i);
}
uint16_t bm_tga_translate_pixel(int pixel, int format) {
int red = ((pixel >> 16) & 0xFF);
int green = ((pixel >> 8) & 0xFF);
int blue = ((pixel) & 0xFF);
int alpha = ((pixel >> 24) & 0xFF);
uint16_t newpix;
if (format == BITMAP_FORMAT_4444) {
int newred = red >> 4;
int newgreen = green >> 4;
int newblue = blue >> 4;
int newalpha = alpha >> 4;
newpix = (newalpha << 12) | (newred << 8) | (newgreen << 4) | (newblue);
} else {
int newred = red >> 3;
int newgreen = green >> 3;
int newblue = blue >> 3;
newpix = OPAQUE_FLAG | (newred << 10) | (newgreen << 5) | (newblue);
if (alpha == 0)
newpix = NEW_TRANSPARENT_COLOR;
}
return newpix;
}
int bm_tga_read_outrage_compressed16(CFILE *infile, int n, int num_mips, int type) {
uint16_t *dest_data;
uint16_t pixel;
int width, height;
int m;
for (m = 0; m < num_mips; m++) {
width = bm_w(n, m);
height = bm_h(n, m);
int total = height * width;
int count = 0;
dest_data = (uint16_t *)bm_data(n, m);
while (count != total) {
ASSERT(count < total);
uint8_t command = tga_read_byte();
if (Bad_tga)
return 0;
if (command == 0) // next pixel is raw
{
pixel = tga_read_short();
if (Bad_tga)
return 0;
if (type != OUTRAGE_1555_COMPRESSED_MIPPED && type != OUTRAGE_4444_COMPRESSED_MIPPED) {
if (pixel == 0x07e0)
pixel = NEW_TRANSPARENT_COLOR;
else {
int r = ((pixel & 0xF800) >> 11) << 3;
int g = ((pixel & 0x07e0) >> 5) << 2;
int b = (pixel & 0x001f) << 3;
pixel = OPAQUE_FLAG | GR_RGB16(r, g, b);
}
}
int i = count / width;
int t = count % width;
dest_data[i * width + t] = pixel;
count++;
} else if (command >= 2 && command <= 250) // next pixel is run of pixels
{
pixel = tga_read_short();
if (Bad_tga)
return 0;
if (type != OUTRAGE_1555_COMPRESSED_MIPPED && type != OUTRAGE_4444_COMPRESSED_MIPPED) {
if (pixel == 0x07e0)
pixel = NEW_TRANSPARENT_COLOR;
else {
int r = ((pixel & 0xF800) >> 11) << 3;
int g = ((pixel & 0x07e0) >> 5) << 2;
int b = (pixel & 0x001f) << 3;
pixel = OPAQUE_FLAG | GR_RGB16(r, g, b);
}
}
for (int k = 0; k < command; k++) {
int i = count / width;
int t = count % width;
dest_data[i * width + t] = pixel;
count++;
}
} else
Int3(); // bad compression run
}
}
// DAJ added to fill out the mip maps down to the 1x1 size (memory is already there)
// does not average since we are only a pixel or two in size
if (num_mips > 1) {
GameBitmaps[n].mip_levels = bm_miplevels(n);
for (m = num_mips; m < bm_miplevels(n); m++) {
width = bm_w(n, m);
height = bm_h(n, m);
uint16_t w_prev = bm_w(n, m - 1);
uint16_t *dst = bm_data(n, m);
uint16_t *src = bm_data(n, m - 1);
for (int h_inc = 0; h_inc < height; h_inc++) {
for (int w_inc = 0; w_inc < width; w_inc++) {
dst[h_inc * width + w_inc] = src[2 * (h_inc * w_prev + w_inc)];
}
}
}
}
return 1;
}
// Loads a tga or ogf file into a bitmap...returns handle to bm or -1 on error
int bm_tga_alloc_file(CFILE *infile, char *name, int format) {
uint8_t image_id_len, color_map_type, image_type, pixsize, descriptor;
uint8_t upside_down = 0;
uint16_t width, height;
uint32_t pixel;
int i, t, n, data8bit = 0, savepos = 0;
int mipped = 0;
int num_mips = 1;
int read_ok = 1;
image_id_len = cf_ReadByte(infile);
color_map_type = cf_ReadByte(infile);
image_type = cf_ReadByte(infile);
if (color_map_type != 0 ||
(image_type != 10 && image_type != 2 && image_type != OUTRAGE_TGA_TYPE && image_type != OUTRAGE_COMPRESSED_OGF &&
image_type != OUTRAGE_COMPRESSED_MIPPED && image_type != OUTRAGE_NEW_COMPRESSED_MIPPED &&
image_type != OUTRAGE_1555_COMPRESSED_MIPPED && image_type != OUTRAGE_4444_COMPRESSED_MIPPED)) {
LOG_ERROR << "bm_tga: Can't read this type of TGA.";
return -1;
}
if (image_type == OUTRAGE_COMPRESSED_OGF_8BIT)
data8bit = 1;
if (image_type == OUTRAGE_4444_COMPRESSED_MIPPED || image_type == OUTRAGE_1555_COMPRESSED_MIPPED ||
image_type == OUTRAGE_NEW_COMPRESSED_MIPPED || image_type == OUTRAGE_TGA_TYPE ||
image_type == OUTRAGE_COMPRESSED_MIPPED || image_type == OUTRAGE_COMPRESSED_OGF ||
image_type == OUTRAGE_COMPRESSED_OGF_8BIT) {
if (image_type == OUTRAGE_4444_COMPRESSED_MIPPED || image_type == OUTRAGE_NEW_COMPRESSED_MIPPED ||
image_type == OUTRAGE_1555_COMPRESSED_MIPPED) {
cf_ReadString(name, BITMAP_NAME_LEN - 1, infile);
} else {
for (i = 0; i < BITMAP_NAME_LEN; i++)
name[i] = cf_ReadByte(infile);
}
if (image_type == OUTRAGE_4444_COMPRESSED_MIPPED || image_type == OUTRAGE_1555_COMPRESSED_MIPPED ||
image_type == OUTRAGE_COMPRESSED_MIPPED || image_type == OUTRAGE_NEW_COMPRESSED_MIPPED)
num_mips = cf_ReadByte(infile);
else
num_mips = 1;
if (num_mips > 1)
mipped = 1;
}
for (i = 0; i < 9; i++) // ingore next 9 bytes
cf_ReadByte(infile);
width = cf_ReadShort(infile);
height = cf_ReadShort(infile);
pixsize = cf_ReadByte(infile);
if (pixsize != 32 && pixsize != 24) {
LOG_ERROR.printf("bm_tga: This file has a pixsize of field of %d, it should be 32.", pixsize);
return -1;
}
descriptor = cf_ReadByte(infile);
if (((descriptor & 0x0F) != 8) && ((descriptor & 0x0F) != 0)) {
LOG_ERROR.printf("bm_tga: Descriptor field & 0x0F must be 8 or 0, but this is %d.", descriptor & 0x0F);
return -1;
}
for (i = 0; i < image_id_len; i++)
cf_ReadByte(infile);
n = bm_AllocBitmap(width, height, mipped * ((width * height * 2) / 3));
if (format == BITMAP_FORMAT_4444 || image_type == OUTRAGE_4444_COMPRESSED_MIPPED)
GameBitmaps[n].format = BITMAP_FORMAT_4444;
// Copy the name
strcpy(GameBitmaps[n].name, name);
if (mipped)
GameBitmaps[n].flags |= BF_MIPMAPPED;
if (n < 0) {
LOG_ERROR << "bm_tga: Failed to allocate memory.";
Int3();
return -1;
}
upside_down = (descriptor & 0x20) >> 5;
upside_down = 1 - upside_down;
// Load the actual bitmap data in, converting it from 32 bit to 16 bit, and replacing
// that pesky transparency color without our replacement
if (image_type == 10 || image_type == 2) {
if (image_type == 10) // compressed tga
{
int total = 0;
while (total < (height * width)) {
uint8_t command = cf_ReadByte(infile);
uint8_t len = (command & 127) + 1;
if (command & 128) // rle chunk
{
if (pixsize == 32)
pixel = cf_ReadInt(infile);
else {
int r, g, b;
r = cf_ReadByte(infile);
g = cf_ReadByte(infile);
b = cf_ReadByte(infile);
pixel = (255 << 24) | (r << 16) | (g << 8) | b;
}
uint16_t newpix = bm_tga_translate_pixel(pixel, format);
for (int k = 0; k < len; k++, total++) {
i = total / width;
t = total % width;
if (upside_down)
GameBitmaps[n].data16[((height - 1) - i) * width + t] = newpix;
else
GameBitmaps[n].data16[i * width + t] = newpix;
}
} else // raw chunk
{
for (int k = 0; k < len; k++, total++) {
if (pixsize == 32)
pixel = cf_ReadInt(infile);
else {
int r, g, b;
b = (uint8_t)cf_ReadByte(infile);
g = (uint8_t)cf_ReadByte(infile);
r = (uint8_t)cf_ReadByte(infile);
pixel = (255 << 24) | (r << 16) | (g << 8) | b;
}
uint16_t newpix = bm_tga_translate_pixel(pixel, format);
i = total / width;
t = total % width;
if (upside_down)
GameBitmaps[n].data16[((height - 1) - i) * width + t] = newpix;
else
GameBitmaps[n].data16[i * width + t] = newpix;
}
}
}
} else // uncompressed TGA
{
for (i = 0; i < height; i++) {
for (t = 0; t < width; t++) {
if (pixsize == 32)
pixel = cf_ReadInt(infile);
else {
int r, g, b;
b = (uint8_t)cf_ReadByte(infile);
g = (uint8_t)cf_ReadByte(infile);
r = (uint8_t)cf_ReadByte(infile);
pixel = (255 << 24) | (r << 16) | (g << 8) | b;
}
uint16_t newpix = bm_tga_translate_pixel(pixel, format);
if (upside_down)
GameBitmaps[n].data16[((height - 1) - i) * width + t] = newpix;
else
GameBitmaps[n].data16[i * width + t] = newpix;
}
}
}
} else if (image_type == OUTRAGE_4444_COMPRESSED_MIPPED || image_type == OUTRAGE_1555_COMPRESSED_MIPPED ||
image_type == OUTRAGE_NEW_COMPRESSED_MIPPED || image_type == OUTRAGE_COMPRESSED_MIPPED ||
image_type == OUTRAGE_COMPRESSED_OGF || image_type == OUTRAGE_COMPRESSED_OGF_8BIT) // COMPRESSED OGF
{
// read this ogf in all at once (much faster)
savepos = cftell(infile);
cfseek(infile, 0, SEEK_END);
int lastpos = cftell(infile);
int numleft = lastpos - savepos;
cfseek(infile, savepos, SEEK_SET);
Tga_file_data = (char *)mem_malloc(numleft);
ASSERT(Tga_file_data != NULL);
Fake_pos = 0;
Bad_tga = 0;
Fake_file_size = numleft;
cf_ReadBytes((uint8_t *)Tga_file_data, numleft, infile);
read_ok = bm_tga_read_outrage_compressed16(infile, n, num_mips, image_type);
}
else
Int3(); // Get Jason
if (image_type == OUTRAGE_COMPRESSED_OGF_8BIT)
Int3();
// bm_tga_read_outrage_compressed8 (infile,n);
if (Tga_file_data != NULL) {
mem_free(Tga_file_data);
Tga_file_data = NULL;
cfseek(infile, savepos + Fake_pos, SEEK_SET);
}
if (!read_ok)
return -1;
else
return (n);
}
// Pages in bitmap index n. Returns 1 if successful, 0 if not
int bm_page_in_file(int n) {
uint8_t image_id_len, color_map_type, image_type, pixsize, descriptor;
uint8_t upside_down = 0;
uint16_t width, height;
int i, data8bit = 0, savepos = 0;
int mipped = 0, file_mipped = 0;
int num_mips = 1;
char name[BITMAP_NAME_LEN];
CFILE *infile;
ASSERT((GameBitmaps[n].flags & BF_NOT_RESIDENT));
infile = (CFILE *)cfopen(GameBitmaps[n].name, "rb");
if (!infile) {
LOG_ERROR.printf("Couldn't page in bitmap %s!", GameBitmaps[n].name);
return 0;
}
// Used for progress bar when loading the level
paged_in_count += cfilelength(infile);
paged_in_num++;
image_id_len = cf_ReadByte(infile);
color_map_type = cf_ReadByte(infile);
image_type = cf_ReadByte(infile);
if (color_map_type != 0 ||
(image_type != 10 && image_type != 2 && image_type != OUTRAGE_TGA_TYPE && image_type != OUTRAGE_COMPRESSED_OGF &&
image_type != OUTRAGE_COMPRESSED_MIPPED && image_type != OUTRAGE_NEW_COMPRESSED_MIPPED &&
image_type != OUTRAGE_1555_COMPRESSED_MIPPED && image_type != OUTRAGE_4444_COMPRESSED_MIPPED)) {
LOG_ERROR << "bm_tga: Can't read this type of TGA.";
return -1;
}
if (image_type == OUTRAGE_COMPRESSED_OGF_8BIT)
data8bit = 1;
if (image_type == OUTRAGE_4444_COMPRESSED_MIPPED || image_type == OUTRAGE_1555_COMPRESSED_MIPPED ||
image_type == OUTRAGE_NEW_COMPRESSED_MIPPED || image_type == OUTRAGE_TGA_TYPE ||
image_type == OUTRAGE_COMPRESSED_MIPPED || image_type == OUTRAGE_COMPRESSED_OGF ||
image_type == OUTRAGE_COMPRESSED_OGF_8BIT) {
if (image_type == OUTRAGE_4444_COMPRESSED_MIPPED || image_type == OUTRAGE_1555_COMPRESSED_MIPPED ||
image_type == OUTRAGE_NEW_COMPRESSED_MIPPED) {
cf_ReadString(name, BITMAP_NAME_LEN - 1, infile);
} else {
for (i = 0; i < BITMAP_NAME_LEN; i++)
name[i] = cf_ReadByte(infile);
}
if (image_type == OUTRAGE_4444_COMPRESSED_MIPPED || image_type == OUTRAGE_1555_COMPRESSED_MIPPED ||
image_type == OUTRAGE_COMPRESSED_MIPPED || image_type == OUTRAGE_NEW_COMPRESSED_MIPPED)
num_mips = cf_ReadByte(infile);
else
num_mips = 1;
if (num_mips > 1)
file_mipped = 1;
}
for (i = 0; i < 9; i++) // ingore next 9 bytes
cf_ReadByte(infile);
width = cf_ReadShort(infile);
height = cf_ReadShort(infile);
pixsize = cf_ReadByte(infile);
if (pixsize != 32 && pixsize != 24) {
LOG_ERROR.printf("bm_tga: This file has a pixsize of field of %d, it should be 32. ", pixsize);
return 0;
}
descriptor = cf_ReadByte(infile);
if (((descriptor & 0x0F) != 8) && ((descriptor & 0x0F) != 0)) {
LOG_ERROR.printf("bm_tga: Descriptor field & 0x0F must be 8 or 0, but this is %d.", descriptor & 0x0F);
return 0;
}
for (i = 0; i < image_id_len; i++)
cf_ReadByte(infile);
if ((GameBitmaps[n].flags & BF_WANTS_MIP) || file_mipped)
mipped = 1;
int size = (width * height * 2) + (mipped * ((width * height * 2) / 3)) + 2;
GameBitmaps[n].data16 = (uint16_t *)mem_malloc(size);
if (!GameBitmaps[n].data16) {
LOG_ERROR << "Out of memory in bm_page_in_file!";
return 0;
}
Bitmap_memory_used += size;
if ((GameBitmaps[n].flags & BF_WANTS_4444) || image_type == OUTRAGE_4444_COMPRESSED_MIPPED)
GameBitmaps[n].format = BITMAP_FORMAT_4444;
else
GameBitmaps[n].format = BITMAP_FORMAT_STANDARD;
GameBitmaps[n].width = width;
GameBitmaps[n].height = height;
GameBitmaps[n].flags &= ~BF_NOT_RESIDENT;
// Copy the name
// if ((stricmp(GameBitmaps[n].name,name)))
// Int3(); //Get Jason!
strcpy(GameBitmaps[n].name, name);
LOG_DEBUG.printf("Paging in bitmap %s!", GameBitmaps[n].name);
if (file_mipped)
GameBitmaps[n].flags |= BF_MIPMAPPED;
upside_down = (descriptor & 0x20) >> 5;
upside_down = 1 - upside_down;
// Load the actual bitmap data in, converting it from 32 bit to 16 bit, and replacing
// that pesky transparency color without our replacement
if (image_type == OUTRAGE_4444_COMPRESSED_MIPPED || image_type == OUTRAGE_1555_COMPRESSED_MIPPED ||
image_type == OUTRAGE_NEW_COMPRESSED_MIPPED || image_type == OUTRAGE_COMPRESSED_MIPPED ||
image_type == OUTRAGE_COMPRESSED_OGF || image_type == OUTRAGE_COMPRESSED_OGF_8BIT) // COMPRESSED OGF
{
// read this ogf in all at once (much faster)
savepos = cftell(infile);
cfseek(infile, 0, SEEK_END);
int lastpos = cftell(infile);
int numleft = lastpos - savepos;
cfseek(infile, savepos, SEEK_SET);
Tga_file_data = (char *)mem_malloc(numleft);
ASSERT(Tga_file_data != NULL);
Fake_pos = 0;
Bad_tga = 0;
Fake_file_size = numleft;
cf_ReadBytes((uint8_t *)Tga_file_data, numleft, infile);
bm_tga_read_outrage_compressed16(infile, n, num_mips, image_type);
}
else
Int3(); // Get Jason
if (image_type == OUTRAGE_COMPRESSED_OGF_8BIT)
Int3();
// bm_tga_read_outrage_compressed8 (infile,n);
if (Tga_file_data != NULL) {
mem_free(Tga_file_data);
Tga_file_data = NULL;
cfseek(infile, savepos + Fake_pos, SEEK_SET);
}
cfclose(infile);
if ((GameBitmaps[n].flags & BF_WANTS_MIP) && !file_mipped) {
bm_GenerateMipMaps(n);
}
return 1;
}