|
| 1 | +#ifndef MY_AWESOME_GAME_COLOR_HPP |
| 2 | +#define MY_AWESOME_GAME_COLOR_HPP |
| 3 | + |
| 4 | +#include <cmath> |
| 5 | +#include <cstdint> |
| 6 | +#include <limits> |
| 7 | +#include <type_traits> |
| 8 | + |
| 9 | + |
| 10 | +// Todo: move this into `tools` project? |
| 11 | +namespace lefticus::my_awesome_game { |
| 12 | +template<typename Type> struct Basic_Color; |
| 13 | + |
| 14 | +template<typename OutType, typename InType> |
| 15 | +[[nodiscard]] constexpr Basic_Color<OutType> color_cast(Basic_Color<InType> input) noexcept |
| 16 | +{ |
| 17 | + if constexpr (std::is_same_v<OutType, InType>) { return input; } |
| 18 | + |
| 19 | + constexpr auto to_function = [] { |
| 20 | + if constexpr (std::is_floating_point_v<InType>) { |
| 21 | + if constexpr (std::is_floating_point_v<OutType>) { |
| 22 | + // just static cast from one floating point to_function another |
| 23 | + return [](const auto input_color_component) { return static_cast<OutType>(input_color_component); }; |
| 24 | + } else { |
| 25 | + // from floating point to_function integral |
| 26 | + // we want to_function scale 0-1 to_function 0-maxint for given type |
| 27 | + return [](const auto input_color_component) { |
| 28 | + return static_cast<OutType>(std::llround(input_color_component * std::numeric_limits<OutType>::max())); |
| 29 | + }; |
| 30 | + } |
| 31 | + } else { |
| 32 | + // input is not floating point |
| 33 | + if constexpr (std::is_integral_v<OutType>) { |
| 34 | + // and neither is output |
| 35 | + return [](const auto input_color_component) { |
| 36 | + // scale 0-1, then scale that to_function output type |
| 37 | + return static_cast<OutType>(std::llround( |
| 38 | + (static_cast<double>(input_color_component) / static_cast<double>(std::numeric_limits<InType>::max())) |
| 39 | + * static_cast<double>(std::numeric_limits<OutType>::max()))); |
| 40 | + }; |
| 41 | + } else { |
| 42 | + // output is floating point |
| 43 | + return [](const auto input_color_component) { |
| 44 | + return static_cast<OutType>(input_color_component) / static_cast<OutType>(std::numeric_limits<InType>::max()); |
| 45 | + }; |
| 46 | + } |
| 47 | + } |
| 48 | + }(); |
| 49 | + |
| 50 | + return Basic_Color<OutType>{ to_function(input.R), to_function(input.G), to_function(input.B), to_function(input.A) }; |
| 51 | +} |
| 52 | + |
| 53 | +template<typename Type> struct Basic_Color |
| 54 | +{ |
| 55 | + Type R{}; |
| 56 | + Type G{}; |
| 57 | + Type B{}; |
| 58 | + Type A{}; |
| 59 | + |
| 60 | + |
| 61 | + template<typename RHS> constexpr auto &operator+=(Basic_Color<RHS> rhs) noexcept |
| 62 | + { |
| 63 | + // from stackoverflow |
| 64 | + // Short answer: |
| 65 | + // if we want to overlay color0 over color1 both with some alpha then |
| 66 | + // a01 = (1 - a0)·a1 + a0 |
| 67 | + // r01 = ((1 - a0)·a1·r1 + a0·r0) / a01 |
| 68 | + // g01 = ((1 - a0)·a1·g1 + a0·g0) / a01 |
| 69 | + // b01 = ((1 - a0)·a1·b1 + a0·b0) / a01 |
| 70 | + |
| 71 | + const auto color1 = color_cast<double>(*this); |
| 72 | + const auto color0 = color_cast<double>(rhs); |
| 73 | + auto color01 = Basic_Color<double>{}; |
| 74 | + |
| 75 | + color01.A = (1 - color0.A) * color1.A + color0.A; |
| 76 | + color01.R = ((1 - color0.A) * color1.A * color1.R + color0.A * color0.R) / color01.A; |
| 77 | + color01.G = ((1 - color0.A) * color1.A * color1.G + color0.A * color0.G) / color01.A; |
| 78 | + color01.B = ((1 - color0.A) * color1.A * color1.B + color0.A * color0.B) / color01.A; |
| 79 | + |
| 80 | + *this = color_cast<Type>(color01); |
| 81 | + return *this; |
| 82 | + } |
| 83 | +}; |
| 84 | + |
| 85 | +using Color = Basic_Color<std::uint8_t>; |
| 86 | +}// namespace lefticus::my_awesome_game |
| 87 | + |
| 88 | +#endif// MY_AWESOME_GAME_COLOR_HPP |
0 commit comments