Skip to content

Commit

Permalink
Merge pull request #2472 from uklotzde/rgbcolor
Browse files Browse the repository at this point in the history
Optional and opaque RGB colors
  • Loading branch information
Holzhaus authored Feb 10, 2020
2 parents 1ca12a3 + 06d393e commit f7f52f6
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@ add_executable(mixxx-test
src/test/readaheadmanager_test.cpp
src/test/replaygaintest.cpp
src/test/rescalertest.cpp
src/test/rgbcolor_test.cpp
src/test/samplebuffertest.cpp
src/test/sampleutiltest.cpp
src/test/schemamanager_test.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/mixxxapplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "soundio/soundmanagerutil.h"
#include "track/track.h"
#include "track/trackref.h"
#include "util/color/rgbcolor.h"
#include "util/math.h"

// When linking Qt statically on Windows we have to Q_IMPORT_PLUGIN all the
Expand Down Expand Up @@ -67,6 +68,7 @@ void MixxxApplication::registerMetaTypes() {
qRegisterMetaType<mixxx::ReplayGain>("mixxx::ReplayGain");
qRegisterMetaType<mixxx::Bpm>("mixxx::Bpm");
qRegisterMetaType<mixxx::Duration>("mixxx::Duration");
qRegisterMetaType<std::optional<mixxx::RgbColor>>("std::optional<mixxx::RgbColor>");
qRegisterMetaType<SoundDeviceId>("SoundDeviceId");
QMetaType::registerComparators<SoundDeviceId>();
}
Expand Down
65 changes: 65 additions & 0 deletions src/test/rgbcolor_test.cpp
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
153 changes: 153 additions & 0 deletions src/util/color/rgbcolor.h
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>)
24 changes: 24 additions & 0 deletions src/util/optional.h
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

0 comments on commit f7f52f6

Please sign in to comment.