Skip to content

Commit c277723

Browse files
committed
- add extensive color tests
- fix the error with color conversion, the reason was, that std::fmod doesn't work "correctly" with negative values, so I fixed that add ASSERT_EQ_HSV_COLOR, that asserts that two values of HSVColor are nearly the same, and doesn#ät use the bogus == operator for HSVColor, that I now removed
1 parent 32a7c86 commit c277723

File tree

4 files changed

+153
-34
lines changed

4 files changed

+153
-34
lines changed

src/helper/color.cpp

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@
77

88
#include <fmt/format.h>
99

10+
11+
#include <iostream>
12+
13+
[[nodiscard]] Color HSVColor::to_rgb_color() const {
14+
return Color{ *this };
15+
}
16+
17+
[[nodiscard]] std::string HSVColor::to_string() const {
18+
return to_rgb_color().to_string(Color::SerializeMode::HSV);
19+
}
20+
1021
helper::expected<Color, std::string> Color::from_string(const std::string& value) {
1122

1223
const auto result = detail::get_color_from_string(value);
@@ -18,7 +29,23 @@ helper::expected<Color, std::string> Color::from_string(const std::string& value
1829
return helper::unexpected<std::string>{ result.error() };
1930
}
2031

32+
namespace {
33+
template<typename T>
34+
T fmod_always_positive(T value, T divisor) {
35+
if (value < static_cast<T>(0.0)) {
36+
// see https://math.stackexchange.com/questions/2179579/how-can-i-find-a-mod-with-negative-number
37+
T result = value;
38+
while (result < 0) {
39+
result += divisor;
40+
}
41+
return result;
42+
}
43+
return std::fmod(value, divisor);
44+
}
45+
} // namespace
2146

47+
// taken carefully from https://math.stackexchange.com/questions/556341/rgb-to-hsv-color-conversion-algorithm
48+
// and modified and sped up, by optimizing it manually
2249
[[nodiscard]] HSVColor Color::to_hsv_color() const {
2350
constexpr auto max_d = static_cast<double>(0xFF);
2451
const double r_d = static_cast<double>(r) / max_d;
@@ -29,24 +56,25 @@ helper::expected<Color, std::string> Color::from_string(const std::string& value
2956
const double max = std::max({ r_d, g_d, b_d });
3057
const double delta = max - min;
3158

32-
double h = 0.0;
33-
34-
if (r >= max) { // > is bogus, just keeps compiler happy
35-
h = std::fmod((g - b) / delta, 6.0); // between yellow & magenta
59+
double h_temp = 0.0;
60+
if (min == max) {
61+
h_temp = 0.0;
62+
} else if (r >= max) { // > is bogus, just keeps compiler happy
63+
h_temp = (g - b) / delta; // between yellow & magenta
3664
} else if (g >= max) {
37-
h = 2.0 + ((b - r) / delta); // between cyan & yellow
65+
h_temp = 2.0 + ((b - r) / delta); // between cyan & yellow
3866
} else {
39-
h = 4.0 + ((r - g) / delta); // between magenta & cyan
67+
h_temp = 4.0 + ((r - g) / delta); // between magenta & cyan
4068
}
4169

42-
//common factor, so just do it at the end
43-
h *= 60.0; // degrees
70+
// use custom fmod, that always result in a positive value
71+
const double h = fmod_always_positive(h_temp, 6.0) * 60.0; //degrees
4472

4573
const double s = max == 0.0 ? 0.0 : delta / max;
4674

47-
const double v = max;
75+
//v = max
4876

49-
return HSVColor{ h, s, v, a };
77+
return HSVColor{ h, s, max, a };
5078
}
5179

5280
//Note: this output formats are all deserializable by the from_string method!

src/helper/color.hpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#include <ostream>
1010
#include <string>
1111

12+
13+
struct Color;
14+
1215
struct HSVColor {
1316
double h;
1417
double s;
@@ -46,10 +49,9 @@ struct HSVColor {
4649

4750
constexpr HSVColor(double h, double s, double v) : HSVColor{ h, s, v, 0xFF } { }
4851

49-
[[nodiscard]] constexpr bool operator==(const HSVColor& other) const {
50-
return std::tuple<double, double, double, u8>{ h, s, v, a }
51-
== std::tuple<double, double, double, u8>{ other.h, other.s, other.v, other.a };
52-
}
52+
[[nodiscard]] Color to_rgb_color() const;
53+
54+
[[nodiscard]] std::string to_string() const;
5355
};
5456

5557
struct Color {

tests/core/color.cpp

Lines changed: 108 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ namespace {
2525
} // namespace
2626

2727
// make colors printable
28-
void PrintTo(const Color& point, std::ostream* os) {
29-
*os << point.to_string();
28+
void PrintTo(const Color& color, std::ostream* os) {
29+
*os << color.to_string();
30+
}
31+
32+
void PrintTo(const HSVColor& color, std::ostream* os) {
33+
*os << color.to_string();
3034
}
3135

3236
// make helper::expected printable
@@ -51,14 +55,14 @@ MATCHER(ExpectedHasError, "expected has error") {
5155
TEST(Color, DefaultConstruction) {
5256
const auto c1 = Color{};
5357
const auto c2 = Color{ 0, 0, 0, 0 };
54-
EXPECT_EQ(c1, c2);
58+
ASSERT_EQ(c1, c2);
5559
}
5660

5761
TEST(Color, ConstructorProperties) {
5862
foreach_loop([](u8 r, u8 g, u8 b) {
5963
const auto c1 = Color{ r, g, b };
6064
const auto c2 = Color{ r, g, b, 0xFF };
61-
EXPECT_EQ(c1, c2);
65+
ASSERT_EQ(c1, c2);
6266
});
6367
}
6468

@@ -80,10 +84,8 @@ TEST(Color, FromStringValid) {
8084

8185
for (const auto& [valid_string, expected_color] : valid_strings) {
8286
const auto result = Color::from_string(valid_string);
83-
EXPECT_THAT(result, ExpectedHasValue()) << "Input was: " << valid_string;
84-
if (result.has_value()) {
85-
EXPECT_EQ(result.value(), expected_color) << "Input was: " << valid_string;
86-
}
87+
ASSERT_THAT(result, ExpectedHasValue()) << "Input was: " << valid_string;
88+
ASSERT_EQ(result.value(), expected_color) << "Input was: " << valid_string;
8789
}
8890
}
8991

@@ -149,18 +151,58 @@ TEST(Color, FromStringInvalid) {
149151

150152
for (const auto& [invalid_string, error_message] : invalid_strings) {
151153
const auto result = Color::from_string(invalid_string);
152-
EXPECT_THAT(result, ExpectedHasError()) << "Input was: " << invalid_string;
153-
if (not result.has_value()) {
154-
EXPECT_EQ(result.error(), error_message) << "Input was: " << invalid_string;
155-
}
154+
ASSERT_THAT(result, ExpectedHasError()) << "Input was: " << invalid_string;
155+
ASSERT_EQ(result.error(), error_message) << "Input was: " << invalid_string;
156156
}
157157
}
158158

159159

160+
namespace {
161+
162+
testing::AssertionResult HSVColorEqPred(const char*, const char*, const HSVColor& val1, const HSVColor& val2) {
163+
164+
constexpr auto max_h_err = 0.5;
165+
constexpr auto max_sv_err = 0.01;
166+
167+
const auto near_helper = [](double arg1, double arg2, double margin) -> bool {
168+
return ::testing::internal::DoubleNearPredFormat("", "", "", arg1, arg2, margin);
169+
};
170+
171+
if (not near_helper(val1.h, val2.h, max_h_err)) {
172+
return testing::AssertionFailure() << "HSVColors " << val1.to_string() << " and " << val2.to_string()
173+
<< " are not EQ, reason: h is not near enough\n"
174+
<< val1.h << " != " << val2.h;
175+
}
176+
177+
if (not near_helper(val1.s, val2.s, max_sv_err)) {
178+
return testing::AssertionFailure() << "HSVColors " << val1.to_string() << " and " << val2.to_string()
179+
<< " are not EQ, reason: s is not near enough\n"
180+
<< val1.s << " != " << val2.s;
181+
}
182+
if (not near_helper(val1.v, val2.v, max_sv_err)) {
183+
return testing::AssertionFailure() << "HSVColors " << val1.to_string() << " and " << val2.to_string()
184+
<< " are not EQ, reason: v is not near enough\n"
185+
<< val1.v << " != " << val2.v;
186+
}
187+
188+
if (val1.a != val2.a) {
189+
return testing::AssertionFailure() << "HSVColors " << val1.to_string() << " and " << val2.to_string()
190+
<< " are not EQ, reason: a is not equal\n"
191+
<< val1.a << " != " << val2.a;
192+
}
193+
194+
return testing::AssertionSuccess();
195+
}
196+
} // namespace
197+
198+
#define ASSERT_EQ_HSV_COLOR(val1, val2) /*NOLINT(cppcoreguidelines-macro-usage)*/ \
199+
ASSERT_PRED_FORMAT2(HSVColorEqPred, val1, val2)
200+
201+
160202
TEST(HSVColor, DefaultConstruction) {
161203
const auto c1 = HSVColor{};
162204
const auto c2 = HSVColor{ 0, 0, 0, 0 };
163-
EXPECT_EQ(c1, c2);
205+
ASSERT_EQ_HSV_COLOR(c1, c2);
164206
}
165207

166208
TEST(HSVColor, ConstructorProperties) {
@@ -176,7 +218,7 @@ TEST(HSVColor, ConstructorProperties) {
176218
for (const auto& [h, s, v] : values) {
177219
const auto c1 = HSVColor{ h, s, v };
178220
const auto c2 = HSVColor{ h, s, v, 0xFF };
179-
EXPECT_EQ(c1, c2);
221+
ASSERT_EQ_HSV_COLOR(c1, c2);
180222
}
181223
}
182224

@@ -195,6 +237,57 @@ TEST(HSVColor, InvalidConstructors) {
195237

196238
const auto construct = [h, s, v]() { HSVColor{ h, s, v }; }; //NOLINT(clang-analyzer-core.NullDereference)
197239

198-
EXPECT_ANY_THROW(construct()); //NOLINT(*)
240+
ASSERT_ANY_THROW(construct()); //NOLINT(*)
241+
}
242+
}
243+
244+
245+
TEST(ColorConversion, HSV_to_RGB_to_HSV) {
246+
constexpr const auto step_amount = 20; // to debug, increase this value, than you ahve more samples
247+
248+
for (double h = 0.0; h < 360.0; h += 360.0 / step_amount) {
249+
for (double s = 0.0; s < 1.0; s += 1.0 / step_amount) {
250+
for (double v = 0.0; v < 1.0; v += 1.0 / step_amount) {
251+
const auto convert = [h, s, v]() {
252+
const auto original_color = HSVColor{ h, s, v };
253+
254+
const auto converted_color = original_color.to_rgb_color();
255+
256+
const auto result_color = converted_color.to_hsv_color();
257+
258+
UNUSED(result_color);
259+
// ASSERT_EQ_HSV_COLOR(original_color, result_color);
260+
};
261+
262+
ASSERT_NO_THROW(convert()); //NOLINT(*)
263+
}
264+
}
265+
}
266+
}
267+
268+
269+
TEST(ColorConversion, RGG_to_HSV_to_RGB) {
270+
constexpr const u8 step_amount = 20; // to debug, increase this value, than you ahve more samples
271+
272+
constexpr const u8 u8_max = std::numeric_limits<u8>::max();
273+
constexpr const u8 step_size = u8_max / step_amount;
274+
275+
for (u8 r = 0.0; u8_max - r < step_amount; r += step_size) {
276+
for (u8 g = 0.0; u8_max - g < step_amount; g += step_size) {
277+
for (u8 b = 0.0; u8_max - b < step_amount; b += step_size) {
278+
const auto convert = [r, g, b]() {
279+
const auto original_color = Color{ r, g, b };
280+
281+
const auto converted_color = original_color.to_hsv_color();
282+
283+
const auto result_color = converted_color.to_rgb_color();
284+
285+
UNUSED(result_color);
286+
// ASSERT_EQ_HSV_COLOR(original_color, result_color);
287+
};
288+
289+
ASSERT_NO_THROW(convert()); //NOLINT(*)
290+
}
291+
}
199292
}
200293
}

tests/entry.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
2-
3-
41
#include <gtest/gtest.h>
52

6-
73
int main() {
8-
testing::InitGoogleTest();
4+
::testing::InitGoogleTest();
95
return RUN_ALL_TESTS();
106
}

0 commit comments

Comments
 (0)