mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
1386 lines
31 KiB
C++
1386 lines
31 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
IceTech GPL Source Code
|
|
Copyright (C) 2026 Justin Marshall
|
|
|
|
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
|
|
|
|
IceTech Source Code 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.
|
|
|
|
IceTech Source Code 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 IceTech Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the IceTech Source Code is also subject to certain additional terms.
|
|
You should have received a copy of these additional terms immediately following
|
|
the terms and conditions of the GNU General Public License which accompanied the
|
|
IceTech Source Code. If not, please request a copy in writing from id Software
|
|
at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms,
|
|
you may contact in writing Justin Marshall, justinmarshall20@gmail.com
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "tr_local.h"
|
|
#include <vector>
|
|
|
|
/*
|
|
|
|
This file only has a single entry point:
|
|
|
|
void R_LoadImage( const char *name, byte **pic, int *width, int *height, bool makePowerOf2 );
|
|
|
|
*/
|
|
|
|
/*
|
|
=========================================================
|
|
|
|
JPEG / PNG LOADING
|
|
|
|
Uses stb_image.h instead of jpeg-6/jpeglib.h.
|
|
|
|
Put stb_image.h somewhere your renderer project can include it.
|
|
|
|
IMPORTANT:
|
|
Only define STB_IMAGE_IMPLEMENTATION in one cpp file.
|
|
This file is that cpp file.
|
|
|
|
=========================================================
|
|
*/
|
|
|
|
#define STBI_ONLY_JPEG
|
|
#define STBI_ONLY_PNG
|
|
#define STBI_NO_STDIO
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#ifndef INT_MIN
|
|
#define INT_MIN (-2147483647 - 1)
|
|
#endif
|
|
|
|
#ifndef INT_MAX
|
|
#define INT_MAX 2147483647
|
|
#endif
|
|
#include "stb_image.h"
|
|
|
|
/*
|
|
================
|
|
R_WriteTGA
|
|
================
|
|
*/
|
|
void R_WriteTGA(const char* filename, const byte* data, int width, int height, bool flipVertical) {
|
|
byte* buffer;
|
|
int i;
|
|
int bufferSize = width * height * 4 + 18;
|
|
int imgStart = 18;
|
|
|
|
buffer = (byte*)Mem_Alloc(bufferSize);
|
|
memset(buffer, 0, 18);
|
|
|
|
buffer[2] = 2; // uncompressed type
|
|
buffer[12] = width & 255;
|
|
buffer[13] = width >> 8;
|
|
buffer[14] = height & 255;
|
|
buffer[15] = height >> 8;
|
|
buffer[16] = 32; // pixel size
|
|
|
|
if (!flipVertical) {
|
|
buffer[17] = (1 << 5); // flip bit, for normal top to bottom raster order
|
|
}
|
|
|
|
// swap rgb to bgr
|
|
for (i = imgStart; i < bufferSize; i += 4) {
|
|
buffer[i] = data[i - imgStart + 2]; // blue
|
|
buffer[i + 1] = data[i - imgStart + 1]; // green
|
|
buffer[i + 2] = data[i - imgStart + 0]; // red
|
|
buffer[i + 3] = data[i - imgStart + 3]; // alpha
|
|
}
|
|
|
|
fileSystem->WriteFile(filename, buffer, bufferSize);
|
|
|
|
Mem_Free(buffer);
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_WritePalTGA
|
|
================
|
|
*/
|
|
void R_WritePalTGA(const char* filename, const byte* data, const byte* palette, int width, int height, bool flipVertical) {
|
|
byte* buffer;
|
|
int i;
|
|
int bufferSize = (width * height) + (256 * 3) + 18;
|
|
int palStart = 18;
|
|
int imgStart = 18 + (256 * 3);
|
|
|
|
buffer = (byte*)Mem_Alloc(bufferSize);
|
|
memset(buffer, 0, 18);
|
|
|
|
buffer[1] = 1; // color map type
|
|
buffer[2] = 1; // uncompressed color mapped image
|
|
buffer[5] = 0; // number of palette entries lo
|
|
buffer[6] = 1; // number of palette entries hi
|
|
buffer[7] = 24; // color map bpp
|
|
buffer[12] = width & 255;
|
|
buffer[13] = width >> 8;
|
|
buffer[14] = height & 255;
|
|
buffer[15] = height >> 8;
|
|
buffer[16] = 8; // pixel size
|
|
|
|
if (!flipVertical) {
|
|
buffer[17] = (1 << 5); // flip bit, for normal top to bottom raster order
|
|
}
|
|
|
|
// store palette, swapping rgb to bgr
|
|
for (i = palStart; i < imgStart; i += 3) {
|
|
buffer[i] = palette[i - palStart + 2]; // blue
|
|
buffer[i + 1] = palette[i - palStart + 1]; // green
|
|
buffer[i + 2] = palette[i - palStart + 0]; // red
|
|
}
|
|
|
|
// store image data
|
|
for (i = imgStart; i < bufferSize; i++) {
|
|
buffer[i] = data[i - imgStart];
|
|
}
|
|
|
|
fileSystem->WriteFile(filename, buffer, bufferSize);
|
|
|
|
Mem_Free(buffer);
|
|
}
|
|
|
|
static void LoadBMP(const char* name, byte** pic, int* width, int* height, ID_TIME_T* timestamp);
|
|
static void LoadTGA(const char* name, byte** pic, int* width, int* height, ID_TIME_T* timestamp);
|
|
static void LoadJPG(const char* name, byte** pic, int* width, int* height, ID_TIME_T* timestamp);
|
|
static void LoadPNG(const char* name, byte** pic, int* width, int* height, ID_TIME_T* timestamp);
|
|
static void LoadSTBImage(const char* name, byte** pic, int* width, int* height, ID_TIME_T* timestamp);
|
|
|
|
/*
|
|
========================================================================
|
|
|
|
PCX files are used for 8 bit images
|
|
|
|
========================================================================
|
|
*/
|
|
|
|
typedef struct {
|
|
char manufacturer;
|
|
char version;
|
|
char encoding;
|
|
char bits_per_pixel;
|
|
unsigned short xmin, ymin, xmax, ymax;
|
|
unsigned short hres, vres;
|
|
unsigned char palette[48];
|
|
char reserved;
|
|
char color_planes;
|
|
unsigned short bytes_per_line;
|
|
unsigned short palette_type;
|
|
char filler[58];
|
|
unsigned char data; // unbounded
|
|
} pcx_t;
|
|
|
|
/*
|
|
========================================================================
|
|
|
|
TGA files are used for 24/32 bit images
|
|
|
|
========================================================================
|
|
*/
|
|
|
|
typedef struct _TargaHeader {
|
|
unsigned char id_length;
|
|
unsigned char colormap_type;
|
|
unsigned char image_type;
|
|
unsigned short colormap_index;
|
|
unsigned short colormap_length;
|
|
unsigned char colormap_size;
|
|
unsigned short x_origin;
|
|
unsigned short y_origin;
|
|
unsigned short width;
|
|
unsigned short height;
|
|
unsigned char pixel_size;
|
|
unsigned char attributes;
|
|
} TargaHeader;
|
|
|
|
/*
|
|
=========================================================
|
|
|
|
BMP LOADING
|
|
|
|
=========================================================
|
|
*/
|
|
|
|
typedef struct {
|
|
char id[2];
|
|
unsigned long fileSize;
|
|
unsigned long reserved0;
|
|
unsigned long bitmapDataOffset;
|
|
unsigned long bitmapHeaderSize;
|
|
unsigned long width;
|
|
unsigned long height;
|
|
unsigned short planes;
|
|
unsigned short bitsPerPixel;
|
|
unsigned long compression;
|
|
unsigned long bitmapDataSize;
|
|
unsigned long hRes;
|
|
unsigned long vRes;
|
|
unsigned long colors;
|
|
unsigned long importantColors;
|
|
unsigned char palette[256][4];
|
|
} BMPHeader_t;
|
|
|
|
/*
|
|
==============
|
|
LoadBMP
|
|
==============
|
|
*/
|
|
static void LoadBMP(const char* name, byte** pic, int* width, int* height, ID_TIME_T* timestamp) {
|
|
int columns, rows, numPixels;
|
|
byte* pixbuf;
|
|
int row, column;
|
|
byte* buf_p;
|
|
byte* buffer;
|
|
int length;
|
|
BMPHeader_t bmpHeader;
|
|
byte* bmpRGBA;
|
|
|
|
if (!pic) {
|
|
fileSystem->ReadFile(name, NULL, timestamp);
|
|
return;
|
|
}
|
|
|
|
*pic = NULL;
|
|
|
|
length = fileSystem->ReadFile(name, (void**)&buffer, timestamp);
|
|
if (!buffer) {
|
|
return;
|
|
}
|
|
|
|
buf_p = buffer;
|
|
|
|
bmpHeader.id[0] = *buf_p++;
|
|
bmpHeader.id[1] = *buf_p++;
|
|
bmpHeader.fileSize = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
bmpHeader.reserved0 = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
bmpHeader.bitmapDataOffset = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
bmpHeader.bitmapHeaderSize = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
bmpHeader.width = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
bmpHeader.height = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
bmpHeader.planes = LittleShort(*(short*)buf_p);
|
|
buf_p += 2;
|
|
bmpHeader.bitsPerPixel = LittleShort(*(short*)buf_p);
|
|
buf_p += 2;
|
|
bmpHeader.compression = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
bmpHeader.bitmapDataSize = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
bmpHeader.hRes = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
bmpHeader.vRes = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
bmpHeader.colors = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
bmpHeader.importantColors = LittleLong(*(long*)buf_p);
|
|
buf_p += 4;
|
|
|
|
memcpy(bmpHeader.palette, buf_p, sizeof(bmpHeader.palette));
|
|
|
|
if (bmpHeader.bitsPerPixel == 8) {
|
|
buf_p += 1024;
|
|
}
|
|
|
|
if (bmpHeader.id[0] != 'B' || bmpHeader.id[1] != 'M') {
|
|
common->Error("LoadBMP: only Windows-style BMP files supported (%s)\n", name);
|
|
}
|
|
|
|
if (bmpHeader.fileSize != (unsigned long)length) {
|
|
common->Error("LoadBMP: header size does not match file size (%lu vs. %d) (%s)\n", bmpHeader.fileSize, length, name);
|
|
}
|
|
|
|
if (bmpHeader.compression != 0) {
|
|
common->Error("LoadBMP: only uncompressed BMP files supported (%s)\n", name);
|
|
}
|
|
|
|
if (bmpHeader.bitsPerPixel < 8) {
|
|
common->Error("LoadBMP: monochrome and 4-bit BMP files not supported (%s)\n", name);
|
|
}
|
|
|
|
columns = bmpHeader.width;
|
|
rows = bmpHeader.height;
|
|
|
|
if (rows < 0) {
|
|
rows = -rows;
|
|
}
|
|
|
|
numPixels = columns * rows;
|
|
|
|
if (width) {
|
|
*width = columns;
|
|
}
|
|
|
|
if (height) {
|
|
*height = rows;
|
|
}
|
|
|
|
bmpRGBA = (byte*)R_StaticAlloc(numPixels * 4);
|
|
*pic = bmpRGBA;
|
|
|
|
for (row = rows - 1; row >= 0; row--) {
|
|
pixbuf = bmpRGBA + row * columns * 4;
|
|
|
|
for (column = 0; column < columns; column++) {
|
|
unsigned char red, green, blue, alpha;
|
|
int palIndex;
|
|
unsigned short shortPixel;
|
|
|
|
switch (bmpHeader.bitsPerPixel) {
|
|
case 8:
|
|
palIndex = *buf_p++;
|
|
*pixbuf++ = bmpHeader.palette[palIndex][2];
|
|
*pixbuf++ = bmpHeader.palette[palIndex][1];
|
|
*pixbuf++ = bmpHeader.palette[palIndex][0];
|
|
*pixbuf++ = 0xff;
|
|
break;
|
|
|
|
case 16:
|
|
shortPixel = *(unsigned short*)buf_p;
|
|
buf_p += 2;
|
|
*pixbuf++ = (shortPixel & (31 << 10)) >> 7;
|
|
*pixbuf++ = (shortPixel & (31 << 5)) >> 2;
|
|
*pixbuf++ = (shortPixel & 31) << 3;
|
|
*pixbuf++ = 0xff;
|
|
break;
|
|
|
|
case 24:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
|
|
case 32:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alpha = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alpha;
|
|
break;
|
|
|
|
default:
|
|
common->Error("LoadBMP: illegal pixel_size '%d' in file '%s'\n", bmpHeader.bitsPerPixel, name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
fileSystem->FreeFile(buffer);
|
|
}
|
|
|
|
/*
|
|
=================================================================
|
|
|
|
PCX LOADING
|
|
|
|
=================================================================
|
|
*/
|
|
|
|
/*
|
|
==============
|
|
LoadPCX
|
|
==============
|
|
*/
|
|
static void LoadPCX(const char* filename, byte** pic, byte** palette, int* width, int* height, ID_TIME_T* timestamp) {
|
|
byte* raw;
|
|
pcx_t* pcx;
|
|
int x, y;
|
|
int len;
|
|
int dataByte, runLength;
|
|
byte* out, * pix;
|
|
int xmax, ymax;
|
|
|
|
if (!pic) {
|
|
fileSystem->ReadFile(filename, NULL, timestamp);
|
|
return;
|
|
}
|
|
|
|
*pic = NULL;
|
|
*palette = NULL;
|
|
|
|
len = fileSystem->ReadFile(filename, (void**)&raw, timestamp);
|
|
if (!raw) {
|
|
return;
|
|
}
|
|
|
|
pcx = (pcx_t*)raw;
|
|
raw = &pcx->data;
|
|
|
|
xmax = LittleShort(pcx->xmax);
|
|
ymax = LittleShort(pcx->ymax);
|
|
|
|
if (pcx->manufacturer != 0x0a ||
|
|
pcx->version != 5 ||
|
|
pcx->encoding != 1 ||
|
|
pcx->bits_per_pixel != 8 ||
|
|
xmax >= 1024 ||
|
|
ymax >= 1024) {
|
|
common->Printf("Bad pcx file %s (%i x %i) (%i x %i)\n", filename, xmax + 1, ymax + 1, pcx->xmax, pcx->ymax);
|
|
fileSystem->FreeFile(pcx);
|
|
return;
|
|
}
|
|
|
|
out = (byte*)R_StaticAlloc((ymax + 1) * (xmax + 1));
|
|
*pic = out;
|
|
pix = out;
|
|
|
|
if (palette) {
|
|
*palette = (byte*)R_StaticAlloc(768);
|
|
memcpy(*palette, (byte*)pcx + len - 768, 768);
|
|
}
|
|
|
|
if (width) {
|
|
*width = xmax + 1;
|
|
}
|
|
|
|
if (height) {
|
|
*height = ymax + 1;
|
|
}
|
|
|
|
for (y = 0; y <= ymax; y++, pix += xmax + 1) {
|
|
for (x = 0; x <= xmax; ) {
|
|
dataByte = *raw++;
|
|
|
|
if ((dataByte & 0xC0) == 0xC0) {
|
|
runLength = dataByte & 0x3F;
|
|
dataByte = *raw++;
|
|
}
|
|
else {
|
|
runLength = 1;
|
|
}
|
|
|
|
while (runLength-- > 0) {
|
|
pix[x++] = dataByte;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (raw - (byte*)pcx > len) {
|
|
common->Printf("PCX file %s was malformed", filename);
|
|
R_StaticFree(*pic);
|
|
*pic = NULL;
|
|
}
|
|
|
|
fileSystem->FreeFile(pcx);
|
|
}
|
|
|
|
/*
|
|
==============
|
|
LoadPCX32
|
|
==============
|
|
*/
|
|
static void LoadPCX32(const char* filename, byte** pic, int* width, int* height, ID_TIME_T* timestamp) {
|
|
byte* palette;
|
|
byte* pic8;
|
|
int i, c, p;
|
|
byte* pic32;
|
|
|
|
if (!pic) {
|
|
fileSystem->ReadFile(filename, NULL, timestamp);
|
|
return;
|
|
}
|
|
|
|
LoadPCX(filename, &pic8, &palette, width, height, timestamp);
|
|
|
|
if (!pic8) {
|
|
*pic = NULL;
|
|
return;
|
|
}
|
|
|
|
c = (*width) * (*height);
|
|
pic32 = *pic = (byte*)R_StaticAlloc(4 * c);
|
|
|
|
for (i = 0; i < c; i++) {
|
|
p = pic8[i];
|
|
pic32[0] = palette[p * 3];
|
|
pic32[1] = palette[p * 3 + 1];
|
|
pic32[2] = palette[p * 3 + 2];
|
|
pic32[3] = 255;
|
|
pic32 += 4;
|
|
}
|
|
|
|
R_StaticFree(pic8);
|
|
R_StaticFree(palette);
|
|
}
|
|
|
|
/*
|
|
=========================================================
|
|
|
|
TARGA LOADING
|
|
|
|
=========================================================
|
|
*/
|
|
|
|
/*
|
|
=============
|
|
LoadTGA
|
|
=============
|
|
*/
|
|
static void LoadTGA(const char* name, byte** pic, int* width, int* height, ID_TIME_T* timestamp) {
|
|
int columns, rows, numPixels, fileSize, numBytes;
|
|
byte* pixbuf;
|
|
int row, column;
|
|
byte* buf_p;
|
|
byte* buffer;
|
|
TargaHeader targa_header;
|
|
byte* targa_rgba;
|
|
|
|
if (!pic) {
|
|
fileSystem->ReadFile(name, NULL, timestamp);
|
|
return;
|
|
}
|
|
|
|
*pic = NULL;
|
|
|
|
fileSize = fileSystem->ReadFile(name, (void**)&buffer, timestamp);
|
|
if (!buffer) {
|
|
return;
|
|
}
|
|
|
|
buf_p = buffer;
|
|
|
|
targa_header.id_length = *buf_p++;
|
|
targa_header.colormap_type = *buf_p++;
|
|
targa_header.image_type = *buf_p++;
|
|
|
|
targa_header.colormap_index = LittleShort(*(short*)buf_p);
|
|
buf_p += 2;
|
|
targa_header.colormap_length = LittleShort(*(short*)buf_p);
|
|
buf_p += 2;
|
|
targa_header.colormap_size = *buf_p++;
|
|
targa_header.x_origin = LittleShort(*(short*)buf_p);
|
|
buf_p += 2;
|
|
targa_header.y_origin = LittleShort(*(short*)buf_p);
|
|
buf_p += 2;
|
|
targa_header.width = LittleShort(*(short*)buf_p);
|
|
buf_p += 2;
|
|
targa_header.height = LittleShort(*(short*)buf_p);
|
|
buf_p += 2;
|
|
targa_header.pixel_size = *buf_p++;
|
|
targa_header.attributes = *buf_p++;
|
|
|
|
if (targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3) {
|
|
common->Error("LoadTGA( %s ): Only type 2 RGB, 3 gray, and 10 RLE RGB TGA images supported\n", name);
|
|
}
|
|
|
|
if (targa_header.colormap_type != 0) {
|
|
common->Error("LoadTGA( %s ): colormaps not supported\n", name);
|
|
}
|
|
|
|
if ((targa_header.pixel_size != 32 && targa_header.pixel_size != 24) && targa_header.image_type != 3) {
|
|
common->Error("LoadTGA( %s ): Only 32 or 24 bit images supported, no colormaps\n", name);
|
|
}
|
|
|
|
if (targa_header.image_type == 2 || targa_header.image_type == 3) {
|
|
numBytes = targa_header.width * targa_header.height * (targa_header.pixel_size >> 3);
|
|
if (numBytes > fileSize - 18 - targa_header.id_length) {
|
|
common->Error("LoadTGA( %s ): incomplete file\n", name);
|
|
}
|
|
}
|
|
|
|
columns = targa_header.width;
|
|
rows = targa_header.height;
|
|
numPixels = columns * rows;
|
|
|
|
if (width) {
|
|
*width = columns;
|
|
}
|
|
|
|
if (height) {
|
|
*height = rows;
|
|
}
|
|
|
|
targa_rgba = (byte*)R_StaticAlloc(numPixels * 4);
|
|
*pic = targa_rgba;
|
|
|
|
if (targa_header.id_length != 0) {
|
|
buf_p += targa_header.id_length;
|
|
}
|
|
|
|
if (targa_header.image_type == 2 || targa_header.image_type == 3) {
|
|
for (row = rows - 1; row >= 0; row--) {
|
|
pixbuf = targa_rgba + row * columns * 4;
|
|
|
|
for (column = 0; column < columns; column++) {
|
|
unsigned char red, green, blue, alphabyte;
|
|
|
|
switch (targa_header.pixel_size) {
|
|
case 8:
|
|
blue = *buf_p++;
|
|
green = blue;
|
|
red = blue;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
|
|
case 24:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
|
|
case 32:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alphabyte;
|
|
break;
|
|
|
|
default:
|
|
common->Error("LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (targa_header.image_type == 10) {
|
|
unsigned char red, green, blue, alphabyte, packetHeader, packetSize, j;
|
|
|
|
red = 0;
|
|
green = 0;
|
|
blue = 0;
|
|
alphabyte = 0xff;
|
|
|
|
for (row = rows - 1; row >= 0; row--) {
|
|
pixbuf = targa_rgba + row * columns * 4;
|
|
|
|
for (column = 0; column < columns; ) {
|
|
packetHeader = *buf_p++;
|
|
packetSize = 1 + (packetHeader & 0x7f);
|
|
|
|
if (packetHeader & 0x80) {
|
|
switch (targa_header.pixel_size) {
|
|
case 24:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = 255;
|
|
break;
|
|
|
|
case 32:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = *buf_p++;
|
|
break;
|
|
|
|
default:
|
|
common->Error("LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size);
|
|
break;
|
|
}
|
|
|
|
for (j = 0; j < packetSize; j++) {
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alphabyte;
|
|
|
|
column++;
|
|
|
|
if (column == columns) {
|
|
column = 0;
|
|
|
|
if (row > 0) {
|
|
row--;
|
|
}
|
|
else {
|
|
goto breakOut;
|
|
}
|
|
|
|
pixbuf = targa_rgba + row * columns * 4;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (j = 0; j < packetSize; j++) {
|
|
switch (targa_header.pixel_size) {
|
|
case 24:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
|
|
case 32:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alphabyte;
|
|
break;
|
|
|
|
default:
|
|
common->Error("LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size);
|
|
break;
|
|
}
|
|
|
|
column++;
|
|
|
|
if (column == columns) {
|
|
column = 0;
|
|
|
|
if (row > 0) {
|
|
row--;
|
|
}
|
|
else {
|
|
goto breakOut;
|
|
}
|
|
|
|
pixbuf = targa_rgba + row * columns * 4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
breakOut:
|
|
;
|
|
}
|
|
}
|
|
|
|
if (targa_header.attributes & (1 << 5)) {
|
|
R_VerticalFlip(*pic, *width, *height);
|
|
}
|
|
|
|
fileSystem->FreeFile(buffer);
|
|
}
|
|
|
|
/*
|
|
=========================================================
|
|
|
|
STB JPG / PNG LOADING
|
|
|
|
=========================================================
|
|
*/
|
|
|
|
/*
|
|
=============
|
|
LoadSTBImage
|
|
|
|
Loads JPEG or PNG from Doom/id fileSystem memory into canonical RGBA8.
|
|
=============
|
|
*/
|
|
static void LoadSTBImage(const char* filename, byte** pic, int* width, int* height, ID_TIME_T* timestamp) {
|
|
byte* fileBuffer;
|
|
int fileLength;
|
|
int imageWidth;
|
|
int imageHeight;
|
|
int sourceComponents;
|
|
stbi_uc* rgba;
|
|
byte* out;
|
|
int imageSize;
|
|
|
|
if (!pic) {
|
|
fileSystem->ReadFile(filename, NULL, timestamp);
|
|
return;
|
|
}
|
|
|
|
*pic = NULL;
|
|
|
|
if (width) {
|
|
*width = 0;
|
|
}
|
|
|
|
if (height) {
|
|
*height = 0;
|
|
}
|
|
|
|
fileLength = fileSystem->ReadFile(filename, (void**)&fileBuffer, timestamp);
|
|
if (!fileBuffer || fileLength <= 0) {
|
|
return;
|
|
}
|
|
|
|
imageWidth = 0;
|
|
imageHeight = 0;
|
|
sourceComponents = 0;
|
|
|
|
rgba = stbi_load_from_memory(
|
|
(const stbi_uc*)fileBuffer,
|
|
fileLength,
|
|
&imageWidth,
|
|
&imageHeight,
|
|
&sourceComponents,
|
|
STBI_rgb_alpha
|
|
);
|
|
|
|
fileSystem->FreeFile(fileBuffer);
|
|
|
|
if (!rgba) {
|
|
common->DWarning("LoadSTBImage: failed to load image '%s': %s", filename, stbi_failure_reason());
|
|
return;
|
|
}
|
|
|
|
if (imageWidth <= 0 || imageHeight <= 0) {
|
|
stbi_image_free(rgba);
|
|
common->DWarning("LoadSTBImage: bad image size '%s' (%d x %d)", filename, imageWidth, imageHeight);
|
|
return;
|
|
}
|
|
|
|
imageSize = imageWidth * imageHeight * 4;
|
|
|
|
out = (byte*)R_StaticAlloc(imageSize);
|
|
memcpy(out, rgba, imageSize);
|
|
|
|
stbi_image_free(rgba);
|
|
|
|
*pic = out;
|
|
|
|
if (width) {
|
|
*width = imageWidth;
|
|
}
|
|
|
|
if (height) {
|
|
*height = imageHeight;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
LoadJPG
|
|
=============
|
|
*/
|
|
static void LoadJPG(const char* filename, byte** pic, int* width, int* height, ID_TIME_T* timestamp) {
|
|
LoadSTBImage(filename, pic, width, height, timestamp);
|
|
}
|
|
|
|
/*
|
|
=============
|
|
LoadPNG
|
|
=============
|
|
*/
|
|
static void LoadPNG(const char* filename, byte** pic, int* width, int* height, ID_TIME_T* timestamp) {
|
|
LoadSTBImage(filename, pic, width, height, timestamp);
|
|
}
|
|
|
|
/*
|
|
=============
|
|
LoadIES
|
|
|
|
Converts an LM-63 IES photometric profile into a lat-long RGBA8 lookup:
|
|
X = horizontal angle (0..360), Y = vertical angle (0..180).
|
|
=============
|
|
*/
|
|
static bool IES_ReadFloat(const char*& cursor, const char* end, float& out) {
|
|
while (cursor < end) {
|
|
const char c = *cursor;
|
|
if ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.') {
|
|
break;
|
|
}
|
|
cursor++;
|
|
}
|
|
if (cursor >= end) {
|
|
return false;
|
|
}
|
|
|
|
char* parseEnd = NULL;
|
|
out = (float)strtod(cursor, &parseEnd);
|
|
if (parseEnd == cursor) {
|
|
return false;
|
|
}
|
|
cursor = parseEnd;
|
|
return true;
|
|
}
|
|
|
|
static const char* IES_FindTiltLine(const char* begin, const char* end) {
|
|
static const char marker[] = "TILT=";
|
|
const int markerLen = sizeof(marker) - 1;
|
|
for (const char* p = begin; p + markerLen <= end; p++) {
|
|
bool match = true;
|
|
for (int i = 0; i < markerLen; i++) {
|
|
char a = p[i];
|
|
if (a >= 'a' && a <= 'z') {
|
|
a = a - 'a' + 'A';
|
|
}
|
|
if (a != marker[i]) {
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
if (match) {
|
|
return p;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static float IES_SampleArray(const std::vector<float>& values, int count, float angle) {
|
|
if (count <= 0 || values.empty()) {
|
|
return 0.0f;
|
|
}
|
|
if (count == 1) {
|
|
return values[0];
|
|
}
|
|
|
|
if (angle <= values[0]) {
|
|
return 0.0f;
|
|
}
|
|
if (angle >= values[count - 1]) {
|
|
return (angle == values[count - 1]) ? (float)(count - 1) : 0.0f;
|
|
}
|
|
|
|
for (int i = 0; i < count - 1; i++) {
|
|
const float a0 = values[i];
|
|
const float a1 = values[i + 1];
|
|
if (angle >= a0 && angle <= a1) {
|
|
const float denom = a1 - a0;
|
|
const float f = denom > 1e-6f ? (angle - a0) / denom : 0.0f;
|
|
return (float)i + f;
|
|
}
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
static float IES_CandelaAt(
|
|
const std::vector<float>& verticalAngles,
|
|
const std::vector<float>& horizontalAngles,
|
|
const std::vector<float>& candela,
|
|
int numVertical,
|
|
int numHorizontal,
|
|
float vertical,
|
|
float horizontal) {
|
|
if (numVertical <= 0 || numHorizontal <= 0 || candela.empty()) {
|
|
return 0.0f;
|
|
}
|
|
|
|
horizontal = fmodf(horizontal, 360.0f);
|
|
if (horizontal < 0.0f) {
|
|
horizontal += 360.0f;
|
|
}
|
|
|
|
const float vf = IES_SampleArray(verticalAngles, numVertical, vertical);
|
|
const int v0 = idMath::ClampInt(0, numVertical - 1, (int)floorf(vf));
|
|
const int v1 = idMath::ClampInt(0, numVertical - 1, v0 + 1);
|
|
const float vt = idMath::ClampFloat(0.0f, 1.0f, vf - (float)v0);
|
|
|
|
float hf = 0.0f;
|
|
if (numHorizontal > 1) {
|
|
hf = IES_SampleArray(horizontalAngles, numHorizontal, horizontal);
|
|
}
|
|
const int h0 = idMath::ClampInt(0, numHorizontal - 1, (int)floorf(hf));
|
|
const int h1 = idMath::ClampInt(0, numHorizontal - 1, h0 + 1);
|
|
const float ht = idMath::ClampFloat(0.0f, 1.0f, hf - (float)h0);
|
|
|
|
const float c00 = candela[h0 * numVertical + v0];
|
|
const float c10 = candela[h1 * numVertical + v0];
|
|
const float c01 = candela[h0 * numVertical + v1];
|
|
const float c11 = candela[h1 * numVertical + v1];
|
|
const float c0 = c00 + (c10 - c00) * ht;
|
|
const float c1 = c01 + (c11 - c01) * ht;
|
|
return c0 + (c1 - c0) * vt;
|
|
}
|
|
|
|
static void LoadIES(const char* filename, byte** pic, int* width, int* height, ID_TIME_T* timestamp) {
|
|
byte* fileBuffer = NULL;
|
|
const int fileLength = fileSystem->ReadFile(filename, (void**)&fileBuffer, timestamp);
|
|
|
|
if (pic) {
|
|
*pic = NULL;
|
|
}
|
|
if (width) {
|
|
*width = 0;
|
|
}
|
|
if (height) {
|
|
*height = 0;
|
|
}
|
|
if (!pic || !fileBuffer || fileLength <= 0) {
|
|
if (fileBuffer) {
|
|
fileSystem->FreeFile(fileBuffer);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const char* begin = (const char*)fileBuffer;
|
|
const char* end = begin + fileLength;
|
|
const char* numeric = IES_FindTiltLine(begin, end);
|
|
if (!numeric || numeric >= end) {
|
|
fileSystem->FreeFile(fileBuffer);
|
|
return;
|
|
}
|
|
while (numeric < end && *numeric != '\n' && *numeric != '\r') {
|
|
numeric++;
|
|
}
|
|
|
|
float header[13];
|
|
for (int i = 0; i < 13; i++) {
|
|
if (!IES_ReadFloat(numeric, end, header[i])) {
|
|
fileSystem->FreeFile(fileBuffer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const int numVertical = idMath::ClampInt(1, 1024, (int)header[3]);
|
|
const int numHorizontal = idMath::ClampInt(1, 1024, (int)header[4]);
|
|
const float candelaScale = header[2] > 0.0f ? header[2] : 1.0f;
|
|
|
|
std::vector<float> verticalAngles(numVertical);
|
|
std::vector<float> horizontalAngles(numHorizontal);
|
|
std::vector<float> candela(numVertical * numHorizontal);
|
|
|
|
for (int i = 0; i < numVertical; i++) {
|
|
if (!IES_ReadFloat(numeric, end, verticalAngles[i])) {
|
|
fileSystem->FreeFile(fileBuffer);
|
|
return;
|
|
}
|
|
}
|
|
for (int i = 0; i < numHorizontal; i++) {
|
|
if (!IES_ReadFloat(numeric, end, horizontalAngles[i])) {
|
|
fileSystem->FreeFile(fileBuffer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
float maxCandela = 0.0f;
|
|
for (int h = 0; h < numHorizontal; h++) {
|
|
for (int v = 0; v < numVertical; v++) {
|
|
float c = 0.0f;
|
|
if (!IES_ReadFloat(numeric, end, c)) {
|
|
fileSystem->FreeFile(fileBuffer);
|
|
return;
|
|
}
|
|
c *= candelaScale;
|
|
if (c < 0.0f) {
|
|
c = 0.0f;
|
|
}
|
|
candela[h * numVertical + v] = c;
|
|
if (c > maxCandela) {
|
|
maxCandela = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
fileSystem->FreeFile(fileBuffer);
|
|
|
|
if (maxCandela <= 0.0f) {
|
|
return;
|
|
}
|
|
|
|
const int outWidth = 512;
|
|
const int outHeight = 256;
|
|
byte* out = (byte*)R_StaticAlloc(outWidth * outHeight * 4);
|
|
|
|
for (int y = 0; y < outHeight; y++) {
|
|
const float vertical = ((float)y + 0.5f) * (180.0f / (float)outHeight);
|
|
for (int x = 0; x < outWidth; x++) {
|
|
const float horizontal = ((float)x + 0.5f) * (360.0f / (float)outWidth);
|
|
const float c = IES_CandelaAt(verticalAngles, horizontalAngles, candela, numVertical, numHorizontal, vertical, horizontal);
|
|
const byte v = (byte)idMath::ClampInt(0, 255, (int)(255.0f * idMath::ClampFloat(0.0f, 1.0f, c / maxCandela) + 0.5f));
|
|
byte* dst = out + ((y * outWidth + x) * 4);
|
|
dst[0] = v;
|
|
dst[1] = v;
|
|
dst[2] = v;
|
|
dst[3] = 255;
|
|
}
|
|
}
|
|
|
|
*pic = out;
|
|
if (width) {
|
|
*width = outWidth;
|
|
}
|
|
if (height) {
|
|
*height = outHeight;
|
|
}
|
|
}
|
|
|
|
//===================================================================
|
|
|
|
/*
|
|
=================
|
|
R_LoadImage
|
|
|
|
Loads any of the supported image types into a canonical
|
|
32 bit format.
|
|
|
|
Automatically attempts to load .jpg and .png files if .tga files fail to load.
|
|
|
|
*pic will be NULL if the load failed.
|
|
|
|
Anything that is going to make this into a texture would use
|
|
makePowerOf2 = true, but something loading an image as a lookup
|
|
table of some sort would leave it in identity form.
|
|
|
|
It is important to do this at image load time instead of texture load
|
|
time for bump maps.
|
|
|
|
Timestamp may be NULL if the value is going to be ignored.
|
|
|
|
If pic is NULL, the image won't actually be loaded, it will just find the
|
|
timestamp.
|
|
=================
|
|
*/
|
|
void R_LoadImage(const char* cname, byte** pic, int* width, int* height, ID_TIME_T* timestamp, bool makePowerOf2) {
|
|
idStr name = cname;
|
|
|
|
if (pic) {
|
|
*pic = NULL;
|
|
}
|
|
|
|
if (timestamp) {
|
|
*timestamp = 0xFFFFFFFF;
|
|
}
|
|
|
|
if (width) {
|
|
*width = 0;
|
|
}
|
|
|
|
if (height) {
|
|
*height = 0;
|
|
}
|
|
|
|
name.DefaultFileExtension(".tga");
|
|
|
|
if (name.Length() < 5) {
|
|
return;
|
|
}
|
|
|
|
name.ToLower();
|
|
|
|
idStr ext;
|
|
name.ExtractFileExtension(ext);
|
|
|
|
if (ext == "tga") {
|
|
LoadTGA(name.c_str(), pic, width, height, timestamp);
|
|
|
|
if ((pic && *pic == NULL) || (timestamp && *timestamp == FILE_NOT_FOUND_TIMESTAMP)) {
|
|
name.StripFileExtension();
|
|
name.DefaultFileExtension(".jpg");
|
|
LoadJPG(name.c_str(), pic, width, height, timestamp);
|
|
}
|
|
|
|
if ((pic && *pic == NULL) || (timestamp && *timestamp == FILE_NOT_FOUND_TIMESTAMP)) {
|
|
name.StripFileExtension();
|
|
name.DefaultFileExtension(".png");
|
|
LoadPNG(name.c_str(), pic, width, height, timestamp);
|
|
}
|
|
}
|
|
else if (ext == "pcx") {
|
|
LoadPCX32(name.c_str(), pic, width, height, timestamp);
|
|
}
|
|
else if (ext == "bmp") {
|
|
LoadBMP(name.c_str(), pic, width, height, timestamp);
|
|
}
|
|
else if (ext == "jpg" || ext == "jpeg") {
|
|
LoadJPG(name.c_str(), pic, width, height, timestamp);
|
|
}
|
|
else if (ext == "png") {
|
|
LoadPNG(name.c_str(), pic, width, height, timestamp);
|
|
}
|
|
else if (ext == "ies") {
|
|
LoadIES(name.c_str(), pic, width, height, timestamp);
|
|
}
|
|
|
|
if ((width && *width < 1) || (height && *height < 1)) {
|
|
if (pic && *pic) {
|
|
R_StaticFree(*pic);
|
|
*pic = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Convert to exact power of 2 sizes.
|
|
//
|
|
if (pic && *pic && makePowerOf2) {
|
|
int w, h;
|
|
int scaled_width, scaled_height;
|
|
byte* resampledBuffer;
|
|
|
|
w = *width;
|
|
h = *height;
|
|
|
|
for (scaled_width = 1; scaled_width < w; scaled_width <<= 1) {
|
|
}
|
|
|
|
for (scaled_height = 1; scaled_height < h; scaled_height <<= 1) {
|
|
}
|
|
|
|
if (scaled_width != w || scaled_height != h) {
|
|
if (globalImages->image_roundDown.GetBool() && scaled_width > w) {
|
|
scaled_width >>= 1;
|
|
}
|
|
|
|
if (globalImages->image_roundDown.GetBool() && scaled_height > h) {
|
|
scaled_height >>= 1;
|
|
}
|
|
|
|
resampledBuffer = R_ResampleTexture(*pic, w, h, scaled_width, scaled_height);
|
|
|
|
R_StaticFree(*pic);
|
|
|
|
*pic = resampledBuffer;
|
|
*width = scaled_width;
|
|
*height = scaled_height;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
R_LoadCubeImages
|
|
|
|
Loads six files with proper extensions
|
|
=======================
|
|
*/
|
|
bool R_LoadCubeImages(const char* imgName, cubeFiles_t extensions, byte* pics[6], int* outSize, ID_TIME_T* timestamp) {
|
|
int i, j;
|
|
char* cameraSides[6] = {
|
|
"_forward.tga",
|
|
"_back.tga",
|
|
"_left.tga",
|
|
"_right.tga",
|
|
"_up.tga",
|
|
"_down.tga"
|
|
};
|
|
char* axisSides[6] = {
|
|
"_px.tga",
|
|
"_nx.tga",
|
|
"_py.tga",
|
|
"_ny.tga",
|
|
"_pz.tga",
|
|
"_nz.tga"
|
|
};
|
|
char** sides;
|
|
char fullName[MAX_IMAGE_NAME];
|
|
int width, height, size = 0;
|
|
|
|
if (extensions == CF_CAMERA) {
|
|
sides = cameraSides;
|
|
}
|
|
else {
|
|
sides = axisSides;
|
|
}
|
|
|
|
if (pics) {
|
|
memset(pics, 0, 6 * sizeof(pics[0]));
|
|
}
|
|
|
|
if (timestamp) {
|
|
*timestamp = 0;
|
|
}
|
|
|
|
for (i = 0; i < 6; i++) {
|
|
idStr::snPrintf(fullName, sizeof(fullName), "%s%s", imgName, sides[i]);
|
|
|
|
ID_TIME_T thisTime;
|
|
|
|
if (!pics) {
|
|
R_LoadImageProgram(fullName, NULL, &width, &height, &thisTime);
|
|
}
|
|
else {
|
|
R_LoadImageProgram(fullName, &pics[i], &width, &height, &thisTime);
|
|
}
|
|
|
|
if (thisTime == FILE_NOT_FOUND_TIMESTAMP) {
|
|
break;
|
|
}
|
|
|
|
if (i == 0) {
|
|
size = width;
|
|
}
|
|
|
|
if (width != size || height != size) {
|
|
common->Warning("Mismatched sizes on cube map '%s'", imgName);
|
|
break;
|
|
}
|
|
|
|
if (timestamp) {
|
|
if (thisTime > *timestamp) {
|
|
*timestamp = thisTime;
|
|
}
|
|
}
|
|
|
|
if (pics && extensions == CF_CAMERA) {
|
|
switch (i) {
|
|
case 0: // forward
|
|
R_RotatePic(pics[i], width);
|
|
break;
|
|
|
|
case 1: // back
|
|
R_RotatePic(pics[i], width);
|
|
R_HorizontalFlip(pics[i], width, height);
|
|
R_VerticalFlip(pics[i], width, height);
|
|
break;
|
|
|
|
case 2: // left
|
|
R_VerticalFlip(pics[i], width, height);
|
|
break;
|
|
|
|
case 3: // right
|
|
R_HorizontalFlip(pics[i], width, height);
|
|
break;
|
|
|
|
case 4: // up
|
|
R_RotatePic(pics[i], width);
|
|
break;
|
|
|
|
case 5: // down
|
|
R_RotatePic(pics[i], width);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i != 6) {
|
|
if (pics) {
|
|
for (j = 0; j < i; j++) {
|
|
R_StaticFree(pics[j]);
|
|
}
|
|
}
|
|
|
|
if (timestamp) {
|
|
*timestamp = 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (outSize) {
|
|
*outSize = size;
|
|
}
|
|
|
|
return true;
|
|
}
|