1 /**
2  * @file palette.cpp
3  *
4  * Implementation of functions for handling the engines color palette.
5  */
6 #include "all.h"
7 #include "options.h"
8 #include "../SourceX/display.h"
9 #include "../3rdParty/Storm/Source/storm.h"
10 
11 DEVILUTION_BEGIN_NAMESPACE
12 
13 SDL_Color logical_palette[256];
14 SDL_Color system_palette[256];
15 SDL_Color orig_palette[256];
16 Uint8 paletteTransparencyLookup[256][256]; //Lookup table for transparency
17 
18 /* data */
19 
20 /** Specifies whether the palette has max brightness. */
21 BOOLEAN sgbFadedIn = TRUE;
22 
palette_update()23 void palette_update()
24 {
25 	assert(palette);
26 	if (SDLC_SetSurfaceAndPaletteColors(pal_surface, palette, system_palette, 0, 256) < 0) {
27 		ErrSdl();
28 	}
29 	pal_surface_palette_version++;
30 }
31 
ApplyGamma(SDL_Color * dst,const SDL_Color * src,int n)32 void ApplyGamma(SDL_Color *dst, const SDL_Color *src, int n)
33 {
34 	int i;
35 	double g;
36 
37 	g = sgOptions.Graphics.nGammaCorrection / 100.0;
38 
39 	for (i = 0; i < n; i++) {
40 		dst[i].r = pow(src[i].r / 256.0, g) * 256.0;
41 		dst[i].g = pow(src[i].g / 256.0, g) * 256.0;
42 		dst[i].b = pow(src[i].b / 256.0, g) * 256.0;
43 	}
44 	force_redraw = 255;
45 }
46 
LoadGamma()47 static void LoadGamma()
48 {
49 	int gamma_value = sgOptions.Graphics.nGammaCorrection;
50 
51 	if (gamma_value < 30) {
52 		gamma_value = 30;
53 	} else if (gamma_value > 100) {
54 		gamma_value = 100;
55 	}
56 	sgOptions.Graphics.nGammaCorrection = gamma_value - gamma_value % 5;
57 }
58 
palette_init()59 void palette_init()
60 {
61 	LoadGamma();
62 	memcpy(system_palette, orig_palette, sizeof(orig_palette));
63 	InitPalette();
64 }
65 
66 /**
67  * @brief Generate lookup table for transparency
68  *
69  * This is based of the same technique found in Quake2.
70  *
71  * To mimic 50% transparency we figure out what colors in the existing palette are the best match for the combination of any 2 colors.
72  * We save this into a lookup table for use during rendering.
73  *
74  * @param palette The colors to operate on
75  * @param skipFrom Do not use colors between this index and skipTo
76  * @param skipTo Do not use colors between skipFrom and this index
77  * @param toUpdate Only update the first n colors
78  */
GenerateBlendedLookupTable(SDL_Color * palette,int skipFrom,int skipTo,int toUpdate=256)79 static void GenerateBlendedLookupTable(SDL_Color *palette, int skipFrom, int skipTo, int toUpdate = 256)
80 {
81 	for (int i = 0; i < 256; i++) {
82 		for (int j = 0; j < 256; j++) {
83 			if (i == j) { // No need to calculate transparency between 2 identical colors
84 				paletteTransparencyLookup[i][j] = j;
85 				continue;
86 			}
87 			if (i > j) { // Half the blends will be mirror identical ([i][j] is the same as [j][i]), so simply copy the existing combination.
88 				paletteTransparencyLookup[i][j] = paletteTransparencyLookup[j][i];
89 				continue;
90 			}
91 			if (i > toUpdate && j > toUpdate) {
92 				continue;
93 			}
94 
95 			Uint8 r = ((int)palette[i].r + (int)palette[j].r) / 2;
96 			Uint8 g = ((int)palette[i].g + (int)palette[j].g) / 2;
97 			Uint8 b = ((int)palette[i].b + (int)palette[j].b) / 2;
98 			Uint8 best;
99 			Uint32 bestDiff = SDL_MAX_UINT32;
100 			for (int k = 0; k < 256; k++) {
101 				if (k >= skipFrom && k <= skipTo)
102 					continue;
103 				int diffr = palette[k].r - r;
104 				int diffg = palette[k].g - g;
105 				int diffb = palette[k].b - b;
106 				int diff = diffr * diffr + diffg * diffg + diffb * diffb;
107 
108 				if (bestDiff > diff) {
109 					best = k;
110 					bestDiff = diff;
111 				}
112 			}
113 			paletteTransparencyLookup[i][j] = best;
114 		}
115 	}
116 }
117 
LoadPalette(const char * pszFileName)118 void LoadPalette(const char *pszFileName)
119 {
120 	int i;
121 	void *pBuf;
122 	BYTE PalData[256][3];
123 
124 	assert(pszFileName);
125 
126 	SFileOpenFile(pszFileName, &pBuf);
127 	SFileReadFile(pBuf, (char *)PalData, sizeof(PalData), NULL, NULL);
128 	SFileCloseFile(pBuf);
129 
130 	for (i = 0; i < 256; i++) {
131 		orig_palette[i].r = PalData[i][0];
132 		orig_palette[i].g = PalData[i][1];
133 		orig_palette[i].b = PalData[i][2];
134 #ifndef USE_SDL1
135 		orig_palette[i].a = SDL_ALPHA_OPAQUE;
136 #endif
137 	}
138 
139 	if (sgOptions.Graphics.bBlendedTransparancy) {
140 		if (leveltype == DTYPE_CAVES || leveltype == DTYPE_CRYPT) {
141 			GenerateBlendedLookupTable(orig_palette, 1, 31);
142 		} else if (leveltype == DTYPE_NEST) {
143 			GenerateBlendedLookupTable(orig_palette, 1, 15);
144 		} else {
145 			GenerateBlendedLookupTable(orig_palette, -1, -1);
146 		}
147 	}
148 }
149 
LoadRndLvlPal(int l)150 void LoadRndLvlPal(int l)
151 {
152 	int rv;
153 	char szFileName[MAX_PATH];
154 
155 	if (l == DTYPE_TOWN) {
156 		LoadPalette("Levels\\TownData\\Town.pal");
157 	} else {
158 		rv = random_(0, 4) + 1;
159 		sprintf(szFileName, "Levels\\L%iData\\L%i_%i.PAL", l, l, rv);
160 		if (l == 5) {
161 			sprintf(szFileName, "NLevels\\L5Data\\L5Base.PAL");
162 		}
163 		if (l == 6) {
164 			if (!gbNestArt) {
165 				rv++;
166 			}
167 			sprintf(szFileName, "NLevels\\L%iData\\L%iBase%i.PAL", 6, 6, rv);
168 		}
169 		LoadPalette(szFileName);
170 	}
171 }
172 
ResetPal()173 void ResetPal()
174 {
175 }
176 
IncreaseGamma()177 void IncreaseGamma()
178 {
179 	if (sgOptions.Graphics.nGammaCorrection < 100) {
180 		sgOptions.Graphics.nGammaCorrection += 5;
181 		if (sgOptions.Graphics.nGammaCorrection > 100)
182 			sgOptions.Graphics.nGammaCorrection = 100;
183 		ApplyGamma(system_palette, logical_palette, 256);
184 		palette_update();
185 	}
186 }
187 
DecreaseGamma()188 void DecreaseGamma()
189 {
190 	if (sgOptions.Graphics.nGammaCorrection > 30) {
191 		sgOptions.Graphics.nGammaCorrection -= 5;
192 		if (sgOptions.Graphics.nGammaCorrection < 30)
193 			sgOptions.Graphics.nGammaCorrection = 30;
194 		ApplyGamma(system_palette, logical_palette, 256);
195 		palette_update();
196 	}
197 }
198 
UpdateGamma(int gamma)199 int UpdateGamma(int gamma)
200 {
201 	if (gamma) {
202 		sgOptions.Graphics.nGammaCorrection = 130 - gamma;
203 		ApplyGamma(system_palette, logical_palette, 256);
204 		palette_update();
205 	}
206 	return 130 - sgOptions.Graphics.nGammaCorrection;
207 }
208 
SetFadeLevel(DWORD fadeval)209 void SetFadeLevel(DWORD fadeval)
210 {
211 	int i;
212 
213 	for (i = 0; i < 256; i++) { // BUGFIX: should be 256 (fixed)
214 		system_palette[i].r = (fadeval * logical_palette[i].r) >> 8;
215 		system_palette[i].g = (fadeval * logical_palette[i].g) >> 8;
216 		system_palette[i].b = (fadeval * logical_palette[i].b) >> 8;
217 	}
218 	palette_update();
219 }
220 
BlackPalette()221 void BlackPalette()
222 {
223 	SetFadeLevel(0);
224 }
225 
PaletteFadeIn(int fr)226 void PaletteFadeIn(int fr)
227 {
228 	int i;
229 
230 	ApplyGamma(logical_palette, orig_palette, 256);
231 	DWORD tc = SDL_GetTicks();
232 	for (i = 0; i < 256; i = (SDL_GetTicks() - tc) / 2.083) { // 32 frames @ 60hz
233 		SetFadeLevel(i);
234 		SDL_Rect SrcRect = { BUFFER_BORDER_LEFT, BUFFER_BORDER_TOP, gnScreenWidth, gnScreenHeight };
235 		BltFast(&SrcRect, NULL);
236 		RenderPresent();
237 	}
238 	SetFadeLevel(256);
239 	memcpy(logical_palette, orig_palette, sizeof(orig_palette));
240 	sgbFadedIn = TRUE;
241 }
242 
PaletteFadeOut(int fr)243 void PaletteFadeOut(int fr)
244 {
245 	int i;
246 
247 	if (sgbFadedIn) {
248 		DWORD tc = SDL_GetTicks();
249 		for (i = 256; i > 0; i = 256 - (SDL_GetTicks() - tc) / 2.083) { // 32 frames @ 60hz
250 			SetFadeLevel(i);
251 			SDL_Rect SrcRect = { BUFFER_BORDER_LEFT, BUFFER_BORDER_TOP, gnScreenWidth, gnScreenHeight };
252 			BltFast(&SrcRect, NULL);
253 			RenderPresent();
254 		}
255 		SetFadeLevel(0);
256 		sgbFadedIn = FALSE;
257 	}
258 }
259 
260 /**
261  * @brief Cycle the given range of colors in the palette
262  * @param from First color index of the range
263  * @param to First color index of the range
264  */
CycleColors(int from,int to)265 static void CycleColors(int from, int to)
266 {
267 	SDL_Color col = system_palette[from];
268 	for (int i = from; i < to; i++) {
269 		system_palette[i] = system_palette[i + 1];
270 	}
271 	system_palette[to] = col;
272 
273 	if (!sgOptions.Graphics.bBlendedTransparancy)
274 		return;
275 
276 	for (int i = 0; i < 256; i++) {
277 		Uint8 col = paletteTransparencyLookup[i][from];
278 		for (int j = from; j < to; j++) {
279 			paletteTransparencyLookup[i][j] = paletteTransparencyLookup[i][j + 1];
280 		}
281 		paletteTransparencyLookup[i][to] = col;
282 	}
283 
284 	Uint8 colRow[256];
285 	memcpy(colRow, &paletteTransparencyLookup[from], sizeof(*paletteTransparencyLookup));
286 	for (int i = from; i < to; i++) {
287 		memcpy(&paletteTransparencyLookup[i], &paletteTransparencyLookup[i + 1], sizeof(*paletteTransparencyLookup));
288 	}
289 	memcpy(&paletteTransparencyLookup[to], colRow, sizeof(colRow));
290 }
291 
292 /**
293  * @brief Cycle the given range of colors in the palette in reverse direction
294  * @param from First color index of the range
295  * @param to First color index of the range
296  */
CycleColorsReverse(int from,int to)297 static void CycleColorsReverse(int from, int to)
298 {
299 	SDL_Color col = system_palette[to];
300 	for (int i = to; i > from; i--) {
301 		system_palette[i] = system_palette[i - 1];
302 	}
303 	system_palette[from] = col;
304 
305 	if (!sgOptions.Graphics.bBlendedTransparancy)
306 		return;
307 
308 	for (int i = 0; i < 256; i++) {
309 		Uint8 col = paletteTransparencyLookup[i][to];
310 		for (int j = to; j > from; j--) {
311 			paletteTransparencyLookup[i][j] = paletteTransparencyLookup[i][j - 1];
312 		}
313 		paletteTransparencyLookup[i][from] = col;
314 	}
315 
316 	Uint8 colRow[256];
317 	memcpy(colRow, &paletteTransparencyLookup[to], sizeof(*paletteTransparencyLookup));
318 	for (int i = to; i > from; i--) {
319 		memcpy(&paletteTransparencyLookup[i], &paletteTransparencyLookup[i - 1], sizeof(*paletteTransparencyLookup));
320 	}
321 	memcpy(&paletteTransparencyLookup[from], colRow, sizeof(colRow));
322 }
323 
palette_update_caves()324 void palette_update_caves()
325 {
326 	CycleColors(1, 31);
327 	palette_update();
328 }
329 
330 int dword_6E2D58;
331 int dword_6E2D54;
palette_update_crypt()332 void palette_update_crypt()
333 {
334 	int i;
335 	SDL_Color col;
336 
337 	if (dword_6E2D58 > 1) {
338 		CycleColorsReverse(1, 15);
339 		dword_6E2D58 = 0;
340 	} else {
341 		dword_6E2D58++;
342 	}
343 	if (dword_6E2D54 > 0) {
344 		CycleColorsReverse(16, 31);
345 		palette_update();
346 		dword_6E2D54++;
347 	} else {
348 		dword_6E2D54 = 1;
349 	}
350 }
351 
352 int dword_6E2D5C;
353 int dword_6E2D60;
palette_update_hive()354 void palette_update_hive()
355 {
356 	int i;
357 	SDL_Color col;
358 
359 	if (dword_6E2D60 == 2) {
360 		CycleColorsReverse(1, 8);
361 		dword_6E2D60 = 0;
362 	} else {
363 		dword_6E2D60++;
364 	}
365 	if (dword_6E2D5C == 2) {
366 		CycleColorsReverse(9, 15);
367 		palette_update();
368 		dword_6E2D5C = 0;
369 	} else {
370 		dword_6E2D5C++;
371 	}
372 }
373 
palette_update_quest_palette(int n)374 void palette_update_quest_palette(int n)
375 {
376 	int i;
377 
378 	for (i = 32 - n; i >= 0; i--) {
379 		logical_palette[i] = orig_palette[i];
380 	}
381 	ApplyGamma(system_palette, logical_palette, 32);
382 	palette_update();
383 	GenerateBlendedLookupTable(logical_palette, 1, 31, 32 - n); // Possible optimization would be to only update color 0 as only the UI can overlap with transparency in this quest
384 }
385 
386 DEVILUTION_END_NAMESPACE
387