1 /*
2  *  Copyright (C) 2000-2013  The Exult Team
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22 
23 #include <cstring>
24 
25 #include "files/U7file.h"
26 #include "palette.h"
27 #include "ibuf8.h"
28 #include "utils.h"
29 #include "fnames.h"
30 #include "gameclk.h"
31 #include "gamewin.h"
32 #include "exceptions.h"
33 #include "ignore_unused_variable_warning.h"
34 
35 #include "SDL_timer.h"
36 
37 using std::memcpy;
38 using std::memset;
39 using std::size_t;
40 using std::string;
41 
42 unsigned char Palette::border[3] = {
43 	0, 0, 0
44 };
45 
Palette()46 Palette::Palette()
47 	: win(Game_window::get_instance()->get_win()),
48 	  palette(-1), brightness(100), max_val(63), border255(false),
49 	  faded_out(false), fades_enabled(true) {
50 	memset(pal1, 0, 768);
51 	memset(pal2, 0, 768);
52 }
53 
Palette(Palette * pal)54 Palette::Palette(Palette *pal)
55 	: win(Game_window::get_instance()->get_win()), max_val(63) {
56 	take(pal);
57 }
58 
take(Palette * pal)59 void Palette::take(Palette *pal) {
60 	palette = pal->palette;
61 	brightness = pal->brightness;
62 	faded_out = pal->faded_out;
63 	fades_enabled = pal->fades_enabled;
64 	memcpy(pal1, pal->pal1, 768);
65 	memcpy(pal2, pal->pal2, 768);
66 }
67 
68 /*
69  *  Fade the current palette in or out.
70  *  Note:  If pal_num != -1, the current palette is set to it.
71  */
72 
fade(int cycles,int inout,int pal_num)73 void Palette::fade(
74     int cycles,         // Length of fade.
75     int inout,          // 1 to fade in, 0 to fade to black.
76     int pal_num         // 0-11, or -1 for current.
77 ) {
78 	if (pal_num == -1) pal_num = palette;
79 	palette = pal_num;
80 
81 	border255 = (palette >= 0 && palette <= 12) && palette != 9;
82 
83 	load(PALETTES_FLX, PATCH_PALETTES, pal_num);
84 	if (inout)
85 		fade_in(cycles);
86 	else
87 		fade_out(cycles);
88 	faded_out = !inout;     // Be sure to set flag.
89 }
90 
91 /*
92  *  Flash the current palette red.
93  */
94 
flash_red()95 void Palette::flash_red(
96 ) {
97 	int savepal = palette;
98 	set(PALETTE_RED);       // Palette 8 is the red one.
99 	win->show();
100 	SDL_Delay(100);
101 	set(savepal);
102 	Game_window::get_instance()->set_painted();
103 }
104 
105 /*
106  *  Read in a palette.
107  */
108 
set(int pal_num,int new_brightness,bool repaint)109 void Palette::set(
110     int pal_num,            // 0-11, or -1 to leave unchanged.
111     int new_brightness,     // New percentage, or -1.
112     bool repaint
113 ) {
114 	border255 = (pal_num >= 0 && pal_num <= 12) && pal_num != 9;
115 
116 	if ((palette == pal_num || pal_num == -1) &&
117 	        (brightness == new_brightness || new_brightness == -1))
118 		// Already set.
119 		return;
120 	if (pal_num != -1)
121 		palette = pal_num;  // Store #.
122 	if (new_brightness > 0)
123 		brightness = new_brightness;
124 	if (faded_out)
125 		return;         // In the black.
126 
127 	// could throw!
128 	load(PALETTES_FLX, PATCH_PALETTES, palette);
129 	set_brightness(brightness);
130 	apply(repaint);
131 }
132 
133 /*
134  *  Read in a palette.
135  */
136 
set(unsigned char palnew[768],int new_brightness,bool repaint,bool border255)137 void Palette::set(
138     unsigned char palnew[768],
139     int new_brightness,     // New percentage, or -1.
140     bool repaint,
141     bool border255
142 ) {
143 	this->border255 = border255;
144 	memcpy(pal1, palnew, 768);
145 	memset(pal2, 0, 768);
146 	palette = -1;
147 	if (new_brightness > 0)
148 		brightness = new_brightness;
149 	if (faded_out)
150 		return;         // In the black.
151 
152 	set_brightness(brightness);
153 	apply(repaint);
154 }
155 
apply(bool repaint)156 void Palette::apply(bool repaint) {
157 	uint8 r = pal1[255 * 3 + 0];
158 	uint8 g = pal1[255 * 3 + 1];
159 	uint8 b = pal1[255 * 3 + 2];
160 
161 	if (border255) {
162 		pal1[255 * 3 + 0] = border[0] * 63 / 255;
163 		pal1[255 * 3 + 1] = border[1] * 63 / 255;
164 		pal1[255 * 3 + 2] = border[2] * 63 / 255;
165 	}
166 
167 	win->set_palette(pal1, max_val, brightness);
168 
169 	pal1[255 * 3 + 0] = r;
170 	pal1[255 * 3 + 1] = g;
171 	pal1[255 * 3 + 2] = b;
172 
173 	if (!repaint)
174 		return;
175 	win->show();
176 }
177 
178 /**
179  *  Loads and a xform table and sets palette from a buffer.
180  *  @param buf  What to base palette on.
181  *  @param xfname   xform file name.
182  *  @param xindex   xform index.
183  */
loadxform(const unsigned char * buf,const char * xfname,int & xindex)184 void Palette::loadxform(const unsigned char *buf, const char *xfname, int &xindex) {
185 	U7object xform(xfname, xindex);
186 	size_t xlen;
187 	auto xbuf = xform.retrieve(xlen);
188 	if (!xbuf || xlen <= 0) {
189 		xindex = -1;
190 	} else {
191 		for (int i = 0; i < 256; i++) {
192 			int ix = xbuf[i];
193 			pal1[3 * i] = buf[3 * ix];
194 			pal1[3 * i + 1] = buf[3 * ix + 1];
195 			pal1[3 * i + 2] = buf[3 * ix + 2];
196 		}
197 	}
198 }
199 
200 /**
201  *  Actually loads and sets the palette and xform table.
202  *  @param pal  What is being loaded.
203  *  @param xfname   xform file name.
204  *  @param xindex   xform index.
205  */
set_loaded(const U7multiobject & pal,const char * xfname,int xindex)206 void Palette::set_loaded(
207     const U7multiobject &pal,
208     const char *xfname,
209     int xindex
210 ) {
211 	size_t len;
212 	auto xfbuf = pal.retrieve(len);
213 	const unsigned char *buf = xfbuf.get();
214 	if (len == 768) {
215 		// Simple palette
216 		if (xindex >= 0)
217 			// Get xform table.
218 			loadxform(buf, xfname, xindex);
219 
220 		if (xindex < 0)     // Set the first palette
221 			memcpy(pal1, buf, 768);
222 		// The second one is black.
223 		memset(pal2, 0, 768);
224 	} else if (buf && len > 0) {
225 		// Double palette
226 		for (int i = 0; i < 768; i++) {
227 			pal1[i] = buf[i * 2];
228 			pal2[i] = buf[i * 2 + 1];
229 		}
230 	} else {
231 		// Something went wrong during palette load. This probably
232 		// happens because a dev is being used, which means that
233 		// the palette won't be loaded.
234 		// For now, let's try to avoid overwriting any palette that
235 		// may be loaded and just cleanup.
236 		return;
237 	}
238 }
239 
240 /**
241  *  Loads a palette from the given spec. Optionally loads a
242  *  xform from the desired file.
243  *  @param fname0   Specification of pallete to load.
244  *  @param index    Index of the palette.
245  *  @param xfname   Optional xform file name.
246  *  @param xindex   Optional xform index.
247  */
load(const File_spec & fname0,int index,const char * xfname,int xindex)248 void Palette::load(
249     const File_spec &fname0,
250     int index,
251     const char *xfname,
252     int xindex
253 ) {
254 	U7multiobject pal(fname0, index);
255 	set_loaded(pal, xfname, xindex);
256 }
257 
258 /**
259  *  Loads a palette from the given spec. Optionally loads a
260  *  xform from the desired file.
261  *  @param fname0   Specification of first pallete to load (likely <STATIC>).
262  *  @param fname1   Specification of second pallete to load (likely <PATCH>).
263  *  @param index    Index of the palette.
264  *  @param xfname   Optional xform file name.
265  *  @param xindex   Optional xform index.
266  */
load(const File_spec & fname0,const File_spec & fname1,int index,const char * xfname,int xindex)267 void Palette::load(
268     const File_spec &fname0,
269     const File_spec &fname1,
270     int index,
271     const char *xfname,
272     int xindex
273 ) {
274 	U7multiobject pal(fname0, fname1, index);
275 	set_loaded(pal, xfname, xindex);
276 }
277 
278 /**
279  *  Loads a palette from the given spec. Optionally loads a
280  *  xform from the desired file.
281  *  @param fname0   Specification of first pallete to load (likely <STATIC>).
282  *  @param fname1   Specification of second pallete to load.
283  *  @param fname2   Specification of third pallete to load (likely <PATCH>).
284  *  @param index    Index of the palette.
285  *  @param xfname   Optional xform file name.
286  *  @param xindex   Optional xform index.
287  */
load(const File_spec & fname0,const File_spec & fname1,const File_spec & fname2,int index,const char * xfname,int xindex)288 void Palette::load(
289     const File_spec &fname0,
290     const File_spec &fname1,
291     const File_spec &fname2,
292     int index,
293     const char *xfname,
294     int xindex
295 ) {
296 	U7multiobject pal(fname0, fname1, fname2, index);
297 	set_loaded(pal, xfname, xindex);
298 }
299 
set_brightness(int bright)300 void Palette::set_brightness(int bright) {
301 	brightness = bright;
302 }
303 
fade_in(int cycles)304 void Palette::fade_in(int cycles) {
305 	if (cycles && fades_enabled) {
306 		unsigned char fade_pal[768];
307 		unsigned int ticks = SDL_GetTicks() + 20;
308 		for (int i = 0; i <= cycles; i++) {
309 			uint8 r = pal1[255 * 3 + 0];
310 			uint8 g = pal1[255 * 3 + 1];
311 			uint8 b = pal1[255 * 3 + 2];
312 
313 			if (border255) {
314 				pal1[255 * 3 + 0] = border[0] * 63 / 255;
315 				pal1[255 * 3 + 1] = border[1] * 63 / 255;
316 				pal1[255 * 3 + 2] = border[2] * 63 / 255;
317 			}
318 
319 			for (int c = 0; c < 768; c++)
320 				fade_pal[c] = ((pal1[c] - pal2[c]) * i) / cycles + pal2[c];
321 
322 			pal1[255 * 3 + 0] = r;
323 			pal1[255 * 3 + 1] = g;
324 			pal1[255 * 3 + 2] = b;
325 
326 			win->set_palette(fade_pal, max_val, brightness);
327 
328 			// Frame skipping on slow systems
329 			if (i == cycles || ticks >= SDL_GetTicks() ||
330 			        !Game_window::get_instance()->get_frame_skipping())
331 				win->show();
332 			while (ticks >= SDL_GetTicks())
333 				;
334 			ticks += 20;
335 		}
336 	} else {
337 		uint8 r = pal1[255 * 3 + 0];
338 		uint8 g = pal1[255 * 3 + 1];
339 		uint8 b = pal1[255 * 3 + 2];
340 
341 		if ((palette >= 0 && palette <= 12) && palette != 9) {
342 			pal1[255 * 3 + 0] = border[0] * 63 / 255;
343 			pal1[255 * 3 + 1] = border[1] * 63 / 255;
344 			pal1[255 * 3 + 2] = border[2] * 63 / 255;
345 		}
346 
347 		win->set_palette(pal1, max_val, brightness);
348 
349 		pal1[255 * 3 + 0] = r;
350 		pal1[255 * 3 + 1] = g;
351 		pal1[255 * 3 + 2] = b;
352 
353 		win->show();
354 	}
355 }
356 
fade_out(int cycles)357 void Palette::fade_out(int cycles) {
358 	faded_out = true;       // Be sure to set flag.
359 	if (cycles && fades_enabled) {
360 		unsigned char fade_pal[768];
361 		unsigned int ticks = SDL_GetTicks() + 20;
362 		for (int i = cycles; i >= 0; i--) {
363 			uint8 r = pal1[255 * 3 + 0];
364 			uint8 g = pal1[255 * 3 + 1];
365 			uint8 b = pal1[255 * 3 + 2];
366 
367 			if (border255) {
368 				pal1[255 * 3 + 0] = border[0] * 63 / 255;
369 				pal1[255 * 3 + 1] = border[1] * 63 / 255;
370 				pal1[255 * 3 + 2] = border[2] * 63 / 255;
371 			}
372 
373 			for (int c = 0; c < 768; c++)
374 				fade_pal[c] = ((pal1[c] - pal2[c]) * i) / cycles + pal2[c];
375 
376 			pal1[255 * 3 + 0] = r;
377 			pal1[255 * 3 + 1] = g;
378 			pal1[255 * 3 + 2] = b;
379 
380 			win->set_palette(fade_pal, max_val, brightness);
381 			// Frame skipping on slow systems
382 			if (i == 0 || ticks >= SDL_GetTicks() ||
383 			        !Game_window::get_instance()->get_frame_skipping())
384 				win->show();
385 			while (ticks >= SDL_GetTicks())
386 				;
387 			ticks += 20;
388 		}
389 	} else {
390 		win->set_palette(pal2, max_val, brightness);
391 		win->show();
392 	}
393 //Messes up sleep.          win->set_palette(pal1, max_val, brightness);
394 }
395 
396 //	Find index (0-255) of closest color (r,g,b < 64).
find_color(int r,int g,int b,int last) const397 int Palette::find_color(int r, int g, int b, int last) const {
398 	int best_index = -1;
399 	long best_distance = 0xfffffff;
400 	// But don't search rotating colors.
401 	for (int i = 0; i < last; i++) {
402 		// Get deltas.
403 		long dr = r - pal1[3 * i];
404 		long dg = g - pal1[3 * i + 1];
405 		long db = b - pal1[3 * i + 2];
406 		// Figure distance-squared.
407 		long dist = dr * dr + dg * dg + db * db;
408 		if (dist < best_distance) { // Better than prev?
409 			best_index = i;
410 			best_distance = dist;
411 		}
412 	}
413 	return best_index;
414 }
415 
416 /*
417  *  Creates a translation table between two palettes.
418  */
419 
create_palette_map(const Palette * to,unsigned char * & buf) const420 void Palette::create_palette_map(const Palette *to, unsigned char *&buf) const {
421 	// Assume buf has 256 elements
422 	for (int i = 0; i < 256; i++)
423 		buf[i] = to->find_color(pal1[3 * i], pal1[3 * i + 1], pal1[3 * i + 2], 256);
424 }
425 
426 /*
427  *  Creates a palette in-between two palettes.
428  */
429 
create_intermediate(const Palette & to,int nsteps,int pos) const430 std::unique_ptr<Palette> Palette::create_intermediate(const Palette &to, int nsteps, int pos) const {
431 	auto newpal = std::make_unique<Palette>();
432 	if (fades_enabled) {
433 		for (int c = 0; c < 768; c++)
434 			newpal->pal1[c] = ((to.pal1[c] - pal1[c]) * pos) / nsteps + pal1[c];
435 	} else {
436 		const unsigned char *palold;
437 		if (2 * pos >= nsteps)
438 			palold = to.pal1;
439 		else
440 			palold = pal1;
441 		memcpy(newpal->pal1, palold, 768);
442 	}
443 
444 	// Reset palette data and set.
445 	memset(newpal->pal2, 0, 768);
446 	newpal->border255 = true;
447 	newpal->apply(true);
448 	return newpal;
449 }
450 
451 /*
452  *  Create a translucency table for this palette seen through a given
453  *  color.  (Based on a www.gamedev.net article by Jesse Towner.)
454  */
455 
create_trans_table(unsigned char br,unsigned bg,unsigned bb,int alpha,unsigned char * table) const456 void Palette::create_trans_table(
457     // Color to blend with:
458     unsigned char br, unsigned bg, unsigned bb,
459     int alpha,          // 0-255, applied to 'blend' color.
460     unsigned char *table        // 256 indices are stored here.
461 ) const {
462 	for (int i = 0; i < 256; i++) {
463 		int newr = (static_cast<int>(br) * alpha) / 255 +
464 		           (static_cast<int>(pal1[i * 3]) * (255 - alpha)) / 255;
465 		int newg = (static_cast<int>(bg) * alpha) / 255 +
466 		           (static_cast<int>(pal1[i * 3 + 1]) * (255 - alpha)) / 255;
467 		int newb = (static_cast<int>(bb) * alpha) / 255 +
468 		           (static_cast<int>(pal1[i * 3 + 2]) * (255 - alpha)) / 255;
469 		table[i] = find_color(newr, newg, newb);
470 	}
471 }
472 
show()473 void Palette::show() {
474 	for (int x = 0; x < 16; x++) {
475 		for (int y = 0; y < 16; y++) {
476 			win->fill8(y * 16 + x, 8, 8, x * 8, y * 8);
477 		}
478 	}
479 }
480 
set_color(int nr,int r,int g,int b)481 void Palette::set_color(int nr, int r, int g, int b) {
482 	pal1[nr * 3] = r;
483 	pal1[nr * 3 + 1] = g;
484 	pal1[nr * 3 + 2] = b;
485 }
486 
set_palette(unsigned char palnew[768])487 void Palette::set_palette(unsigned char palnew[768]) {
488 	memcpy(pal1, palnew, 768);
489 	memset(pal2, 0, 768);
490 }
491 
Palette_transition(int from,int to,int ch,int cm,int ct,int r,int nsteps,int sh,int smin,int stick)492 Palette_transition::Palette_transition(
493     int from, int to,
494     int ch, int cm, int ct,
495     int r,
496     int nsteps,
497     int sh, int smin, int stick
498 )
499 	: current(nullptr), step(0), max_steps(nsteps),
500 	  start_hour(sh), start_minute(smin), start_ticks(stick), rate(r) {
501 	start.load(PALETTES_FLX, PATCH_PALETTES, from);
502 	end.load(PALETTES_FLX, PATCH_PALETTES, to);
503 	set_step(ch, cm, ct);
504 }
505 
Palette_transition(Palette * from,int to,int ch,int cm,int ct,int r,int nsteps,int sh,int smin,int stick)506 Palette_transition::Palette_transition(
507     Palette *from, int to,
508     int ch, int cm, int ct,
509     int r,
510     int nsteps,
511     int sh, int smin, int stick
512 )
513 	: start(from), current(nullptr), step(0), max_steps(nsteps),
514 	  start_hour(sh), start_minute(smin), start_ticks(stick), rate(r) {
515 	end.load(PALETTES_FLX, PATCH_PALETTES, to);
516 	set_step(ch, cm, ct);
517 }
518 
Palette_transition(Palette * from,Palette * to,int ch,int cm,int ct,int r,int nsteps,int sh,int smin,int stick)519 Palette_transition::Palette_transition(
520     Palette *from, Palette *to,
521     int ch, int cm, int ct,
522     int r,
523     int nsteps,
524     int sh, int smin, int stick
525 )
526 	: start(from), end(to), current(nullptr), step(0), max_steps(nsteps),
527 	  start_hour(sh), start_minute(smin), start_ticks(stick), rate(r) {
528 	set_step(ch, cm, ct);
529 }
530 
set_step(int hour,int min,int tick)531 bool Palette_transition::set_step(int hour, int min, int tick) {
532 	int new_step = ticks_per_minute * (60 * hour + min) + tick;
533 	int old_step = ticks_per_minute * (60 * start_hour + start_minute) + start_ticks;
534 	new_step -= old_step;
535 	while (new_step < 0)
536 		new_step += 60 * ticks_per_minute;
537 	new_step /= rate;
538 
539 	Game_window *gwin = Game_window::get_instance();
540 	if (gwin->get_pal()->is_faded_out())
541 		return false;
542 
543 	if (!current || new_step != step) {
544 		step = new_step;
545 		current = start.create_intermediate(end, max_steps, step);
546 	}
547 
548 	if (current)
549 		current->apply(true);
550 	return step < max_steps;
551 }
552