/***************************************************************************** * Copyright (c) 2014-2025 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #include "Colour.h" #include "../SpriteIds.h" #include "../core/EnumMap.hpp" #include "../core/EnumUtils.hpp" #include "../drawing/Drawing.h" #include using namespace OpenRCT2::Drawing; ColourShadeMap ColourMapA[COLOUR_COUNT] = {}; static constexpr uint8_t kLegacyColourMaskBase = 0x1F; static constexpr uint8_t kLegacyColourFlagOutline = (1 << 5); static constexpr uint8_t kLegacyColourFlagInset = (1 << 6); enum { INDEX_COLOUR_0 = 243, INDEX_COLOUR_1 = 245, INDEX_DARKEST = 245, INDEX_DARKER = 246, INDEX_DARK = 247, INDEX_MID_DARK = 248, INDEX_MID_LIGHT = 249, INDEX_LIGHT = 250, INDEX_LIGHTER = 251, INDEX_LIGHTEST = 252, INDEX_COLOUR_10 = 253, INDEX_COLOUR_11 = 254, }; void ColoursInitMaps() { // Get colour maps from g1 for (int32_t i = 0; i < COLOUR_COUNT; i++) { // Get palette index in g1 / g2 const auto paletteIndex = (i < kColourNumOriginal) ? SPR_PALETTE_2_START : SPR_G2_PALETTE_BEGIN - kColourNumOriginal; const G1Element* g1 = GfxGetG1Element(paletteIndex + i); if (g1 != nullptr) { ColourMapA[i].colour_0 = static_cast(g1->offset[INDEX_COLOUR_0]); ColourMapA[i].colour_1 = static_cast(g1->offset[INDEX_COLOUR_1]); ColourMapA[i].darkest = static_cast(g1->offset[INDEX_DARKEST]); ColourMapA[i].darker = static_cast(g1->offset[INDEX_DARKER]); ColourMapA[i].dark = static_cast(g1->offset[INDEX_DARK]); ColourMapA[i].mid_dark = static_cast(g1->offset[INDEX_MID_DARK]); ColourMapA[i].mid_light = static_cast(g1->offset[INDEX_MID_LIGHT]); ColourMapA[i].light = static_cast(g1->offset[INDEX_LIGHT]); ColourMapA[i].lighter = static_cast(g1->offset[INDEX_LIGHTER]); ColourMapA[i].lightest = static_cast(g1->offset[INDEX_LIGHTEST]); ColourMapA[i].colour_10 = static_cast(g1->offset[INDEX_COLOUR_10]); ColourMapA[i].colour_11 = static_cast(g1->offset[INDEX_COLOUR_11]); } } } namespace OpenRCT2::Colour { static const EnumMap LookupTable{ { "black", COLOUR_BLACK }, { "grey", COLOUR_GREY }, { "white", COLOUR_WHITE }, { "dark_purple", COLOUR_DARK_PURPLE }, { "light_purple", COLOUR_LIGHT_PURPLE }, { "bright_purple", COLOUR_BRIGHT_PURPLE }, { "dark_blue", COLOUR_DARK_BLUE }, { "light_blue", COLOUR_LIGHT_BLUE }, { "icy_blue", COLOUR_ICY_BLUE }, { "teal", COLOUR_TEAL }, { "aquamarine", COLOUR_AQUAMARINE }, { "saturated_green", COLOUR_SATURATED_GREEN }, { "dark_green", COLOUR_DARK_GREEN }, { "moss_green", COLOUR_MOSS_GREEN }, { "bright_green", COLOUR_BRIGHT_GREEN }, { "olive_green", COLOUR_OLIVE_GREEN }, { "dark_olive_green", COLOUR_DARK_OLIVE_GREEN }, { "bright_yellow", COLOUR_BRIGHT_YELLOW }, { "yellow", COLOUR_YELLOW }, { "dark_yellow", COLOUR_DARK_YELLOW }, { "light_orange", COLOUR_LIGHT_ORANGE }, { "dark_orange", COLOUR_DARK_ORANGE }, { "light_brown", COLOUR_LIGHT_BROWN }, { "saturated_brown", COLOUR_SATURATED_BROWN }, { "dark_brown", COLOUR_DARK_BROWN }, { "salmon_pink", COLOUR_SALMON_PINK }, { "bordeaux_red", COLOUR_BORDEAUX_RED }, { "saturated_red", COLOUR_SATURATED_RED }, { "bright_red", COLOUR_BRIGHT_RED }, { "dark_pink", COLOUR_DARK_PINK }, { "bright_pink", COLOUR_BRIGHT_PINK }, { "light_pink", COLOUR_LIGHT_PINK }, { "dark_olive_dark", COLOUR_DARK_OLIVE_DARK }, { "dark_olive_light", COLOUR_DARK_OLIVE_LIGHT }, { "saturated_brown_light", COLOUR_SATURATED_BROWN_LIGHT }, { "bordeaux_red_dark", COLOUR_BORDEAUX_RED_DARK }, { "bordeaux_red_light", COLOUR_BORDEAUX_RED_LIGHT }, { "grass_green_dark", COLOUR_GRASS_GREEN_DARK }, { "grass_green_light", COLOUR_GRASS_GREEN_LIGHT }, { "olive_dark", COLOUR_OLIVE_DARK }, { "olive_light", COLOUR_OLIVE_LIGHT }, { "saturated_green_light", COLOUR_SATURATED_GREEN_LIGHT }, { "tan_dark", COLOUR_TAN_DARK }, { "tan_light", COLOUR_TAN_LIGHT }, { "dull_purple_light", COLOUR_DULL_PURPLE_LIGHT }, { "dull_green_dark", COLOUR_DULL_GREEN_DARK }, { "dull_green_light", COLOUR_DULL_GREEN_LIGHT }, { "saturated_purple_dark", COLOUR_SATURATED_PURPLE_DARK }, { "saturated_purple_light", COLOUR_SATURATED_PURPLE_LIGHT }, { "orange_light", COLOUR_ORANGE_LIGHT }, { "aqua_dark", COLOUR_AQUA_DARK }, { "magenta_light", COLOUR_MAGENTA_LIGHT }, { "dull_brown_dark", COLOUR_DULL_BROWN_DARK }, { "dull_brown_light", COLOUR_DULL_BROWN_LIGHT }, { "invisible", COLOUR_INVISIBLE }, { "void", COLOUR_VOID }, }; colour_t FromString(std::string_view s, colour_t defaultValue) { auto result = LookupTable.find(s); return (result != LookupTable.end()) ? result->second : defaultValue; } u8string ToString(colour_t colour) { auto result = LookupTable.find(colour); if (result != LookupTable.end()) return u8string(result->first); return "black"; } } // namespace OpenRCT2::Colour #ifndef DISABLE_TTF static BlendColourMapType BlendColourMap = { 0 }; static bool BlendColourMapInitialised = false; static uint8_t FindClosestPaletteIndex(uint8_t red, uint8_t green, uint8_t blue) { int16_t closest = -1; int32_t closestDistance = INT32_MAX; for (int i = PaletteIndex::pi0; i < PaletteIndex::pi230; i++) { const int32_t distance = std::pow(gPalette[i].Red - red, 2) + std::pow(gPalette[i].Green - green, 2) + std::pow(gPalette[i].Blue - blue, 2); if (distance < closestDistance) { closest = i; closestDistance = distance; } } return closest; } static void InitBlendColourMap() { for (size_t i = 0; i < kGamePaletteSize; i++) { for (size_t j = i; j < kGamePaletteSize; j++) { uint8_t red = (gPalette[i].Red + gPalette[j].Red) / 2; uint8_t green = (gPalette[i].Green + gPalette[j].Green) / 2; uint8_t blue = (gPalette[i].Blue + gPalette[j].Blue) / 2; auto colour = FindClosestPaletteIndex(red, green, blue); BlendColourMap[i][j] = colour; BlendColourMap[j][i] = colour; } } BlendColourMapInitialised = true; } BlendColourMapType* GetBlendColourMap() { if (!BlendColourMapInitialised) { InitBlendColourMap(); } return &BlendColourMap; } uint8_t BlendColours(const uint8_t paletteIndex1, const uint8_t paletteIndex2) { if (!BlendColourMapInitialised) { InitBlendColourMap(); } return BlendColourMap[paletteIndex1][paletteIndex2]; } #else BlendColourMapType* GetBlendColourMap() { return nullptr; } #endif ColourWithFlags ColourWithFlags::withFlag(ColourFlag flag, bool on) const { struct ColourWithFlags result = *this; result.flags.set(flag, on); return result; } ColourWithFlags ColourWithFlags::fromLegacy(uint8_t legacy) { ColourWithFlags result{}; result.colour = legacy & kLegacyColourMaskBase; if (legacy & kLegacyColourFlagTranslucent) result.flags.set(ColourFlag::translucent); if (legacy & kLegacyColourFlagInset) result.flags.set(ColourFlag::inset); if (legacy & kLegacyColourFlagOutline) result.flags.set(ColourFlag::withOutline); return result; } ColourWithFlags& ColourWithFlags::operator=(colour_t rhs) { colour = rhs; flags = {}; return *this; }