#ifndef BWIDGETS_COLOR_HPP #define BWIDGETS_COLOR_HPP #include #include #include #include #include namespace bwidgets { // Wrap SDL_Color to provide operator overloads. class Color final { public: SDL_Color sdl_type; Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a, bool op_on_alpha = false, bool op_on_colors = true) noexcept : sdl_type {r, g, b, a}, _operate_on_alpha {op_on_alpha}, _operate_on_colors {op_on_colors} {} Color(SDL_Color c = {}, bool op_on_alpha = false, bool op_on_colors = true) noexcept : sdl_type(c), _operate_on_alpha(op_on_alpha), _operate_on_colors(op_on_colors) {} Color(const Color&) noexcept = default; Color(Color&&) noexcept = default; ~Color() = default; // Enable/disable operations on alpha channel. Disabled by default on // construction. [[nodiscard]] auto alpha(const bool state = true) const noexcept -> Color { return {sdl_type, _operate_on_colors, state}; } // Enable/disable operations on color channels.Enabled by default on // construction. [[nodiscard]] auto colors(const bool state = true) const noexcept -> Color { return {sdl_type, state, _operate_on_alpha}; } // Get reference to the wrapped SDL native object. [[nodiscard]] auto& operator()() noexcept { return sdl_type; } [[nodiscard]] const auto& operator()() const noexcept { return sdl_type; } // Add operand to enabled channels. template [[nodiscard]] auto operator+(const N operand) const noexcept { return _operate(operand, [](N a, N b) { return a + b; }); } // Substract operand from enabled channels. template [[nodiscard]] auto operator-(const N operand) const noexcept { return _operate(operand, [](N a, N b) { return a - b; }); } // Multiply enabled channel values by opererand. template [[nodiscard]] auto operator*(const N operand) const noexcept { return _operate(operand, [](N a, N b) { return a * b; }); } // Divide enabled channel values by operand. template [[nodiscard]] auto operator/(const N operand) const { if (operand == 0) throw std::domain_error("division by zero."); return _operate(operand, [](N a, N b) { return a / b; }); } auto operator=(const Color& c) noexcept -> Color& = default; auto operator=(Color&&) noexcept -> Color& = default; auto& operator=(SDL_Color c) noexcept { sdl_type = c; return *this; } // Compare for equality of the enabled channel values between two Color // instances. [[nodiscard]] auto operator==(const Color other) const noexcept { return (_operate_on_colors && sdl_type.r == other().r && sdl_type.g == other().g && sdl_type.b == other().b) || (_operate_on_alpha && sdl_type.a == other().a); } // Compare for inequality of the enabled channel values between two Color // instances. [[nodiscard]] auto operator!=(const Color other) const noexcept { return (sdl_type.r != other().r || sdl_type.g != other().g || sdl_type.b != other().b) && ((_operate_on_alpha && sdl_type.a != other().a) || !_operate_on_alpha); } private: bool _operate_on_alpha; bool _operate_on_colors; template auto _operate(const N operand, const std::function& operator_) const { const auto overunderflow_check = [](auto x) { if (x > (uint8_t)x) throw std::overflow_error("uint8_t overflow."); if (x < (uint8_t)x) throw std::underflow_error("uint8_t underflow."); }; Color c(sdl_type, _operate_on_alpha, _operate_on_colors); if (_operate_on_alpha) { const auto a = std::round(operator_(c().a, operand)); overunderflow_check(a); c().a = (uint8_t)a; } if (_operate_on_colors) { const auto r = std::round(operator_(c().r, operand)); const auto g = std::round(operator_(c().g, operand)); const auto b = std::round(operator_(c().b, operand)); for (const auto x : {r, g, b}) overunderflow_check(x); c().r = (uint8_t)r; c().g = (uint8_t)g; c().b = (uint8_t)b; } return c; } }; } #endif