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/system.h"
25 #include "graphics/palette.h"
26 
27 #include "sci/sci.h"
28 #include "sci/event.h"
29 #include "sci/resource/resource.h"
30 #include "sci/util.h"
31 #include "sci/engine/features.h"
32 #include "sci/graphics/palette32.h"
33 #include "sci/graphics/remap32.h"
34 #include "sci/graphics/screen.h"
35 
36 namespace Sci {
37 
38 #pragma mark HunkPalette
39 
HunkPalette(const SciSpan<const byte> & rawPalette)40 HunkPalette::HunkPalette(const SciSpan<const byte> &rawPalette) :
41 	_version(0),
42 	// The header size in palettes is garbage. In at least KQ7 2.00b and Phant1,
43 	// the 999.pal sets this value to 0. In most other palettes it is set to 14,
44 	// but the *actual* size of the header structure used in SSCI is 13, which
45 	// is reflected by `kHunkPaletteHeaderSize`.
46 	_numPalettes(rawPalette.getUint8At(kNumPaletteEntriesOffset)),
47 	_data() {
48 	assert(_numPalettes == 0 || _numPalettes == 1);
49 	if (_numPalettes) {
50 		_data = rawPalette;
51 		_version = getEntryHeader().version;
52 	}
53 }
54 
write(SciSpan<byte> & out,const Palette & palette)55 void HunkPalette::write(SciSpan<byte> &out, const Palette &palette) {
56 	const uint8 numPalettes = 1;
57 	const uint16 paletteOffset = kHunkPaletteHeaderSize + 2 * numPalettes;
58 
59 	out[kNumPaletteEntriesOffset] = numPalettes;
60 	out[kHunkPaletteHeaderSize + 2] = paletteOffset;
61 
62 	SciSpan<byte> entry = out.subspan(paletteOffset);
63 	entry[kEntryStartColorOffset] = 0;
64 	entry.setUint16SEAt(kEntryNumColorsOffset, ARRAYSIZE(palette.colors));
65 	entry[kEntryUsedOffset] = 1;
66 	entry[kEntrySharedUsedOffset] = 0;
67 	entry.setUint32SEAt(kEntryVersionOffset, 1);
68 
69 	SciSpan<byte> paletteData = entry.subspan(kEntryHeaderSize);
70 	for (uint i = 0; i < ARRAYSIZE(palette.colors); ++i) {
71 		*paletteData++ = palette.colors[i].used;
72 		*paletteData++ = palette.colors[i].r;
73 		*paletteData++ = palette.colors[i].g;
74 		*paletteData++ = palette.colors[i].b;
75 	}
76 }
77 
setVersion(const uint32 version) const78 void HunkPalette::setVersion(const uint32 version) const {
79 	if (_numPalettes != _data.getUint8At(kNumPaletteEntriesOffset)) {
80 		error("Invalid HunkPalette");
81 	}
82 
83 	if (_numPalettes) {
84 		const EntryHeader header = getEntryHeader();
85 		if (header.version != _version) {
86 			error("Invalid HunkPalette");
87 		}
88 
89 		byte *palette = const_cast<byte *>(getPalPointer().getUnsafeDataAt(kEntryVersionOffset, sizeof(uint32)));
90 		WRITE_SCI11ENDIAN_UINT32(palette, version);
91 		_version = version;
92 	}
93 }
94 
getEntryHeader() const95 const HunkPalette::EntryHeader HunkPalette::getEntryHeader() const {
96 	const SciSpan<const byte> data(getPalPointer());
97 
98 	EntryHeader header;
99 	header.startColor = data.getUint8At(kEntryStartColorOffset);
100 	header.numColors = data.getUint16SEAt(kEntryNumColorsOffset);
101 	header.used = data.getUint8At(kEntryUsedOffset);
102 	header.sharedUsed = data.getUint8At(kEntrySharedUsedOffset);
103 	header.version = data.getUint32SEAt(kEntryVersionOffset);
104 
105 	return header;
106 }
107 
toPalette() const108 const Palette HunkPalette::toPalette() const {
109 	Palette outPalette;
110 
111 	// Set outPalette structures to 0
112 	for (int16 i = 0; i < ARRAYSIZE(outPalette.mapping); ++i) {
113 		outPalette.mapping[i] = 0;
114 	}
115 	outPalette.timestamp = 0;
116 	for (int16 i = 0; i < ARRAYSIZE(outPalette.colors); ++i) {
117 		outPalette.colors[i].used = false;
118 		outPalette.colors[i].r = 0;
119 		outPalette.colors[i].g = 0;
120 		outPalette.colors[i].b = 0;
121 	}
122 	for (int16 i = 0; i < ARRAYSIZE(outPalette.intensity); ++i) {
123 		outPalette.intensity[i] = 0;
124 	}
125 
126 	if (_numPalettes) {
127 		const EntryHeader header = getEntryHeader();
128 		const uint32 dataSize = header.numColors * (/* RGB */ 3 + (header.sharedUsed ? 0 : 1));
129 		const byte *data = getPalPointer().getUnsafeDataAt(kEntryHeaderSize, dataSize);
130 
131 		const int16 end = header.startColor + header.numColors;
132 		assert(end <= 256);
133 
134 		if (header.sharedUsed) {
135 			for (int16 i = header.startColor; i < end; ++i) {
136 				outPalette.colors[i].used = header.used;
137 				outPalette.colors[i].r = *data++;
138 				outPalette.colors[i].g = *data++;
139 				outPalette.colors[i].b = *data++;
140 			}
141 		} else {
142 			for (int16 i = header.startColor; i < end; ++i) {
143 				outPalette.colors[i].used = *data++;
144 				outPalette.colors[i].r = *data++;
145 				outPalette.colors[i].g = *data++;
146 				outPalette.colors[i].b = *data++;
147 			}
148 		}
149 	}
150 
151 	return outPalette;
152 }
153 
154 #pragma mark -
155 #pragma mark Gamma correction tables
156 
157 static const uint8 gammaTables[GfxPalette32::numGammaTables][256] = {
158 	{ 0, 2, 3, 5, 6, 7, 9, 10,
159 	11, 13, 14, 15, 16, 18, 19, 20,
160 	21, 22, 23, 25, 26, 27, 28, 29,
161 	30, 32, 33, 34, 35, 36, 37, 38,
162 	39, 41, 42, 43, 44, 45, 46, 47,
163 	48, 49, 50, 51, 52, 54, 55, 56,
164 	57, 58, 59, 60, 61, 62, 63, 64,
165 	65, 66, 67, 68, 69, 70, 71, 72,
166 	74, 75, 76, 77, 78, 79, 80, 81,
167 	82, 83, 84, 85, 86, 87, 88, 89,
168 	90, 91, 92, 93, 94, 95, 96, 97,
169 	98, 99, 100, 101, 102, 103, 104, 105,
170 	106, 107, 108, 109, 110, 111, 112, 113,
171 	114, 115, 116, 117, 118, 119, 120, 121,
172 	122, 123, 124, 125, 126, 127, 128, 128,
173 	129, 130, 131, 132, 133, 134, 135, 136,
174 	137, 138, 139, 140, 141, 142, 143, 144,
175 	145, 146, 147, 148, 149, 150, 151, 152,
176 	153, 153, 154, 155, 156, 157, 158, 159,
177 	160, 161, 162, 163, 164, 165, 166, 167,
178 	168, 169, 170, 171, 171, 172, 173, 174,
179 	175, 176, 177, 178, 179, 180, 181, 182,
180 	183, 184, 185, 186, 186, 187, 188, 189,
181 	190, 191, 192, 193, 194, 195, 196, 197,
182 	198, 199, 199, 200, 201, 202, 203, 204,
183 	205, 206, 207, 208, 209, 210, 211, 211,
184 	212, 213, 214, 215, 216, 217, 218, 219,
185 	220, 221, 222, 222, 223, 224, 225, 226,
186 	227, 228, 229, 230, 231, 232, 232, 233,
187 	234, 235, 236, 237, 238, 239, 240, 241,
188 	242, 242, 243, 244, 245, 246, 247, 248,
189 	249, 250, 251, 251, 252, 253, 254, 255 },
190 
191 	{ 0, 3, 5, 6, 8, 10, 11, 13,
192 	14, 16, 17, 19, 20, 22, 23, 24,
193 	26, 27, 28, 30, 31, 32, 33, 35,
194 	36, 37, 38, 40, 41, 42, 43, 44,
195 	46, 47, 48, 49, 50, 51, 53, 54,
196 	55, 56, 57, 58, 59, 60, 62, 63,
197 	64, 65, 66, 67, 68, 69, 70, 71,
198 	73, 74, 75, 76, 77, 78, 79, 80,
199 	81, 82, 83, 84, 85, 86, 87, 88,
200 	89, 90, 91, 92, 93, 94, 95, 96,
201 	97, 99, 100, 101, 102, 103, 104, 105,
202 	106, 107, 108, 108, 109, 110, 111, 112,
203 	113, 114, 115, 116, 117, 118, 119, 120,
204 	121, 122, 123, 124, 125, 126, 127, 128,
205 	129, 130, 131, 132, 133, 134, 135, 136,
206 	136, 137, 138, 139, 140, 141, 142, 143,
207 	144, 145, 146, 147, 148, 149, 150, 151,
208 	151, 152, 153, 154, 155, 156, 157, 158,
209 	159, 160, 161, 162, 162, 163, 164, 165,
210 	166, 167, 168, 169, 170, 171, 172, 172,
211 	173, 174, 175, 176, 177, 178, 179, 180,
212 	180, 181, 182, 183, 184, 185, 186, 187,
213 	188, 188, 189, 190, 191, 192, 193, 194,
214 	195, 196, 196, 197, 198, 199, 200, 201,
215 	202, 202, 203, 204, 205, 206, 207, 208,
216 	209, 209, 210, 211, 212, 213, 214, 215,
217 	215, 216, 217, 218, 219, 220, 221, 221,
218 	222, 223, 224, 225, 226, 227, 227, 228,
219 	229, 230, 231, 232, 233, 233, 234, 235,
220 	236, 237, 238, 238, 239, 240, 241, 242,
221 	243, 243, 244, 245, 246, 247, 248, 249,
222 	249, 250, 251, 252, 253, 254, 254, 255 },
223 
224 	{ 0, 4, 6, 8, 10, 12, 14, 16,
225 	18, 19, 21, 23, 24, 26, 27, 29,
226 	30, 32, 33, 35, 36, 37, 39, 40,
227 	41, 43, 44, 45, 47, 48, 49, 50,
228 	52, 53, 54, 55, 57, 58, 59, 60,
229 	61, 62, 64, 65, 66, 67, 68, 69,
230 	71, 72, 73, 74, 75, 76, 77, 78,
231 	79, 81, 82, 83, 84, 85, 86, 87,
232 	88, 89, 90, 91, 92, 93, 94, 95,
233 	96, 97, 98, 99, 100, 102, 103, 104,
234 	105, 106, 107, 108, 109, 110, 111, 112,
235 	112, 113, 114, 115, 116, 117, 118, 119,
236 	120, 121, 122, 123, 124, 125, 126, 127,
237 	128, 129, 130, 131, 132, 133, 134, 135,
238 	135, 136, 137, 138, 139, 140, 141, 142,
239 	143, 144, 145, 146, 146, 147, 148, 149,
240 	150, 151, 152, 153, 154, 155, 156, 156,
241 	157, 158, 159, 160, 161, 162, 163, 163,
242 	164, 165, 166, 167, 168, 169, 170, 170,
243 	171, 172, 173, 174, 175, 176, 177, 177,
244 	178, 179, 180, 181, 182, 183, 183, 184,
245 	185, 186, 187, 188, 188, 189, 190, 191,
246 	192, 193, 194, 194, 195, 196, 197, 198,
247 	199, 199, 200, 201, 202, 203, 203, 204,
248 	205, 206, 207, 208, 208, 209, 210, 211,
249 	212, 212, 213, 214, 215, 216, 217, 217,
250 	218, 219, 220, 221, 221, 222, 223, 224,
251 	225, 225, 226, 227, 228, 229, 229, 230,
252 	231, 232, 233, 233, 234, 235, 236, 237,
253 	237, 238, 239, 240, 240, 241, 242, 243,
254 	244, 244, 245, 246, 247, 247, 248, 249,
255 	250, 251, 251, 252, 253, 254, 254, 255 },
256 
257 	{ 0, 5, 9, 11, 14, 16, 19, 21,
258 	23, 25, 26, 28, 30, 32, 33, 35,
259 	37, 38, 40, 41, 43, 44, 46, 47,
260 	49, 50, 52, 53, 54, 56, 57, 58,
261 	60, 61, 62, 64, 65, 66, 67, 69,
262 	70, 71, 72, 73, 75, 76, 77, 78,
263 	79, 80, 82, 83, 84, 85, 86, 87,
264 	88, 89, 91, 92, 93, 94, 95, 96,
265 	97, 98, 99, 100, 101, 102, 103, 104,
266 	105, 106, 107, 108, 109, 110, 111, 112,
267 	113, 114, 115, 116, 117, 118, 119, 120,
268 	121, 122, 123, 124, 125, 126, 127, 128,
269 	129, 130, 131, 132, 133, 134, 134, 135,
270 	136, 137, 138, 139, 140, 141, 142, 143,
271 	144, 144, 145, 146, 147, 148, 149, 150,
272 	151, 152, 152, 153, 154, 155, 156, 157,
273 	158, 158, 159, 160, 161, 162, 163, 164,
274 	164, 165, 166, 167, 168, 169, 169, 170,
275 	171, 172, 173, 174, 174, 175, 176, 177,
276 	178, 179, 179, 180, 181, 182, 183, 183,
277 	184, 185, 186, 187, 187, 188, 189, 190,
278 	191, 191, 192, 193, 194, 195, 195, 196,
279 	197, 198, 199, 199, 200, 201, 202, 202,
280 	203, 204, 205, 205, 206, 207, 208, 209,
281 	209, 210, 211, 212, 212, 213, 214, 215,
282 	215, 216, 217, 218, 218, 219, 220, 221,
283 	221, 222, 223, 224, 224, 225, 226, 227,
284 	227, 228, 229, 230, 230, 231, 232, 232,
285 	233, 234, 235, 235, 236, 237, 238, 238,
286 	239, 240, 240, 241, 242, 243, 243, 244,
287 	245, 245, 246, 247, 248, 248, 249, 250,
288 	250, 251, 252, 252, 253, 254, 255, 255 },
289 
290 	{ 0, 9, 14, 18, 21, 24, 27, 29,
291 	32, 34, 37, 39, 41, 43, 45, 47,
292 	48, 50, 52, 54, 55, 57, 59, 60,
293 	62, 63, 65, 66, 68, 69, 71, 72,
294 	73, 75, 76, 77, 79, 80, 81, 83,
295 	84, 85, 86, 88, 89, 90, 91, 92,
296 	94, 95, 96, 97, 98, 99, 100, 102,
297 	103, 104, 105, 106, 107, 108, 109, 110,
298 	111, 112, 113, 114, 115, 116, 117, 118,
299 	119, 120, 121, 122, 123, 124, 125, 126,
300 	127, 128, 129, 130, 131, 132, 133, 134,
301 	135, 136, 137, 137, 138, 139, 140, 141,
302 	142, 143, 144, 145, 145, 146, 147, 148,
303 	149, 150, 151, 151, 152, 153, 154, 155,
304 	156, 156, 157, 158, 159, 160, 161, 161,
305 	162, 163, 164, 165, 165, 166, 167, 168,
306 	169, 169, 170, 171, 172, 173, 173, 174,
307 	175, 176, 176, 177, 178, 179, 179, 180,
308 	181, 182, 182, 183, 184, 185, 185, 186,
309 	187, 188, 188, 189, 190, 191, 191, 192,
310 	193, 194, 194, 195, 196, 196, 197, 198,
311 	199, 199, 200, 201, 201, 202, 203, 203,
312 	204, 205, 206, 206, 207, 208, 208, 209,
313 	210, 210, 211, 212, 212, 213, 214, 214,
314 	215, 216, 216, 217, 218, 218, 219, 220,
315 	220, 221, 222, 222, 223, 224, 224, 225,
316 	226, 226, 227, 228, 228, 229, 230, 230,
317 	231, 231, 232, 233, 233, 234, 235, 235,
318 	236, 237, 237, 238, 238, 239, 240, 240,
319 	241, 242, 242, 243, 243, 244, 245, 245,
320 	246, 247, 247, 248, 248, 249, 250, 250,
321 	251, 251, 252, 253, 253, 254, 254, 255 },
322 
323 	{ 0, 16, 23, 28, 32, 36, 39, 42,
324 	45, 48, 50, 53, 55, 58, 60, 62,
325 	64, 66, 68, 70, 71, 73, 75, 77,
326 	78, 80, 81, 83, 84, 86, 87, 89,
327 	90, 92, 93, 94, 96, 97, 98, 100,
328 	101, 102, 103, 105, 106, 107, 108, 109,
329 	111, 112, 113, 114, 115, 116, 117, 118,
330 	119, 121, 122, 123, 124, 125, 126, 127,
331 	128, 129, 130, 131, 132, 133, 134, 135,
332 	135, 136, 137, 138, 139, 140, 141, 142,
333 	143, 144, 145, 145, 146, 147, 148, 149,
334 	150, 151, 151, 152, 153, 154, 155, 156,
335 	156, 157, 158, 159, 160, 160, 161, 162,
336 	163, 164, 164, 165, 166, 167, 167, 168,
337 	169, 170, 170, 171, 172, 173, 173, 174,
338 	175, 176, 176, 177, 178, 179, 179, 180,
339 	181, 181, 182, 183, 183, 184, 185, 186,
340 	186, 187, 188, 188, 189, 190, 190, 191,
341 	192, 192, 193, 194, 194, 195, 196, 196,
342 	197, 198, 198, 199, 199, 200, 201, 201,
343 	202, 203, 203, 204, 204, 205, 206, 206,
344 	207, 208, 208, 209, 209, 210, 211, 211,
345 	212, 212, 213, 214, 214, 215, 215, 216,
346 	217, 217, 218, 218, 219, 220, 220, 221,
347 	221, 222, 222, 223, 224, 224, 225, 225,
348 	226, 226, 227, 228, 228, 229, 229, 230,
349 	230, 231, 231, 232, 233, 233, 234, 234,
350 	235, 235, 236, 236, 237, 237, 238, 238,
351 	239, 240, 240, 241, 241, 242, 242, 243,
352 	243, 244, 244, 245, 245, 246, 246, 247,
353 	247, 248, 248, 249, 249, 250, 250, 251,
354 	251, 252, 252, 253, 253, 254, 254, 255 }
355 };
356 
357 #pragma mark -
358 #pragma mark GfxPalette32
359 
GfxPalette32(ResourceManager * resMan)360 	GfxPalette32::GfxPalette32(ResourceManager *resMan)
361 	: _resMan(resMan),
362 
363 	// Palette versioning
364 	_version(1),
365 	_needsUpdate(false),
366 #ifdef USE_RGB_COLOR
367 	_hardwarePalette(),
368 #endif
369 	_currentPalette(),
370 	_sourcePalette(),
371 	_nextPalette(),
372 
373 	// Palette varying
374 	_varyStartPalette(nullptr),
375 	_varyTargetPalette(nullptr),
376 	_varyFromColor(0),
377 	_varyToColor(255),
378 	_varyLastTick(0),
379 	_varyTime(0),
380 	_varyDirection(0),
381 	_varyPercent(0),
382 	_varyTargetPercent(0),
383 	_varyNumTimesPaused(0),
384 
385 	// Palette cycling
386 	_cycleMap(),
387 
388 	// Gamma correction
389 	_gammaLevel(g_sci->_features->useMacGammaLevel() ? 2 : -1),
390 	_gammaChanged(false) {
391 
392 	for (int i = 0, len = ARRAYSIZE(_fadeTable); i < len; ++i) {
393 		_fadeTable[i] = 100;
394 	}
395 
396 	loadPalette(999);
397 }
398 
loadPalette(const GuiResourceId resourceId)399 bool GfxPalette32::loadPalette(const GuiResourceId resourceId) {
400 	Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
401 
402 	if (!palResource) {
403 		return false;
404 	}
405 
406 	const HunkPalette palette(*palResource);
407 	submit(palette);
408 	return true;
409 }
410 
matchColor(const uint8 r,const uint8 g,const uint8 b)411 int16 GfxPalette32::matchColor(const uint8 r, const uint8 g, const uint8 b) {
412 	int16 bestIndex = 0;
413 	int bestDifference = 0xFFFFF;
414 	int difference;
415 
416 	for (int i = 0, channelDifference; i < g_sci->_gfxRemap32->getStartColor(); ++i) {
417 		difference = _currentPalette.colors[i].r - r;
418 		difference *= difference;
419 		if (bestDifference <= difference) {
420 			continue;
421 		}
422 
423 		channelDifference = _currentPalette.colors[i].g - g;
424 		difference += channelDifference * channelDifference;
425 		if (bestDifference <= difference) {
426 			continue;
427 		}
428 
429 		channelDifference = _currentPalette.colors[i].b - b;
430 		difference += channelDifference * channelDifference;
431 		if (bestDifference <= difference) {
432 			continue;
433 		}
434 		bestDifference = difference;
435 		bestIndex = i;
436 	}
437 
438 	return bestIndex;
439 }
440 
getPlatformBlack() const441 uint8 GfxPalette32::getPlatformBlack() const {
442 	return (g_sci->getPlatform() == Common::kPlatformMacintosh) ? 255 : 0;
443 }
444 
getPlatformWhite() const445 uint8 GfxPalette32::getPlatformWhite() const {
446 	return (g_sci->getPlatform() == Common::kPlatformMacintosh) ? 0 : 255;
447 }
448 
submit(const Palette & palette)449 void GfxPalette32::submit(const Palette &palette) {
450 	// If `_needsUpdate` is already set, there is no need to test whether
451 	// this palette submission causes a change to `_sourcePalette` since it is
452 	// going to be updated already anyway
453 	if (_needsUpdate) {
454 		mergePalette(_sourcePalette, palette);
455 	} else {
456 		const Palette oldSourcePalette(_sourcePalette);
457 		mergePalette(_sourcePalette, palette);
458 
459 		if (_sourcePalette != oldSourcePalette) {
460 			++_version;
461 			_needsUpdate = true;
462 		}
463 	}
464 }
465 
submit(const HunkPalette & hunkPalette)466 void GfxPalette32::submit(const HunkPalette &hunkPalette) {
467 	if (hunkPalette.getVersion() == _version) {
468 		return;
469 	}
470 
471 	submit(hunkPalette.toPalette());
472 	hunkPalette.setVersion(_version);
473 }
474 
updateForFrame()475 bool GfxPalette32::updateForFrame() {
476 	applyAll();
477 	_needsUpdate = false;
478 	return g_sci->_gfxRemap32->remapAllTables(_nextPalette != _currentPalette);
479 }
480 
updateFFrame()481 void GfxPalette32::updateFFrame() {
482 	for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
483 		_nextPalette.colors[i] = _sourcePalette.colors[i];
484 	}
485 	_needsUpdate = false;
486 	g_sci->_gfxRemap32->remapAllTables(_nextPalette != _currentPalette);
487 }
488 
updateHardware()489 void GfxPalette32::updateHardware() {
490 	if (_currentPalette == _nextPalette && !_gammaChanged) {
491 		return;
492 	}
493 
494 #ifdef USE_RGB_COLOR
495 	uint8 *bpal = _hardwarePalette;
496 #else
497 	uint8 bpal[256 * 3];
498 #endif
499 
500 	// HACK: There are resources in a couple of Windows-only games that seem to
501 	// include bogus palette entries above 236. SSCI does a lot of extra work
502 	// when running in Windows to shift palettes and rewrite view & pic pixel
503 	// data on-the-fly to account for the way Windows palettes work, which
504 	// seems to end up masking the fact that there is some bad palette data.
505 	// Since only one demo and one game seem to have this problem, we instead
506 	// "fix" the problem here by ignoring attempts to send high palette entries
507 	// to the backend. This makes those high pixels render black, which seems to
508 	// match what would happen in the original interpreter, and saves us from
509 	// having to clutter up the engine with a bunch of palette shifting garbage.
510 	//
511 	// This workaround also handles Mac games, as they use 236 for black to avoid
512 	//  conflicting with the operating system's palette which uses 0 for white.
513 	int maxIndex = ARRAYSIZE(_currentPalette.colors) - 2;
514 	if (g_sci->getGameId() == GID_HOYLE5 ||
515 		(g_sci->getGameId() == GID_GK2 && g_sci->isDemo()) ||
516 		g_sci->getPlatform() == Common::kPlatformMacintosh) {
517 		maxIndex = 235;
518 	}
519 
520 	for (int i = 0; i <= maxIndex; ++i) {
521 		_currentPalette.colors[i] = _nextPalette.colors[i];
522 
523 		// All color entries MUST be copied, not just "used" entries, otherwise
524 		// uninitialised memory from bpal makes its way into the system palette.
525 		// This would not normally be a problem, except that games sometimes use
526 		// unused palette entries. e.g. Phant1 title screen references palette
527 		// entries outside its own palette, so will render garbage colors where
528 		// the game expects them to be black
529 		if (_gammaLevel == -1) {
530 			bpal[i * 3    ] = _currentPalette.colors[i].r;
531 			bpal[i * 3 + 1] = _currentPalette.colors[i].g;
532 			bpal[i * 3 + 2] = _currentPalette.colors[i].b;
533 		} else {
534 			bpal[i * 3    ] = gammaTables[_gammaLevel][_currentPalette.colors[i].r];
535 			bpal[i * 3 + 1] = gammaTables[_gammaLevel][_currentPalette.colors[i].g];
536 			bpal[i * 3 + 2] = gammaTables[_gammaLevel][_currentPalette.colors[i].b];
537 		}
538 	}
539 
540 #ifndef USE_RGB_COLOR
541 	// When creating a raw palette on the stack, any skipped area of the palette
542 	// needs to be blacked out or else it will contain garbage memory
543 	memset(bpal + (maxIndex + 1) * 3, 0, (255 - maxIndex - 1) * 3);
544 #endif
545 
546 	// The last color must always be white
547 	bpal[255 * 3    ] = 255;
548 	bpal[255 * 3 + 1] = 255;
549 	bpal[255 * 3 + 2] = 255;
550 
551 	// If the system is in a high color mode, which can happen during video
552 	// playback, attempting to send the palette to OSystem is illegal and will
553 	// result in a crash
554 	if (g_system->getScreenFormat().bytesPerPixel == 1) {
555 		g_system->getPaletteManager()->setPalette(bpal, 0, 256);
556 	}
557 
558 	_gammaChanged = false;
559 }
560 
getPaletteFromResource(const GuiResourceId resourceId) const561 Palette GfxPalette32::getPaletteFromResource(const GuiResourceId resourceId) const {
562 	Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
563 
564 	if (!palResource) {
565 		error("Could not load vary palette %d", resourceId);
566 	}
567 
568 	const HunkPalette rawPalette(*palResource);
569 	return rawPalette.toPalette();
570 }
571 
mergePalette(Palette & to,const Palette & from)572 void GfxPalette32::mergePalette(Palette &to, const Palette &from) {
573 	// All colors MUST be copied, even index 255, despite the fact that games
574 	// cannot actually change index 255 (it is forced to white when generating
575 	// the hardware palette in updateHardware). While this causes some
576 	// additional unnecessary source palette invalidations, not doing it breaks
577 	// some badly programmed rooms, like room 6400 in Phant1 (see Trac#9788).
578 	// (Note, however, that that specific glitch is fully fixed by ignoring a
579 	// bad palette in the CelObjView constructor)
580 	for (int i = 0; i < ARRAYSIZE(to.colors); ++i) {
581 		if (from.colors[i].used) {
582 			to.colors[i] = from.colors[i];
583 		}
584 	}
585 }
586 
applyAll()587 void GfxPalette32::applyAll() {
588 	applyVary();
589 	applyCycles();
590 	applyFade();
591 }
592 
593 #pragma mark -
594 #pragma mark Varying
595 
setVary(const Palette & target,const int16 percent,const int32 ticks,const int16 fromColor,const int16 toColor)596 void GfxPalette32::setVary(const Palette &target, const int16 percent, const int32 ticks, const int16 fromColor, const int16 toColor) {
597 	setTarget(target);
598 	setVaryTime(percent, ticks);
599 
600 	if (fromColor > -1) {
601 		_varyFromColor = fromColor;
602 	}
603 	if (toColor > -1) {
604 		assert(toColor < 256);
605 		_varyToColor = toColor;
606 	}
607 }
608 
setVaryPercent(const int16 percent,const int32 ticks)609 void GfxPalette32::setVaryPercent(const int16 percent, const int32 ticks) {
610 	if (_varyTargetPalette) {
611 		setVaryTime(percent, ticks);
612 	}
613 
614 	// SSCI had two additional parameters for this function to change the
615 	// `_varyFromColor`, but they were always hardcoded to be ignored
616 }
617 
setVaryTime(const int32 time)618 void GfxPalette32::setVaryTime(const int32 time) {
619 	if (_varyTargetPalette) {
620 		setVaryTime(_varyTargetPercent, time);
621 	}
622 }
623 
setVaryTime(const int16 percent,const int32 ticks)624 void GfxPalette32::setVaryTime(const int16 percent, const int32 ticks) {
625 	_varyLastTick = g_sci->getTickCount();
626 	if (!ticks || _varyPercent == percent) {
627 		_varyDirection = 0;
628 		_varyTargetPercent = _varyPercent = percent;
629 	} else {
630 		_varyTime = ticks / (percent - _varyPercent);
631 		_varyTargetPercent = percent;
632 
633 		if (_varyTime > 0) {
634 			_varyDirection = 1;
635 		} else if (_varyTime < 0) {
636 			_varyDirection = -1;
637 			_varyTime = -_varyTime;
638 		} else {
639 			_varyDirection = 0;
640 			_varyTargetPercent = _varyPercent = percent;
641 		}
642 	}
643 }
644 
varyOff()645 void GfxPalette32::varyOff() {
646 	_varyNumTimesPaused = 0;
647 	_varyPercent = _varyTargetPercent = 0;
648 	_varyFromColor = 0;
649 	_varyToColor = 255;
650 	_varyDirection = 0;
651 	_varyTargetPalette.reset();
652 	_varyStartPalette.reset();
653 }
654 
varyPause()655 void GfxPalette32::varyPause() {
656 	_varyDirection = 0;
657 	++_varyNumTimesPaused;
658 }
659 
varyOn()660 void GfxPalette32::varyOn() {
661 	if (_varyNumTimesPaused > 0) {
662 		--_varyNumTimesPaused;
663 	}
664 
665 	if (_varyTargetPalette && _varyNumTimesPaused == 0) {
666 		if (_varyPercent != _varyTargetPercent && _varyTime != 0) {
667 			_varyDirection = (_varyTargetPercent - _varyPercent > 0) ? 1 : -1;
668 		} else {
669 			_varyPercent = _varyTargetPercent;
670 		}
671 	}
672 }
673 
setTarget(const Palette & palette)674 void GfxPalette32::setTarget(const Palette &palette) {
675 	_varyTargetPalette.reset(new Palette(palette));
676 }
677 
setStart(const Palette & palette)678 void GfxPalette32::setStart(const Palette &palette) {
679 	_varyStartPalette.reset(new Palette(palette));
680 }
681 
mergeStart(const Palette & palette)682 void GfxPalette32::mergeStart(const Palette &palette) {
683 	if (_varyStartPalette) {
684 		mergePalette(*_varyStartPalette, palette);
685 	} else {
686 		_varyStartPalette.reset(new Palette(palette));
687 	}
688 }
689 
mergeTarget(const Palette & palette)690 void GfxPalette32::mergeTarget(const Palette &palette) {
691 	if (_varyTargetPalette) {
692 		mergePalette(*_varyTargetPalette, palette);
693 	} else {
694 		_varyTargetPalette.reset(new Palette(palette));
695 	}
696 }
697 
applyVary()698 void GfxPalette32::applyVary() {
699 	const uint32 now = g_sci->getTickCount();
700 	while ((int32)(now - _varyLastTick) > _varyTime && _varyDirection != 0) {
701 		_varyLastTick += _varyTime;
702 
703 		if (_varyPercent == _varyTargetPercent) {
704 			_varyDirection = 0;
705 		}
706 
707 		_varyPercent += _varyDirection;
708 	}
709 
710 	if (_varyPercent == 0 || !_varyTargetPalette) {
711 		for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
712 			if (_varyStartPalette && i >= _varyFromColor && i <= _varyToColor) {
713 				_nextPalette.colors[i] = _varyStartPalette->colors[i];
714 			} else {
715 				_nextPalette.colors[i] = _sourcePalette.colors[i];
716 			}
717 		}
718 	} else {
719 		for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
720 			if (i >= _varyFromColor && i <= _varyToColor) {
721 				Color targetColor = _varyTargetPalette->colors[i];
722 				Color sourceColor;
723 
724 				if (_varyStartPalette) {
725 					sourceColor = _varyStartPalette->colors[i];
726 				} else {
727 					sourceColor = _sourcePalette.colors[i];
728 				}
729 
730 				Color computedColor;
731 
732 				int color;
733 				color = targetColor.r - sourceColor.r;
734 				computedColor.r = ((color * _varyPercent) / 100) + sourceColor.r;
735 				color = targetColor.g - sourceColor.g;
736 				computedColor.g = ((color * _varyPercent) / 100) + sourceColor.g;
737 				color = targetColor.b - sourceColor.b;
738 				computedColor.b = ((color * _varyPercent) / 100) + sourceColor.b;
739 				computedColor.used = sourceColor.used;
740 
741 				_nextPalette.colors[i] = computedColor;
742 			}
743 			else {
744 				_nextPalette.colors[i] = _sourcePalette.colors[i];
745 			}
746 		}
747 	}
748 }
749 
kernelPalVarySet(const GuiResourceId paletteId,const int16 percent,const int32 ticks,const int16 fromColor,const int16 toColor)750 void GfxPalette32::kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int32 ticks, const int16 fromColor, const int16 toColor) {
751 	Palette palette;
752 
753 	if (getSciVersion() == SCI_VERSION_3 && paletteId == 0xFFFF) {
754 		palette = _currentPalette;
755 		assert(fromColor >= 0 && fromColor < 256);
756 		assert(toColor >= 0 && toColor < 256);
757 		// While palette varying is normally inclusive of `toColor`, the
758 		// palette inversion code in SSCI excludes `toColor`, and RAMA room
759 		// 6201 requires this or else parts of the game's UI get inverted
760 		for (int i = fromColor; i < toColor; ++i) {
761 			palette.colors[i].r = ~palette.colors[i].r;
762 			palette.colors[i].g = ~palette.colors[i].g;
763 			palette.colors[i].b = ~palette.colors[i].b;
764 		}
765 	} else {
766 		palette = getPaletteFromResource(paletteId);
767 	}
768 
769 	setVary(palette, percent, ticks, fromColor, toColor);
770 }
771 
kernelPalVaryMergeTarget(const GuiResourceId paletteId)772 void GfxPalette32::kernelPalVaryMergeTarget(const GuiResourceId paletteId) {
773 	const Palette palette = getPaletteFromResource(paletteId);
774 	mergeTarget(palette);
775 }
776 
kernelPalVarySetTarget(const GuiResourceId paletteId)777 void GfxPalette32::kernelPalVarySetTarget(const GuiResourceId paletteId) {
778 	const Palette palette = getPaletteFromResource(paletteId);
779 	setTarget(palette);
780 }
781 
kernelPalVarySetStart(const GuiResourceId paletteId)782 void GfxPalette32::kernelPalVarySetStart(const GuiResourceId paletteId) {
783 	const Palette palette = getPaletteFromResource(paletteId);
784 	setStart(palette);
785 }
786 
kernelPalVaryMergeStart(const GuiResourceId paletteId)787 void GfxPalette32::kernelPalVaryMergeStart(const GuiResourceId paletteId) {
788 	const Palette palette = getPaletteFromResource(paletteId);
789 	mergeStart(palette);
790 }
791 
kernelPalVaryPause(const bool pause)792 void GfxPalette32::kernelPalVaryPause(const bool pause) {
793 	if (pause) {
794 		varyPause();
795 	} else {
796 		varyOn();
797 	}
798 }
799 
800 #pragma mark -
801 #pragma mark Cycling
802 
setCycle(const uint8 fromColor,const uint8 toColor,const int16 direction,const int16 delay)803 void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay) {
804 	assert(fromColor < toColor);
805 
806 	PalCycler *cycler = getCycler(fromColor);
807 
808 	if (cycler != nullptr) {
809 		clearCycleMap(fromColor, cycler->numColorsToCycle);
810 	} else {
811 		for (int i = 0; i < kNumCyclers; ++i) {
812 			if (!_cyclers[i]) {
813 				cycler = new PalCycler;
814 				_cyclers[i].reset(cycler);
815 				break;
816 			}
817 		}
818 	}
819 
820 	// If there are no free cycler slots, SSCI overrides the first oldest cycler
821 	// that it finds, where "oldest" is determined by the difference between the
822 	// tick and now
823 	if (cycler == nullptr) {
824 		const uint32 now = g_sci->getTickCount();
825 		uint32 minUpdateDelta = 0xFFFFFFFF;
826 
827 		for (int i = 0; i < kNumCyclers; ++i) {
828 			PalCyclerOwner &candidate = _cyclers[i];
829 
830 			const uint32 updateDelta = now - candidate->lastUpdateTick;
831 			if (updateDelta < minUpdateDelta) {
832 				minUpdateDelta = updateDelta;
833 				cycler = candidate.get();
834 			}
835 		}
836 
837 		clearCycleMap(cycler->fromColor, cycler->numColorsToCycle);
838 	}
839 
840 	uint16 numColorsToCycle = toColor - fromColor;
841 	if (g_sci->_features->hasMidPaletteCode()) {
842 		numColorsToCycle += 1;
843 	}
844 	cycler->fromColor = fromColor;
845 	cycler->numColorsToCycle = numColorsToCycle;
846 	cycler->currentCycle = fromColor;
847 	cycler->direction = direction < 0 ? kPalCycleBackward : kPalCycleForward;
848 	cycler->delay = delay;
849 	cycler->lastUpdateTick = g_sci->getTickCount();
850 	cycler->numTimesPaused = 0;
851 
852 	setCycleMap(fromColor, numColorsToCycle);
853 }
854 
doCycle(const uint8 fromColor,const int16 speed)855 void GfxPalette32::doCycle(const uint8 fromColor, const int16 speed) {
856 	PalCycler *const cycler = getCycler(fromColor);
857 	if (cycler != nullptr) {
858 		cycler->lastUpdateTick = g_sci->getTickCount();
859 		updateCycler(*cycler, speed);
860 	}
861 }
862 
cycleOn(const uint8 fromColor)863 void GfxPalette32::cycleOn(const uint8 fromColor) {
864 	PalCycler *const cycler = getCycler(fromColor);
865 	if (cycler != nullptr && cycler->numTimesPaused > 0) {
866 		--cycler->numTimesPaused;
867 	}
868 }
869 
cyclePause(const uint8 fromColor)870 void GfxPalette32::cyclePause(const uint8 fromColor) {
871 	PalCycler *const cycler = getCycler(fromColor);
872 	if (cycler != nullptr) {
873 		++cycler->numTimesPaused;
874 	}
875 }
876 
cycleAllOn()877 void GfxPalette32::cycleAllOn() {
878 	for (int i = 0; i < kNumCyclers; ++i) {
879 		PalCyclerOwner &cycler = _cyclers[i];
880 		if (cycler && cycler->numTimesPaused > 0) {
881 			--cycler->numTimesPaused;
882 		}
883 	}
884 }
885 
cycleAllPause()886 void GfxPalette32::cycleAllPause() {
887 	// SSCI did not check for null pointers in the palette cyclers pointer array
888 	for (int i = 0; i < kNumCyclers; ++i) {
889 		PalCyclerOwner &cycler = _cyclers[i];
890 		if (cycler) {
891 			// This seems odd, because currentCycle is 0..numColorsPerCycle,
892 			// but fromColor is 0..255. When applyAllCycles runs, the values
893 			// end up back in range
894 			cycler->currentCycle = cycler->fromColor;
895 		}
896 	}
897 
898 	applyAllCycles();
899 
900 	for (int i = 0; i < kNumCyclers; ++i) {
901 		PalCyclerOwner &cycler = _cyclers[i];
902 		if (cycler) {
903 			++cycler->numTimesPaused;
904 		}
905 	}
906 }
907 
cycleOff(const uint8 fromColor)908 void GfxPalette32::cycleOff(const uint8 fromColor) {
909 	for (int i = 0; i < kNumCyclers; ++i) {
910 		PalCyclerOwner &cycler = _cyclers[i];
911 		if (cycler && cycler->fromColor == fromColor) {
912 			clearCycleMap(fromColor, cycler->numColorsToCycle);
913 			_cyclers[i].reset();
914 			break;
915 		}
916 	}
917 }
918 
cycleAllOff()919 void GfxPalette32::cycleAllOff() {
920 	for (int i = 0; i < kNumCyclers; ++i) {
921 		PalCyclerOwner &cycler = _cyclers[i];
922 		if (cycler) {
923 			clearCycleMap(cycler->fromColor, cycler->numColorsToCycle);
924 			_cyclers[i].reset();
925 		}
926 	}
927 }
928 
updateCycler(PalCycler & cycler,const int16 speed)929 void GfxPalette32::updateCycler(PalCycler &cycler, const int16 speed) {
930 	int16 currentCycle = cycler.currentCycle;
931 	const uint16 numColorsToCycle = cycler.numColorsToCycle;
932 
933 	if (cycler.direction == kPalCycleBackward) {
934 		currentCycle = (currentCycle - (speed % numColorsToCycle)) + numColorsToCycle;
935 	} else {
936 		currentCycle = currentCycle + speed;
937 	}
938 
939 	cycler.currentCycle = currentCycle % numColorsToCycle;
940 }
941 
clearCycleMap(const uint16 fromColor,const uint16 numColorsToClear)942 void GfxPalette32::clearCycleMap(const uint16 fromColor, const uint16 numColorsToClear) {
943 	bool *mapEntry = _cycleMap + fromColor;
944 	const bool *const lastEntry = _cycleMap + numColorsToClear;
945 	while (mapEntry < lastEntry) {
946 		*mapEntry++ = false;
947 	}
948 }
949 
setCycleMap(const uint16 fromColor,const uint16 numColorsToSet)950 void GfxPalette32::setCycleMap(const uint16 fromColor, const uint16 numColorsToSet) {
951 	bool *mapEntry = _cycleMap + fromColor;
952 	const bool *const lastEntry = _cycleMap + numColorsToSet;
953 	while (mapEntry < lastEntry) {
954 		if (*mapEntry != false) {
955 			error("Cycles intersect");
956 		}
957 		*mapEntry++ = true;
958 	}
959 }
960 
getCycler(const uint16 fromColor)961 PalCycler *GfxPalette32::getCycler(const uint16 fromColor) {
962 	for (int cyclerIndex = 0; cyclerIndex < kNumCyclers; ++cyclerIndex) {
963 		PalCyclerOwner &cycler = _cyclers[cyclerIndex];
964 		if (cycler && cycler->fromColor == fromColor) {
965 			return cycler.get();
966 		}
967 	}
968 
969 	return nullptr;
970 }
971 
applyAllCycles()972 void GfxPalette32::applyAllCycles() {
973 	Color paletteCopy[256];
974 	memcpy(paletteCopy, _nextPalette.colors, sizeof(paletteCopy));
975 
976 	for (int i = 0; i < kNumCyclers; ++i) {
977 		PalCyclerOwner &cycler = _cyclers[i];
978 		if (cycler) {
979 			cycler->currentCycle = (cycler->currentCycle + 1) % cycler->numColorsToCycle;
980 			for (int j = 0; j < cycler->numColorsToCycle; j++) {
981 				_nextPalette.colors[cycler->fromColor + j] = paletteCopy[cycler->fromColor + (cycler->currentCycle + j) % cycler->numColorsToCycle];
982 			}
983 		}
984 	}
985 }
986 
applyCycles()987 void GfxPalette32::applyCycles() {
988 	Color paletteCopy[256];
989 	memcpy(paletteCopy, _nextPalette.colors, sizeof(paletteCopy));
990 
991 	const uint32 now = g_sci->getTickCount();
992 	for (int i = 0; i < kNumCyclers; ++i) {
993 		PalCyclerOwner &cycler = _cyclers[i];
994 		if (!cycler) {
995 			continue;
996 		}
997 
998 		if (cycler->delay != 0 && cycler->numTimesPaused == 0) {
999 			while ((cycler->delay + cycler->lastUpdateTick) < now) {
1000 				updateCycler(*cycler, 1);
1001 				cycler->lastUpdateTick += cycler->delay;
1002 			}
1003 		}
1004 
1005 		for (int j = 0; j < cycler->numColorsToCycle; j++) {
1006 			_nextPalette.colors[cycler->fromColor + j] = paletteCopy[cycler->fromColor + (cycler->currentCycle + j) % cycler->numColorsToCycle];
1007 		}
1008 	}
1009 }
1010 
1011 #pragma mark -
1012 #pragma mark Fading
1013 
setFade(const uint16 percent,const uint8 fromColor,uint16 toColor)1014 void GfxPalette32::setFade(const uint16 percent, const uint8 fromColor, uint16 toColor) {
1015 	if (fromColor > toColor) {
1016 		return;
1017 	}
1018 
1019 	// Some game scripts (like SQ6 Sierra logo and main menu) incorrectly call
1020 	// setFade with toColor set to 256
1021 	if (toColor > 255) {
1022 		toColor = 255;
1023 	}
1024 
1025 	for (int i = fromColor; i <= toColor; i++) {
1026 		_fadeTable[i] = percent;
1027 	}
1028 }
1029 
fadeOff()1030 void GfxPalette32::fadeOff() {
1031 	setFade(100, 0, 255);
1032 }
1033 
applyFade()1034 void GfxPalette32::applyFade() {
1035 	for (int i = 0; i < ARRAYSIZE(_fadeTable); ++i) {
1036 		if (_fadeTable[i] == 100) {
1037 			continue;
1038 		}
1039 
1040 		Color &color = _nextPalette.colors[i];
1041 
1042 		color.r = MIN(255, (uint16)color.r * _fadeTable[i] / 100);
1043 		color.g = MIN(255, (uint16)color.g * _fadeTable[i] / 100);
1044 		color.b = MIN(255, (uint16)color.b * _fadeTable[i] / 100);
1045 	}
1046 }
1047 
1048 } // End of namespace Sci
1049