colorcvt.c 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. #include "pocketpy/pocketpy.h"
  2. #include "pocketpy/common/utils.h"
  3. #include "pocketpy/objects/object.h"
  4. #include "pocketpy/common/sstream.h"
  5. #include "pocketpy/interpreter/vm.h"
  6. #include <math.h>
  7. // https://bottosson.github.io/posts/gamutclipping/#oklab-to-linear-srgb-conversion
  8. // clang-format off
  9. static c11_vec3 linear_srgb_to_oklab(c11_vec3 c)
  10. {
  11. float l = 0.4122214708f * c.x + 0.5363325363f * c.y + 0.0514459929f * c.z;
  12. float m = 0.2119034982f * c.x + 0.6806995451f * c.y + 0.1073969566f * c.z;
  13. float s = 0.0883024619f * c.x + 0.2817188376f * c.y + 0.6299787005f * c.z;
  14. float l_ = cbrtf(l);
  15. float m_ = cbrtf(m);
  16. float s_ = cbrtf(s);
  17. return (c11_vec3){{
  18. 0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
  19. 1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
  20. 0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
  21. }};
  22. }
  23. static c11_vec3 oklab_to_linear_srgb(c11_vec3 c)
  24. {
  25. float l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z;
  26. float m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * c.z;
  27. float s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z;
  28. float l = l_ * l_ * l_;
  29. float m = m_ * m_ * m_;
  30. float s = s_ * s_ * s_;
  31. return (c11_vec3){{
  32. +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
  33. -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
  34. -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
  35. }};
  36. }
  37. // clang-format on
  38. static float _gamma_correct_inv(float x) {
  39. return (x <= 0.04045f) ? (x / 12.92f) : powf((x + 0.055f) / 1.055f, 2.4f);
  40. }
  41. static float _gamma_correct(float x) {
  42. return (x <= 0.0031308f) ? (12.92f * x) : (1.055f * powf(x, 1.0f / 2.4f) - 0.055f);
  43. }
  44. static c11_vec3 srgb_to_linear_srgb(c11_vec3 c) {
  45. c.x = _gamma_correct_inv(c.x);
  46. c.y = _gamma_correct_inv(c.y);
  47. c.z = _gamma_correct_inv(c.z);
  48. return c;
  49. }
  50. static c11_vec3 linear_srgb_to_srgb(c11_vec3 c) {
  51. c.x = _gamma_correct(c.x);
  52. c.y = _gamma_correct(c.y);
  53. c.z = _gamma_correct(c.z);
  54. return c;
  55. }
  56. static c11_vec3 _oklab_to_oklch(c11_vec3 c) {
  57. c11_vec3 res;
  58. res.x = c.x;
  59. res.y = sqrtf(c.y * c.y + c.z * c.z);
  60. res.z = fmodf(atan2f(c.z, c.y), 2 * (float)PK_M_PI);
  61. res.z = res.z * PK_M_RAD2DEG;
  62. return res;
  63. }
  64. static c11_vec3 _oklch_to_oklab(c11_vec3 c) {
  65. c11_vec3 res;
  66. res.x = c.x;
  67. res.y = c.y * cosf(c.z * PK_M_DEG2RAD);
  68. res.z = c.y * sinf(c.z * PK_M_DEG2RAD);
  69. return res;
  70. }
  71. static c11_vec3 linear_srgb_to_oklch(c11_vec3 c) {
  72. return _oklab_to_oklch(linear_srgb_to_oklab(c));
  73. }
  74. static bool _is_valid_srgb(c11_vec3 c) {
  75. return c.x >= 0.0f && c.x <= 1.0f && c.y >= 0.0f && c.y <= 1.0f && c.z >= 0.0f && c.z <= 1.0f;
  76. }
  77. static c11_vec3 oklch_to_linear_srgb(c11_vec3 c) {
  78. c11_vec3 candidate = oklab_to_linear_srgb(_oklch_to_oklab(c));
  79. if(_is_valid_srgb(candidate)) return candidate;
  80. // try with chroma = 0
  81. c11_vec3 clamped = {
  82. {c.x, 0.0f, c.z}
  83. };
  84. // if not even chroma = 0 is displayable
  85. // fall back to RGB clamping
  86. candidate = oklab_to_linear_srgb(_oklch_to_oklab(clamped));
  87. if(!_is_valid_srgb(candidate)) {
  88. candidate.x = fmaxf(0.0f, fminf(1.0f, candidate.x));
  89. candidate.y = fmaxf(0.0f, fminf(1.0f, candidate.y));
  90. candidate.z = fmaxf(0.0f, fminf(1.0f, candidate.z));
  91. return candidate;
  92. }
  93. // By this time we know chroma = 0 is displayable and our current chroma is not.
  94. // Find the displayable chroma through the bisection method.
  95. float start = 0.0f;
  96. float end = c.y;
  97. float range[2] = {0.0f, 0.4f};
  98. float resolution = (range[1] - range[0]) / powf(2, 13);
  99. float _last_good_c = clamped.y;
  100. while(end - start > resolution) {
  101. clamped.y = start + (end - start) * 0.5f;
  102. candidate = oklab_to_linear_srgb(_oklch_to_oklab(clamped));
  103. if(_is_valid_srgb(candidate)) {
  104. _last_good_c = clamped.y;
  105. start = clamped.y;
  106. } else {
  107. end = clamped.y;
  108. }
  109. }
  110. candidate = oklab_to_linear_srgb(_oklch_to_oklab(clamped));
  111. if(_is_valid_srgb(candidate)) return candidate;
  112. clamped.y = _last_good_c;
  113. return oklab_to_linear_srgb(_oklch_to_oklab(clamped));
  114. }
  115. // https://github.com/python/cpython/blob/3.13/Lib/colorsys.py
  116. static c11_vec3 srgb_to_hsv(c11_vec3 c) {
  117. float r = c.x;
  118. float g = c.y;
  119. float b = c.z;
  120. float maxc = fmaxf(r, fmaxf(g, b));
  121. float minc = fminf(r, fminf(g, b));
  122. float v = maxc;
  123. if(minc == maxc) {
  124. return (c11_vec3){
  125. {0.0f, 0.0f, v}
  126. };
  127. }
  128. float s = (maxc - minc) / maxc;
  129. float rc = (maxc - r) / (maxc - minc);
  130. float gc = (maxc - g) / (maxc - minc);
  131. float bc = (maxc - b) / (maxc - minc);
  132. float h;
  133. if(r == maxc) {
  134. h = bc - gc;
  135. } else if(g == maxc) {
  136. h = 2.0f + rc - bc;
  137. } else {
  138. h = 4.0f + gc - rc;
  139. }
  140. h = fmodf(h / 6.0f, 1.0f);
  141. return (c11_vec3){
  142. {h, s, v}
  143. };
  144. }
  145. static c11_vec3 hsv_to_srgb(c11_vec3 c) {
  146. float h = c.x;
  147. float s = c.y;
  148. float v = c.z;
  149. if(s == 0.0f) {
  150. return (c11_vec3){
  151. {v, v, v}
  152. };
  153. }
  154. int i = (int)(h * 6.0f);
  155. float f = (h * 6.0f) - i;
  156. float p = v * (1.0f - s);
  157. float q = v * (1.0f - s * f);
  158. float t = v * (1.0f - s * (1.0f - f));
  159. i = i % 6;
  160. switch(i) {
  161. // clang-format off
  162. case 0: return (c11_vec3){{v, t, p}};
  163. case 1: return (c11_vec3){{q, v, p}};
  164. case 2: return (c11_vec3){{p, v, t}};
  165. case 3: return (c11_vec3){{p, q, v}};
  166. case 4: return (c11_vec3){{t, p, v}};
  167. case 5: return (c11_vec3){{v, p, q}};
  168. // clang-format on
  169. default: c11__unreachable();
  170. }
  171. }
  172. #define DEF_VEC3_WRAPPER(F) \
  173. static bool colorcvt_##F(int argc, py_Ref argv); \
  174. static bool colorcvt_##F(int argc, py_Ref argv) { \
  175. PY_CHECK_ARGC(1); \
  176. PY_CHECK_ARG_TYPE(0, tp_vec3); \
  177. c11_vec3 c = py_tovec3(argv); \
  178. py_newvec3(py_retval(), F(c)); \
  179. return true; \
  180. }
  181. DEF_VEC3_WRAPPER(linear_srgb_to_srgb)
  182. DEF_VEC3_WRAPPER(srgb_to_linear_srgb)
  183. DEF_VEC3_WRAPPER(srgb_to_hsv)
  184. DEF_VEC3_WRAPPER(hsv_to_srgb)
  185. DEF_VEC3_WRAPPER(oklch_to_linear_srgb)
  186. DEF_VEC3_WRAPPER(linear_srgb_to_oklch)
  187. void pk__add_module_colorcvt() {
  188. py_Ref mod = py_newmodule("colorcvt");
  189. py_bindfunc(mod, "linear_srgb_to_srgb", colorcvt_linear_srgb_to_srgb);
  190. py_bindfunc(mod, "srgb_to_linear_srgb", colorcvt_srgb_to_linear_srgb);
  191. py_bindfunc(mod, "srgb_to_hsv", colorcvt_srgb_to_hsv);
  192. py_bindfunc(mod, "hsv_to_srgb", colorcvt_hsv_to_srgb);
  193. py_bindfunc(mod, "oklch_to_linear_srgb", colorcvt_oklch_to_linear_srgb);
  194. py_bindfunc(mod, "linear_srgb_to_oklch", colorcvt_linear_srgb_to_oklch);
  195. }
  196. #undef DEF_VEC3_WRAPPER