-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optional and opaque RGB colors #2472
Changes from 34 commits
5055657
a831e39
2144b51
e3b5353
cfd14f1
c657a9f
af1fdb1
7b6acf0
ef4ce69
cc2e262
a991d23
c298ab3
1b52a67
cac064b
3326c92
ec9a027
30f203d
b6f37c1
3f4ca14
d565a89
439b985
6184663
666422c
bcf18b9
5e41678
b31edfd
bc2fed7
a864454
18b6ffe
fc6a36a
3df8889
dc597b0
14a29f8
f6b781c
06d393e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
#include "util/color/rgbcolor.h" | ||
|
||
#include <gtest/gtest.h> | ||
|
||
#include "test/mixxxtest.h" | ||
|
||
namespace mixxx { | ||
|
||
TEST(RgbColorTest, OptionalRgbColorFromInvalidQColor) { | ||
EXPECT_FALSE(RgbColor::optional(QColor())); | ||
EXPECT_EQ(RgbColor::nullopt(), RgbColor::optional(QColor())); | ||
} | ||
|
||
TEST(RgbColorTest, OptionalRgbColorFromQColorWithoutAlpha) { | ||
EXPECT_TRUE(RgbColor::optional(QColor::fromRgb(0x000000))); | ||
EXPECT_EQ(0x000000, *RgbColor::optional(QColor::fromRgb(0x000000))); | ||
EXPECT_TRUE(RgbColor::optional(QColor::fromRgb(0xFF0000))); | ||
EXPECT_EQ(RgbColor::optional(0xFF0000), RgbColor::optional(QColor::fromRgb(0xFF0000))); | ||
EXPECT_TRUE(RgbColor::optional(QColor::fromRgb(0x00FF00))); | ||
EXPECT_EQ(RgbColor::optional(0x00FF00), RgbColor::optional(QColor::fromRgb(0x00FF00))); | ||
EXPECT_TRUE(RgbColor::optional(QColor::fromRgb(0x0000FF))); | ||
EXPECT_EQ(RgbColor::optional(0x0000FF), RgbColor::optional(QColor::fromRgb(0x0000FF))); | ||
EXPECT_TRUE(RgbColor::optional(QColor::fromRgb(0xFFFFFF))); | ||
EXPECT_EQ(RgbColor::optional(0xFFFFFF), RgbColor::optional(QColor::fromRgb(0xFFFFFF))); | ||
} | ||
|
||
TEST(RgbColorTest, OptionalRgbColorFromQColorWithAlpha) { | ||
EXPECT_TRUE(RgbColor::optional(QColor::fromRgba(0xAA000000))); | ||
EXPECT_EQ(RgbColor::optional(0x000000), RgbColor::optional(QColor::fromRgba(0xAA000000))); | ||
EXPECT_TRUE(RgbColor::optional(QColor::fromRgba(0xAAFF0000))); | ||
EXPECT_EQ(RgbColor::optional(0xFF0000), RgbColor::optional(QColor::fromRgba(0xAAFF0000))); | ||
EXPECT_TRUE(RgbColor::optional(QColor::fromRgba(0xAA00FF00))); | ||
EXPECT_EQ(RgbColor::optional(0x00FF00), RgbColor::optional(QColor::fromRgba(0xAA00FF00))); | ||
EXPECT_TRUE(RgbColor::optional(QColor::fromRgba(0xAA0000FF))); | ||
EXPECT_EQ(RgbColor::optional(0x0000FF), RgbColor::optional(QColor::fromRgba(0xAA0000FF))); | ||
EXPECT_TRUE(RgbColor::optional(QColor::fromRgba(0xAAFFFFFF))); | ||
EXPECT_EQ(RgbColor::optional(0xFFFFFF), RgbColor::optional(QColor::fromRgba(0xAAFFFFFF))); | ||
} | ||
|
||
TEST(RgbColorTest, OptionalRgbColorToQColor) { | ||
EXPECT_EQ(QColor(), toQColor(RgbColor::nullopt())); | ||
EXPECT_EQ(QColor::fromRgba(0xAABBCCDD), | ||
toQColor(RgbColor::nullopt(), QColor::fromRgba(0xAABBCCDD))); | ||
EXPECT_EQ(QColor::fromRgb(0x123456), | ||
toQColor(RgbColor::optional(QColor::fromRgba(0xAA123456)))); | ||
EXPECT_EQ(QColor::fromRgb(0x123456), | ||
toQColor(RgbColor::optional(QColor::fromRgba(0xAA123456)), QColor::fromRgba(0xAABBCCDD))); | ||
} | ||
|
||
} // namespace mixxx |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
#pragma once | ||
|
||
#include <QColor> | ||
#include <QVariant> | ||
#include <QtDebug> | ||
#include <QtGlobal> | ||
|
||
#include "util/assert.h" | ||
#include "util/optional.h" | ||
|
||
namespace mixxx { | ||
|
||
// Type-safe wrapper for 24-bit RGB color codes without an alpha | ||
// channel. Code values are implicitly validated upon construction. | ||
// | ||
// Apart from the assignment operator this type is immutable. | ||
class RgbColor { | ||
public: | ||
// A pure 24-bit 0xxRRGGBB color code with 8-bit per channel | ||
// without an an alpha channel. | ||
// We are using a separate typedef, because QRgb implicitly | ||
// includes an alpha channel whereas this type does not! | ||
typedef quint32 code_t; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shorthand for "type", common suffix in C/C++ for types and typedefs. The lowercase type names also pickup the spelling of the corresponding C++ types, i.e. |
||
|
||
static constexpr code_t validateCode(code_t code) { | ||
return code & kRgbCodeMask; | ||
} | ||
static bool isValidCode(code_t code) { | ||
return code == validateCode(code); | ||
} | ||
|
||
// The default constructor is not available, because there is | ||
// no common default value that fits all possible use cases! | ||
RgbColor() = delete; | ||
// Explicit conversion from code_t with implicit validation. | ||
explicit constexpr RgbColor(code_t code) | ||
: m_code(validateCode(code)) { | ||
} | ||
uklotzde marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Explicit conversion from QColor. | ||
RgbColor(QColor anyColor, code_t codeIfInvalid) | ||
: m_code(anyColorToCode(anyColor, codeIfInvalid)) { | ||
} | ||
|
||
// Implicit conversion to a color code. | ||
constexpr operator code_t() const { | ||
return m_code; | ||
} | ||
|
||
friend bool operator==(RgbColor lhs, RgbColor rhs) { | ||
return lhs.m_code == rhs.m_code; | ||
} | ||
|
||
typedef std::optional<RgbColor> optional_t; | ||
|
||
static constexpr optional_t nullopt() { | ||
return std::nullopt; | ||
} | ||
|
||
// Overloaded conversion functions for conveniently creating | ||
// std::optional<RgbColor>. | ||
static constexpr optional_t optional(code_t code) { | ||
return optional(RgbColor(code)); | ||
} | ||
static constexpr optional_t optional(RgbColor color) { | ||
return std::make_optional(color); | ||
} | ||
static optional_t optional(QColor color) { | ||
if (color.isValid()) { | ||
return optional(validateCode(color.rgb())); | ||
} else { | ||
return nullopt(); | ||
} | ||
} | ||
static optional_t optional(const QVariant& varCode) { | ||
if (varCode.isNull()) { | ||
return nullopt(); | ||
} else { | ||
DEBUG_ASSERT(varCode.canConvert(QMetaType::UInt)); | ||
bool ok = false; | ||
const auto code = varCode.toUInt(&ok); | ||
VERIFY_OR_DEBUG_ASSERT(ok) { | ||
return nullopt(); | ||
} | ||
return optional(static_cast<code_t>(code)); | ||
} | ||
} | ||
|
||
protected: | ||
// Bitmask of valid codes = 0x00RRGGBB | ||
static constexpr code_t kRgbCodeMask = 0x00FFFFFF; | ||
|
||
static code_t anyColorToCode(QColor anyColor, code_t codeIfInvalid) { | ||
if (anyColor.isValid()) { | ||
// Strip alpha channel bits! | ||
return validateCode(anyColor.rgb()); | ||
} else { | ||
return codeIfInvalid; | ||
} | ||
} | ||
|
||
code_t m_code; | ||
}; | ||
|
||
inline bool operator!=(RgbColor lhs, RgbColor rhs) { | ||
return !(lhs == rhs); | ||
} | ||
|
||
// Explicit conversion of both non-optional and optional | ||
// RgbColor values to QColor as overloaded free functions. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why you decide her for a free function? I think it is more QT like to have a member function for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We cannot add new functions to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is std::optional relevant here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. Because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I don't understand the point here. We have here the toQColor() function taking a RgbColor. There is no std::nullopt involved.
What is the issue with the later? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh I have some ideas, hit me up on zulip There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok sounds like we have consensus on this PR! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The decision to map There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DRY There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same arguments apply for the conversion to |
||
|
||
inline QColor toQColor(RgbColor color) { | ||
return QColor::fromRgb(color); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Qt will ignore the alpha bits, so you can remove the bitmask. Is there any other reason to have it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. QColor::fromRgb() implicitly adds alpha bits. But QColor::rgb() doesn't remove them. Try it without the bitmask and the tests will fail. |
||
} | ||
|
||
inline QColor toQColor(RgbColor::optional_t optional, QColor defaultColor = QColor()) { | ||
if (optional) { | ||
return toQColor(*optional); | ||
} else { | ||
return defaultColor; | ||
} | ||
} | ||
|
||
// Explicit conversion of both non-optional and optional | ||
// RgbColor values to QVariant as overloaded free functions. | ||
|
||
inline QVariant toQVariant(RgbColor color) { | ||
return QVariant(static_cast<RgbColor::code_t>(color)); | ||
} | ||
|
||
inline QVariant toQVariant(RgbColor::optional_t optional) { | ||
if (optional) { | ||
return toQVariant(*optional); | ||
} else { | ||
return QVariant(); | ||
} | ||
} | ||
|
||
// Debug output stream operators | ||
|
||
inline QDebug operator<<(QDebug dbg, RgbColor color) { | ||
return dbg << toQColor(color); | ||
} | ||
|
||
inline QDebug operator<<(QDebug dbg, RgbColor::optional_t optional) { | ||
return dbg << toQColor(optional); | ||
} | ||
|
||
} // namespace mixxx | ||
|
||
// Assumption: A primitive type wrapped into std::optional is | ||
// still a primitive type. | ||
Q_DECLARE_TYPEINFO(std::optional<mixxx::RgbColor>, Q_PRIMITIVE_TYPE); | ||
Q_DECLARE_METATYPE(std::optional<mixxx::RgbColor>) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
#pragma once | ||
|
||
#if __has_include(<optional>) || \ | ||
(defined(__cpp_lib_optional) && __cpp_lib_optional >= 201606L) | ||
|
||
#include <optional> | ||
|
||
#else | ||
|
||
#include <experimental/optional> | ||
|
||
namespace std { | ||
|
||
using std::experimental::make_optional; | ||
using std::experimental::nullopt; | ||
using std::experimental::optional; | ||
|
||
// Workarounds for missing member functions: | ||
// option::has_value() -> explicit operator bool() | ||
// option::value() -> operator*() | ||
|
||
} // namespace std | ||
|
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@uklotzde Is it ok if I move this class into the Color namespace in my cue color branch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please coordinate this with @Holzhaus to avoid merge conflicts. Better do it in a separate PR that is expected to be merged quickly.`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also note that namespace names should be lowercase:
https://google.github.io/styleguide/cppguide.html#Namespace_Names
The
Color
namespace violates this rule.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ferranpujolcamins Do we actually need a separate
color
namespace withinmixxx
for just a bunch of classes, all with color in their class name?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The color namespace was there to group some free functions. We can reuse it to include your class. But it doesn't really matter to me. Just let me know what you think it's best.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With argument dependent lookup and function overloading available in C++ there is no need to hide free functions in artificial namespaces. Or do I miss a use case?
We are free to chose whatever works best for us. However keep an eye on discoverability and developer experience in general.