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