/* 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 "sci/sci.h" #include "sci/engine/features.h" #include "sci/graphics/palette32.h" #include "sci/graphics/remap32.h" namespace Sci { #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; } } 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); } return false; } bool SingleRemap::updateRange() { const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); bool updated = false; for (uint i = 0; i < remapStartColor; ++i) { uint8 targetColor; if (_from <= i && i <= _to) { targetColor = i + _delta; } else { targetColor = i; } if (_remapColors[i] != targetColor) { updated = true; _remapColors[i] = targetColor; } _originalColorsChanged[i] = true; } return updated; } 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]); if (_originalColors[i] != color) { _originalColorsChanged[i] = true; _originalColors[i] = color; } 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; } } } const bool updated = apply(); Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); _lastPercent = _percent; return updated; } 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; } if (_gray != _lastGray || _originalColorsChanged[i]) { const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100; 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; } } } const bool updated = apply(); Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); _lastGray = _gray; return updated; } 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; } if (_percent != _lastPercent || _gray != _lastGray || _originalColorsChanged[i]) { const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100; 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; } } } const bool updated = apply(); Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); _lastPercent = _percent; _lastGray = _gray; return updated; } 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); const bool *const paletteCycleMap = g_sci->_gfxPalette32->getCycleMap(); const int16 blockedRangeCount = gfxRemap32->getBlockedRangeCount(); if (blockedRangeCount) { const uint8 blockedRangeStart = gfxRemap32->getBlockedRangeStart(); Common::fill(blockedColors + blockedRangeStart, blockedColors + blockedRangeStart + blockedRangeCount, true); } for (uint i = 0; i < remapStartColor; ++i) { if (paletteCycleMap[i]) { blockedColors[i] = true; } } // NOTE: SSCI did a loop over colors here to create a // new array of updated, unblocked colors, but then // never used it bool updated = false; for (uint i = 1; i < remapStartColor; ++i) { int distance; if (!_idealColorsChanged[i] && !_originalColorsChanged[_remapColors[i]]) { continue; } if ( _idealColorsChanged[i] && _originalColorsChanged[_remapColors[i]] && _matchDistances[i] < 100 && colorDistance(_idealColors[i], _originalColors[_remapColors[i]]) <= _matchDistances[i] ) { continue; } const int16 bestColor = matchColor(_idealColors[i], _matchDistances[i], distance, blockedColors); if (bestColor != -1 && _remapColors[i] != bestColor) { updated = true; _remapColors[i] = bestColor; _matchDistances[i] = distance; } } return updated; } 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 bestDistance = 0xFFFFF; int distance = minimumDistance; const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette(); for (uint i = 0, channelDistance; i < g_sci->_gfxRemap32->getStartColor(); ++i) { if (blockedIndexes[i]) { continue; } distance = nextPalette.colors[i].r - color.r; distance *= distance; if (bestDistance <= distance) { continue; } channelDistance = nextPalette.colors[i].g - color.g; distance += channelDistance * channelDistance; if (bestDistance <= distance) { continue; } channelDistance = nextPalette.colors[i].b - color.b; distance += channelDistance * channelDistance; if (bestDistance <= distance) { continue; } bestDistance = distance; bestIndex = i; } // 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 (g_sci->_features->hasMidPaletteCode()) { _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