-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2472 from uklotzde/rgbcolor
Optional and opaque RGB colors
- Loading branch information
Showing
5 changed files
with
245 additions
and
0 deletions.
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,65 @@ | ||
#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,RgbColorToQColor) { | ||
EXPECT_EQ(QColor::fromRgb(0x123456), toQColor(RgbColor(0x123456))); | ||
} | ||
|
||
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))); | ||
} | ||
|
||
TEST(RgbColorTest, RgbColorToQVariant) { | ||
EXPECT_EQ(QVariant(), toQVariant(RgbColor::nullopt())); | ||
EXPECT_EQ(QVariant(0x123456), toQVariant(RgbColor(0x123456))); | ||
} | ||
|
||
TEST(RgbColorTest, OptionalRgbColorToQVariant) { | ||
EXPECT_EQ(QVariant(), toQVariant(RgbColor::nullopt())); | ||
EXPECT_EQ(QVariant(0x123456), | ||
toQVariant(RgbColor::optional(QColor::fromRgba(0xAA123456)))); | ||
} | ||
|
||
} // 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,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; | ||
|
||
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)) { | ||
} | ||
// 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. | ||
|
||
inline QColor toQColor(RgbColor color) { | ||
return QColor::fromRgb(color); | ||
} | ||
|
||
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>) |
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 |