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