diff --git a/src/gfx_type.h b/src/gfx_type.h index 842c1b5ed1..bbd7e005c2 100644 --- a/src/gfx_type.h +++ b/src/gfx_type.h @@ -303,6 +303,7 @@ enum Colours : uint32_t { }; DECLARE_INCREMENT_DECREMENT_OPERATORS(Colours) DECLARE_ENUM_AS_ADDABLE(Colours) +DECLARE_ENUM_AS_BIT_SET(Colours) /** Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palette.png */ enum TextColour : uint32_t { @@ -420,4 +421,15 @@ struct PixelColour { TextColour ToTextColour() const; }; +struct HsvColour { + static constexpr int HUE_MAX = 360 * 128; ///< Maximum value for hue. + static constexpr int SAT_MAX = UINT8_MAX; ///< Maximum value for saturation. + static constexpr int VAL_MAX = UINT8_MAX; ///< Maximum value for value. + static constexpr int HUE_RGN = HUE_MAX / 6; + + uint16_t h; ///< Hue ranging from 0 to HUE_MAX. + uint8_t s; ///< Saturation ranging from 0 to SAT_MAX. + uint8_t v; ///< Value (brightness) ranging from 0 to VAL_MAX. +}; + #endif /* GFX_TYPE_H */ diff --git a/src/palette.cpp b/src/palette.cpp index d0853b5385..daa483308c 100644 --- a/src/palette.cpp +++ b/src/palette.cpp @@ -368,6 +368,81 @@ TextColour GetContrastColour(PixelColour background, uint8_t threshold) return sq1000_brightness < ((uint) threshold) * ((uint) threshold) * 1000 ? TC_WHITE : TC_BLACK; } +HsvColour ConvertRgbToHsv(Colour rgb) +{ + HsvColour hsv; + + uint8_t rgbmin = std::min({rgb.r, rgb.g, rgb.b}); + uint8_t rgbmax = std::max({rgb.r, rgb.g, rgb.b}); + + hsv.v = rgbmax; + if (hsv.v == 0) { + hsv.h = 0; + hsv.s = 0; + return hsv; + } + + int d = rgbmax - rgbmin; + hsv.s = HsvColour::SAT_MAX * d / rgbmax; + if (hsv.s == 0) { + hsv.h = 0; + return hsv; + } + + int hue; + if (rgbmax == rgb.r) { + hue = HsvColour::HUE_RGN * 0 + HsvColour::HUE_RGN * ((int)rgb.g - (int)rgb.b) / d; + } else if (rgbmax == rgb.g) { + hue = HsvColour::HUE_RGN * 2 + HsvColour::HUE_RGN * ((int)rgb.b - (int)rgb.r) / d; + } else { + hue = HsvColour::HUE_RGN * 4 + HsvColour::HUE_RGN * ((int)rgb.r - (int)rgb.g) / d; + } + if (hue > HsvColour::HUE_MAX) hue -= HsvColour::HUE_MAX; + if (hue < 0) hue += HsvColour::HUE_MAX; + hsv.h = hue; + + return hsv; +} + +Colour ConvertHsvToRgb(HsvColour hsv) +{ + if (hsv.s == 0) return Colour(hsv.v, hsv.v, hsv.v); + if (hsv.h >= HsvColour::HUE_MAX) hsv.h = 0; + + int region = hsv.h / HsvColour::HUE_RGN; + int remainder = (hsv.h - (region * HsvColour::HUE_RGN)) * 6; + int p = (hsv.v * (HsvColour::SAT_MAX - hsv.s)) / HsvColour::SAT_MAX; + int q = (hsv.v * (HsvColour::SAT_MAX - ((hsv.s * remainder) / HsvColour::HUE_MAX))) / HsvColour::SAT_MAX; + int t = (hsv.v * (HsvColour::SAT_MAX - ((hsv.s * (HsvColour::HUE_MAX - remainder)) / HsvColour::HUE_MAX))) / HsvColour::SAT_MAX; + + switch (region) { + case 0: return Colour(hsv.v, t, p); + case 1: return Colour(q, hsv.v, p); + case 2: return Colour(p, hsv.v, t); + case 3: return Colour(p, q, hsv.v); + case 4: return Colour(t, p, hsv.v); + default: return Colour(hsv.v, p, q); + } +} + +/** + * Adjust brightness of an HSV colour. + * @param hsv colour to adjust. + * @param shade shade to apply. + * @param contrast contrast of shade. + * @returns Adjusted HSV colour. + **/ +HsvColour AdjustHsvColourBrightness(HsvColour hsv, ColourShade shade, int contrast) +{ + HsvColour r = hsv; + int amt = (shade - SHADE_NORMAL) * (16 + contrast) / 8; + int overflow = (hsv.v + amt) - HsvColour::VAL_MAX; + r.v = ClampTo(hsv.v + amt); + r.s = ClampTo(hsv.s - std::max(0, overflow)); + return r; +} + + /** * Lookup table of colour shades for all 16 colour gradients. * 8 colours per gradient from darkest (0) to lightest (7) @@ -387,6 +462,10 @@ struct ColourGradients */ PixelColour GetColourGradient(Colours colour, ColourShade shade) { + ColoursPacker cp(colour); + if (cp.IsCustom()) { + return ConvertHsvToRgb(AdjustHsvColourBrightness(cp.Hsv(), shade, cp.GetContrast())); + } return ColourGradients::gradient[colour % COLOUR_END][shade % SHADE_END]; } diff --git a/src/palette_func.h b/src/palette_func.h index 33ab88801b..e07ffe2cbb 100644 --- a/src/palette_func.h +++ b/src/palette_func.h @@ -83,6 +83,10 @@ enum ColourShade : uint8_t { }; DECLARE_INCREMENT_DECREMENT_OPERATORS(ColourShade) +HsvColour ConvertRgbToHsv(Colour rgb); +Colour ConvertHsvToRgb(HsvColour hsv); +HsvColour AdjustHsvColourBrightness(HsvColour hsv, ColourShade shade, int contrast); + PixelColour GetColourGradient(Colours colour, ColourShade shade); void SetColourGradient(Colours colour, ColourShade shade, PixelColour palette_colour); @@ -137,6 +141,47 @@ inline constexpr uint8_t StretchBits(uint8_t v) return (v << (8 - TNumBits)) | (v >> (8 - (8 - TNumBits) * 2)); } +struct ColoursPacker +{ + Colours &c; + + explicit constexpr ColoursPacker(Colours &c) : c(c) { } + + /* + * Constants for the bit packing used by Colours. + */ + static constexpr const uint IS_CUSTOM_BIT = 4; + static constexpr const uint INDEX_START = 0; ///< Packed start of index component + static constexpr const uint INDEX_SIZE = 4; ///< Packed size of index component + static constexpr const uint HUE_START = 7; ///< Packed start of hue component + static constexpr const uint HUE_SIZE = 9; ///< Packed size of hue component + static constexpr const uint SAT_START = 16; ///< Packed start of saturation component + static constexpr const uint SAT_SIZE = 6; ///< Packed size of saturation component + static constexpr const uint VAL_START = 22; ///< Packed start of value component + static constexpr const uint VAL_SIZE = 6; ///< Packed size of value component + static constexpr const uint CON_START = 28; ///< Packed start of contrast component + static constexpr const uint CON_SIZE = 4; ///< Packed size of contrast component + + /* Colours is considered unused and blank if only the I component is set. */ + inline constexpr bool IsCustom() const { return HasBit(this->c, IS_CUSTOM_BIT); } + + inline constexpr uint8_t GetIndex() const { return GB(this->c, INDEX_START, INDEX_SIZE); } + inline constexpr uint16_t GetHue() const { return GB(this->c, HUE_START, HUE_SIZE) * HsvColour::HUE_MAX / (1U << 9); } + inline constexpr uint8_t GetSaturation() const { return StretchBits(GB(this->c, SAT_START, SAT_SIZE)); } + inline constexpr uint8_t GetValue() const { return StretchBits(GB(this->c, VAL_START, VAL_SIZE)); } + inline constexpr uint8_t GetContrast() const { return StretchBits(GB(this->c, CON_START, CON_SIZE)); } + + inline void SetCustom(bool v) { SB(this->c, IS_CUSTOM_BIT, 1, v); } + + inline void SetIndex(uint8_t v) { SB(this->c, INDEX_START, INDEX_SIZE, v); } + inline void SetHue(uint16_t v) { SB(this->c, HUE_START, HUE_SIZE, v * (1U << HUE_SIZE) / HsvColour::HUE_MAX); } + inline void SetSaturation(uint8_t v) { SB(this->c, SAT_START, SAT_SIZE, v >> (8 - SAT_SIZE)); } + inline void SetValue(uint8_t v) { SB(this->c, VAL_START, VAL_SIZE, v >> (8 - VAL_SIZE)); } + inline void SetContrast(uint8_t v) { SB(this->c, CON_START, CON_SIZE, v >> (8 - CON_SIZE)); } + + inline constexpr HsvColour Hsv() const { return {this->GetHue(), this->GetSaturation(), this->GetValue()}; } +}; + struct TextColourPacker { TextColour &tc;