Skip to content

Commit

Permalink
Merge pull request #20 from 4J-company/feature/color
Browse files Browse the repository at this point in the history
Add mr::Color
  • Loading branch information
cone-forest authored Dec 16, 2024
2 parents c67b71e + c05ccff commit f68c777
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ add_library(${MR_MATH_LIB_NAME} INTERFACE
include/mr-math/vec.hpp
include/mr-math/math.hpp
include/mr-math/bound_box.hpp
include/mr-math/color.hpp
)

target_include_directories(${MR_MATH_LIB_NAME} INTERFACE
Expand Down
136 changes: 136 additions & 0 deletions include/mr-math/color.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#ifndef __color_hpp_
#define __color_hpp_

#include "def.hpp"
#include "vec.hpp"

namespace mr {

// color in RGBA float format
struct [[nodiscard]] Color {
public:
using ValueT = float;

template <std::floating_point T>
Color(T r, T g, T b, T a = 1) noexcept
: _data{Vec4f{r, g, b, a}} {}

Color(Vec4f rgba) noexcept
: _data{rgba} {}

template <std::integral T>
Color(T r, T g, T b, T a = 255) noexcept
: _data{r, g, b, a} {
assert(r <= 255);
assert(g <= 255);
assert(b <= 255);
assert(a <= 255);
_data /= 255;
}

explicit Color(uint32_t rgba) noexcept
: Color(
uint8_t((rgba & 0xFF'00'00'00) >> 24),
uint8_t((rgba & 0x00'FF'00'00) >> 16),
uint8_t((rgba & 0x00'00'FF'00) >> 8),
uint8_t((rgba & 0x00'00'00'FF))
) {}

// setters
void r(ValueT r) noexcept { _data.x(r); }
void g(ValueT g) noexcept { _data.y(g); }
void b(ValueT b) noexcept { _data.z(b); }
void a(ValueT a) noexcept { _data.w(a); }
void set(size_t i, ValueT value) noexcept {
assert(i < 4);
_data.set(i, value);
}

// getters
[[nodiscard]] constexpr ValueT r() const noexcept { return _data.x(); }
[[nodiscard]] constexpr ValueT g() const noexcept { return _data.y(); }
[[nodiscard]] constexpr ValueT b() const noexcept { return _data.z(); }
[[nodiscard]] constexpr ValueT a() const noexcept { return _data.w(); }
[[nodiscard]] constexpr ValueT operator[](size_t i) const {
assert(i < 4);
return _data[i];
}

// structured binding support
template <size_t I> requires (I < 4)
ValueT get() const { return _data[I]; }

operator Vec4f() noexcept {
return _data;
}

// format conversions
// TODO: implement using shuffle
Vec4f argb() const noexcept {
return {a(), r(), g(), b()};
}

Vec4f bgra() const noexcept {
return {b(), g(), r(), a()};
}

Vec4f abgr() const noexcept {
return {a(), b(), g(), r()};
}

friend Color operator+(Color lhs, const Color &rhs) noexcept {
lhs += rhs;
return lhs;
}

Color &operator+=(const Color &other) noexcept {
_data += other._data;;
return *this;
}

bool operator==(const Color &other) const noexcept {
return _data == other._data;
}

bool equal(const Color &other, ValueT eps = epsilon<ValueT>()) const noexcept {
return _data.equal(other._data, eps);
}

friend std::ostream & operator<<(std::ostream &s, const Color &color) noexcept {
Vec4u comps = color._data * 255;
comps.clamp(0, 256);
s << '#' << std::hex << std::uppercase << comps[0] << comps[1] << comps[2] << comps[3] << std::nouppercase << std::dec;
return s;
}

private:
Vec4f _data;
};

namespace literals {

Color operator"" _rgba(unsigned long long value) {
assert(value <= 0xFF'FF'FF'FF);
return Color{static_cast<uint32_t>(value)};
}

} // namespace literals

} // namespace mr

#ifdef __cpp_structured_bindings
// specializations for structured binding support
namespace std
{
template <>
struct tuple_size<mr::Color>
: std::integral_constant<size_t, 4> {};

template <size_t I>
struct tuple_element<I, mr::Color> {
using type = mr::Color::ValueT;
};
}
#endif // __cpp_structured_bindings

#endif // __color_hpp_
46 changes: 46 additions & 0 deletions include/mr-math/def.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,52 @@ namespace mr {
}

inline struct UncheckedTag {} unchecked;

// inclusive numeric interval [low, high]
// use operator() to check if value is in range
template<typename L, typename H>
class Interval {
public:
Interval(const L& low_, const H& high_)
: low(low_), high(high_) {}

template<typename T>
requires std::totally_ordered_with<T, L> && std::totally_ordered_with<T, H>
bool operator()(const T& value) { return low <= value && value <= high; }

const L& low;
const H& high;
};

// returns inclusive interval
// usage: if (mr::within_(1, 10)(x))
template<typename L, typename H>
constexpr Interval<L, H> within(const L& low, const H& high) {
return {low, high};
}

// exclusive numeric interval (low, high)
// use operator() to check if value is in range
template<typename L, typename H>
class IntervalEx {
public:
IntervalEx(const L& low_, const H& high_)
: low(low_), high(high_) {}

template<typename T>
requires std::totally_ordered_with<T, L> && std::totally_ordered_with<T, H>
bool operator()(const T& value) { return low < value && value < high; }

const L& low;
const H& high;
};

// returns exclusive interval
// usage: if (mr::within_ex(1, 10)(x))
template<typename L, typename H>
constexpr IntervalEx<L, H> within_ex(const L& low, const H& high) {
return {low, high};
}
} // namespace mr

#endif // __def_hpp_
1 change: 1 addition & 0 deletions include/mr-math/math.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
#include "units.hpp"
#include "camera.hpp"
#include "bound_box.hpp"
#include "color.hpp"

#endif // __math_hpp_
11 changes: 11 additions & 0 deletions include/mr-math/vec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,17 @@ namespace mr {
return *this;
}

constexpr Vec clamped(T low, T high) const noexcept {
assert(low < high);
const auto &data = _data._data;
return {stdx::iif(data <= low, SimdT(low), stdx::iif(data >= high, SimdT(high), data))};
}

constexpr Vec & clamp(T low, T high) noexcept {
*this = clamped(low, high);
return *this;
}

constexpr bool operator==(const Vec &other) const noexcept {
return _data == other._data;
}
Expand Down
85 changes: 85 additions & 0 deletions tests/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ TEST_F(Vector3DTest, Abs) {
EXPECT_EQ(v.abs(), mr::Vec3f(30, 47, 80));
}

TEST_F(Vector3DTest, Clamp) {
mr::Vec3f v{-30, 47, -80};
EXPECT_EQ(v.clamped(-47, 0), mr::Vec3f(-30, 0, -47));
EXPECT_EQ(v.clamp(-47, 0), mr::Vec3f(-30, 0, -47));
}

class MatrixTest : public ::testing::Test {
protected:
mr::Matr4f m1 {
Expand Down Expand Up @@ -230,3 +236,82 @@ TEST_F(MatrixTest, RotateVector) {
}

// TODO: camera tests

TEST(ColorTest, Constructors) {
const mr::Color expected1{0.3, 0.47, 0.8, 1.0};
EXPECT_EQ(mr::Color(0.3, 0.47, 0.8), expected1);
EXPECT_EQ(mr::Color(mr::Vec4f(0.3, 0.47, 0.8, 1)), expected1);

const mr::Color expected2{0.2980392156862745, 0.4666666666666667, 0.8, 1.0};
EXPECT_EQ(mr::Color(76, 119, 204, 255), expected2);
EXPECT_EQ(mr::Color(0x4C'77'CC'FF), expected2);
EXPECT_EQ(0x4C'77'CC'FF_rgba, expected2);
}

TEST(ColorTest, Formats) {
const auto color = 0x4C'77'CC'FF_rgba;
EXPECT_EQ(color.argb(), 0xFF'4C'77'CC_rgba);
EXPECT_EQ(color.bgra(), 0xCC'77'4c'FF_rgba);
EXPECT_EQ(color.abgr(), 0xFF'CC'77'4c_rgba);
}

TEST(ColorTest, Getters) {
const auto color = 0x4C'77'CC'FF_rgba;
EXPECT_FLOAT_EQ(color.r(), 0.2980392156862745f);
EXPECT_FLOAT_EQ(color.g(), 0.4666666666666667f);
EXPECT_FLOAT_EQ(color.b(), 0.8);
EXPECT_FLOAT_EQ(color.a(), 1.0);

EXPECT_EQ(color[0], color.r());
EXPECT_EQ(color[1], color.g());
EXPECT_EQ(color[2], color.b());
EXPECT_EQ(color[3], color.a());

const auto[r, g, b, a] = color;
EXPECT_EQ(r, color.r());
EXPECT_EQ(g, color.g());
EXPECT_EQ(b, color.b());
EXPECT_EQ(a, color.a());
}

TEST(ColorTest, Setters) {
auto color = 0x4C'77'CC'FF_rgba;
color.r(1.0);
color.set(1, 0.5);
EXPECT_EQ(color, mr::Color(1.0, 0.5, 0.8, 1.0));
}

TEST(ColorTest, Equality) {
const auto color1 = 0x4C'77'CC'FF_rgba;
const auto copy = color1;
EXPECT_EQ(color1, copy);
EXPECT_TRUE(color1.equal(copy));
EXPECT_TRUE(equal(color1, copy));

const auto color2 = 0x00'00'00'00_rgba;
EXPECT_NE(color1, color2);
EXPECT_FALSE(color1.equal(color2));
EXPECT_FALSE(equal(color1, color2));
}

TEST(ColorTest, Addition) {
// Values can exeed 1.0 (should they?)
EXPECT_EQ(mr::Color(1.0, 0.0, 0.5, 1.0) + mr::Color(0.0, 1.0, 0.5, 1.0), mr::Color(1.0, 1.0, 1.0, 2.0));
}

TEST(UtilityTest, Within) {
EXPECT_FALSE(mr::within(1, 10)(0));
EXPECT_TRUE(mr::within(1, 10)(1));
EXPECT_TRUE(mr::within(1, 10)(5));
EXPECT_TRUE(mr::within(1, 10)(10));
EXPECT_FALSE(mr::within(1, 10)(11));

EXPECT_FALSE(mr::within_ex(1, 10)(0));
EXPECT_FALSE(mr::within_ex(1, 10)(1));
EXPECT_TRUE(mr::within_ex(1, 10)(5));
EXPECT_FALSE(mr::within_ex(1, 10)(10));
EXPECT_FALSE(mr::within_ex(1, 10)(11));

EXPECT_TRUE(mr::within(1., 10.f)(5));
EXPECT_TRUE(mr::within_ex(1., 10.f)(5));
}

0 comments on commit f68c777

Please sign in to comment.