/ source / blood / src / screen.cpp
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  }