1
0
Fork 0

Add: Functions to convert colours between RGB and HSV space.

pull/11634/head
Peter Nelson 2025-07-25 08:56:49 +01:00
parent 504a26a60a
commit fb7c5e04cf
No known key found for this signature in database
GPG Key ID: 8EF8F0A467DF75ED
3 changed files with 136 additions and 0 deletions

View File

@ -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 */

View File

@ -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<uint8_t>(hsv.v + amt);
r.s = ClampTo<uint8_t>(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];
}

View File

@ -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<SAT_SIZE>(GB(this->c, SAT_START, SAT_SIZE)); }
inline constexpr uint8_t GetValue() const { return StretchBits<VAL_SIZE>(GB(this->c, VAL_START, VAL_SIZE)); }
inline constexpr uint8_t GetContrast() const { return StretchBits<CON_SIZE>(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;