|
| 1 | +#include "mixbox.h" |
| 2 | + |
| 3 | +extern "C" unsigned char const _Mixbox_LUT[]; |
| 4 | +extern "C" unsigned long long int const _Mixbox_LUT_size; |
| 5 | + |
| 6 | +namespace Mixbox { |
| 7 | + |
| 8 | +Bytes const LUT = Bytes(_Mixbox_LUT, _Mixbox_LUT_size); |
| 9 | + |
| 10 | +static f64 srgb2linear(f64 srgb) { |
| 11 | + if (srgb <= 0.04045f) |
| 12 | + return srgb / 12.92f; |
| 13 | + return ::pow((srgb + 0.055f) / 1.055f, 2.4f); |
| 14 | +} |
| 15 | + |
| 16 | +static Math::Vec3f srgb2linear(Gfx::Color color) { |
| 17 | + return { |
| 18 | + srgb2linear(color.red / 255.0), |
| 19 | + srgb2linear(color.green / 255.0), |
| 20 | + srgb2linear(color.blue / 255.0), |
| 21 | + }; |
| 22 | +} |
| 23 | + |
| 24 | +static f64 linear2srgb(f64 linear) { |
| 25 | + if (linear <= 0.0031308f) |
| 26 | + return linear * 12.92f; |
| 27 | + return 1.055f * ::pow(linear, 1.0f / 2.4f) - 0.055f; |
| 28 | +} |
| 29 | + |
| 30 | +static Gfx::Color linear2srgb(Math::Vec3f linear) { |
| 31 | + return { |
| 32 | + static_cast<u8>(linear2srgb(linear[0]) * 255.0), |
| 33 | + static_cast<u8>(linear2srgb(linear[1]) * 255.0), |
| 34 | + static_cast<u8>(linear2srgb(linear[2]) * 255.0), |
| 35 | + 255, |
| 36 | + }; |
| 37 | +} |
| 38 | + |
| 39 | +static Math::Vec3f evalPolynomial(f64 c0, f64 c1, f64 c2, f64 c3) { |
| 40 | + auto c00 = c0 * c0; |
| 41 | + auto c11 = c1 * c1; |
| 42 | + auto c22 = c2 * c2; |
| 43 | + auto c33 = c3 * c3; |
| 44 | + auto c01 = c0 * c1; |
| 45 | + auto c02 = c0 * c2; |
| 46 | + auto c12 = c1 * c2; |
| 47 | + |
| 48 | + auto r = 0.0, g = 0.0, b = 0.0; |
| 49 | + |
| 50 | + // clang-format off |
| 51 | + auto w00 = c0 * c00; r += 0.07717053*w00; g += 0.02826978*w00; b += 0.24832992*w00; |
| 52 | + auto w01 = c1 * c11; r += 0.95912302*w01; g += 0.80256528*w01; b += 0.03561839*w01; |
| 53 | + auto w02 = c2 * c22; r += 0.74683774*w02; g += 0.04868586*w02; b += 0.00000000*w02; |
| 54 | + auto w03 = c3 * c33; r += 0.99518138*w03; g += 0.99978149*w03; b += 0.99704802*w03; |
| 55 | + auto w04 = c00 * c1; r += 0.04819146*w04; g += 0.83363781*w04; b += 0.32515377*w04; |
| 56 | + auto w05 = c01 * c1; r += -0.68146950*w05; g += 1.46107803*w05; b += 1.06980936*w05; |
| 57 | + auto w06 = c00 * c2; r += 0.27058419*w06; g += -0.15324870*w06; b += 1.98735057*w06; |
| 58 | + auto w07 = c02 * c2; r += 0.80478189*w07; g += 0.67093710*w07; b += 0.18424500*w07; |
| 59 | + auto w08 = c00 * c3; r += -0.35031003*w08; g += 1.37855826*w08; b += 3.68865000*w08; |
| 60 | + auto w09 = c0 * c33; r += 1.05128046*w09; g += 1.97815239*w09; b += 2.82989073*w09; |
| 61 | + auto w10 = c11 * c2; r += 3.21607125*w10; g += 0.81270228*w10; b += 1.03384539*w10; |
| 62 | + auto w11 = c1 * c22; r += 2.78893374*w11; g += 0.41565549*w11; b += -0.04487295*w11; |
| 63 | + auto w12 = c11 * c3; r += 3.02162577*w12; g += 2.55374103*w12; b += 0.32766114*w12; |
| 64 | + auto w13 = c1 * c33; r += 2.95124691*w13; g += 2.81201112*w13; b += 1.17578442*w13; |
| 65 | + auto w14 = c22 * c3; r += 2.82677043*w14; g += 0.79933038*w14; b += 1.81715262*w14; |
| 66 | + auto w15 = c2 * c33; r += 2.99691099*w15; g += 1.22593053*w15; b += 1.80653661*w15; |
| 67 | + auto w16 = c01 * c2; r += 1.87394106*w16; g += 2.05027182*w16; b += -0.29835996*w16; |
| 68 | + auto w17 = c01 * c3; r += 2.56609566*w17; g += 7.03428198*w17; b += 0.62575374*w17; |
| 69 | + auto w18 = c02 * c3; r += 4.08329484*w18; g += -1.40408358*w18; b += 2.14995522*w18; |
| 70 | + auto w19 = c12 * c3; r += 6.00078678*w19; g += 2.55552042*w19; b += 1.90739502*w19; |
| 71 | + // clang-format on |
| 72 | + |
| 73 | + return {r, g, b}; |
| 74 | +} |
| 75 | + |
| 76 | +Latent unmix(Math::Vec3f rgb) { |
| 77 | + auto r01 = clamp01(rgb[0]); |
| 78 | + auto g01 = clamp01(rgb[1]); |
| 79 | + auto b01 = clamp01(rgb[2]); |
| 80 | + |
| 81 | + auto x = r01 * 63.0; |
| 82 | + auto y = g01 * 63.0; |
| 83 | + auto z = b01 * 63.0; |
| 84 | + |
| 85 | + isize ix = x; |
| 86 | + isize iy = y; |
| 87 | + isize iz = z; |
| 88 | + |
| 89 | + auto tx = x - ((f64)ix); |
| 90 | + auto ty = y - ((f64)iy); |
| 91 | + auto tz = z - ((f64)iz); |
| 92 | + |
| 93 | + auto lut = next(LUT, (((ix + iy * 64 + iz * 64 * 64) & 0x3FFFF) * 3)); |
| 94 | + |
| 95 | + auto c0 = 0.0; |
| 96 | + auto c1 = 0.0; |
| 97 | + auto c2 = 0.0; |
| 98 | + |
| 99 | + // clang-format off |
| 100 | + auto w0 = (1.0 - tx) * (1.0 - ty) * (1.0 - tz); c0 += w0 * lut[ 192]; c1 += w0 * lut[ 193]; c2 += w0 * lut[ 194]; |
| 101 | + auto w1 = ( tx) * (1.0 - ty) * (1.0 - tz); c0 += w1 * lut[ 195]; c1 += w1 * lut[ 196]; c2 += w1 * lut[ 197]; |
| 102 | + auto w2 = (1.0 - tx) * ( ty) * (1.0 - tz); c0 += w2 * lut[ 384]; c1 += w2 * lut[ 385]; c2 += w2 * lut[ 386]; |
| 103 | + auto w3 = ( tx) * ( ty) * (1.0 - tz); c0 += w3 * lut[ 387]; c1 += w3 * lut[ 388]; c2 += w3 * lut[ 389]; |
| 104 | + auto w4 = (1.0 - tx) * (1.0 - ty) * ( tz); c0 += w4 * lut[12480]; c1 += w4 * lut[12481]; c2 += w4 * lut[12482]; |
| 105 | + auto w5 = ( tx) * (1.0 - ty) * ( tz); c0 += w5 * lut[12483]; c1 += w5 * lut[12484]; c2 += w5 * lut[12485]; |
| 106 | + auto w6 = (1.0 - tx) * ( ty) * ( tz); c0 += w6 * lut[12672]; c1 += w6 * lut[12673]; c2 += w6 * lut[12674]; |
| 107 | + auto w7 = ( tx) * ( ty) * ( tz); c0 += w7 * lut[12675]; c1 += w7 * lut[12676]; c2 += w7 * lut[12677]; |
| 108 | + // clang-format on |
| 109 | + |
| 110 | + c0 *= 1.0 / 255.0; |
| 111 | + c1 *= 1.0 / 255.0; |
| 112 | + c2 *= 1.0 / 255.0; |
| 113 | + |
| 114 | + auto c3 = 1.0 - (c0 + c1 + c2); |
| 115 | + |
| 116 | + auto mixrgb = evalPolynomial(c0, c1, c2, c3); |
| 117 | + |
| 118 | + return {c0, c1, c2, c3, |
| 119 | + r01 - mixrgb[0], g01 - mixrgb[1], b01 - mixrgb[2]}; |
| 120 | +} |
| 121 | + |
| 122 | +Math::Vec3f mix(Latent latent) { |
| 123 | + auto rgb = evalPolynomial( |
| 124 | + latent[0], latent[1], latent[2], latent[3]); |
| 125 | + |
| 126 | + return { |
| 127 | + clamp01(rgb[0] + latent[4]), |
| 128 | + clamp01(rgb[1] + latent[5]), |
| 129 | + clamp01(rgb[2] + latent[6]), |
| 130 | + }; |
| 131 | +} |
| 132 | + |
| 133 | +Latent unmixColor(Gfx::Color color) { |
| 134 | + return unmix(srgb2linear(color)); |
| 135 | +} |
| 136 | + |
| 137 | +Gfx::Color mixColor(Latent latent) { |
| 138 | + return linear2srgb(mix(latent)); |
| 139 | +}; |
| 140 | + |
| 141 | +Math::Vec3f lerp(Math::Vec3f a, Math::Vec3f b, f64 t) { |
| 142 | + auto la = unmix(a); |
| 143 | + auto lb = unmix(b); |
| 144 | + |
| 145 | + for (usize i = 0; i < LATENT_SIZE; i++) { |
| 146 | + la[i] = ::lerp(la[i], lb[i], t); |
| 147 | + } |
| 148 | + |
| 149 | + return mix(la); |
| 150 | +} |
| 151 | + |
| 152 | +Gfx::Color lerpColor(Gfx::Color a, Gfx::Color b, f64 t) { |
| 153 | + return linear2srgb( |
| 154 | + lerp( |
| 155 | + srgb2linear(a), |
| 156 | + srgb2linear(b), t)); |
| 157 | +} |
| 158 | + |
| 159 | +} // namespace Mixbox |
0 commit comments