-
-
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
Merged
Merged
Changes from 20 commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
5055657
Add RgbColor class
uklotzde a831e39
Add equality comparison operators
uklotzde 2144b51
Add note about RgbColorCode vs. QRgb
uklotzde e3b5353
Add note about immutability
uklotzde cfd14f1
Add more documentation
uklotzde c657a9f
Use std::optional for color code conversions
uklotzde af1fdb1
Reduce variations of conversion operations
uklotzde 7b6acf0
Declare Qt type information
uklotzde ef4ce69
Replace internal QColor with RgbColorCode
uklotzde cc2e262
Treat RgbColor as a POD
uklotzde a991d23
Register global meta type for RgbColor
uklotzde c298ab3
Fix build for ancient GCC version 5.4.0
uklotzde 1b52a67
Update documentation
uklotzde cac064b
Replace isValid() with operator bool()
uklotzde 3326c92
Prefer explicit conversion and fix various issues
uklotzde ec9a027
Remove constexpr
uklotzde 30f203d
Fix type inference in tests
uklotzde b6f37c1
Add comment about use cases for this class
uklotzde 3f4ca14
Split OptionalRgbColor from RgbColor
uklotzde d565a89
Use constexpr
uklotzde 439b985
Fix typos in documentation
uklotzde 6184663
Declare a Qt meta type for std::optional<mixxx::RgbColor>>
uklotzde 666422c
Replace OptionalRgbColor with std::optional<RgbColor>
uklotzde bcf18b9
Remove obsolete validation
uklotzde 5e41678
Add a debug assertion
uklotzde b31edfd
Move RgbColorCode typedef into RgbColor
uklotzde bc2fed7
Add missing constexpr
uklotzde a864454
Add comment about constexpr ctor
uklotzde 18b6ffe
Implicitly validate color codes in RgbColor ctor
uklotzde fc6a36a
Update class documentation
uklotzde 3df8889
Add debug output stream operators
uklotzde dc597b0
Fix invalid constexpr
uklotzde 14a29f8
Add conversions from/to QVariant
uklotzde f6b781c
Run clang-format
uklotzde 06d393e
Add missing RgbColor tests
uklotzde File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#include "util/color/rgbcolor.h" | ||
|
||
#include <gtest/gtest.h> | ||
|
||
#include "test/mixxxtest.h" | ||
|
||
namespace mixxx { | ||
|
||
TEST(OptionalRgbColorTest, DefaultIsUndefined) { | ||
EXPECT_FALSE(OptionalRgbColor().optional()); | ||
EXPECT_FALSE(static_cast<bool>(OptionalRgbColor().optional())); | ||
} | ||
|
||
TEST(OptionalRgbColorTest, FromRgbColorCode) { | ||
EXPECT_TRUE(OptionalRgbColor(0x000000).optional()); | ||
EXPECT_EQ(RgbColor(0x000000), *OptionalRgbColor(0x000000).optional()); | ||
EXPECT_TRUE(OptionalRgbColor(0xFF0000).optional()); | ||
EXPECT_EQ(RgbColor(0xFF0000), *OptionalRgbColor(0xFF0000).optional()); | ||
EXPECT_TRUE(OptionalRgbColor(0x00FF00).optional()); | ||
EXPECT_EQ(RgbColor(0x00FF00), *OptionalRgbColor(0x00FF00).optional()); | ||
EXPECT_TRUE(OptionalRgbColor(0x0000FF).optional()); | ||
EXPECT_EQ(RgbColor(0x0000FF), *OptionalRgbColor(0x0000FF).optional()); | ||
EXPECT_TRUE(OptionalRgbColor(0xFFFFFF).optional()); | ||
EXPECT_EQ(RgbColor(0xFFFFFF), *OptionalRgbColor(0xFFFFFF).optional()); | ||
} | ||
|
||
TEST(OptionalRgbColorTest, FromOptionalRgbColorCode) { | ||
EXPECT_EQ(OptionalRgbColor(), OptionalRgbColor(std::nullopt)); | ||
EXPECT_EQ(OptionalRgbColor(0x123456), OptionalRgbColor(std::make_optional(RgbColor(0x123456)))); | ||
} | ||
|
||
TEST(OptionalRgbColorTest, FromQColor) { | ||
EXPECT_FALSE(OptionalRgbColor(QColor()).optional()); | ||
EXPECT_EQ(OptionalRgbColor(), OptionalRgbColor(QColor())); | ||
EXPECT_TRUE(OptionalRgbColor(QColor::fromRgb(0x000000)).optional()); | ||
EXPECT_EQ(OptionalRgbColor(0x000000), OptionalRgbColor(QColor::fromRgb(0x000000))); | ||
EXPECT_TRUE(OptionalRgbColor(QColor::fromRgb(0xFF0000)).optional()); | ||
EXPECT_EQ(OptionalRgbColor(0xFF0000), OptionalRgbColor(QColor::fromRgb(0xFF0000))); | ||
EXPECT_TRUE(OptionalRgbColor(QColor::fromRgb(0x00FF00)).optional()); | ||
EXPECT_EQ(OptionalRgbColor(0x00FF00), OptionalRgbColor(QColor::fromRgb(0x00FF00))); | ||
EXPECT_TRUE(OptionalRgbColor(QColor::fromRgb(0x0000FF)).optional()); | ||
EXPECT_EQ(OptionalRgbColor(0x0000FF), OptionalRgbColor(QColor::fromRgb(0x0000FF))); | ||
EXPECT_TRUE(OptionalRgbColor(QColor::fromRgb(0xFFFFFF)).optional()); | ||
EXPECT_EQ(OptionalRgbColor(0xFFFFFF), OptionalRgbColor(QColor::fromRgb(0xFFFFFF))); | ||
} | ||
|
||
TEST(OptionalRgbColorTest, FromQColorWithAlpha) { | ||
EXPECT_TRUE(OptionalRgbColor(QColor::fromRgba(0xAA000000)).optional()); | ||
EXPECT_EQ(OptionalRgbColor(0x000000), OptionalRgbColor(QColor::fromRgba(0xAA000000))); | ||
EXPECT_TRUE(OptionalRgbColor(QColor::fromRgba(0xAAFF0000)).optional()); | ||
EXPECT_EQ(OptionalRgbColor(0xFF0000), OptionalRgbColor(QColor::fromRgba(0xAAFF0000))); | ||
EXPECT_TRUE(OptionalRgbColor(QColor::fromRgba(0xAA00FF00)).optional()); | ||
EXPECT_EQ(OptionalRgbColor(0x00FF00), OptionalRgbColor(QColor::fromRgba(0xAA00FF00))); | ||
EXPECT_TRUE(OptionalRgbColor(QColor::fromRgba(0xAA0000FF)).optional()); | ||
EXPECT_EQ(OptionalRgbColor(0x0000FF), OptionalRgbColor(QColor::fromRgba(0xAA0000FF))); | ||
EXPECT_TRUE(OptionalRgbColor(QColor::fromRgba(0xAAFFFFFF)).optional()); | ||
EXPECT_EQ(OptionalRgbColor(0xFFFFFF), OptionalRgbColor(QColor::fromRgba(0xAAFFFFFF))); | ||
} | ||
|
||
TEST(OptionalRgbColorTest, ToQColor) { | ||
EXPECT_EQ(QColor(), OptionalRgbColor().toQColor()); | ||
EXPECT_EQ(QColor::fromRgba(0xAABBCCDD), | ||
OptionalRgbColor().toQColor(QColor::fromRgba(0xAABBCCDD))); | ||
EXPECT_EQ(QColor::fromRgb(0x123456), | ||
OptionalRgbColor(QColor::fromRgba(0xAA123456)).toQColor(QColor::fromRgba(0xAABBCCDD))); | ||
} | ||
|
||
} // namespace mixxx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
#pragma once | ||
|
||
#include <QColor> | ||
#include <QtGlobal> | ||
|
||
#include "util/assert.h" | ||
#include "util/optional.h" | ||
|
||
namespace mixxx { | ||
|
||
// 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 RgbColorCode; | ||
|
||
// Type-safe wrapper around RgbColorCode without implicit | ||
// range checks and no implicit validation. | ||
// Apart from the assignment operator this type is immutable. | ||
class RgbColor { | ||
public: | ||
// The default constructor is not available, because there is | ||
// no common default value that fits all possible use cases! | ||
RgbColor() = delete; | ||
// Explicit conversion from RgbColorCode. | ||
explicit constexpr RgbColor(RgbColorCode code) | ||
: m_code(code) { | ||
} | ||
// Explicit conversion from QColor. | ||
RgbColor(QColor anyColor, RgbColorCode codeIfInvalid) | ||
: m_code(anyColorToCode(anyColor, codeIfInvalid)) { | ||
} | ||
|
||
// Explicit conversion to a valid color code. | ||
RgbColorCode validCode() const { | ||
return validateCode(m_code); | ||
} | ||
|
||
// Explicit conversion into the corresponding QColor. | ||
QColor validQColor() const { | ||
return QColor::fromRgb(validCode()); | ||
} | ||
|
||
friend bool operator==(RgbColor lhs, RgbColor rhs) { | ||
return lhs.m_code == rhs.m_code; | ||
} | ||
|
||
protected: | ||
// Bitmask of valid codes = 0x00RRGGBB | ||
static constexpr RgbColorCode kRgbCodeMask = 0x00FFFFFF; | ||
|
||
static RgbColorCode validateCode(RgbColorCode code) { | ||
return code & kRgbCodeMask; | ||
} | ||
static bool isValidCode(RgbColorCode code) { | ||
return code == validateCode(code); | ||
} | ||
|
||
static RgbColorCode anyColorToCode(QColor anyColor, RgbColorCode codeIfInvalid) { | ||
if (anyColor.isValid()) { | ||
// Strip alpha channel bits! | ||
return validateCode(anyColor.rgb()); | ||
} else { | ||
return codeIfInvalid; | ||
} | ||
} | ||
|
||
RgbColorCode m_code; | ||
}; | ||
|
||
inline bool operator!=(RgbColor lhs, RgbColor rhs) { | ||
return !(lhs == rhs); | ||
} | ||
|
||
// A thin wrapper around 24-bit opaque RGB values that includes | ||
// a single undefined state. The undefined state could be used | ||
// to represent missing or transparent values. | ||
// This class might be used as a "hub" for the conversion between | ||
// RgbColorCode, std::optional<RgbColorCode>, and QColor. For | ||
// this particulare use case it's only purpose is to store an | ||
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. typos: "this particular use case its only purpose" |
||
// intermediate representation before the internal value that | ||
// has been converted from the source type is finally converted | ||
// into the desired target type. | ||
// std::optional<RgbColorCode> is equivalent, RgbColorCode is | ||
// less versatile, and QColor is more versatile. | ||
// Apart from the assignment operator this type is immutable. | ||
class OptionalRgbColor final : public RgbColor { | ||
public: | ||
OptionalRgbColor() | ||
: RgbColor(kUndefinedInternalCode) { | ||
DEBUG_ASSERT(!isValidCode(m_code)); | ||
} | ||
// Explicit conversion from RgbColor | ||
explicit OptionalRgbColor(RgbColor base) | ||
: RgbColor(base.validCode()) { | ||
DEBUG_ASSERT(isValidCode(m_code)); | ||
} | ||
// Explicit conversion from RgbColorCode | ||
explicit OptionalRgbColor(RgbColorCode code) | ||
: RgbColor(RgbColor(validateCode(code))) { | ||
DEBUG_ASSERT(isValidCode(m_code) == isValidCode(code)); | ||
} | ||
// Explicit conversion from an optional RgbColor that | ||
// is equivalent to this class. The conversion is explicit | ||
// to avoid ambiguities with the implicit conversion back | ||
// into std::optional<RgbColor>. | ||
explicit OptionalRgbColor(std::optional<RgbColor> optional) | ||
: RgbColor(optional ? optional->validCode() : kUndefinedInternalCode) { | ||
DEBUG_ASSERT(isValidCode(m_code) == static_cast<bool>(optional)); | ||
} | ||
// Explicit conversion from QColor | ||
explicit OptionalRgbColor(QColor anyColor) | ||
: RgbColor(anyColorToInternalCode(anyColor)) { | ||
DEBUG_ASSERT(isValidCode(m_code) == anyColor.isValid()); | ||
} | ||
OptionalRgbColor(const OptionalRgbColor&) = default; | ||
OptionalRgbColor(OptionalRgbColor&&) = default; | ||
|
||
// Explicit conversion to an optional RGB color. | ||
std::optional<RgbColor> optional() const { | ||
if (isValidCode(m_code)) { | ||
// Defined | ||
return std::make_optional(RgbColor(m_code)); | ||
} else { | ||
// Undefined | ||
return std::nullopt; | ||
} | ||
} | ||
|
||
// Explicit conversion into the corresponding QColor. | ||
// Optionally the desired QColor that represents undefined | ||
// values could be provided. | ||
QColor toQColor(QColor undefinedColor = QColor()) const { | ||
if (isValidCode(m_code)) { | ||
// Defined | ||
return validQColor(); | ||
} else { | ||
// Undefined | ||
return undefinedColor; | ||
} | ||
} | ||
|
||
private: | ||
static constexpr RgbColorCode kUndefinedInternalCode = 0xFFFFFFFF; | ||
|
||
static RgbColorCode codeToInternalCode(RgbColorCode code) { | ||
// The unused bits of an RgbColorCode should never be set. | ||
DEBUG_ASSERT(isValidCode(code)); | ||
// This normalization should not be necessary, just in case. | ||
return code & kRgbCodeMask; | ||
} | ||
static RgbColorCode optionalCodeToInternalCode(std::optional<RgbColorCode> optionalCode) { | ||
if (optionalCode) { | ||
return codeToInternalCode(*optionalCode); | ||
} else { | ||
return kUndefinedInternalCode; | ||
} | ||
} | ||
|
||
static RgbColorCode anyColorToInternalCode(QColor anyColor) { | ||
return anyColorToCode(anyColor, kUndefinedInternalCode); | ||
} | ||
}; | ||
|
||
} // namespace mixxx | ||
|
||
Q_DECLARE_TYPEINFO(mixxx::OptionalRgbColor, Q_PRIMITIVE_TYPE); | ||
Q_DECLARE_METATYPE(mixxx::OptionalRgbColor) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.