/* =========================================================================== 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 . 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 /* 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& 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& verticalAngles, const std::vector& horizontalAngles, const std::vector& 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 verticalAngles(numVertical); std::vector horizontalAngles(numHorizontal); std::vector 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 for legacy backends, 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 for legacy backends that require it. // const bool convertToPowerOfTwo = makePowerOf2 && !(glConfig.isInitialized && glConfig.textureNonPowerOfTwoAvailable); if (pic && *pic && convertToPowerOfTwo) { 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; }