#pragma once #include "color.h" #include "coord.h" template class Lut3d : public std::array, G>, R> { public: static std::unique_ptr> Identity(); Color MapColor(const Color& in) const; template std::unique_ptr> MapImage(const Image& in) const; private: constexpr static Color InterpolateColor(const Color& i0, const Color& i1, uint32_t mul, uint32_t div); constexpr static uint32_t Interpolate(uint32_t i0, uint32_t i1, uint32_t mul, uint32_t div); // Return value is (root_indices, remainders) constexpr static std::pair FindRoot(const Color& in); constexpr static std::pair FindChannelRoot(uint32_t value, uint32_t points); constexpr static uint32_t BlockSize(uint32_t points); }; // Minimum size LUT typedef Lut3d<2, 2, 2> MinimalLut3d; template std::unique_ptr> Lut3d::Identity() { auto ret = std::make_unique>(); Color color; for (uint32_t r = 0; r < R; ++r) { auto& rect = ret->at(r); color.r = std::min(kNumColors - 1, BlockSize(R) * r); for (uint32_t g = 0; g < G; ++g) { auto& row = rect.at(g); color.g = std::min(kNumColors - 1, BlockSize(G) * g); for (uint32_t b = 0; b < B; ++b) { color.b = std::min(kNumColors - 1, BlockSize(B) * b); row.at(b) = color; } } } return ret; } template Color Lut3d::MapColor(const Color& in) const { const auto root_rem = FindRoot(in); const auto& root = root_rem.first; const auto& rem = root_rem.second; // https://en.wikipedia.org/wiki/Trilinear_interpolation auto inter00 = InterpolateColor( this->at(root.r + 0).at(root.g + 0).at(root.b + 0), this->at(root.r + 1).at(root.g + 0).at(root.b + 0), rem.r, BlockSize(R)); auto inter01 = InterpolateColor( this->at(root.r + 0).at(root.g + 0).at(root.b + 1), this->at(root.r + 1).at(root.g + 0).at(root.b + 1), rem.r, BlockSize(R)); auto inter10 = InterpolateColor( this->at(root.r + 0).at(root.g + 1).at(root.b + 0), this->at(root.r + 1).at(root.g + 1).at(root.b + 0), rem.r, BlockSize(R)); auto inter11 = InterpolateColor( this->at(root.r + 0).at(root.g + 1).at(root.b + 1), this->at(root.r + 1).at(root.g + 1).at(root.b + 1), rem.r, BlockSize(R)); auto inter0 = InterpolateColor(inter00, inter10, rem.g, BlockSize(G)); auto inter1 = InterpolateColor(inter01, inter11, rem.g, BlockSize(G)); return InterpolateColor(inter0, inter1, rem.b, BlockSize(B)); } template template std::unique_ptr> Lut3d::MapImage(const Image& in) const { auto out = std::make_unique>(); for (uint32_t y = 0; y < Y; ++y) { for (uint32_t x = 0; x < X; ++x) { Coord coord = {x, y}; out->SetPixel(coord, MapColor(in.GetPixel(coord))); } } return out; } template constexpr Color Lut3d::InterpolateColor(const Color& i0, const Color& i1, uint32_t mul, uint32_t div) { return { Interpolate(i0.r, i1.r, mul, div), Interpolate(i0.g, i1.g, mul, div), Interpolate(i0.b, i1.b, mul, div), }; } template constexpr uint32_t Lut3d::Interpolate(uint32_t i0, uint32_t i1, uint32_t mul, uint32_t div) { assert(i1 >= i0); return i0 + ((mul * (i1 - i0)) / div); } template constexpr std::pair Lut3d::FindRoot(const Color& in) { auto root_r = FindChannelRoot(in.r, R); auto root_g = FindChannelRoot(in.g, G); auto root_b = FindChannelRoot(in.b, B); return { {root_r.first, root_g.first, root_b.first}, {root_r.second, root_g.second, root_b.second}, }; } template constexpr std::pair Lut3d::FindChannelRoot(const uint32_t value, const uint32_t points) { // points - 1 is the last point index. Since we're going to fidn the cube // around this point by adding to the root, we need to be at least 1 less // than that. uint32_t index = std::min(points - 2, value / BlockSize(points)); return std::make_pair(index, value - (index * BlockSize(points))); } template constexpr uint32_t Lut3d::BlockSize(uint32_t points) { return kNumColors / (points - 1); }