From ca279390a36d0f87be134c6788c6420a748b99b8 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sat, 25 Jun 2016 21:19:47 +0200 Subject: SCI32: Fix broken Remap implementation Remap would crash SCI2.1early games with 19 remap slots, and did not actually work in most cases in SCI2.1mid+ games. 1. Avoid accidental corruption of values from the VM that may be valid when signed or larger than 8 bits 2. Fix bad `matchColor` function. 3. Remove unnecessary initialisation of SingleRemaps 4. Update architecture to more closely mirror how SSCI worked 5. Split large `apply` function into smaller units 6. Fix buffer overrun when loading a SCI2.1early game with remap 7. Warn instead of crashing with an error on invalid input (to match SSCI more closely) 8. Add save/load function --- engines/sci/engine/kgraphics32.cpp | 60 ++-- engines/sci/engine/savegame.cpp | 30 +- engines/sci/engine/savegame.h | 3 +- engines/sci/graphics/frameout.cpp | 4 +- engines/sci/graphics/palette32.cpp | 9 - engines/sci/graphics/palette32.h | 11 +- engines/sci/graphics/remap.h | 2 +- engines/sci/graphics/remap32.cpp | 616 ++++++++++++++++++++++--------------- engines/sci/graphics/remap32.h | 431 +++++++++++++++++++++----- engines/sci/sci.cpp | 2 +- 10 files changed, 807 insertions(+), 361 deletions(-) diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index e85d2a9877..019a06930c 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -933,50 +933,62 @@ reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv) { } reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv) { - byte color = (argc >= 1) ? argv[0].toUint16() : 0; - g_sci->_gfxRemap32->remapOff(color); + if (argc == 0) { + g_sci->_gfxRemap32->remapAllOff(); + } else { + const uint8 color = argv[0].toUint16(); + g_sci->_gfxRemap32->remapOff(color); + } return s->r_acc; } reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv) { - byte color = argv[0].toUint16(); - byte from = argv[1].toUint16(); - byte to = argv[2].toUint16(); - byte base = argv[3].toUint16(); - // The last parameter, depth, is unused - g_sci->_gfxRemap32->setRemappingRange(color, from, to, base); + const uint8 color = argv[0].toUint16(); + const int16 from = argv[1].toSint16(); + const int16 to = argv[2].toSint16(); + const int16 base = argv[3].toSint16(); + // NOTE: There is an optional last parameter after `base` + // which was only used by the priority map debugger, which + // does not exist in release versions of SSCI + g_sci->_gfxRemap32->remapByRange(color, from, to, base); return s->r_acc; } reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv) { - byte color = argv[0].toUint16(); - byte percent = argv[1].toUint16(); - // The last parameter, depth, is unused - g_sci->_gfxRemap32->setRemappingPercent(color, percent); + const uint8 color = argv[0].toUint16(); + const int16 percent = argv[1].toSint16(); + // NOTE: There is an optional last parameter after `percent` + // which was only used by the priority map debugger, which + // does not exist in release versions of SSCI + g_sci->_gfxRemap32->remapByPercent(color, percent); return s->r_acc; } reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv) { - byte color = argv[0].toUint16(); - byte gray = argv[1].toUint16(); - // The last parameter, depth, is unused - g_sci->_gfxRemap32->setRemappingToGray(color, gray); + const uint8 color = argv[0].toUint16(); + const int16 gray = argv[1].toSint16(); + // NOTE: There is an optional last parameter after `gray` + // which was only used by the priority map debugger, which + // does not exist in release versions of SSCI + g_sci->_gfxRemap32->remapToGray(color, gray); return s->r_acc; } reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv) { - byte color = argv[0].toUint16(); - byte gray = argv[1].toUint16(); - byte percent = argv[2].toUint16(); - // The last parameter, depth, is unused - g_sci->_gfxRemap32->setRemappingToPercentGray(color, gray, percent); + const uint8 color = argv[0].toUint16(); + const int16 gray = argv[1].toSint16(); + const int16 percent = argv[2].toSint16(); + // NOTE: There is an optional last parameter after `percent` + // which was only used by the priority map debugger, which + // does not exist in release versions of SSCI + g_sci->_gfxRemap32->remapToPercentGray(color, gray, percent); return s->r_acc; } reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv) { - byte from = argv[0].toUint16(); - byte count = argv[1].toUint16(); - g_sci->_gfxRemap32->setNoMatchRange(from, count); + const uint8 from = argv[0].toUint16(); + const uint8 count = argv[1].toUint16(); + g_sci->_gfxRemap32->blockRange(from, count); return s->r_acc; } diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index 302f046458..0972aec4a4 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -48,8 +48,9 @@ #include "sci/sound/music.h" #ifdef ENABLE_SCI32 -#include "sci/graphics/palette32.h" #include "sci/graphics/frameout.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/remap32.h" #endif namespace Sci { @@ -807,6 +808,33 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) { } } } + +void GfxRemap32::saveLoadWithSerializer(Common::Serializer &s) { + if (s.getVersion() < 35) { + return; + } + + s.syncAsByte(_numActiveRemaps); + s.syncAsByte(_blockedRangeStart); + s.syncAsSint16LE(_blockedRangeCount); + + for (uint i = 0; i < _remaps.size(); ++i) { + SingleRemap &singleRemap = _remaps[i]; + s.syncAsByte(singleRemap._type); + if (s.isLoading() && singleRemap._type != kRemapNone) { + singleRemap.reset(); + } + s.syncAsByte(singleRemap._from); + s.syncAsByte(singleRemap._to); + s.syncAsByte(singleRemap._delta); + s.syncAsByte(singleRemap._percent); + s.syncAsByte(singleRemap._gray); + } + + if (s.isLoading()) { + _needsUpdate = true; + } +} #endif void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) { diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h index 459e992e24..43909accf2 100644 --- a/engines/sci/engine/savegame.h +++ b/engines/sci/engine/savegame.h @@ -37,6 +37,7 @@ struct EngineState; * * Version - new/changed feature * ============================= + * 35 - SCI32 remap * 34 - SCI32 palettes, and store play time in ticks * 33 - new overridePriority flag in MusicEntry * 32 - new playBed flag in MusicEntry @@ -59,7 +60,7 @@ struct EngineState; */ enum { - CURRENT_SAVEGAME_VERSION = 34, + CURRENT_SAVEGAME_VERSION = 35, MINIMUM_SAVEGAME_VERSION = 14 }; diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index 9d3ab0463e..fd37020896 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -1005,7 +1005,7 @@ void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showLi } void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) { - Palette sourcePalette(*_palette->getNextPalette()); + Palette sourcePalette(_palette->getNextPalette()); alterVmap(sourcePalette, sourcePalette, -1, styleRanges); int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16(); @@ -1045,7 +1045,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry drawScreenItemList(screenItemLists[i]); } - Palette nextPalette(*_palette->getNextPalette()); + Palette nextPalette(_palette->getNextPalette()); if (prevRoom < 1000) { for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) { diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp index 5c9bfd66e9..0840e82a40 100644 --- a/engines/sci/graphics/palette32.cpp +++ b/engines/sci/graphics/palette32.cpp @@ -78,15 +78,6 @@ inline void mergePaletteInternal(Palette *const to, const Palette *const from) { } } -const Palette *GfxPalette32::getNextPalette() const { - return &_nextPalette; -} - -const Palette *GfxPalette32::getCurrentPalette() const { - return &_sysPalette; -} - - void GfxPalette32::submit(Palette &palette) { // TODO: The resource manager in SCI32 retains raw data of palettes from // the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h index ec75b58dd5..7dda53e5c1 100644 --- a/engines/sci/graphics/palette32.h +++ b/engines/sci/graphics/palette32.h @@ -113,8 +113,8 @@ private: public: virtual void saveLoadWithSerializer(Common::Serializer &s) override; - const Palette *getNextPalette() const; - const Palette *getCurrentPalette() const; + inline const Palette &getNextPalette() const { return _nextPalette; }; + inline const Palette &getCurrentPalette() const { return _sysPalette; }; bool kernelSetFromResource(GuiResourceId resourceId, bool force) override; int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override; @@ -240,6 +240,11 @@ private: * According to SCI engine code, when two cyclers overlap, * a fatal error has occurred and the engine will display * an error and then exit. + * + * The cycle map is also by the color remapping system to + * avoid attempting to remap to palette entries that are + * cycling (so won't be the expected color once the cycler + * runs again). */ bool _cycleMap[256]; inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear); @@ -257,7 +262,7 @@ public: void cycleAllOff(); void applyAllCycles(); void applyCycles(); - const bool *getCyclemap() { return _cycleMap; } + inline const bool *getCycleMap() const { return _cycleMap; } #pragma mark - #pragma mark Fading diff --git a/engines/sci/graphics/remap.h b/engines/sci/graphics/remap.h index a9cd76ae7b..98177f6d19 100644 --- a/engines/sci/graphics/remap.h +++ b/engines/sci/graphics/remap.h @@ -24,7 +24,7 @@ #define SCI_GRAPHICS_REMAP_H #include "common/array.h" -#include "sci/graphics/helpers.h" +#include "common/serializer.h" namespace Sci { diff --git a/engines/sci/graphics/remap32.cpp b/engines/sci/graphics/remap32.cpp index ef27c8491d..d5a2362f14 100644 --- a/engines/sci/graphics/remap32.cpp +++ b/engines/sci/graphics/remap32.cpp @@ -26,313 +26,443 @@ namespace Sci { -GfxRemap32::GfxRemap32(GfxPalette32 *palette) : _palette(palette) { - for (int i = 0; i < REMAP_COLOR_COUNT; i++) - _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemapNone); - _noMapStart = _noMapCount = 0; - _update = false; - _remapCount = 0; - - // The remap range was 245 - 254 in SCI2, but was changed to 235 - 244 in SCI21 middle. - // All versions of KQ7 are using the older remap range semantics. - _remapEndColor = (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) ? 244 : 254; +#pragma mark SingleRemap + +void SingleRemap::reset() { + _lastPercent = 100; + _lastGray = 0; + + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); + for (uint i = 0; i < remapStartColor; ++i) { + const Color &color = currentPalette.colors[i]; + _remapColors[i] = i; + _originalColors[i] = color; + _originalColorsChanged[i] = true; + _idealColors[i] = color; + _idealColorsChanged[i] = false; + _matchDistances[i] = 0; + } } -void GfxRemap32::remapOff(byte color) { - if (!color) { - for (int i = 0; i < REMAP_COLOR_COUNT; i++) - _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemapNone); - - _remapCount = 0; - } else { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - const byte index = _remapEndColor - color; - _remaps[index] = RemapParams(0, 0, 0, 0, 100, kRemapNone); - _remapCount--; +bool SingleRemap::update() { + switch (_type) { + case kRemapNone: + break; + case kRemapByRange: + return updateRange(); + case kRemapByPercent: + return updateBrightness(); + case kRemapToGray: + return updateSaturation(); + case kRemapToPercentGray: + return updateSaturationAndBrightness(); + default: + error("Illegal remap type %d", _type); } - _update = true; + return false; } -void GfxRemap32::setRemappingRange(byte color, byte from, byte to, byte base) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(from, to, base, 0, 100, kRemapByRange); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} +bool SingleRemap::updateRange() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + bool updated = false; -void GfxRemap32::setRemappingPercent(byte color, byte percent) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, 0, percent, kRemapByPercent); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} + for (uint i = 0; i < remapStartColor; ++i) { + uint8 targetColor; + if (_from <= i && i <= _to) { + targetColor = i + _delta; + } else { + targetColor = i; + } -void GfxRemap32::setRemappingToGray(byte color, byte gray) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, 100, kRemapToGray); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} + if (_remapColors[i] != targetColor) { + updated = true; + _remapColors[i] = targetColor; + } -void GfxRemap32::setRemappingToPercentGray(byte color, byte gray, byte percent) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, percent, kRemapToPercentGray); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} + _originalColorsChanged[i] = true; + } -void GfxRemap32::setNoMatchRange(byte from, byte count) { - _noMapStart = from; - _noMapCount = count; + return updated; } -bool GfxRemap32::remapEnabled(byte color) const { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - const byte index = _remapEndColor - color; - return (_remaps[index].type != kRemapNone); -} +bool SingleRemap::updateBrightness() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette(); + for (uint i = 1; i < remapStartColor; ++i) { + Color color(nextPalette.colors[i]); -byte GfxRemap32::remapColor(byte color, byte target) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - const byte index = _remapEndColor - color; - if (_remaps[index].type != kRemapNone) - return _remaps[index].remap[target]; - else - return target; -} + if (_originalColors[i] != color) { + _originalColorsChanged[i] = true; + _originalColors[i] = color; + } -void GfxRemap32::initColorArrays(byte index) { - Palette *curPalette = &_palette->_sysPalette; - RemapParams *curRemap = &_remaps[index]; + if (_percent != _lastPercent || _originalColorsChanged[i]) { + // NOTE: SSCI checked if percent was over 100 and only + // then clipped values, but we always unconditionally + // ensure the result is in the correct range + color.r = MIN(255, (uint16)color.r * _percent / 100); + color.g = MIN(255, (uint16)color.g * _percent / 100); + color.b = MIN(255, (uint16)color.b * _percent / 100); + + if (_idealColors[i] != color) { + _idealColorsChanged[i] = true; + _idealColors[i] = color; + } + } + } - memcpy(curRemap->curColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color)); - memcpy(curRemap->targetColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color)); + const bool updated = apply(); + Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); + Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); + _lastPercent = _percent; + return updated; } -bool GfxRemap32::updateRemap(byte index, bool palChanged) { - int result; - RemapParams *curRemap = &_remaps[index]; - const Palette *curPalette = &_palette->_sysPalette; - const Palette *nextPalette = _palette->getNextPalette(); - bool changed = false; - - if (!_update && !palChanged) - return false; - - Common::fill(_targetChanged, _targetChanged + NON_REMAPPED_COLOR_COUNT, false); - - switch (curRemap->type) { - case kRemapNone: - return false; - case kRemapByRange: - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) { - if (curRemap->from <= i && i <= curRemap->to) - result = i + curRemap->base; - else - result = i; - - if (curRemap->remap[i] != result) { - changed = true; - curRemap->remap[i] = result; - } - - curRemap->colorChanged[i] = true; +bool SingleRemap::updateSaturation() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); + for (uint i = 1; i < remapStartColor; ++i) { + Color color(currentPalette.colors[i]); + if (_originalColors[i] != color) { + _originalColorsChanged[i] = true; + _originalColors[i] = color; } - return changed; - case kRemapByPercent: - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - // NOTE: This method uses nextPalette instead of curPalette - Color color = nextPalette->colors[i]; - if (curRemap->curColor[i] != color) { - curRemap->colorChanged[i] = true; - curRemap->curColor[i] = color; - } + if (_gray != _lastGray || _originalColorsChanged[i]) { + const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100; - if (curRemap->percent != curRemap->oldPercent || curRemap->colorChanged[i]) { - byte red = CLIP(color.r * curRemap->percent / 100, 0, 255); - byte green = CLIP(color.g * curRemap->percent / 100, 0, 255); - byte blue = CLIP(color.b * curRemap->percent / 100, 0, 255); - byte used = curRemap->targetColor[i].used; - - Color newColor = { used, red, green, blue }; - if (curRemap->targetColor[i] != newColor) { - _targetChanged[i] = true; - curRemap->targetColor[i] = newColor; - } + color.r = MIN(255, color.r - ((color.r - luminosity) * _gray / 100)); + color.g = MIN(255, color.g - ((color.g - luminosity) * _gray / 100)); + color.b = MIN(255, color.b - ((color.b - luminosity) * _gray / 100)); + + if (_idealColors[i] != color) { + _idealColorsChanged[i] = true; + _idealColors[i] = color; } } - - changed = applyRemap(index); - Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); - curRemap->oldPercent = curRemap->percent; - return changed; - case kRemapToGray: - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - Color color = curPalette->colors[i]; + } - if (curRemap->curColor[i] != color) { - curRemap->colorChanged[i] = true; - curRemap->curColor[i] = color; - } + const bool updated = apply(); + Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); + Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); + _lastGray = _gray; + return updated; +} - if (curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) { - byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8; - byte red = CLIP(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255); - byte green = CLIP(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255); - byte blue = CLIP(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255); - byte used = curRemap->targetColor[i].used; - - Color newColor = { used, red, green, blue }; - if (curRemap->targetColor[i] != newColor) { - _targetChanged[i] = true; - curRemap->targetColor[i] = newColor; - } - } +bool SingleRemap::updateSaturationAndBrightness() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); + for (uint i = 1; i < remapStartColor; i++) { + Color color(currentPalette.colors[i]); + if (_originalColors[i] != color) { + _originalColorsChanged[i] = true; + _originalColors[i] = color; } - changed = applyRemap(index); - Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); - curRemap->oldGray = curRemap->gray; - return changed; - case kRemapToPercentGray: - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - Color color = curPalette->colors[i]; + if (_percent != _lastPercent || _gray != _lastGray || _originalColorsChanged[i]) { + const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100; - if (curRemap->curColor[i] != color) { - curRemap->colorChanged[i] = true; - curRemap->curColor[i] = color; - } + color.r = MIN(255, color.r - ((color.r - luminosity) * _gray) / 100); + color.g = MIN(255, color.g - ((color.g - luminosity) * _gray) / 100); + color.b = MIN(255, color.b - ((color.b - luminosity) * _gray) / 100); - if (curRemap->percent != curRemap->oldPercent || curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) { - byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8; - lumosity = lumosity * curRemap->percent / 100; - byte red = CLIP(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255); - byte green = CLIP(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255); - byte blue = CLIP(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255); - byte used = curRemap->targetColor[i].used; - - Color newColor = { used, red, green, blue }; - if (curRemap->targetColor[i] != newColor) { - _targetChanged[i] = true; - curRemap->targetColor[i] = newColor; - } + if (_idealColors[i] != color) { + _idealColorsChanged[i] = true; + _idealColors[i] = color; } } - - changed = applyRemap(index); - Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); - curRemap->oldPercent = curRemap->percent; - curRemap->oldGray = curRemap->gray; - return changed; - default: - return false; } -} -static int colorDistance(Color a, Color b) { - int rDiff = (a.r - b.r) * (a.r - b.r); - int gDiff = (a.g - b.g) * (a.g - b.g); - int bDiff = (a.b - b.b) * (a.b - b.b); - return rDiff + gDiff + bDiff; + const bool updated = apply(); + Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); + Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); + _lastPercent = _percent; + _lastGray = _gray; + return updated; } -bool GfxRemap32::applyRemap(byte index) { - RemapParams *curRemap = &_remaps[index]; - const bool *cycleMap = _palette->getCyclemap(); - bool unmappedColors[NON_REMAPPED_COLOR_COUNT]; - bool changed = false; +bool SingleRemap::apply() { + const GfxRemap32 *const gfxRemap32 = g_sci->_gfxRemap32; + const uint8 remapStartColor = gfxRemap32->getStartColor(); + + // Blocked colors are not allowed to be used as target + // colors for the remap + bool blockedColors[236]; + Common::fill(blockedColors, blockedColors + remapStartColor, false); - Common::fill(unmappedColors, unmappedColors + NON_REMAPPED_COLOR_COUNT, false); - if (_noMapCount) - Common::fill(unmappedColors + _noMapStart, unmappedColors + _noMapStart + _noMapCount, true); + const bool *const paletteCycleMap = g_sci->_gfxPalette32->getCycleMap(); - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) { - if (cycleMap[i]) - unmappedColors[i] = true; + const int16 blockedRangeCount = gfxRemap32->getBlockedRangeCount(); + if (blockedRangeCount) { + const uint8 blockedRangeStart = gfxRemap32->getBlockedRangeStart(); + Common::fill(blockedColors + blockedRangeStart, blockedColors + blockedRangeStart + blockedRangeCount, true); } - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - Color targetColor = curRemap->targetColor[i]; - bool colorChanged = curRemap->colorChanged[curRemap->remap[i]]; + for (uint i = 0; i < remapStartColor; ++i) { + if (paletteCycleMap[i]) { + blockedColors[i] = true; + } + } - if (!_targetChanged[i] && !colorChanged) - continue; + // NOTE: SSCI did a loop over colors here to create a + // new array of updated, unblocked colors, but then + // never used it - if (_targetChanged[i] && colorChanged) - if (curRemap->distance[i] < 100 && colorDistance(targetColor, curRemap->curColor[curRemap->remap[i]]) <= curRemap->distance[i]) - continue; + bool updated = false; + for (uint i = 1; i < remapStartColor; ++i) { + int distance; - int diff = 0; - int16 result = matchColor(targetColor.r, targetColor.g, targetColor.b, curRemap->distance[i], diff, unmappedColors); - if (result != -1 && curRemap->remap[i] != result) { - changed = true; - curRemap->remap[i] = result; - curRemap->distance[i] = diff; + if (!_idealColorsChanged[i] && !_originalColorsChanged[_remapColors[i]]) { + continue; } - } - return changed; -} + if ( + _idealColorsChanged[i] && + _originalColorsChanged[_remapColors[i]] && + _matchDistances[i] < 100 && + colorDistance(_idealColors[i], _originalColors[_remapColors[i]]) <= _matchDistances[i] + ) { + continue; + } -bool GfxRemap32::remapAllTables(bool palChanged) { - bool changed = false; + const int16 bestColor = matchColor(_idealColors[i], _matchDistances[i], distance, blockedColors); - for (int i = 0; i < REMAP_COLOR_COUNT; i++) { - changed |= updateRemap(i, palChanged); + if (bestColor != -1 && _remapColors[i] != bestColor) { + updated = true; + _remapColors[i] = bestColor; + _matchDistances[i] = distance; + } } - _update = false; - return changed; + return updated; } -// In SCI32 engine this method is SOLPalette::Match(Rgb24 *, int, int *, int *) -// and is used by Remap -// TODO: Anything that calls GfxPalette::matchColor(int, int, int) is going to -// match using an algorithm from SCI16 engine right now. This needs to be -// corrected in the future so either nothing calls -// GfxPalette::matchColor(int, int, int), or it is fixed to match the other -// SCI32 algorithms. -int16 GfxRemap32::matchColor(const byte r, const byte g, const byte b, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable) const { +int SingleRemap::colorDistance(const Color &a, const Color &b) const { + int channelDistance = a.r - b.r; + int distance = channelDistance * channelDistance; + channelDistance = a.g - b.g; + distance += channelDistance * channelDistance; + channelDistance = a.b - b.b; + distance += channelDistance * channelDistance; + return distance; +} + +int16 SingleRemap::matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const { int16 bestIndex = -1; - int bestDifference = 0xFFFFF; - int difference = defaultDifference; - const Palette &_sysPalette = *g_sci->_gfxPalette32->getCurrentPalette(); + int bestDistance = 0xFFFFF; + int distance = minimumDistance; + const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette(); - // SQ6 DOS really does check only the first 236 entries - for (int i = 0, channelDifference; i < 236; ++i) { - if (matchTable[i] == 0) { + for (uint i = 0, channelDistance; i < g_sci->_gfxRemap32->getStartColor(); ++i) { + if (blockedIndexes[i]) { continue; } - difference = _sysPalette.colors[i].r - r; - difference *= difference; - if (bestDifference <= difference) { + distance = nextPalette.colors[i].r - color.r; + distance *= distance; + if (bestDistance <= distance) { continue; } - channelDifference = _sysPalette.colors[i].g - g; - difference += channelDifference * channelDifference; - if (bestDifference <= difference) { + channelDistance = nextPalette.colors[i].g - color.g; + distance += channelDistance * channelDistance; + if (bestDistance <= distance) { continue; } - channelDifference = _sysPalette.colors[i].b - b; - difference += channelDifference * channelDifference; - if (bestDifference <= difference) { + channelDistance = nextPalette.colors[i].b - color.b; + distance += channelDistance * channelDistance; + if (bestDistance <= distance) { continue; } - bestDifference = difference; + bestDistance = distance; bestIndex = i; } - // NOTE: This value is only valid if the last index to - // perform a difference calculation was the best index - lastCalculatedDifference = difference; + // This value is only valid if the last index to + // perform a distance calculation was the best index + outDistance = distance; return bestIndex; } +#pragma mark - +#pragma mark GfxRemap32 + +GfxRemap32::GfxRemap32() : + _needsUpdate(false), + _blockedRangeStart(0), + _blockedRangeCount(0), + _remapStartColor(236), + _numActiveRemaps(0) { + // The `_remapStartColor` seems to always be 236 in SSCI, + // but if it is ever changed then the various C-style + // member arrays hard-coded to 236 need to be changed to + // match the highest possible value of `_remapStartColor` + assert(_remapStartColor == 236); + + if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) { + _remaps.resize(9); + } else { + _remaps.resize(19); + } + + _remapEndColor = _remapStartColor + _remaps.size() - 1; +} + +void GfxRemap32::remapOff(const uint8 color) { + if (color == 0) { + remapAllOff(); + return; + } + + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapOff: %d out of remap range", color); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + singleRemap._type = kRemapNone; + --_numActiveRemaps; + _needsUpdate = true; +} + +void GfxRemap32::remapAllOff() { + for (uint i = 0, len = _remaps.size(); i < len; ++i) { + _remaps[i]._type = kRemapNone; + } + + _numActiveRemaps = 0; + _needsUpdate = true; +} + +void GfxRemap32::remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapByRange: %d out of remap range", color); + return; + } + + if (from < 0) { + warning("GfxRemap32::remapByRange: attempt to remap negative color %d", from); + return; + } + + if (to >= _remapStartColor) { + warning("GfxRemap32::remapByRange: attempt to remap into the remap zone at %d", to); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._from = from; + singleRemap._to = to; + singleRemap._delta = delta; + singleRemap._type = kRemapByRange; + _needsUpdate = true; +} + +void GfxRemap32::remapByPercent(const uint8 color, const int16 percent) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapByPercent: %d out of remap range", color); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._percent = percent; + singleRemap._type = kRemapByPercent; + _needsUpdate = true; +} + +void GfxRemap32::remapToGray(const uint8 color, const int8 gray) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapToGray: %d out of remap range", color); + return; + } + + if (gray < 0 || gray > 100) { + error("RemapToGray percent out of range; gray = %d", gray); + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._gray = gray; + singleRemap._type = kRemapToGray; + _needsUpdate = true; +} + +void GfxRemap32::remapToPercentGray(const uint8 color, const int16 gray, const int16 percent) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapToPercentGray: %d out of remap range", color); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._percent = percent; + singleRemap._gray = gray; + singleRemap._type = kRemapToPercentGray; + _needsUpdate = true; +} + +void GfxRemap32::blockRange(const uint8 from, const int16 count) { + _blockedRangeStart = from; + _blockedRangeCount = count; +} + +bool GfxRemap32::remapAllTables(const bool paletteUpdated) { + if (!_needsUpdate && !paletteUpdated) { + return false; + } + + bool updated = false; + + for (SingleRemapsList::iterator it = _remaps.begin(); it != _remaps.end(); ++it) { + if (it->_type != kRemapNone) { + updated |= it->update(); + } + } + + _needsUpdate = false; + return updated; +} } // End of namespace Sci diff --git a/engines/sci/graphics/remap32.h b/engines/sci/graphics/remap32.h index 52736f59d6..5f629d733e 100644 --- a/engines/sci/graphics/remap32.h +++ b/engines/sci/graphics/remap32.h @@ -23,12 +23,13 @@ #ifndef SCI_GRAPHICS_REMAP32_H #define SCI_GRAPHICS_REMAP32_H +#include "common/algorithm.h" #include "common/array.h" +#include "common/scummsys.h" +#include "sci/graphics/helpers.h" namespace Sci { - -#define REMAP_COLOR_COUNT 9 -#define NON_REMAPPED_COLOR_COUNT 236 +class GfxPalette32; enum RemapType { kRemapNone = 0, @@ -38,84 +39,362 @@ enum RemapType { kRemapToPercentGray = 4 }; -struct RemapParams { - byte from; - byte to; - byte base; - byte gray; - byte oldGray; - byte percent; - byte oldPercent; - RemapType type; - Color curColor[256]; - Color targetColor[256]; - byte distance[256]; - byte remap[256]; - bool colorChanged[256]; - - RemapParams() { - from = to = base = gray = oldGray = percent = oldPercent = 0; - type = kRemapNone; - - // curColor and targetColor are initialized in GfxRemap32::initColorArrays - memset(curColor, 0, 256 * sizeof(Color)); - memset(targetColor, 0, 256 * sizeof(Color)); - memset(distance, 0, 256); - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) - remap[i] = i; - Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true); - } +#pragma mark - +#pragma mark SingleRemap - RemapParams(byte from_, byte to_, byte base_, byte gray_, byte percent_, RemapType type_) { - from = from_; - to = to_; - base = base_; - gray = oldGray = gray_; - percent = oldPercent = percent_; - type = type_; - - // curColor and targetColor are initialized in GfxRemap32::initColorArrays - memset(curColor, 0, 256 * sizeof(Color)); - memset(targetColor, 0, 256 * sizeof(Color)); - memset(distance, 0, 256); - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) - remap[i] = i; - Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true); - } +/** + * SingleRemap objects each manage one remapping operation. + */ +class SingleRemap { +public: + SingleRemap() : _type(kRemapNone) {} + + /** + * The type of remap. + */ + RemapType _type; + + /** + * The first color that should be shifted by a range + * remap. + */ + uint8 _from; + + /** + * The last color that should be shifted a range remap. + */ + uint8 _to; + + /** + * The direction and amount that the colors should be + * shifted in a range remap. + */ + int16 _delta; + + /** + * The difference in brightness that should be + * applied by a brightness (percent) remap. + * + * This value may be be greater than 100, in + * which case the color will be oversaturated. + */ + int16 _percent; + + /** + * The amount of desaturation that should be + * applied by a saturation (gray) remap, where + * 0 is full saturation and 100 is full + * desaturation. + */ + uint8 _gray; + + /** + * The final array used by CelObj renderers to composite + * remapped pixels to the screen buffer. + * + * Here is how it works: + * + * The source bitmap being rendered will have pixels + * within the remap range (236-245 or 236-254), and the + * target buffer will have colors in the non-remapped + * range (0-235). + * + * To arrive at the correct color, first the source + * pixel is used to look up the correct SingleRemap for + * that pixel. Then, the final composited color is + * looked up in this array using the target's pixel + * color. In other words, + * `target = _remaps[remapEndColor - source].remapColors[target]`. + */ + uint8 _remapColors[236]; + + /** + * Resets this SingleRemap's color information to + * default values. + */ + void reset(); + + /** + * Recalculates and reapplies remap colors to the + * `_remapColors` array. + */ + bool update(); + +private: + /** + * The previous brightness value. Used to + * determine whether or not targetColors needs + * to be updated. + */ + int16 _lastPercent; + + /** + * The previous saturation value. Used to + * determine whether or not targetColors needs + * to be updated. + */ + uint8 _lastGray; + + /** + * The colors from the current GfxPalette32 palette + * before this SingleRemap is applied. + */ + Color _originalColors[236]; + + /** + * Map of colors that changed in `_originalColors` + * when this SingleRemap was updated. This map is + * transient and gets reset to `false` after the + * SingleRemap finishes updating. + */ + bool _originalColorsChanged[236]; + + /** + * The ideal target RGB color values for each generated + * remap color. + */ + Color _idealColors[236]; + + /** + * Map of colors that changed in `_idealColors` when + * this SingleRemap was updated. This map is transient + * and gets reset to `false` after the SingleRemap + * finishes applying. + */ + bool _idealColorsChanged[236]; + + /** + * When applying a SingleRemap, finding an appropriate + * color in the palette is the responsibility of a + * distance function. Once a match is found, the + * distance of that match is stored here so that the + * next time the SingleRemap is applied, it can check + * the distance from the previous application and avoid + * triggering an expensive redraw of the entire screen + * if the new palette value only changed slightly. + */ + int _matchDistances[236]; + + /** + * Computes the final target values for a range remap + * and applies them directly to the `_remaps` map. + * + * @note Was ByRange in SSCI. + */ + bool updateRange(); + + /** + * Computes the intermediate target values for a + * brightness remap and applies them indirectly via + * the `apply` method. + * + * @note Was ByPercent in SSCI. + */ + bool updateBrightness(); + + /** + * Computes the intermediate target values for a + * saturation remap and applies them indirectly via + * the `apply` method. + * + * @note Was ToGray in SSCI. + */ + bool updateSaturation(); + + /** + * Computes the intermediate target values for a + * saturation + brightness bitmap and applies them + * indirectly via the `apply` method. + * + * @note Was ToPercentGray in SSCI. + */ + bool updateSaturationAndBrightness(); + + /** + * Computes and applies the final values to the + * `_remaps` map. + * + * @note In SSCI, a boolean array of changed values + * was passed into this method, but this was done by + * creating arrays on the stack in the caller. Instead + * of doing this, we simply add another member property + * `_idealColorsChanged` and use that instead. + */ + bool apply(); + + /** + * Calculates the square distance of two colors. + * + * @note In SSCI this method is Rgb24::Dist, but it is + * only used by SingleRemap. + */ + int colorDistance(const Color &a, const Color &b) const; + + /** + * Finds the closest index in the next palette matching + * the given RGB color. Returns -1 if no match can be + * found that is closer than `minimumDistance`. + * + * @note In SSCI, this method is SOLPalette::Match, but + * this particular signature is only used by + * SingleRemap. + */ + int16 matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const; }; -class GfxRemap32 { +#pragma mark - +#pragma mark GfxRemap32 + +/** + * This class provides color remapping support for SCI32 + * games. + */ +class GfxRemap32 : public Common::Serializable { public: - GfxRemap32(GfxPalette32 *palette); - ~GfxRemap32() {} - - void remapOff(byte color); - void setRemappingRange(byte color, byte from, byte to, byte base); - void setRemappingPercent(byte color, byte percent); - void setRemappingToGray(byte color, byte gray); - void setRemappingToPercentGray(byte color, byte gray, byte percent); - void setNoMatchRange(byte from, byte count); - bool remapEnabled(byte color) const; - byte remapColor(byte color, byte target); - bool remapAllTables(bool palChanged); - int getRemapCount() const { return _remapCount; } - int getStartColor() const { return _remapEndColor - REMAP_COLOR_COUNT + 1; } - int getEndColor() const { return _remapEndColor; } + GfxRemap32(); + + void saveLoadWithSerializer(Common::Serializer &s); + + inline uint8 getRemapCount() const { return _numActiveRemaps; } + inline uint8 getStartColor() const { return _remapStartColor; } + inline uint8 getEndColor() const { return _remapEndColor; } + inline uint8 getBlockedRangeStart() const { return _blockedRangeStart; } + inline int16 getBlockedRangeCount() const { return _blockedRangeCount; } + + /** + * Turns off remapping of the given color. If `color` is + * 0, all remaps are turned off. + */ + void remapOff(const uint8 color); + + /** + * Turns off all color remaps. + */ + void remapAllOff(); + + /** + * Configures a SingleRemap for the remap color `color`. + * The SingleRemap will shift palette colors between + * `from` and `to` (inclusive) by `delta` palette + * entries when the remap is applied. + */ + void remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta); + + /** + * Configures a SingleRemap for the remap color `color` + * to modify the brightness of remapped colors by + * `percent`. + */ + void remapByPercent(const uint8 color, const int16 percent); + + /** + * Configures a SingleRemap for the remap color `color` + * to modify the saturation of remapped colors by + * `gray`. + */ + void remapToGray(const uint8 color, const int8 gray); + + /** + * Configures a SingleRemap for the remap color `color` + * to modify the brightness of remapped colors by + * `percent`, and saturation of remapped colors by + * `gray`. + */ + void remapToPercentGray(const uint8 color, const int16 gray, const int16 percent); + + /** + * Prevents GfxRemap32 from using the given range of + * palette entries as potential remap targets. + * + * @NOTE Was DontMapToRange in SSCI. + */ + void blockRange(const uint8 from, const int16 count); + + /** + * Determines whether or not the given color has an + * active remapper. If it does not, it is treated as a + * skip color and the pixel is not drawn. + * + * @note SSCI uses a boolean array to decide whether a + * a pixel is remapped, but it is possible to get the + * same information from `_remaps`, as this function + * does. + * Presumably, the separate array was created for + * performance reasons, since this is called a lot in + * the most critical section of the renderer. + */ + inline bool remapEnabled(uint8 color) const { + const uint8 index = _remapEndColor - color; + assert(index < _remaps.size()); + return (_remaps[index]._type != kRemapNone); + } + + /** + * Calculates the correct color for a target by looking + * up the target color in the SingleRemap that controls + * the given sourceColor. If there is no remap for the + * given color, it will be treated as a skip color. + */ + inline uint8 remapColor(const uint8 sourceColor, const uint8 targetColor) const { + const uint8 index = _remapEndColor - sourceColor; + assert(index < _remaps.size()); + const SingleRemap &singleRemap = _remaps[index]; + assert(singleRemap._type != kRemapNone); + return singleRemap._remapColors[targetColor]; + } + + /** + * Updates all active remaps in response to a palette + * change or a remap settings change. + * + * `paletteChanged` is true if the next palette in + * GfxPalette32 has been previously modified by other + * palette operations. + */ + bool remapAllTables(const bool paletteUpdated); + private: - GfxPalette32 *_palette; - RemapParams _remaps[REMAP_COLOR_COUNT]; - bool _update; - byte _noMapStart, _noMapCount; - bool _targetChanged[NON_REMAPPED_COLOR_COUNT]; - byte _remapEndColor; - int _remapCount; - - void initColorArrays(byte index); - bool applyRemap(byte index); - bool updateRemap(byte index, bool palChanged); - int16 matchColor(const byte r, const byte g, const byte b, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable) const; -}; + typedef Common::Array SingleRemapsList; -} // End of namespace Sci + /** + * The first index of the remap area in the system + * palette. + */ + const uint8 _remapStartColor; + + /** + * The last index of the remap area in the system + * palette. + */ + uint8 _remapEndColor; + /** + * The number of currently active remaps. + */ + uint8 _numActiveRemaps; + + /** + * The list of SingleRemaps. + */ + SingleRemapsList _remaps; + + /** + * If true, indicates that one or more SingleRemaps were + * reconfigured and all remaps need to be recalculated. + */ + bool _needsUpdate; + + /** + * The first color that is blocked from being used as a + * remap target color. + */ + uint8 _blockedRangeStart; + + /** + * The size of the range of blocked colors. If zero, + * all colors are potential targets for remapping. + */ + int16 _blockedRangeCount; +}; +} // End of namespace Sci #endif diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 243c12b5cf..41fa144b06 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -700,7 +700,7 @@ void SciEngine::initGraphics() { if (getSciVersion() >= SCI_VERSION_2) { _gfxPalette32 = new GfxPalette32(_resMan, _gfxScreen); _gfxPalette16 = _gfxPalette32; - _gfxRemap32 = new GfxRemap32(_gfxPalette32); + _gfxRemap32 = new GfxRemap32(); } else { #endif _gfxPalette16 = new GfxPalette(_resMan, _gfxScreen); -- cgit v1.2.3