1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
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.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "common/file.h"
24 #include "common/timer.h"
25 #include "common/util.h"
26 #include "common/system.h"
27 
28 #include "sci/sci.h"
29 #include "sci/engine/state.h"
30 #include "sci/graphics/cache.h"
31 #include "sci/graphics/maciconbar.h"
32 #include "sci/graphics/palette.h"
33 #include "sci/graphics/remap.h"
34 #include "sci/graphics/screen.h"
35 #include "sci/graphics/view.h"
36 
37 namespace Sci {
38 
GfxPalette(ResourceManager * resMan,GfxScreen * screen)39 GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen)
40 	: _resMan(resMan), _screen(screen) {
41 	int16 color;
42 
43 	_sysPalette.timestamp = 0;
44 	for (color = 0; color < 256; color++) {
45 		_sysPalette.colors[color].used = 0;
46 		_sysPalette.colors[color].r = 0;
47 		_sysPalette.colors[color].g = 0;
48 		_sysPalette.colors[color].b = 0;
49 		_sysPalette.intensity[color] = 100;
50 		_sysPalette.mapping[color] = color;
51 	}
52 	// Black and white are hardcoded
53 	_sysPalette.colors[0].used = 1;
54 	_sysPalette.colors[255].used = 1;
55 	_sysPalette.colors[255].r = 255;
56 	_sysPalette.colors[255].g = 255;
57 	_sysPalette.colors[255].b = 255;
58 
59 	_sysPaletteChanged = false;
60 
61 	// Quest for Glory 3 demo, Eco Quest 1 demo, Laura Bow 2 demo, Police Quest
62 	// 1 vga and all Nick's Picks all use the older palette format and thus are
63 	// not using the SCI1.1 palette merging (copying over all the colors) but
64 	// the real merging done in earlier games. If we use the copying over, we
65 	// will get issues because some views have marked all colors as being used
66 	// and those will overwrite the current palette in that case
67 	if (getSciVersion() < SCI_VERSION_1_1) {
68 		_useMerging = true;
69 		_use16bitColorMatch = true;
70 	} else if (getSciVersion() == SCI_VERSION_1_1) {
71 		// there are some games that use inbetween SCI1.1 interpreter, so we have
72 		// to detect if the current game is merging or copying
73 		_useMerging = _resMan->detectPaletteMergingSci11();
74 		_use16bitColorMatch = _useMerging;
75 		// Note: Laura Bow 2 floppy uses the new palette format and is detected
76 		//        as 8 bit color matching because of that.
77 	} else {
78 	    // SCI32
79 		_useMerging = false;
80 		_use16bitColorMatch = false; // not verified that SCI32 uses 8-bit color matching
81 	}
82 
83 	palVaryInit();
84 
85 	_macClut = 0;
86 	loadMacIconBarPalette();
87 
88 	switch (_resMan->getViewType()) {
89 	case kViewEga:
90 		_totalScreenColors = 16;
91 		break;
92 	case kViewAmiga:
93 		_totalScreenColors = 32;
94 		break;
95 	case kViewAmiga64:
96 		_totalScreenColors = 64;
97 		break;
98 	case kViewVga:
99 	case kViewVga11:
100 			_totalScreenColors = 256;
101 		break;
102 	default:
103 		error("GfxPalette: Unknown view type");
104 	}
105 }
106 
~GfxPalette()107 GfxPalette::~GfxPalette() {
108 	if (_palVaryResourceId != -1)
109 		palVaryRemoveTimer();
110 
111 	delete[] _macClut;
112 }
113 
isMerging()114 bool GfxPalette::isMerging() {
115 	return _useMerging;
116 }
117 
isUsing16bitColorMatch()118 bool GfxPalette::isUsing16bitColorMatch() {
119 	return _use16bitColorMatch;
120 }
121 
122 // meant to get called only once during init of engine
setDefault()123 void GfxPalette::setDefault() {
124 	if (_resMan->getViewType() == kViewEga)
125 		setEGA();
126 	else if (_resMan->getViewType() == kViewAmiga || _resMan->getViewType() == kViewAmiga64)
127 		setAmiga();
128 	else
129 		kernelSetFromResource(999, true);
130 }
131 
132 #define SCI_PAL_FORMAT_CONSTANT 1
133 #define SCI_PAL_FORMAT_VARIABLE 0
134 
createFromData(const SciSpan<const byte> & data,Palette * paletteOut) const135 void GfxPalette::createFromData(const SciSpan<const byte> &data, Palette *paletteOut) const {
136 	int palFormat = 0;
137 	uint palOffset = 0;
138 	uint palColorStart = 0;
139 	uint palColorCount = 0;
140 	uint colorNo = 0;
141 
142 	memset(paletteOut, 0, sizeof(Palette));
143 
144 	// Setup 1:1 mapping
145 	for (colorNo = 0; colorNo < 256; colorNo++) {
146 		paletteOut->mapping[colorNo] = colorNo;
147 	}
148 
149 	if (data.size() < 37) {
150 		// This happens when loading palette of picture 0 in sq5 - the resource is broken and doesn't contain a full
151 		//  palette
152 		debugC(kDebugLevelResMan, "GfxPalette::createFromData() - not enough bytes in resource (%u), expected palette header", data.size());
153 		return;
154 	}
155 
156 	// palette formats in here are not really version exclusive, we can not use sci-version to differentiate between them
157 	//  they were just called that way, because they started appearing in sci1.1 for example
158 	if ((data[0] == 0 && data[1] == 1) || (data[0] == 0 && data[1] == 0 && data.getUint16SEAt(29) == 0)) {
159 		// SCI0/SCI1 palette
160 		palFormat = SCI_PAL_FORMAT_VARIABLE; // CONSTANT;
161 		palOffset = 260;
162 		palColorStart = 0;
163 		palColorCount = 256;
164 	} else {
165 		// SCI1.1 palette
166 		palFormat = data[32];
167 		palOffset = 37;
168 		palColorStart = data[25];
169 		if (g_sci->getGameId() != GID_HOYLE4) {
170 			palColorCount = data.getUint16SEAt(29);
171 		} else {
172 			// HOYLE4's SCI1.1 palettes are little endian even in
173 			// the Mac version, unlike every other SCI1.1 Mac game.
174 			palColorCount = data.getUint16LEAt(29);
175 		}
176 	}
177 
178 	switch (palFormat) {
179 		case SCI_PAL_FORMAT_CONSTANT:
180 			// Check, if enough bytes left
181 			if (data.size() < palOffset + (3 * palColorCount)) {
182 				warning("GfxPalette::createFromData() - not enough bytes in resource, expected palette colors");
183 				return;
184 			}
185 
186 			for (colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) {
187 				paletteOut->colors[colorNo].used = 1;
188 				paletteOut->colors[colorNo].r = data[palOffset++];
189 				paletteOut->colors[colorNo].g = data[palOffset++];
190 				paletteOut->colors[colorNo].b = data[palOffset++];
191 			}
192 			break;
193 		case SCI_PAL_FORMAT_VARIABLE:
194 			if (data.size() < palOffset + (4 * palColorCount)) {
195 				warning("GfxPalette::createFromData() - not enough bytes in resource, expected palette colors");
196 				return;
197 			}
198 
199 			for (colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) {
200 				paletteOut->colors[colorNo].used = data[palOffset++];
201 				paletteOut->colors[colorNo].r = data[palOffset++];
202 				paletteOut->colors[colorNo].g = data[palOffset++];
203 				paletteOut->colors[colorNo].b = data[palOffset++];
204 			}
205 			break;
206 		default:
207 			break;
208 	}
209 }
210 
211 // Will try to set amiga palette by using "spal" file. If not found, we return false
setAmiga()212 bool GfxPalette::setAmiga() {
213 	Common::File file;
214 
215 	if (file.open("spal")) {
216 		for (int curColor = 0; curColor < 32; curColor++) {
217 			byte byte1 = file.readByte();
218 			byte byte2 = file.readByte();
219 
220 			if (file.eos())
221 				error("Amiga palette file ends prematurely");
222 
223 			_sysPalette.colors[curColor].used = 1;
224 			_sysPalette.colors[curColor].r = (byte1 & 0x0F) * 0x11;
225 			_sysPalette.colors[curColor].g = ((byte2 & 0xF0) >> 4) * 0x11;
226 			_sysPalette.colors[curColor].b = (byte2 & 0x0F) * 0x11;
227 
228 			if (_totalScreenColors == 64) {
229 				// Set the associated color from the Amiga halfbrite colors
230 				_sysPalette.colors[curColor + 32].used = 1;
231 				_sysPalette.colors[curColor + 32].r = _sysPalette.colors[curColor].r >> 1;
232 				_sysPalette.colors[curColor + 32].g = _sysPalette.colors[curColor].g >> 1;
233 				_sysPalette.colors[curColor + 32].b = _sysPalette.colors[curColor].b >> 1;
234 			}
235 		}
236 
237 		// Directly set the palette, because setOnScreen() wont do a thing for amiga
238 		copySysPaletteToScreen(true);
239 		return true;
240 	}
241 
242 	return false;
243 }
244 
245 // Called from picture class, some amiga sci1 games set half of the palette
modifyAmigaPalette(const SciSpan<const byte> & data)246 void GfxPalette::modifyAmigaPalette(const SciSpan<const byte> &data) {
247 	int16 curPos = 0;
248 
249 	for (int curColor = 0; curColor < 16; curColor++) {
250 		byte byte1 = data[curPos++];
251 		byte byte2 = data[curPos++];
252 		_sysPalette.colors[curColor].r = (byte1 & 0x0F) * 0x11;
253 		_sysPalette.colors[curColor].g = ((byte2 & 0xF0) >> 4) * 0x11;
254 		_sysPalette.colors[curColor].b = (byte2 & 0x0F) * 0x11;
255 
256 		if (_totalScreenColors == 64) {
257 			// Set the associated color from the Amiga halfbrite colors
258 			_sysPalette.colors[curColor + 32].r = _sysPalette.colors[curColor].r >> 1;
259 			_sysPalette.colors[curColor + 32].g = _sysPalette.colors[curColor].g >> 1;
260 			_sysPalette.colors[curColor + 32].b = _sysPalette.colors[curColor].b >> 1;
261 		}
262 	}
263 
264 	copySysPaletteToScreen(true);
265 }
266 
blendColors(byte c1,byte c2)267 static byte blendColors(byte c1, byte c2) {
268 	// linear
269 	// return (c1/2+c2/2)+((c1&1)+(c2&1))/2;
270 
271 	// gamma 2.2
272 	double t = (pow(c1/255.0, 2.2/1.0) * 255.0) +
273 	           (pow(c2/255.0, 2.2/1.0) * 255.0);
274 	return (byte)(0.5 + (pow(0.5*t/255.0, 1.0/2.2) * 255.0));
275 }
276 
setEGA()277 void GfxPalette::setEGA() {
278 	int curColor;
279 	byte color1, color2;
280 
281 	_sysPalette.colors[1].r  = 0x000; _sysPalette.colors[1].g  = 0x000; _sysPalette.colors[1].b  = 0x0AA;
282 	_sysPalette.colors[2].r  = 0x000; _sysPalette.colors[2].g  = 0x0AA; _sysPalette.colors[2].b  = 0x000;
283 	_sysPalette.colors[3].r  = 0x000; _sysPalette.colors[3].g  = 0x0AA; _sysPalette.colors[3].b  = 0x0AA;
284 	_sysPalette.colors[4].r  = 0x0AA; _sysPalette.colors[4].g  = 0x000; _sysPalette.colors[4].b  = 0x000;
285 	_sysPalette.colors[5].r  = 0x0AA; _sysPalette.colors[5].g  = 0x000; _sysPalette.colors[5].b  = 0x0AA;
286 	_sysPalette.colors[6].r  = 0x0AA; _sysPalette.colors[6].g  = 0x055; _sysPalette.colors[6].b  = 0x000;
287 	_sysPalette.colors[7].r  = 0x0AA; _sysPalette.colors[7].g  = 0x0AA; _sysPalette.colors[7].b  = 0x0AA;
288 	_sysPalette.colors[8].r  = 0x055; _sysPalette.colors[8].g  = 0x055; _sysPalette.colors[8].b  = 0x055;
289 	_sysPalette.colors[9].r  = 0x055; _sysPalette.colors[9].g  = 0x055; _sysPalette.colors[9].b  = 0x0FF;
290 	_sysPalette.colors[10].r = 0x055; _sysPalette.colors[10].g = 0x0FF; _sysPalette.colors[10].b = 0x055;
291 	_sysPalette.colors[11].r = 0x055; _sysPalette.colors[11].g = 0x0FF; _sysPalette.colors[11].b = 0x0FF;
292 	_sysPalette.colors[12].r = 0x0FF; _sysPalette.colors[12].g = 0x055; _sysPalette.colors[12].b = 0x055;
293 	_sysPalette.colors[13].r = 0x0FF; _sysPalette.colors[13].g = 0x055; _sysPalette.colors[13].b = 0x0FF;
294 	_sysPalette.colors[14].r = 0x0FF; _sysPalette.colors[14].g = 0x0FF; _sysPalette.colors[14].b = 0x055;
295 	_sysPalette.colors[15].r = 0x0FF; _sysPalette.colors[15].g = 0x0FF; _sysPalette.colors[15].b = 0x0FF;
296 	for (curColor = 0; curColor <= 15; curColor++) {
297 		_sysPalette.colors[curColor].used = 1;
298 	}
299 	// Now setting colors 16-254 to the correct mix colors that occur when not doing a dithering run on
300 	//  finished pictures
301 	for (curColor = 0x10; curColor <= 0xFE; curColor++) {
302 		_sysPalette.colors[curColor].used = 1;
303 		color1 = curColor & 0x0F; color2 = curColor >> 4;
304 
305 		_sysPalette.colors[curColor].r = blendColors(_sysPalette.colors[color1].r, _sysPalette.colors[color2].r);
306 		_sysPalette.colors[curColor].g = blendColors(_sysPalette.colors[color1].g, _sysPalette.colors[color2].g);
307 		_sysPalette.colors[curColor].b = blendColors(_sysPalette.colors[color1].b, _sysPalette.colors[color2].b);
308 	}
309 	_sysPalette.timestamp = 1;
310 	setOnScreen();
311 }
312 
set(Palette * newPalette,bool force,bool forceRealMerge,bool includeFirstColor)313 void GfxPalette::set(Palette *newPalette, bool force, bool forceRealMerge, bool includeFirstColor) {
314 	uint32 systime = _sysPalette.timestamp;
315 
316 	if (force || newPalette->timestamp != systime) {
317 		// SCI1.1+ doesn't do real merging anymore, but simply copying over the used colors from other palettes
318 		//  There are some games with inbetween SCI1.1 interpreters, use real merging for them (e.g. laura bow 2 demo)
319 		if (forceRealMerge || _useMerging) {
320 			_sysPaletteChanged |= merge(newPalette, force, forceRealMerge);
321 		} else {
322 			// SCI 1.1 has several functions which write to the system palette but
323 			//  some include the first color and others don't. The first color is
324 			//  always excluded when Pal-vary is active.
325 			includeFirstColor &= (_palVaryResourceId == -1);
326 
327 			_sysPaletteChanged |= insert(newPalette, &_sysPalette, includeFirstColor);
328 		}
329 
330 		// Adjust timestamp on newPalette, so it wont get merged/inserted w/o need
331 		newPalette->timestamp = _sysPalette.timestamp;
332 
333 		bool updatePalette = _sysPaletteChanged && _screen->_picNotValid == 0;
334 
335 		if (_palVaryResourceId != -1) {
336 			// Pal-vary currently active, we don't set at any time, but also insert into origin palette
337 			insert(newPalette, &_palVaryOriginPalette);
338 			palVaryProcess(0, updatePalette);
339 			return;
340 		}
341 
342 		if (updatePalette) {
343 			setOnScreen();
344 			_sysPaletteChanged = false;
345 		}
346 	}
347 }
348 
insert(Palette * newPalette,Palette * destPalette,bool includeFirstColor)349 bool GfxPalette::insert(Palette *newPalette, Palette *destPalette, bool includeFirstColor) {
350 	bool paletteChanged = false;
351 
352 	for (int i = (includeFirstColor ? 0 : 1); i < 255; i++) {
353 		if (newPalette->colors[i].used) {
354 			if ((newPalette->colors[i].r != destPalette->colors[i].r) ||
355 				(newPalette->colors[i].g != destPalette->colors[i].g) ||
356 				(newPalette->colors[i].b != destPalette->colors[i].b)) {
357 				destPalette->colors[i].r = newPalette->colors[i].r;
358 				destPalette->colors[i].g = newPalette->colors[i].g;
359 				destPalette->colors[i].b = newPalette->colors[i].b;
360 				paletteChanged = true;
361 			}
362 			destPalette->colors[i].used = newPalette->colors[i].used;
363 			newPalette->mapping[i] = i;
364 		}
365 	}
366 
367 	// We don't update the timestamp for SCI1.1, it's only updated on kDrawPic calls
368 	return paletteChanged;
369 }
370 
merge(Palette * newPalette,bool force,bool forceRealMerge)371 bool GfxPalette::merge(Palette *newPalette, bool force, bool forceRealMerge) {
372 	uint16 res;
373 	bool paletteChanged = false;
374 
375 	for (int i = 1; i < 255; i++) {
376 		// skip unused colors
377 		if (!newPalette->colors[i].used)
378 			continue;
379 
380 		// forced palette merging or dest color is not used yet
381 		if (force || (!_sysPalette.colors[i].used)) {
382 			_sysPalette.colors[i].used = newPalette->colors[i].used;
383 			if ((newPalette->colors[i].r != _sysPalette.colors[i].r) ||
384 				(newPalette->colors[i].g != _sysPalette.colors[i].g) ||
385 				(newPalette->colors[i].b != _sysPalette.colors[i].b)) {
386 				_sysPalette.colors[i].r = newPalette->colors[i].r;
387 				_sysPalette.colors[i].g = newPalette->colors[i].g;
388 				_sysPalette.colors[i].b = newPalette->colors[i].b;
389 				paletteChanged = true;
390 			}
391 			newPalette->mapping[i] = i;
392 			continue;
393 		}
394 
395 		// is the same color already at the same position? -> match it directly w/o lookup
396 		//  this fixes games like lsl1demo/sq5 where the same rgb color exists multiple times and where we would
397 		//  otherwise match the wrong one (which would result into the pixels affected (or not) by palette changes)
398 		if ((_sysPalette.colors[i].r == newPalette->colors[i].r) &&
399 			(_sysPalette.colors[i].g == newPalette->colors[i].g) &&
400 			(_sysPalette.colors[i].b == newPalette->colors[i].b)) {
401 			newPalette->mapping[i] = i;
402 			continue;
403 		}
404 
405 		// check if exact color could be matched
406 		res = matchColor(newPalette->colors[i].r, newPalette->colors[i].g, newPalette->colors[i].b);
407 		if (res & SCI_PALETTE_MATCH_PERFECT) { // exact match was found
408 			newPalette->mapping[i] = res & SCI_PALETTE_MATCH_COLORMASK;
409 			continue;
410 		}
411 
412 		int j = 1;
413 
414 		// no exact match - see if there is an unused color
415 		for (; j < 256; j++) {
416 			if (!_sysPalette.colors[j].used) {
417 				_sysPalette.colors[j].used = newPalette->colors[i].used;
418 				_sysPalette.colors[j].r = newPalette->colors[i].r;
419 				_sysPalette.colors[j].g = newPalette->colors[i].g;
420 				_sysPalette.colors[j].b = newPalette->colors[i].b;
421 				newPalette->mapping[i] = j;
422 				paletteChanged = true;
423 				break;
424 			}
425 		}
426 
427 		// if still no luck - set an approximate color
428 		if (j == 256) {
429 			newPalette->mapping[i] = res & SCI_PALETTE_MATCH_COLORMASK;
430 			_sysPalette.colors[res & SCI_PALETTE_MATCH_COLORMASK].used |= 0x10;
431 		}
432 	}
433 
434 	if (!forceRealMerge)
435 		_sysPalette.timestamp = g_system->getMillis() * 60 / 1000;
436 
437 	return paletteChanged;
438 }
439 
440 // This is called for SCI1.1, when kDrawPic got done. We update sysPalette timestamp this way for SCI1.1 and also load
441 //  target-palette, if palvary is active
drewPicture(GuiResourceId pictureId)442 void GfxPalette::drewPicture(GuiResourceId pictureId) {
443 	if (!_useMerging) // Don't do this on inbetween SCI1.1 games
444 		_sysPalette.timestamp++;
445 
446 	if (_palVaryResourceId != -1) {
447 		if (g_sci->getEngineState()->gameIsRestarting == 0) // only if not restored nor restarted
448 			palVaryLoadTargetPalette(pictureId);
449 	}
450 }
451 
matchColor(byte matchRed,byte matchGreen,byte matchBlue,bool force16BitColorMatch)452 uint16 GfxPalette::matchColor(byte matchRed, byte matchGreen, byte matchBlue, bool force16BitColorMatch) {
453 	int16 colorNr;
454 	int16 differenceRed, differenceGreen, differenceBlue;
455 	int16 differenceTotal = 0;
456 	int16 bestDifference = 0x7FFF;
457 	uint16 bestColorNr = 255;
458 
459 	if (_use16bitColorMatch || force16BitColorMatch) {
460 		// used by SCI0 to SCI1, also by the first few SCI1.1 games
461 		for (colorNr = 0; colorNr < 256; colorNr++) {
462 			if ((!_sysPalette.colors[colorNr].used))
463 				continue;
464 			differenceRed = ABS(_sysPalette.colors[colorNr].r - matchRed);
465 			differenceGreen = ABS(_sysPalette.colors[colorNr].g - matchGreen);
466 			differenceBlue = ABS(_sysPalette.colors[colorNr].b - matchBlue);
467 			differenceTotal = differenceRed + differenceGreen + differenceBlue;
468 			if (differenceTotal <= bestDifference) {
469 				bestDifference = differenceTotal;
470 				bestColorNr = colorNr;
471 			}
472 		}
473 	} else {
474 		// SCI1.1, starting with QfG3 introduced a bug in the matching code
475 		// we have to implement it as well, otherwise some colors will be "wrong" in comparison to the original interpreter
476 		//  See Space Quest 5 bug #6455
477 		for (colorNr = 0; colorNr < 256; colorNr++) {
478 			if ((!_sysPalette.colors[colorNr].used))
479 				continue;
480 			differenceRed = (uint8)ABS<int8>(_sysPalette.colors[colorNr].r - matchRed);
481 			differenceGreen = (uint8)ABS<int8>(_sysPalette.colors[colorNr].g - matchGreen);
482 			differenceBlue = (uint8)ABS<int8>(_sysPalette.colors[colorNr].b - matchBlue);
483 			differenceTotal = differenceRed + differenceGreen + differenceBlue;
484 			if (differenceTotal <= bestDifference) {
485 				bestDifference = differenceTotal;
486 				bestColorNr = colorNr;
487 			}
488 		}
489 	}
490 	if (differenceTotal == 0) // original interpreter does not do this, instead it does 2 calls for merges in the worst case
491 		return bestColorNr | SCI_PALETTE_MATCH_PERFECT; // we set this flag, so that we can optimize during palette merge
492 	return bestColorNr;
493 }
494 
getSys(Palette * pal)495 void GfxPalette::getSys(Palette *pal) {
496 	if (pal != &_sysPalette)
497 		memcpy(pal, &_sysPalette,sizeof(Palette));
498 }
499 
setOnScreen(bool update)500 void GfxPalette::setOnScreen(bool update) {
501 	copySysPaletteToScreen(update);
502 }
503 
convertMacGammaToSCIGamma(int comp)504 static byte convertMacGammaToSCIGamma(int comp) {
505 	return (byte)sqrt(comp * 255.0f);
506 }
507 
copySysPaletteToScreen(bool update)508 void GfxPalette::copySysPaletteToScreen(bool update) {
509 	// just copy palette to system
510 	byte bpal[3 * 256];
511 
512 	// Get current palette, update it and put back
513 	_screen->grabPalette(bpal, 0, 256);
514 
515 	for (int16 i = 0; i < 256; i++) {
516 		if (colorIsFromMacClut(i)) {
517 			// If we've got a Mac CLUT, override the SCI palette with its non-black colors
518 			bpal[i * 3    ] = convertMacGammaToSCIGamma(_macClut[i * 3    ]);
519 			bpal[i * 3 + 1] = convertMacGammaToSCIGamma(_macClut[i * 3 + 1]);
520 			bpal[i * 3 + 2] = convertMacGammaToSCIGamma(_macClut[i * 3 + 2]);
521 		} else if (_sysPalette.colors[i].used != 0) {
522 			// Otherwise, copy to the screen
523 			bpal[i * 3    ] = CLIP(_sysPalette.colors[i].r * _sysPalette.intensity[i] / 100, 0, 255);
524 			bpal[i * 3 + 1] = CLIP(_sysPalette.colors[i].g * _sysPalette.intensity[i] / 100, 0, 255);
525 			bpal[i * 3 + 2] = CLIP(_sysPalette.colors[i].b * _sysPalette.intensity[i] / 100, 0, 255);
526 		}
527 	}
528 
529 	if (g_sci->_gfxRemap16)
530 		g_sci->_gfxRemap16->updateRemapping();
531 
532 	_screen->setPalette(bpal, 0, 256, update);
533 }
534 
kernelSetFromResource(GuiResourceId resourceId,bool force)535 bool GfxPalette::kernelSetFromResource(GuiResourceId resourceId, bool force) {
536 	Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
537 	Palette palette;
538 
539 	if (palResource) {
540 		createFromData(*palResource, &palette);
541 		set(&palette, force);
542 		return true;
543 	}
544 
545 	return false;
546 }
547 
kernelSetFlag(uint16 fromColor,uint16 toColor,uint16 flag)548 void GfxPalette::kernelSetFlag(uint16 fromColor, uint16 toColor, uint16 flag) {
549 	uint16 colorNr;
550 	for (colorNr = fromColor; colorNr < toColor; colorNr++) {
551 		_sysPalette.colors[colorNr].used |= flag;
552 	}
553 }
554 
kernelUnsetFlag(uint16 fromColor,uint16 toColor,uint16 flag)555 void GfxPalette::kernelUnsetFlag(uint16 fromColor, uint16 toColor, uint16 flag) {
556 	uint16 colorNr;
557 	for (colorNr = fromColor; colorNr < toColor; colorNr++) {
558 		_sysPalette.colors[colorNr].used &= ~flag;
559 	}
560 }
561 
kernelSetIntensity(uint16 fromColor,uint16 toColor,uint16 intensity,bool setPalette)562 void GfxPalette::kernelSetIntensity(uint16 fromColor, uint16 toColor, uint16 intensity, bool setPalette) {
563 	memset(&_sysPalette.intensity[0] + fromColor, intensity, toColor - fromColor);
564 	if (setPalette) {
565 		setOnScreen();
566 	}
567 }
568 
kernelFindColor(uint16 r,uint16 g,uint16 b,bool force16BitColorMatch)569 int16 GfxPalette::kernelFindColor(uint16 r, uint16 g, uint16 b, bool force16BitColorMatch) {
570 	return matchColor(r, g, b, force16BitColorMatch) & SCI_PALETTE_MATCH_COLORMASK;
571 }
572 
573 // Returns true, if palette got changed
kernelAnimate(byte fromColor,byte toColor,int speed)574 bool GfxPalette::kernelAnimate(byte fromColor, byte toColor, int speed) {
575 	Color col;
576 	//byte colorNr;
577 	int16 colorCount;
578 	uint32 now = g_sci->getTickCount();
579 
580 	// search for sheduled animations with the same 'from' value
581 	// schedule animation...
582 	int scheduleCount = _schedules.size();
583 	int scheduleNr;
584 	for (scheduleNr = 0; scheduleNr < scheduleCount; scheduleNr++) {
585 		if (_schedules[scheduleNr].from == fromColor)
586 			break;
587 	}
588 	if (scheduleNr == scheduleCount) {
589 		// adding a new schedule
590 		PalSchedule newSchedule;
591 		newSchedule.from = fromColor;
592 		newSchedule.schedule = now + ABS(speed);
593 		_schedules.push_back(newSchedule);
594 		scheduleCount++;
595 	}
596 
597 	g_sci->getEngineState()->_throttleTrigger = true;
598 
599 	for (scheduleNr = 0; scheduleNr < scheduleCount; scheduleNr++) {
600 		if (_schedules[scheduleNr].from == fromColor) {
601 			if (_schedules[scheduleNr].schedule <= now) {
602 				if (speed > 0) {
603 					// TODO: Not really sure about this, sierra sci seems to do exactly this here
604 					col = _sysPalette.colors[fromColor];
605 					if (fromColor < toColor) {
606 						colorCount = toColor - fromColor - 1;
607 						memmove(&_sysPalette.colors[fromColor], &_sysPalette.colors[fromColor + 1], colorCount * sizeof(Color));
608 					}
609 					_sysPalette.colors[toColor - 1] = col;
610 				} else {
611 					col = _sysPalette.colors[toColor - 1];
612 					if (fromColor < toColor) {
613 						colorCount = toColor - fromColor - 1;
614 						memmove(&_sysPalette.colors[fromColor + 1], &_sysPalette.colors[fromColor], colorCount * sizeof(Color));
615 					}
616 					_sysPalette.colors[fromColor] = col;
617 				}
618 				// removing schedule
619 				_schedules[scheduleNr].schedule = now + ABS(speed);
620 				// TODO: Not sure when sierra actually removes a schedule
621 				//_schedules.remove_at(scheduleNr);
622 				return true;
623 			}
624 			return false;
625 		}
626 	}
627 	return false;
628 }
629 
kernelAnimateSet()630 void GfxPalette::kernelAnimateSet() {
631 	setOnScreen();
632 }
633 
kernelSave()634 reg_t GfxPalette::kernelSave() {
635 	SegManager *segMan = g_sci->getEngineState()->_segMan;
636 	reg_t memoryId = segMan->allocateHunkEntry("kPalette(save)", 1024);
637 	byte *memoryPtr = segMan->getHunkPointer(memoryId);
638 	if (memoryPtr) {
639 		for (int colorNr = 0; colorNr < 256; colorNr++) {
640 			*memoryPtr++ = _sysPalette.colors[colorNr].used;
641 			*memoryPtr++ = _sysPalette.colors[colorNr].r;
642 			*memoryPtr++ = _sysPalette.colors[colorNr].g;
643 			*memoryPtr++ = _sysPalette.colors[colorNr].b;
644 		}
645 	}
646 	return memoryId;
647 }
648 
kernelRestore(reg_t memoryHandle)649 void GfxPalette::kernelRestore(reg_t memoryHandle) {
650 	SegManager *segMan = g_sci->getEngineState()->_segMan;
651 	if (!memoryHandle.isNull()) {
652 		byte *memoryPtr = segMan->getHunkPointer(memoryHandle);
653 		if (!memoryPtr)
654 			error("Bad handle used for kPalette(restore)");
655 
656 		Palette restoredPalette;
657 
658 		restoredPalette.timestamp = 0;
659 		for (int colorNr = 0; colorNr < 256; colorNr++) {
660 			restoredPalette.colors[colorNr].used = *memoryPtr++;
661 			restoredPalette.colors[colorNr].r = *memoryPtr++;
662 			restoredPalette.colors[colorNr].g = *memoryPtr++;
663 			restoredPalette.colors[colorNr].b = *memoryPtr++;
664 		}
665 
666 		// restoring excludes the first color, unlike most
667 		//  operations on the system palette.
668 		set(&restoredPalette, true, false, false);
669 	}
670 }
671 
kernelAssertPalette(GuiResourceId resourceId)672 void GfxPalette::kernelAssertPalette(GuiResourceId resourceId) {
673 	GfxView *view = g_sci->_gfxCache->getView(resourceId);
674 	Palette *viewPalette = view->getPalette();
675 	if (viewPalette) {
676 		// merge/insert this palette
677 		set(viewPalette, true);
678 	}
679 }
680 
kernelSyncScreenPalette()681 void GfxPalette::kernelSyncScreenPalette() {
682 	// just copy palette to system
683 	byte bpal[3 * 256];
684 	_screen->grabPalette(bpal, 0, 256);
685 
686 	for (int16 i = 1; i < 255; i++) {
687 		_sysPalette.colors[i].r = bpal[i * 3];
688 		_sysPalette.colors[i].g = bpal[i * 3 + 1];
689 		_sysPalette.colors[i].b = bpal[i * 3 + 2];
690 	}
691 }
692 
693 // palVary
694 //  init - only does, if palVaryOn == false
695 //         target, start, new palette allocation
696 //         palVaryOn = true
697 //         palDirection = 1
698 //         palStop = 64
699 //         palTime = from caller
700 //         copy resource palette to target
701 //         init target palette (used = 1 on all colors, color 0 = RGB 0, 0, 0 color 255 = RGB 0xFF, 0xFF, 0xFF
702 //         copy sysPalette to startPalette
703 //         init new palette like target palette
704 //         palPercent = 1
705 //         do various things
706 //         return 1
707 //  deinit - unloads target palette, kills timer hook, disables palVaryOn
708 //  pause - counts up or down, if counter != 0 -> signal wont get counted up by timer
709 //           will only count down to 0
710 //
711 // Restarting game
712 //         palVary = false
713 //         palPercent = 0
714 //         call palVary deinit
715 //
716 // Saving/restoring
717 //         need to save start and target-palette, when palVaryOn = true
718 
palVaryInit()719 void GfxPalette::palVaryInit() {
720 	_palVaryResourceId = -1;
721 	_palVaryPaused = 0;
722 	_palVarySignal = 0;
723 	_palVaryStep = 0;
724 	_palVaryStepStop = 0;
725 	_palVaryDirection = 0;
726 	_palVaryTicks = 0;
727 	_palVaryZeroTick = false;
728 }
729 
palVaryLoadTargetPalette(GuiResourceId resourceId)730 bool GfxPalette::palVaryLoadTargetPalette(GuiResourceId resourceId) {
731 	_palVaryResourceId = (resourceId != 65535) ? resourceId : -1;
732 	Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
733 	if (palResource) {
734 		// Load and initialize destination palette
735 		createFromData(*palResource, &_palVaryTargetPalette);
736 		return true;
737 	}
738 	return false;
739 }
740 
palVaryInstallTimer()741 void GfxPalette::palVaryInstallTimer() {
742 	// Remove any possible leftover palVary timer callbacks.
743 	// This happens for example in QFG1VGA, when sleeping at Erana's place
744 	// (bug #5900) - the nighttime to daytime effect clashes with the
745 	// scene transition effect, as we load scene images too quickly for
746 	// the SCI scripts in that case (also refer to kernelPalVaryInit).
747 	palVaryRemoveTimer();
748 
749 	int16 ticks = _palVaryTicks > 0 ? _palVaryTicks : 1;
750 	// Call signal increase every [ticks]
751 	g_sci->getTimerManager()->installTimerProc(&palVaryCallback, 1000000 / 60 * ticks, this, "sciPalette");
752 }
753 
palVaryRemoveTimer()754 void GfxPalette::palVaryRemoveTimer() {
755 	g_sci->getTimerManager()->removeTimerProc(&palVaryCallback);
756 }
757 
kernelPalVaryInit(GuiResourceId resourceId,uint16 ticks,uint16 stepStop,uint16 direction)758 bool GfxPalette::kernelPalVaryInit(GuiResourceId resourceId, uint16 ticks, uint16 stepStop, uint16 direction) {
759 	if (_palVaryResourceId != -1)	// another palvary is taking place, return
760 		return false;
761 
762 	if (palVaryLoadTargetPalette(resourceId)) {
763 		// Save current palette
764 		memcpy(&_palVaryOriginPalette, &_sysPalette, sizeof(Palette));
765 
766 		_palVarySignal = 0;
767 		_palVaryTicks = ticks;
768 		_palVaryStep = 1;
769 		_palVaryStepStop = stepStop;
770 		_palVaryDirection = direction;
771 
772 		// if no ticks are given, jump directly to destination
773 		if (!_palVaryTicks)
774 			_palVaryDirection = stepStop;
775 		_palVaryZeroTick = (_palVaryTicks == 0); //see delayForPalVaryWorkaround()
776 
777 		palVaryInstallTimer();
778 		return true;
779 	}
780 	return false;
781 }
782 
kernelPalVaryReverse(int16 ticks,uint16 stepStop,int16 direction)783 int16 GfxPalette::kernelPalVaryReverse(int16 ticks, uint16 stepStop, int16 direction) {
784 	if (_palVaryResourceId == -1)
785 		return 0;
786 
787 	if (_palVaryStep > 64)
788 		_palVaryStep = 64;
789 	if (ticks != -1)
790 		_palVaryTicks = ticks;
791 	_palVaryStepStop = stepStop;
792 	_palVaryDirection = direction != -1 ? -direction : -_palVaryDirection;
793 
794 	// if no ticks are given, jump directly to destination
795 	if (!_palVaryTicks)
796 		_palVaryDirection = _palVaryStepStop - _palVaryStep;
797 	_palVaryZeroTick = (_palVaryTicks == 0); // see delayForPalVaryWorkaround()
798 
799 	palVaryInstallTimer();
800 
801 	return kernelPalVaryGetCurrentStep();
802 }
803 
kernelPalVaryGetCurrentStep()804 int16 GfxPalette::kernelPalVaryGetCurrentStep() {
805 	if (_palVaryDirection >= 0)
806 		return _palVaryStep;
807 	return -_palVaryStep;
808 }
809 
kernelPalVaryChangeTarget(GuiResourceId resourceId)810 int16 GfxPalette::kernelPalVaryChangeTarget(GuiResourceId resourceId) {
811 	if (_palVaryResourceId != -1) {
812 		Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
813 		if (palResource) {
814 			Palette insertPalette;
815 			createFromData(*palResource, &insertPalette);
816 			// insert new palette into target
817 			insert(&insertPalette, &_palVaryTargetPalette);
818 			// update palette and set on screen
819 			palVaryProcess(0, true);
820 		}
821 	}
822 	return kernelPalVaryGetCurrentStep();
823 }
824 
kernelPalVaryChangeTicks(uint16 ticks)825 void GfxPalette::kernelPalVaryChangeTicks(uint16 ticks) {
826 	_palVaryTicks = ticks;
827 	if (_palVaryStep - _palVaryStepStop) {
828 		palVaryRemoveTimer();
829 		palVaryInstallTimer();
830 	}
831 }
832 
kernelPalVaryPause(bool pause)833 void GfxPalette::kernelPalVaryPause(bool pause) {
834 	if (_palVaryResourceId == -1)
835 		return;
836 	// this call is actually counting states, so calling this 3 times with true will require calling it later
837 	// 3 times with false to actually remove pause
838 	if (pause) {
839 		_palVaryPaused++;
840 	} else {
841 		if (_palVaryPaused)
842 			_palVaryPaused--;
843 	}
844 }
845 
kernelPalVaryDeinit()846 void GfxPalette::kernelPalVaryDeinit() {
847 	palVaryRemoveTimer();
848 
849 	_palVaryResourceId = -1;	// invalidate the target palette
850 }
851 
palVaryCallback(void * refCon)852 void GfxPalette::palVaryCallback(void *refCon) {
853 	((GfxPalette *)refCon)->palVaryIncreaseSignal();
854 }
855 
palVaryIncreaseSignal()856 void GfxPalette::palVaryIncreaseSignal() {
857 	// FIXME: increments from another thread aren't guaranteed to be atomic
858 	if (!_palVaryPaused)
859 		_palVarySignal++;
860 	_palVaryZeroTick = false;
861 }
862 
863 // Actually do the pal vary processing
palVaryUpdate()864 void GfxPalette::palVaryUpdate() {
865 	if (_palVarySignal) {
866 		palVaryProcess(_palVarySignal, true);
867 		_palVarySignal = 0;
868 	}
869 }
870 
delayForPalVaryWorkaround()871 void GfxPalette::delayForPalVaryWorkaround() {
872 	if (_palVaryResourceId == -1)
873 		return;
874 	if (_palVaryPaused)
875 		return;
876 
877 	// This gets called at the very beginning of kAnimate.
878 	// If a zero-tick palVary is running, we delay briefly to give the
879 	// palVary time to trigger. In theory there should be no reason for this
880 	// to have to wait more than a tick, but we time-out after 4 ticks
881 	// to be on the safe side.
882 	//
883 	// This prevents a race condition in Freddy Pharkas during nighttime,
884 	// since we load pictures much faster than on original hardware (bug #5298).
885 
886 	if (_palVaryZeroTick) {
887 		int i;
888 		for (i = 0; i < 4; ++i) {
889 			g_sci->sleep(17);
890 			if (!_palVaryZeroTick)
891 				break;
892 		}
893 		debugC(kDebugLevelGraphics, "Delayed kAnimate for kPalVary, %d times", i+1);
894 		if (_palVaryZeroTick)
895 			warning("Delayed kAnimate for kPalVary timed out");
896 	}
897 }
898 
palVaryPrepareForTransition()899 void GfxPalette::palVaryPrepareForTransition() {
900 	if (_palVaryResourceId != -1) {
901 		// Before doing transitions, we have to prepare palette
902 		palVaryProcess(0, false);
903 	}
904 }
905 
906 // Processes pal vary updates
palVaryProcess(int signal,bool setPalette)907 void GfxPalette::palVaryProcess(int signal, bool setPalette) {
908 	int16 stepChange = signal * _palVaryDirection;
909 
910 	_palVaryStep += stepChange;
911 	if (stepChange > 0) {
912 		if (_palVaryStep > _palVaryStepStop)
913 			_palVaryStep = _palVaryStepStop;
914 	} else {
915 		if (_palVaryStep < _palVaryStepStop) {
916 			if (signal)
917 				_palVaryStep = _palVaryStepStop;
918 		}
919 	}
920 
921 	// We don't need updates anymore, if we reached end-position
922 	if (_palVaryStep == _palVaryStepStop)
923 		palVaryRemoveTimer();
924 	if (_palVaryStep == 0)
925 		_palVaryResourceId = -1;
926 
927 	// Calculate inbetween palette
928 	Color inbetween;
929 	int16 color;
930 	for (int colorNr = 0; colorNr < 256; colorNr++) {
931 		inbetween.used = _sysPalette.colors[colorNr].used;
932 		color = _palVaryTargetPalette.colors[colorNr].r - _palVaryOriginPalette.colors[colorNr].r;
933 		inbetween.r = ((color * _palVaryStep) / 64) + _palVaryOriginPalette.colors[colorNr].r;
934 		color = _palVaryTargetPalette.colors[colorNr].g - _palVaryOriginPalette.colors[colorNr].g;
935 		inbetween.g = ((color * _palVaryStep) / 64) + _palVaryOriginPalette.colors[colorNr].g;
936 		color = _palVaryTargetPalette.colors[colorNr].b - _palVaryOriginPalette.colors[colorNr].b;
937 		inbetween.b = ((color * _palVaryStep) / 64) + _palVaryOriginPalette.colors[colorNr].b;
938 
939 		if (memcmp(&inbetween, &_sysPalette.colors[colorNr], sizeof(Color))) {
940 			_sysPalette.colors[colorNr] = inbetween;
941 			_sysPaletteChanged = true;
942 		}
943 	}
944 
945 	if ((_sysPaletteChanged) && (setPalette) && (_screen->_picNotValid == 0)) {
946 		setOnScreen();
947 		_sysPaletteChanged = false;
948 	}
949 }
950 
getMacColorDiff(byte r1,byte g1,byte b1,byte r2,byte g2,byte b2)951 static inline uint getMacColorDiff(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2) {
952 	// Use the difference of the top 4 bits and add together the differences
953 	return ABS((r2 & 0xf0) - (r1 & 0xf0)) + ABS((g2 & 0xf0) - (g1 & 0xf0)) + ABS((b2 & 0xf0) - (b1 & 0xf0));
954 }
955 
findMacIconBarColor(byte r,byte g,byte b)956 byte GfxPalette::findMacIconBarColor(byte r, byte g, byte b) {
957 	// Find the best color for use with the Mac icon bar
958 	// Check white, then the palette colors, and then black
959 
960 	// Try white first
961 	byte found = 0xff;
962 	uint diff = getMacColorDiff(r, g, b, 0xff, 0xff, 0xff);
963 
964 	if (diff == 0)
965 		return found;
966 
967 	// Go through the main colors of the CLUT
968 	for (uint16 i = 1; i < 255; i++) {
969 		if (!colorIsFromMacClut(i))
970 			continue;
971 
972 		uint cdiff = getMacColorDiff(r, g, b, _macClut[i * 3], _macClut[i * 3 + 1], _macClut[i * 3 + 2]);
973 
974 		if (cdiff == 0)
975 			return i;
976 		else if (cdiff < diff) {
977 			found = i;
978 			diff = cdiff;
979 		}
980 	}
981 
982 	// Also check black here
983 	if (getMacColorDiff(r, g, b, 0, 0, 0) < diff)
984 		return 0;
985 
986 	return found;
987 }
988 
loadMacIconBarPalette()989 void GfxPalette::loadMacIconBarPalette() {
990 	if (!g_sci->hasMacIconBar())
991 		return;
992 
993 	Common::SeekableReadStream *clutStream = g_sci->getMacExecutable()->getResource(MKTAG('c','l','u','t'), 150);
994 
995 	if (!clutStream)
996 		error("Could not find clut 150 for the Mac icon bar");
997 
998 	clutStream->readUint32BE(); // seed
999 	clutStream->readUint16BE(); // flags
1000 	uint16 colorCount = clutStream->readUint16BE() + 1;
1001 	assert(colorCount == 256);
1002 
1003 	_macClut = new byte[256 * 3];
1004 
1005 	for (uint16 i = 0; i < colorCount; i++) {
1006 		clutStream->readUint16BE();
1007 		_macClut[i * 3    ] = clutStream->readUint16BE() >> 8;
1008 		_macClut[i * 3 + 1] = clutStream->readUint16BE() >> 8;
1009 		_macClut[i * 3 + 2] = clutStream->readUint16BE() >> 8;
1010 	}
1011 
1012 	// Adjust bounds on the KQ6 palette
1013 	// We don't use all of it for the icon bar
1014 	if (g_sci->getGameId() == GID_KQ6)
1015 		memset(_macClut + 32 * 3, 0, (256 - 32) * 3);
1016 
1017 	// Force black/white
1018 	_macClut[0x00 * 3    ] = 0;
1019 	_macClut[0x00 * 3 + 1] = 0;
1020 	_macClut[0x00 * 3 + 2] = 0;
1021 	_macClut[0xff * 3    ] = 0xff;
1022 	_macClut[0xff * 3 + 1] = 0xff;
1023 	_macClut[0xff * 3 + 2] = 0xff;
1024 
1025 	delete clutStream;
1026 }
1027 
colorIsFromMacClut(byte index)1028 bool GfxPalette::colorIsFromMacClut(byte index) {
1029 	return index != 0 && _macClut && (_macClut[index * 3] != 0 || _macClut[index * 3 + 1] != 0 || _macClut[index * 3 + 2] != 0);
1030 }
1031 
1032 } // End of namespace Sci
1033