FifeGUI 0.3.0
A C++ GUI library designed for games.
color.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause
2// SPDX-FileCopyrightText: 2004 - 2008 Olof Naessén and Per Larsson
3// SPDX-FileCopyrightText: 2013 - 2026 Fifengine contributors
4
5// Corresponding header include
6#include "fifechan/color.hpp"
7
8// Standard library includes
9#include <algorithm>
10#include <string>
11#include <vector>
12
13namespace fcn
14{
15 // Constructs a color from the bytes in an integer (int).
16 Color::Color(int color) : r((color >> 16) & 0xFF), g((color >> 8) & 0xFF), b((color >> 0) & 0xFF)
17 {
18 }
19
20 // Constructor with integer parameters for each color component (int RGBA).
21 Color::Color(int red, int green, int blue, int alpha) :
22 r(static_cast<std::uint8_t>(red)),
23 g(static_cast<std::uint8_t>(green)),
24 b(static_cast<std::uint8_t>(blue)),
25 a(static_cast<std::uint8_t>(alpha))
26 {
27 }
28
29 // Constructor with uint8_t parameters for each color component and an optional alpha component (uint8_t RGBA).
30 Color::Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) : r(red), g(green), b(blue), a(alpha)
31 {
32 }
33
34 // Constructor accepting a string for hex or RGB.
35 Color::Color(std::string const & colorString)
36 {
37 if (colorString.at(0) == '#') {
38 // handle hex string, remove '#' and parse
39 parseHex(colorString.substr(1));
40 } else if (colorString.find("rgba") != std::string::npos) {
41 // handle rgba string
42 parseRGBA(colorString);
43 } else {
44 // handle rgb string
45 parseRGB(colorString);
46 }
47 }
48
49 void Color::parseHex(std::string const & hex)
50 {
51 if (hex.size() != 6) {
52 throw std::invalid_argument("Hex code must be 6 characters long.");
53 }
54
55 r = static_cast<uint8_t>(std::stoi(hex.substr(0, 2), nullptr, 16));
56 g = static_cast<uint8_t>(std::stoi(hex.substr(2, 2), nullptr, 16));
57 b = static_cast<uint8_t>(std::stoi(hex.substr(4, 2), nullptr, 16));
58 a = 255; // Default alpha
59 }
60
61 void Color::parseRGB(std::string const & rgbString)
62 {
63 // Ensure the string starts with 'rgb(' and ends with ')'
64 if (!rgbString.starts_with("rgb(") || !rgbString.ends_with(")")) {
65 throw std::invalid_argument("Invalid RGB format. Expected: rgb(r,g,b)");
66 }
67
68 // Extract the part inside "rgb()"
69 std::string const values = rgbString.substr(4, rgbString.size() - 5); // Exclude "rgb(" and ")"
70
71 // Parse the RGB components
72 std::vector<int> components = parseColorComponents(values);
73 if (components.size() != 3) {
74 throw std::invalid_argument("Invalid RGB format. Expected 3 components (r,g,b)");
75 }
76
77 r = static_cast<uint8_t>(components.at(0));
78 g = static_cast<uint8_t>(components.at(1));
79 b = static_cast<uint8_t>(components.at(2));
80 a = 255; // Default alpha
81 }
82
83 void Color::parseRGBA(std::string const & rgbaString)
84 {
85 // Ensure the string starts with 'rgba(' and ends with ')'
86 if (!rgbaString.starts_with("rgba(") || !rgbaString.ends_with(")")) {
87 throw std::invalid_argument("Invalid RGBA format. Expected: rgba(r,g,b,a)");
88 }
89
90 // Extract the part inside "rgba()"
91 std::string const values = rgbaString.substr(5, rgbaString.size() - 6); // Exclude "rgba(" and ")"
92
93 // Parse the RGBA components
94 std::vector<int> components = parseColorComponents(values, true);
95 if (components.size() != 4) {
96 throw std::invalid_argument("Invalid RGBA format. Expected 4 components (r,g,b,a)");
97 }
98
99 r = static_cast<uint8_t>(components.at(0));
100 g = static_cast<uint8_t>(components.at(1));
101 b = static_cast<uint8_t>(components.at(2));
102 a = static_cast<uint8_t>(components.at(3));
103 }
104
105 std::vector<int> Color::parseColorComponents(std::string const & colorString, bool withAlpha)
106 {
107 std::vector<int> colors;
108
109 // Extract the RGB(A) values from the content string.
110 size_t const firstComma = colorString.find(',');
111 if (firstComma == std::string::npos) {
112 throw std::invalid_argument("Invalid format. Expected comma after first value.");
113 }
114
115 size_t const secondComma = colorString.find(',', firstComma + 1);
116 if (secondComma == std::string::npos) {
117 throw std::invalid_argument("Invalid format. Expected comma after second value.");
118 }
119
120 size_t const thirdComma = colorString.find(',', secondComma + 1);
121 if (withAlpha && thirdComma == std::string::npos) {
122 throw std::invalid_argument("Invalid format. Expected comma after third value.");
123 }
124
125 // Extract components as substrings
126 std::string const redStr = colorString.substr(0, firstComma);
127 std::string const greenStr = colorString.substr(firstComma + 1, secondComma - firstComma - 1);
128 std::string const blueStr = colorString.substr(secondComma + 1, thirdComma - secondComma - 1);
129 // Use default alpha value, if not present
130 std::string const alphaStr = withAlpha ? colorString.substr(thirdComma + 1) : "255";
131
132 // Convert each component using std::strtol with error handling
133 char* end = nullptr;
134
135 int const red = std::strtol(redStr.c_str(), &end, 10);
136 if (*end != '\0') {
137 throw std::invalid_argument("Invalid red value.");
138 }
139
140 int const green = std::strtol(greenStr.c_str(), &end, 10);
141 if (*end != '\0') {
142 throw std::invalid_argument("Invalid green value.");
143 }
144
145 int const blue = std::strtol(blueStr.c_str(), &end, 10);
146 if (*end != '\0') {
147 throw std::invalid_argument("Invalid blue value.");
148 }
149
150 int const alpha = std::strtol(alphaStr.c_str(), &end, 10);
151 if (withAlpha && *end != '\0') {
152 throw std::invalid_argument("Invalid alpha value.");
153 }
154
155 // Add values to components vector
156 colors.push_back(red);
157 colors.push_back(green);
158 colors.push_back(blue);
159 if (withAlpha) {
160 colors.push_back(alpha);
161 }
162
163 return colors;
164 }
165
166 // Returns the red component of the color.
167 Color Color::operator+(Color const & color) const
168 {
169 return Color{
170 static_cast<uint8_t>(std::clamp(r + color.r, 0, 255)),
171 static_cast<uint8_t>(std::clamp(g + color.g, 0, 255)),
172 static_cast<uint8_t>(std::clamp(b + color.b, 0, 255)),
173 a // Keep the alpha as it is
174 };
175 }
176
177 Color Color::operator-(Color const & color) const
178 {
179 return Color{
180 static_cast<uint8_t>(std::max(0, r - color.r)),
181 static_cast<uint8_t>(std::max(0, g - color.g)),
182 static_cast<uint8_t>(std::max(0, b - color.b)),
183 a // Keep the alpha as it is
184 };
185 }
186
187 Color Color::operator+(float value) const
188 {
189 return Color{
190 static_cast<uint8_t>(std::clamp(r + value, 0.0F, 255.0F)),
191 static_cast<uint8_t>(std::clamp(g + value, 0.0F, 255.0F)),
192 static_cast<uint8_t>(std::clamp(b + value, 0.0F, 255.0F)),
193 a};
194 }
195
196 Color Color::operator-(float value) const
197 {
198 return Color{
199 static_cast<uint8_t>(std::clamp(static_cast<int>(r - value), 0, 255)),
200 static_cast<uint8_t>(std::clamp(static_cast<int>(g - value), 0, 255)),
201 static_cast<uint8_t>(std::clamp(static_cast<int>(b - value), 0, 255)),
202 a // Keep the alpha as it is
203 };
204 }
205
206 Color Color::operator*(float value) const
207 {
208 return Color{
209 static_cast<uint8_t>(std::clamp(static_cast<int>(r * value), 0, 255)),
210 static_cast<uint8_t>(std::clamp(static_cast<int>(g * value), 0, 255)),
211 static_cast<uint8_t>(std::clamp(static_cast<int>(b * value), 0, 255)),
212 a // Keep the alpha as it is
213 };
214 }
215
217 {
218 r = std::clamp(static_cast<int>(r) + static_cast<int>(color.r), 0, 255);
219 g = std::clamp(static_cast<int>(g) + static_cast<int>(color.g), 0, 255);
220 b = std::clamp(static_cast<int>(b) + static_cast<int>(color.b), 0, 255);
221 // Alpha remains unchanged
222 return *this; // Return the modified object (current instance)
223 }
224
226 {
227 r = std::max(0, static_cast<int>(r) - static_cast<int>(color.r));
228 g = std::max(0, static_cast<int>(g) - static_cast<int>(color.g));
229 b = std::max(0, static_cast<int>(b) - static_cast<int>(color.b));
230 // Alpha remains unchanged
231 return *this; // Return the modified object (current instance)
232 }
233
235 {
236 r = std::clamp(static_cast<int>(r * value), 0, 255);
237 g = std::clamp(static_cast<int>(g * value), 0, 255);
238 b = std::clamp(static_cast<int>(b * value), 0, 255);
239 // Alpha remains unchanged
240 return *this; // Return the modified object (current instance)
241 }
242
243 // Lighten the color by a percentage
244 Color Color::lighten(float percentage) const
245 {
246 return Color{
247 static_cast<uint8_t>(std::min(255, static_cast<int>(r + (r * percentage)))),
248 static_cast<uint8_t>(std::min(255, static_cast<int>(g + (g * percentage)))),
249 static_cast<uint8_t>(std::min(255, static_cast<int>(b + (b * percentage)))),
250 a};
251 }
252
253 // Darken the color by a percentage
254 Color Color::darken(float percentage) const
255 {
256 return Color{
257 static_cast<uint8_t>(std::max(0, static_cast<int>(r - (r * percentage)))),
258 static_cast<uint8_t>(std::max(0, static_cast<int>(g - (g * percentage)))),
259 static_cast<uint8_t>(std::max(0, static_cast<int>(b - (b * percentage)))),
260 a};
261 }
262
263 // Convert the current color to grayscale.
265 {
266 auto const gray = static_cast<uint8_t>((r * 0.3) + (g * 0.59) + (b * 0.11));
267 return Color{gray, gray, gray, a};
268 }
269
270 // Blend the current color with another color.
271 Color Color::blendWith(Color const & other) const
272 {
273 auto blendChannel = [](uint8_t c1, uint8_t c2, uint8_t a1, uint8_t a2) {
274 // This is the formula for blending two colors with alpha values:
275 // (c1 * a1 * (255 - a2) + c2 * a2 * 255) / (255 * 255)
276 // 1. First color contribution: c1 * a1 * (255 - a2)
277 // - This scales color 1 (c1) by its alpha (a1) and the inverse of color 2's alpha (255 - a2) to account
278 // for transparency.
279 // 2. Second color contribution: c2 * a2 * 255
280 // - This scales color 2 (c2) by its alpha (a2), treating it as fully opaque (255) for maximum
281 // contribution.
282 // 3. Normalization: The sum of both color contributions is divided by 255 * 255
283 // - This ensures the result remains within the 0-255 range, preventing overflow and maintaining valid
284 // color values.
285
286 int const maxAlpha = 255; // Maximum alpha value for normalization
287 int const inverseA2 = maxAlpha - a2; // Inverse of color 2's alpha
288 int const scaledC1 = c1 * a1 * inverseA2; // Scaled contribution of color 1
289 int const scaledC2 = c2 * a2 * maxAlpha; // Scaled contribution of color 2
290
291 return static_cast<uint8_t>((scaledC1 + scaledC2) / (maxAlpha * maxAlpha));
292 };
293
294 auto const blendedAlpha = static_cast<uint8_t>(a + (other.a * (255 - a) / 255));
295 auto const blendedRed = blendChannel(r, other.r, a, other.a);
296 auto const blendedGreen = blendChannel(g, other.g, a, other.a);
297 auto const blendedBlue = blendChannel(b, other.b, a, other.a);
298
299 return Color{blendedRed, blendedGreen, blendedBlue, blendedAlpha};
300 }
301
302 bool Color::operator==(Color const & color) const
303 {
304 return r == color.r && g == color.g && b == color.b && a == color.a;
305 }
306
307 bool Color::operator!=(Color const & color) const
308 {
309 return !(*this == color);
310 }
311
312 FIFEGUI_API std::ostream& operator<<(std::ostream& out, Color const & color)
313 {
314 out << "Color [r = " << static_cast<int>(color.r) << ", g = " << static_cast<int>(color.g)
315 << ", b = " << static_cast<int>(color.b) << ", a = " << static_cast<int>(color.a) << "]";
316 return out;
317 }
318
319 std::string Color::toHexString() const
320 {
321 std::stringstream ss;
322 ss << "#" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(r) << std::setw(2)
323 << std::setfill('0') << static_cast<int>(g) << std::setw(2) << std::setfill('0') << static_cast<int>(b);
324 return ss.str();
325 }
326
327 std::string Color::toRGBString() const
328 {
329 std::stringstream ss;
330 ss << "rgb(" << static_cast<int>(r) << "," << static_cast<int>(g) << "," << static_cast<int>(b) << ")";
331 return ss.str();
332 }
333
334 std::string Color::toRGBAString() const
335 {
336 std::stringstream ss;
337 ss << "rgba(" << static_cast<int>(r) << "," << static_cast<int>(g) << "," << static_cast<int>(b) << ","
338 << static_cast<int>(a) << ")";
339 return ss.str();
340 }
341} // namespace fcn
uint8_t a
Alpha color component (0-255).
Definition color.hpp:350
Color & operator+=(Color const &color)
Adds the RGB values of another color to this color.
Definition color.cpp:216
Color toGrayScale() const
Converts the color to grayscale.
Definition color.cpp:264
std::string toRGBString() const
Returns the color in the form "rgb(r,g,b)".
Definition color.cpp:327
std::string toHexString() const
Returns the color as a hexadecimal string in the form "#RRGGBB".
Definition color.cpp:319
std::string toRGBAString() const
Returns the color in the form "rgba(r,g,b,a)".
Definition color.cpp:334
Color operator+(Color const &color) const
Adds the RGB values of two colors together.
Definition color.cpp:167
Color lighten(float percentage) const
Lightens the color by a percentage.
Definition color.cpp:244
Color & operator*=(float value)
Multiplies the RGB values of this color with a float value.
Definition color.cpp:234
Color operator*(float value) const
Multiplies the RGB values of a color with a float value.
Definition color.cpp:206
uint8_t b
Blue color component (0-255).
Definition color.hpp:347
Color darken(float percentage) const
Darkens the color by a percentage.
Definition color.cpp:254
bool operator==(Color const &color) const
Compares two colors.
Definition color.cpp:302
uint8_t g
Green color component (0-255).
Definition color.hpp:344
bool operator!=(Color const &color) const
Compares two colors.
Definition color.cpp:307
Color operator-(Color const &color) const
Subtracts the RGB values of one color from another.
Definition color.cpp:177
Color()=default
Default constructor.
Color blendWith(Color const &other) const
Blends the color with another color using alpha blending.
Definition color.cpp:271
uint8_t r
Red color component (0-255).
Definition color.hpp:341
Color & operator-=(Color const &color)
Subtracts the RGB values of another color from this color.
Definition color.cpp:225
Used replacement tokens by configure_file():
FIFEGUI_API std::ostream & operator<<(std::ostream &out, Color const &color)
Definition color.cpp:312