screen.cpp
1 //------------------------------------------------------------------------- 2 /* 3 Copyright (C) 2010-2019 EDuke32 developers and contributors 4 Copyright (C) 2019 Nuke.YKT 5 6 This file is part of NBlood. 7 8 NBlood is free software; you can redistribute it and/or 9 modify it under the terms of the GNU General Public License version 2 10 as published by the Free Software Foundation. 11 12 This program is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 15 16 See the GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with this program; if not, write to the Free Software 20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 */ 22 //------------------------------------------------------------------------- 23 #include <string.h> 24 #include "a.h" 25 #include "build.h" 26 #include "colmatch.h" 27 #include "common_game.h" 28 29 #include "globals.h" 30 #include "config.h" 31 #include "gfx.h" 32 #include "resource.h" 33 #include "screen.h" 34 35 RGB StdPal[32] = { 36 { 0, 0, 0 }, 37 { 0, 0, 170 }, 38 { 0, 170, 170 }, 39 { 0, 170, 170 }, 40 { 170, 0, 0 }, 41 { 170, 0, 170 }, 42 { 170, 85, 0 }, 43 { 170, 170, 170 }, 44 { 85, 85, 85 }, 45 { 85, 85, 255 }, 46 { 85, 255, 85 }, 47 { 85, 255, 255 }, 48 { 255, 85, 85 }, 49 { 255, 85, 255 }, 50 { 255, 255, 85 }, 51 { 255, 255, 255 }, 52 { 241, 241, 241 }, 53 { 226, 226, 226 }, 54 { 211, 211, 211 }, 55 { 196, 196, 196 }, 56 { 181, 181, 181 }, 57 { 166, 166, 166 }, 58 { 151, 151, 151 }, 59 { 136, 136, 136 }, 60 { 120, 120, 120 }, 61 { 105, 105, 105 }, 62 { 90, 90, 90 }, 63 { 75, 75, 75 }, 64 { 60, 60, 60 }, 65 { 45, 45, 45 }, 66 { 30, 30, 30 }, 67 { 15, 15, 15 } 68 }; 69 70 LOADITEM PLU[15] = { 71 { 0, "NORMAL" }, 72 { 1, "SATURATE" }, 73 { 2, "BEAST" }, 74 { 3, "TOMMY" }, 75 { 4, "SPIDER3" }, 76 { 5, "GRAY" }, 77 { 6, "GRAYISH" }, 78 { 7, "SPIDER1" }, 79 { 8, "SPIDER2" }, 80 { 9, "FLAME" }, 81 { 10, "COLD" }, 82 { 11, "P1" }, 83 { 12, "P2" }, 84 { 13, "P3" }, 85 { 14, "P4" } 86 }; 87 88 LOADITEM PAL[5] = { 89 { 0, "BLOOD" }, 90 { 1, "WATER" }, 91 { 2, "BEAST" }, 92 { 3, "SEWER" }, 93 { 4, "INVULN1" } 94 }; 95 96 97 bool DacInvalid = true; 98 static char(*gammaTable)[256]; 99 RGB curDAC[256]; 100 RGB baseDAC[256]; 101 static RGB fromDAC[256]; 102 static RGB toRGB; 103 static RGB *palTable[5]; 104 static int curPalette; 105 static int curGamma; 106 int gGammaLevels; 107 bool gFogMode = false; 108 char gStdColor[32]; 109 int32_t gBrightness; 110 int32_t gCustomPalette; 111 int32_t gCustomPaletteCIEDE2000; 112 int32_t gCustomPaletteGrayscale; 113 int32_t gCustomPaletteInvert; 114 115 char scrFindClosestColor(int red, int green, int blue) 116 { 117 int dist = 0x7fffffff; 118 int best; 119 for (int i = 0; i < 256; i++) 120 { 121 int sum = (palette[i*3+1]-green)*(palette[i*3+1]-green); 122 if (sum >= dist) continue; 123 sum += (palette[i*3+0]-red)*(palette[i*3+0]-red); 124 if (sum >= dist) continue; 125 sum += (palette[i*3+2]-blue)*(palette[i*3+2]-blue); 126 if (sum >= dist) continue; 127 best = i; 128 dist = sum; 129 if (sum == 0) 130 break; 131 } 132 return best; 133 } 134 135 void scrCreateStdColors(void) 136 { 137 for (int i = 0; i < 32; i++) 138 gStdColor[i] = scrFindClosestColor(StdPal[i].red, StdPal[i].green, StdPal[i].blue); 139 } 140 141 void scrResetPalette(void) 142 { 143 if (palTable[0] == nullptr) 144 return; 145 146 paletteSetColorTable(0, (uint8_t*)palTable[0]); 147 } 148 149 void gSetDacRange(int start, int end, RGB *pPal) 150 { 151 UNREFERENCED_PARAMETER(start); 152 UNREFERENCED_PARAMETER(end); 153 if (videoGetRenderMode() == REND_CLASSIC) 154 { 155 memcpy(palette, pPal, sizeof(palette)); 156 videoSetPalette(gBrightness>>2, 0, 0); 157 } 158 } 159 160 void scrLoadPLUs(void) 161 { 162 if (gFogMode) 163 { 164 DICTNODE *pFog = gSysRes.Lookup("FOG", "FLU"); 165 if (!pFog) 166 ThrowError("FOG.FLU not found"); 167 palookup[0] = (char*)gSysRes.Lock(pFog); 168 for (int i = 0; i < 15; i++) 169 palookup[PLU[i].id] = palookup[0]; 170 parallaxvisibility = 3072; 171 return; 172 } 173 174 // load default palookups 175 for (int i = 0; i < 15; i++) { 176 DICTNODE *pPlu = gSysRes.Lookup(PLU[i].name, "PLU"); 177 if (!pPlu) 178 ThrowError("%s.PLU not found", PLU[i].name); 179 if (pPlu->size / 256 != 64) 180 ThrowError("Incorrect PLU size"); 181 palookup[PLU[i].id] = (char*)gSysRes.Lock(pPlu); 182 } 183 184 // by NoOne: load user palookups 185 for (int i = kUserPLUStart; i < MAXPALOOKUPS; i++) { 186 DICTNODE* pPlu = gSysRes.Lookup(i, "PLU"); 187 if (!pPlu) continue; 188 else if (pPlu->size / 256 != 64) { consoleSysMsg("Incorrect filesize of PLU#%d", i); } 189 else palookup[i] = (char*)gSysRes.Lock(pPlu); 190 } 191 192 #ifdef USE_OPENGL 193 palookupfog[1].r = 255; 194 palookupfog[1].g = 255; 195 palookupfog[1].b = 255; 196 #endif 197 } 198 199 #ifdef USE_OPENGL 200 glblend_t const bloodglblend = 201 { 202 { 203 { 1.f/3.f, BLENDFACTOR_SRC_ALPHA, BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 0 }, 204 { 2.f/3.f, BLENDFACTOR_SRC_ALPHA, BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 0 }, 205 }, 206 }; 207 #endif 208 209 void scrLoadPalette(void) 210 { 211 paletteInitClosestColorScale(30, 59, 11); 212 paletteInitClosestColorGrid(); 213 paletteloaded = 0; 214 LOG_F(INFO, "Loading palettes"); 215 for (int i = 0; i < 5; i++) 216 { 217 DICTNODE *pPal = gSysRes.Lookup(PAL[i].name, "PAL"); 218 if (!pPal) 219 ThrowError("%s.PAL not found (RFF files may be wrong version)", PAL[i].name); 220 palTable[PAL[i].id] = (RGB*)gSysRes.Lock(pPal); 221 paletteSetColorTable(PAL[i].id, (uint8_t*)palTable[PAL[i].id]); 222 } 223 memcpy(palette, palTable[0], sizeof(palette)); 224 numshades = 64; 225 paletteloaded |= PALETTE_MAIN; 226 scrLoadPLUs(); 227 paletteloaded |= PALETTE_SHADE; 228 LOG_F(INFO, "Loading translucency table"); 229 DICTNODE *pTrans = gSysRes.Lookup("TRANS", "TLU"); 230 if (!pTrans) 231 ThrowError("TRANS.TLU not found"); 232 blendtable[0] = (char*)gSysRes.Lock(pTrans); 233 paletteloaded |= PALETTE_TRANSLUC; 234 235 #ifdef USE_OPENGL 236 for (auto & x : glblend) 237 x = bloodglblend; 238 239 for (int i = 0; i < MAXPALOOKUPS; i++) 240 palookupfogfactor[i] = 1.f; 241 #endif 242 243 paletteInitClosestColorMap((uint8_t*)palTable[0]); 244 palettePostLoadTables(); 245 // Make color index 255 of palette black. 246 for (int i = 0; i < 5; i++) 247 { 248 if (basepaltable[i] != NULL) 249 Bmemset(&basepaltable[i][255 * 3], 0, 3); 250 } 251 palettePostLoadLookups(); 252 } 253 254 void scrSetPalette(int palId) 255 { 256 curPalette = palId; 257 scrSetGamma(0/*curGamma*/); 258 } 259 260 void scrSetGamma(int nGamma) 261 { 262 dassert(nGamma < gGammaLevels); 263 curGamma = nGamma; 264 for (int i = 0; i < 256; i++) 265 { 266 baseDAC[i].red = gammaTable[curGamma][palTable[curPalette][i].red]; 267 baseDAC[i].green = gammaTable[curGamma][palTable[curPalette][i].green]; 268 baseDAC[i].blue = gammaTable[curGamma][palTable[curPalette][i].blue]; 269 } 270 DacInvalid = 1; 271 } 272 273 void scrSetupFade(char red, char green, char blue) 274 { 275 memcpy(fromDAC, curDAC, sizeof(fromDAC)); 276 toRGB.red = red; 277 toRGB.green = green; 278 toRGB.blue = blue; 279 } 280 281 void scrSetupUnfade(void) 282 { 283 memcpy(fromDAC, baseDAC, sizeof(fromDAC)); 284 } 285 286 void scrFadeAmount(int amount) 287 { 288 for (int i = 0; i < 256; i++) 289 { 290 curDAC[i].red = interpolate(fromDAC[i].red, toRGB.red, amount, 1); 291 curDAC[i].green = interpolate(fromDAC[i].green, toRGB.green, amount, 1); 292 curDAC[i].blue = interpolate(fromDAC[i].blue, toRGB.blue, amount, 1); 293 } 294 gSetDacRange(0, 256, curDAC); 295 } 296 297 void scrSetDac(void) 298 { 299 if (DacInvalid) 300 gSetDacRange(0, 256, baseDAC); 301 DacInvalid = 0; 302 } 303 304 void scrInit(void) 305 { 306 LOG_F(INFO, "Initializing engine"); 307 #ifdef USE_OPENGL 308 glrendmode = REND_POLYMOST; 309 #endif 310 engineInit(); 311 curPalette = 0; 312 curGamma = 0; 313 LOG_F(INFO, "Loading gamma correction table"); 314 DICTNODE *pGamma = gSysRes.Lookup("gamma", "DAT"); 315 if (!pGamma) 316 ThrowError("Gamma table not found"); 317 gGammaLevels = pGamma->size / 256; 318 gammaTable = (char(*)[256])gSysRes.Lock(pGamma); 319 } 320 321 void scrUnInit(void) 322 { 323 memset(palookup, 0, sizeof(palookup)); 324 memset(blendtable, 0, sizeof(blendtable)); 325 engineUnInit(); 326 } 327 328 329 void scrSetGameMode(int vidMode, int XRes, int YRes, int nBits) 330 { 331 videoResetMode(); 332 //videoSetGameMode(vidMode, XRes, YRes, nBits, 0); 333 if (videoSetGameMode(vidMode, XRes, YRes, nBits, gUpscaleFactor) < 0) 334 { 335 LOG_F(ERROR, "Failure setting video mode %dx%dx%d %s! Trying next mode...", XRes, YRes, 336 nBits, vidMode ? "fullscreen" : "windowed"); 337 338 int resIdx = 0; 339 340 for (int i=0; i < validmodecnt; i++) 341 { 342 if (validmode[i].xdim == XRes && validmode[i].ydim == YRes) 343 { 344 resIdx = i; 345 break; 346 } 347 } 348 349 int const savedIdx = resIdx; 350 int bpp = nBits; 351 352 while (videoSetGameMode(0, validmode[resIdx].xdim, validmode[resIdx].ydim, bpp, gUpscaleFactor) < 0) 353 { 354 LOG_F(ERROR, "Failure setting video mode %dx%dx%d windowed! Trying next mode...", 355 validmode[resIdx].xdim, validmode[resIdx].ydim, bpp); 356 357 if (++resIdx == validmodecnt) 358 { 359 if (bpp == 8) 360 ThrowError("Fatal error: unable to set any video mode!"); 361 362 resIdx = savedIdx; 363 bpp = 8; 364 } 365 } 366 367 gSetup.xdim = validmode[resIdx].xdim; 368 gSetup.ydim = validmode[resIdx].ydim; 369 gSetup.bpp = bpp; 370 } 371 videoClearViewableArea(0); 372 scrNextPage(); 373 scrSetPalette(curPalette); 374 gfxSetClip(0, 0, xdim, ydim); 375 } 376 377 void scrNextPage(void) 378 { 379 videoNextPage(); 380 } 381 382 #include "screenpals.h" // where the custom palettes are stored 383 384 // Computes the CIEDE2000 color-difference between two Lab colors 385 // Based on the article: 386 // The CIEDE2000 Color-Difference Formula: Implementation Notes, 387 // Supplementary Test Data, and Mathematical Observations,", G. Sharma, 388 // W. Wu, E. N. Dalal, submitted to Color Research and Application, 389 // January 2004. 390 // Available at http://www.ece.rochester.edu/~/gsharma/ciede2000/ 391 // Based on the C++ implementation by Ofir Pele, The Hebrew University of Jerusalem 2010. 392 // Written in C by Adam Borowski (kilobyte) 393 // Shamelessly taken from https://github.com/kilobyte/colormatch (no license) 394 395 #define pi 3.141592653589793238462643383279 396 397 static double srcDeltaE2000(double *lab1, double *lab2) 398 { 399 double Lstd = *lab1; 400 double astd = *(lab1+1); 401 double bstd = *(lab1+2); 402 403 double Lsample = *lab2; 404 double asample = *(lab2+1); 405 double bsample = *(lab2+2); 406 407 double Cabstd= sqrt(astd*astd+bstd*bstd); 408 double Cabsample= sqrt(asample*asample+bsample*bsample); 409 410 double Cabarithmean= (Cabstd + Cabsample)/2.0; 411 412 double G= 0.5*( 1.0 - sqrt( pow(Cabarithmean,7.0)/(pow(Cabarithmean,7.0) + pow(25.0,7.0)))); 413 414 double apstd= (1.0+G)*astd; // aprime in paper 415 double apsample= (1.0+G)*asample; // aprime in paper 416 double Cpsample= sqrt(apsample*apsample+bsample*bsample); 417 418 double Cpstd= sqrt(apstd*apstd+bstd*bstd); 419 // Compute product of chromas 420 double Cpprod= (Cpsample*Cpstd); 421 422 423 // Ensure hue is between 0 and 2pi 424 double hpstd= atan2(bstd,apstd); 425 if (hpstd<0) hpstd+= 2.0*pi; // rollover ones that come -ve 426 427 double hpsample= atan2(bsample,apsample); 428 if (hpsample<0) hpsample+= 2.0*pi; 429 if ( (fabs(apsample)+fabs(bsample))==0.0) hpsample= 0.0; 430 431 double dL= (Lsample-Lstd); 432 double dC= (Cpsample-Cpstd); 433 434 // Computation of hue difference 435 double dhp= (hpsample-hpstd); 436 if (dhp>pi) dhp-= 2.0*pi; 437 if (dhp<-pi) dhp+= 2.0*pi; 438 // set chroma difference to zero if the product of chromas is zero 439 if (Cpprod == 0.0) dhp= 0.0; 440 441 // Note that the defining equations actually need 442 // signed Hue and chroma differences which is different 443 // from prior color difference formulae 444 445 double dH= 2.0*sqrt(Cpprod)*sin(dhp/2.0); 446 //%dH2 = 4*Cpprod.*(sin(dhp/2)).^2; 447 448 // weighting functions 449 double Lp= (Lsample+Lstd)/2.0; 450 double Cp= (Cpstd+Cpsample)/2.0; 451 452 // Average Hue Computation 453 // This is equivalent to that in the paper but simpler programmatically. 454 // Note average hue is computed in radians and converted to degrees only 455 // where needed 456 double hp= (hpstd+hpsample)/2.0; 457 // Identify positions for which abs hue diff exceeds 180 degrees 458 if ( fabs(hpstd-hpsample) > pi ) hp-= pi; 459 // rollover ones that come -ve 460 if (hp<0) hp+= 2.0*pi; 461 462 // Check if one of the chroma values is zero, in which case set 463 // mean hue to the sum which is equivalent to other value 464 if (Cpprod==0.0) hp= hpsample+hpstd; 465 466 double Lpm502= (Lp-50.0)*(Lp-50.0); 467 double Sl= 1.0+0.015*Lpm502/sqrt(20.0+Lpm502); 468 double Sc= 1.0+0.045*Cp; 469 double T= 1.0 - 0.17*cos(hp - pi/6.0) + 0.24*cos(2.0*hp) + 0.32*cos(3.0*hp+pi/30.0) - 0.20*cos(4.0*hp-63.0*pi/180.0); 470 double Sh= 1.0 + 0.015*Cp*T; 471 double delthetarad= (30.0*pi/180.0)*exp(- pow(( (180.0/pi*hp-275.0)/25.0),2.0)); 472 double Rc= 2.0*sqrt(pow(Cp,7.0)/(pow(Cp,7.0) + pow(25.0,7.0))); 473 double RT= -sin(2.0*delthetarad)*Rc; 474 475 // The CIE 00 color difference 476 return sqrt( pow((dL/Sl),2.0) + pow((dC/Sc),2.0) + pow((dH/Sh),2.0) + RT*(dC/Sc)*(dH/Sh) ); 477 } 478 479 static void srcRGB2LAB(int rgb, double lab[3]) 480 { 481 double r = ((rgb>>16)&0xff)/255.0; 482 double g = ((rgb>> 8)&0xff)/255.0; 483 double b = ((rgb )&0xff)/255.0; 484 double x,y,z; 485 486 r = (r > 0.04045) ? pow((r + 0.055) / 1.055, 2.4) : r / 12.92; 487 g = (g > 0.04045) ? pow((g + 0.055) / 1.055, 2.4) : g / 12.92; 488 b = (b > 0.04045) ? pow((b + 0.055) / 1.055, 2.4) : b / 12.92; 489 490 // at D65 491 x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047; 492 y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000; 493 z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883; 494 495 x = (x > 0.008856) ? pow(x, 1.0/3) : (7.787 * x) + 16.0/116; 496 y = (y > 0.008856) ? pow(y, 1.0/3) : (7.787 * y) + 16.0/116; 497 z = (z > 0.008856) ? pow(z, 1.0/3) : (7.787 * z) + 16.0/116; 498 499 lab[0] = 116 * y - 16; 500 lab[1] = 500 * (x - y); 501 lab[2] = 200 * (y - z); 502 } 503 504 static double srcColorDiff(int rgb1, int rgb2) 505 { 506 double lab1[3], lab2[3]; 507 srcRGB2LAB(rgb1, lab1); 508 srcRGB2LAB(rgb2, lab2); 509 return srcDeltaE2000(lab1, lab2); 510 } 511 512 static void scrTweakPalette(uint8_t *curPal, int replacePal, char bUseCIEDE2000, char bGrayscale, char bInvertPal, int palSize) 513 { 514 if (!replacePal && !bGrayscale && !bInvertPal) // do nothing 515 return; 516 for (int j = 0; j < palSize; j += 3) // count through all palette colors 517 { 518 if (bInvertPal) 519 { 520 curPal[j+0] = 255-curPal[j+0]; 521 curPal[j+1] = 255-curPal[j+1]; 522 curPal[j+2] = 255-curPal[j+2]; 523 } 524 if (replacePal == 1) // sepia tone 525 { 526 char color = clamp((curPal[j]+curPal[j+1]+curPal[j+2])/3, 0, 255); 527 curPal[j+0] = clamp(color+(92/1.5f)-20, 0, 255); 528 curPal[j+1] = clamp(color+(46/1.5f)-20, 0, 255); 529 curPal[j+2] = clamp(color-20, 0, 255); 530 continue; 531 } 532 else if (bGrayscale) 533 { 534 char color = clamp((curPal[j]+curPal[j+1]+curPal[j+2])/3, 0, 255); 535 curPal[j+0] = color; 536 curPal[j+1] = color; 537 curPal[j+2] = color; 538 } 539 if (!replacePal) // only allow invert/grayscale for default palette 540 continue; 541 int palIndex = 0; 542 double nearestColor = 255*3; 543 const uint8_t (*newPal)[3] = (const uint8_t (*)[3])srcCustomPaletteList[replacePal]; 544 if (!newPal) // null entry, skip 545 continue; 546 for (int i = 0; i < srcCustomPaletteColors[replacePal]; i++) 547 { 548 double testedDistance; 549 if (bUseCIEDE2000) 550 testedDistance = srcColorDiff((curPal[j+0]<<16)|(curPal[j+1]<<8)|(curPal[j+2]), (newPal[i][0]<<16)|(newPal[i][1]<<8)|(newPal[i][2])); 551 else 552 testedDistance = sqrt(pow(abs(curPal[j+0]-newPal[i][0]),2.0)+pow(abs(curPal[j+1]-newPal[i][1]),2.0)+pow(abs(curPal[j+2]-newPal[i][2]),2.0)); 553 if (testedDistance < nearestColor) 554 { 555 palIndex = i; 556 nearestColor = testedDistance; 557 } 558 } 559 curPal[j+0] = newPal[palIndex][0]; 560 curPal[j+1] = newPal[palIndex][1]; 561 curPal[j+2] = newPal[palIndex][2]; 562 if (bInvertPal == 2) 563 { 564 curPal[j+0] = 255-curPal[j+0]; 565 curPal[j+1] = 255-curPal[j+1]; 566 curPal[j+2] = 255-curPal[j+2]; 567 } 568 } 569 } 570 571 void scrCustomizePalette(int replacePal, char bUseCIEDE2000, char bGrayscale, char bInvertPal) 572 { 573 static RGB bakPalTable[5][256]; 574 static char bHasBakPalTable = 0; 575 576 for (int i = 0; i < 5; i++) 577 { 578 if (!basepaltable[PAL[i].id]) // not initialized, should never happen 579 continue; 580 if (!bHasBakPalTable) 581 memcpy(bakPalTable[PAL[i].id], palTable[PAL[i].id], sizeof(bakPalTable[0])); // create backup 582 else 583 memcpy(palTable[PAL[i].id], bakPalTable[PAL[i].id], sizeof(bakPalTable[0])); // restore from backup 584 scrTweakPalette((uint8_t *)palTable[PAL[i].id], replacePal, bUseCIEDE2000, bGrayscale, bInvertPal, sizeof(bakPalTable[0])); 585 paletteSetColorTable(PAL[i].id, (uint8_t *)palTable[PAL[i].id]); 586 } 587 memcpy(palette, palTable[0], sizeof(palette)); 588 bHasBakPalTable = 1; 589 590 // Make color index 255 of palette black (or closest) 591 for (int i = 0; i < 5; i++) 592 { 593 if (!basepaltable[i]) 594 continue; 595 Bmemset(&basepaltable[i][255 * 3], 0, 3); 596 scrTweakPalette(&basepaltable[i][255 * 3], replacePal, bUseCIEDE2000, bGrayscale, bInvertPal, 3); // find palette for black 597 } 598 scrSetPalette(0); 599 }