mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-01 00:12:59 +00:00
266806d892
The Mac icon bar uses a palette from the executable and keeps those entries in the palette constantly. In addition, we're now performing gamma correction on the Mac-based colors so that they are in the same gamma as SCI. The color matching now works with this and using the same color finding as the Mac Palette Manager.
962 lines
30 KiB
C++
962 lines
30 KiB
C++
/* 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.
|
|
*
|
|
* $URL$
|
|
* $Id$
|
|
*
|
|
*/
|
|
|
|
#include "common/file.h"
|
|
#include "common/timer.h"
|
|
#include "common/util.h"
|
|
#include "common/system.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/screen.h"
|
|
#include "sci/graphics/view.h"
|
|
|
|
namespace Sci {
|
|
|
|
GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen, bool useMerging)
|
|
: _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
|
|
_useMerging = useMerging;
|
|
|
|
palVaryInit();
|
|
|
|
_macClut = 0;
|
|
loadMacIconBarPalette();
|
|
|
|
#ifdef ENABLE_SCI32
|
|
_clutTable = 0;
|
|
#endif
|
|
}
|
|
|
|
GfxPalette::~GfxPalette() {
|
|
if (_palVaryResourceId != -1)
|
|
palVaryRemoveTimer();
|
|
|
|
delete[] _macClut;
|
|
|
|
#ifdef ENABLE_SCI32
|
|
unloadClut();
|
|
#endif
|
|
}
|
|
|
|
bool GfxPalette::isMerging() {
|
|
return _useMerging;
|
|
}
|
|
|
|
// meant to get called only once during init of engine
|
|
void GfxPalette::setDefault() {
|
|
if (_resMan->getViewType() == kViewEga)
|
|
setEGA();
|
|
else if (_resMan->isAmiga32color())
|
|
setAmiga();
|
|
else
|
|
kernelSetFromResource(999, true);
|
|
}
|
|
|
|
#define SCI_PAL_FORMAT_CONSTANT 1
|
|
#define SCI_PAL_FORMAT_VARIABLE 0
|
|
|
|
void GfxPalette::createFromData(byte *data, int bytesLeft, Palette *paletteOut) {
|
|
int palFormat = 0;
|
|
int palOffset = 0;
|
|
int palColorStart = 0;
|
|
int palColorCount = 0;
|
|
int colorNo = 0;
|
|
|
|
memset(paletteOut, 0, sizeof(Palette));
|
|
|
|
// Setup 1:1 mapping
|
|
for (colorNo = 0; colorNo < 256; colorNo++)
|
|
paletteOut->mapping[colorNo] = colorNo;
|
|
|
|
if (bytesLeft < 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 (%d), expected palette header", bytesLeft);
|
|
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 && READ_SCI11ENDIAN_UINT16(data + 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 = READ_SCI11ENDIAN_UINT16(data + 29);
|
|
}
|
|
|
|
switch (palFormat) {
|
|
case SCI_PAL_FORMAT_CONSTANT:
|
|
// Check, if enough bytes left
|
|
if (bytesLeft < 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 (bytesLeft < 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;
|
|
}
|
|
|
|
// 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(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;
|
|
}
|
|
|
|
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+ doesnt 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 & 0x8000) { // exact match was found
|
|
newPalette->mapping[i] = res & 0xFF;
|
|
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 & 0xFF;
|
|
_sysPalette.colors[res & 0xFF].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 r, byte g, byte b) {
|
|
byte found = 0xFF;
|
|
int diff = 0x2FFFF, cdiff;
|
|
int16 dr,dg,db;
|
|
|
|
for (int i = 1; i < 255; i++) {
|
|
if ((!_sysPalette.colors[i].used))
|
|
continue;
|
|
dr = _sysPalette.colors[i].r - r;
|
|
dg = _sysPalette.colors[i].g - g;
|
|
db = _sysPalette.colors[i].b - b;
|
|
// minimum squares match
|
|
cdiff = (dr*dr) + (dg*dg) + (db*db);
|
|
// minimum sum match (Sierra's)
|
|
// cdiff = ABS(dr) + ABS(dg) + ABS(db);
|
|
if (cdiff < diff) {
|
|
if (cdiff == 0)
|
|
return i | 0x8000; // setting this flag to indicate exact match
|
|
found = i;
|
|
diff = cdiff;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
void GfxPalette::getSys(Palette *pal) {
|
|
if (pal != &_sysPalette)
|
|
memcpy(pal, &_sysPalette,sizeof(Palette));
|
|
}
|
|
|
|
void GfxPalette::setOnScreen() {
|
|
// We dont change palette at all times for amiga
|
|
if (_resMan->isAmiga32color())
|
|
return;
|
|
|
|
copySysPaletteToScreen();
|
|
}
|
|
|
|
static byte convertMacGammaToSCIGamma(int comp) {
|
|
return (byte)sqrt(comp * 255);
|
|
}
|
|
|
|
void GfxPalette::copySysPaletteToScreen() {
|
|
// just copy palette to system
|
|
byte bpal[4 * 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 * 4 ] = convertMacGammaToSCIGamma(_macClut[i * 3 ]);
|
|
bpal[i * 4 + 1] = convertMacGammaToSCIGamma(_macClut[i * 3 + 1]);
|
|
bpal[i * 4 + 2] = convertMacGammaToSCIGamma(_macClut[i * 3 + 2]);
|
|
} else if (_sysPalette.colors[i].used != 0) {
|
|
// Otherwise, copy to the screen
|
|
bpal[i * 4 ] = CLIP(_sysPalette.colors[i].r * _sysPalette.intensity[i] / 100, 0, 255);
|
|
bpal[i * 4 + 1] = CLIP(_sysPalette.colors[i].g * _sysPalette.intensity[i] / 100, 0, 255);
|
|
bpal[i * 4 + 2] = CLIP(_sysPalette.colors[i].b * _sysPalette.intensity[i] / 100, 0, 255);
|
|
}
|
|
}
|
|
|
|
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->data, palResource->size, &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) & 0xFF;
|
|
}
|
|
|
|
// Returns true, if palette got changed
|
|
bool GfxPalette::kernelAnimate(byte fromColor, byte toColor, int speed) {
|
|
Color col;
|
|
//byte colorNr;
|
|
int16 colorCount;
|
|
uint32 now = g_system->getMillis() * 60 / 1000;
|
|
|
|
// 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) {
|
|
// Sometimes invalid viewIds are asked for, ignore those (e.g. qfg1vga)
|
|
//if (!_resMan->testResource(ResourceId(kResourceTypeView, resourceId)))
|
|
// return;
|
|
// maybe we took the wrong parameter before, if this causes invalid view again, enable to commented out code again
|
|
|
|
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[4 * 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 * 4];
|
|
_sysPalette.colors[i].g = bpal[i * 4 + 1];
|
|
_sysPalette.colors[i].b = bpal[i * 4 + 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;
|
|
}
|
|
|
|
bool GfxPalette::palVaryLoadTargetPalette(GuiResourceId resourceId) {
|
|
_palVaryResourceId = resourceId;
|
|
Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
|
|
if (palResource) {
|
|
// Load and initialize destination palette
|
|
createFromData(palResource->data, palResource->size, &_palVaryTargetPalette);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void GfxPalette::palVaryInstallTimer() {
|
|
int16 ticks = _palVaryTicks > 0 ? _palVaryTicks : 1;
|
|
// Call signal increase every [ticks]
|
|
g_sci->getTimerManager()->installTimerProc(&palVaryCallback, 1000000 / 60 * ticks, this);
|
|
}
|
|
|
|
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;
|
|
// sierra sci set the timer to 1 tick instead of calling it directly
|
|
// we have to change this to prevent a race condition to happen in
|
|
// at least freddy pharkas during nighttime. In that case kPalVary is
|
|
// called right before a transition and because we load pictures much
|
|
// faster, the 1 tick won't pass sometimes resulting in the palette
|
|
// being daytime instead of nighttime during the transition.
|
|
palVaryProcess(1, true);
|
|
} else {
|
|
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 (!_palVaryTicks) {
|
|
_palVaryDirection = _palVaryStepStop - _palVaryStep;
|
|
// ffs. see palVaryInit right above, we fix the code here as well
|
|
// just in case
|
|
palVaryProcess(1, true);
|
|
} else {
|
|
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->data, palResource->size, &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() {
|
|
if (!_palVaryPaused)
|
|
_palVarySignal++;
|
|
}
|
|
|
|
// Actually do the pal vary processing
|
|
void GfxPalette::palVaryUpdate() {
|
|
if (_palVarySignal) {
|
|
palVaryProcess(_palVarySignal, true);
|
|
_palVarySignal = 0;
|
|
}
|
|
}
|
|
|
|
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
|
|
Sci::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(Sci::Color))) {
|
|
_sysPalette.colors[colorNr] = inbetween;
|
|
_sysPaletteChanged = true;
|
|
}
|
|
}
|
|
|
|
if ((_sysPaletteChanged) && (setPalette) && (_screen->_picNotValid == 0)) {
|
|
setOnScreen();
|
|
_sysPaletteChanged = false;
|
|
}
|
|
}
|
|
|
|
byte GfxPalette::findMacIconBarColor(byte r, byte g, byte b) {
|
|
// Find the best color for use with the Mac icon bar
|
|
|
|
// For black, always use 0
|
|
if (r == 0 && g == 0 && b == 0)
|
|
return 0;
|
|
|
|
byte found = 0xFF;
|
|
uint diff = 0xFFFFFFFF;
|
|
|
|
for (uint16 i = 1; i < 255; i++) {
|
|
int dr = _macClut[i * 3 ] - r;
|
|
int dg = _macClut[i * 3 + 1] - g;
|
|
int db = _macClut[i * 3 + 2] - b;
|
|
|
|
// Use the largest difference. This is what the Mac Palette Manager does.
|
|
uint cdiff = MAX<int>(ABS(dr), ABS(dg));
|
|
cdiff = MAX<int>(cdiff, ABS(db));
|
|
|
|
if (cdiff == 0)
|
|
return i;
|
|
else if (cdiff < diff) {
|
|
found = i;
|
|
diff = cdiff;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
void GfxPalette::loadMacIconBarPalette() {
|
|
if (!g_sci->hasMacIconBar())
|
|
return;
|
|
|
|
Common::SeekableReadStream *clutStream = g_sci->getMacExecutable()->getResource(MKID_BE('clut'), 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;
|
|
_macClut = new byte[colorCount * 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;
|
|
}
|
|
|
|
delete clutStream;
|
|
}
|
|
|
|
bool GfxPalette::colorIsFromMacClut(byte index) {
|
|
return index != 0 && _macClut && (_macClut[index * 3] != 0 || _macClut[index * 3 + 1] != 0 || _macClut[index * 3 + 1] != 0);
|
|
}
|
|
|
|
#ifdef ENABLE_SCI32
|
|
|
|
bool GfxPalette::loadClut(uint16 clutId) {
|
|
// loadClut() will load a color lookup table from a clu file and set
|
|
// the palette found in the file. This is to be used with Phantasmagoria 2.
|
|
|
|
unloadClut();
|
|
|
|
Common::String filename = Common::String::format("%d.clu", clutId);
|
|
Common::File clut;
|
|
|
|
if (!clut.open(filename) || clut.size() != 0x10000 + 236 * 3)
|
|
return false;
|
|
|
|
// Read in the lookup table
|
|
// It maps each RGB565 color to a palette index
|
|
_clutTable = new byte[0x10000];
|
|
clut.read(_clutTable, 0x10000);
|
|
|
|
Palette pal;
|
|
memset(&pal, 0, sizeof(Palette));
|
|
|
|
// Setup 1:1 mapping
|
|
for (int i = 0; i < 256; i++)
|
|
pal.mapping[i] = i;
|
|
|
|
// Now load in the palette
|
|
for (int i = 1; i <= 236; i++) {
|
|
pal.colors[i].used = 1;
|
|
pal.colors[i].r = clut.readByte();
|
|
pal.colors[i].g = clut.readByte();
|
|
pal.colors[i].b = clut.readByte();
|
|
}
|
|
|
|
set(&pal, true);
|
|
setOnScreen();
|
|
return true;
|
|
}
|
|
|
|
byte GfxPalette::matchClutColor(uint16 color) {
|
|
// Match a color in RGB565 format to a palette index based on the loaded CLUT
|
|
assert(_clutTable);
|
|
return _clutTable[color];
|
|
}
|
|
|
|
void GfxPalette::unloadClut() {
|
|
// This will only unload the actual table, but not reset any palette
|
|
delete[] _clutTable;
|
|
_clutTable = 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
} // End of namespace Sci
|