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