oklab, initial ver
authorMingye Wang <arthur2e5@aosc.io>
Sat, 20 Feb 2021 11:33:03 +0000 (19:33 +0800)
committerØyvind "pippin" Kolås <pippin@gimp.org>
Thu, 28 Oct 2021 20:56:06 +0000 (20:56 +0000)
extensions/oklab.c [new file with mode: 0644]

diff --git a/extensions/oklab.c b/extensions/oklab.c
new file mode 100644 (file)
index 0000000..2078696
--- /dev/null
@@ -0,0 +1,500 @@
+/* babl - dynamically extendable universal pixel conversion library.\r
+ * Copyright (C) 2005, 2014, 2019 Øyvind Kolås.\r
+ * Copyright (C) 2014, 2019 Elle Stone\r
+ * Copyright (C) 2009, Martin Nordholts\r
+ * Copyright (C) 2021, Mingye Wang <arthur2e5@aosc.io>\r
+ *\r
+ * This library is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU Lesser General Public\r
+ * License as published by the Free Software Foundation; either\r
+ * version 3 of the License, or (at your option) any later version.\r
+ *\r
+ * This library is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+ * Lesser General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU Lesser General\r
+ * Public License along with this library; if not, see\r
+ * <https://www.gnu.org/licenses/>.\r
+ */\r
+\r
+/*\r
+ * Björn Ottosson (2020). Oklab, a perceptual color space for image\r
+ * processing. https://bottosson.github.io/posts/oklab/\r
+ */\r
+\r
+#include "config.h"\r
+\r
+#include <math.h>\r
+#include <string.h>\r
+\r
+#include "babl-internal.h"\r
+#include "babl-matrix.h"\r
+#include "babl.h"\r
+#include "base/util.h"\r
+\r
+#define DEGREES_PER_RADIAN (180 / 3.14159265358979323846)\r
+#define RADIANS_PER_DEGREE (1 / DEGREES_PER_RADIAN)\r
+\r
+static void components (void);\r
+static void models (void);\r
+static void conversions (void);\r
+static void formats (void);\r
+\r
+int init (void);\r
+\r
+int\r
+init (void)\r
+{\r
+  components ();\r
+  models ();\r
+  formats ();\r
+  conversions ();\r
+  return 0;\r
+}\r
+\r
+static void\r
+components (void)\r
+{\r
+  babl_component_new ("Ok L", "doc", "Luminance, range 0.0-100.0 in float",\r
+                      NULL);\r
+  babl_component_new ("Ok a", "chroma", "doc",\r
+                      "chroma component 0.0 is no saturation", NULL);\r
+  babl_component_new ("Ok b", "chroma", "doc",\r
+                      "chroma component 0.0 is no saturation", NULL);\r
+  babl_component_new ("Ok C", "chroma", "doc", "chrominance/saturation", NULL);\r
+  babl_component_new ("Ok H", "chroma", "doc", "hue value range 0.0-360.0",\r
+                      NULL);\r
+}\r
+\r
+static void\r
+models (void)\r
+{\r
+  babl_model_new ("name", "Oklab", "doc",\r
+                  "Oklab color model, a perceptually uniform space.",\r
+                  babl_component ("Ok L"), babl_component ("Ok a"),\r
+                  babl_component ("Ok b"), NULL);\r
+\r
+  babl_model_new (\r
+      "name", "OklabA", "doc", "Oklab color model with separate alpha.",\r
+      babl_component ("Ok L"), babl_component ("Ok a"),\r
+      babl_component ("Ok b"), babl_component ("A"), "alpha", NULL);\r
+\r
+  babl_model_new ("name", "Oklch", "doc",\r
+                  "Cylindrical representation of Oklab.",\r
+                  babl_component ("Ok L"), babl_component ("Ok C"),\r
+                  babl_component ("Ok H"), NULL);\r
+\r
+  babl_model_new (\r
+      "name", "OklchA", "doc", "Oklch color model with separate alpha.",\r
+      babl_component ("Ok L"), babl_component ("Ok C"),\r
+      babl_component ("Ok H"), babl_component ("A"), "alpha", NULL);\r
+}\r
+\r
+static void\r
+formats (void)\r
+{\r
+  babl_format_new (\r
+    "name", "Oklab float",\r
+    babl_model ("Oklab"),\r
+    babl_type ("float"),\r
+    babl_component ("Ok L"),\r
+    babl_component ("Ok a"),\r
+    babl_component ("Ok b"),\r
+    NULL\r
+  );\r
+\r
+  babl_format_new (\r
+    "name", "Oklch float",\r
+    babl_model ("Oklch"),\r
+    babl_type ("float"),\r
+    babl_component ("Ok L"),\r
+    babl_component ("Ok C"),\r
+    babl_component ("Ok H"),\r
+    NULL\r
+  );\r
+\r
+  babl_format_new (\r
+    "name", "Oklab alpha float",\r
+    babl_model ("OklabA"),\r
+    babl_type ("float"),\r
+    babl_component ("Ok L"),\r
+    babl_component ("Ok a"),\r
+    babl_component ("Ok b"),\r
+    babl_component ("A"),\r
+    NULL\r
+  );\r
+\r
+  babl_format_new (\r
+    "name", "Oklch alpha float",\r
+    babl_model ("OklchA"),\r
+    babl_type ("float"),\r
+    babl_component ("Ok L"),\r
+    babl_component ("Ok C"),\r
+    babl_component ("Ok H"),\r
+    babl_component ("A"),\r
+    NULL\r
+  );\r
+}\r
+\r
+/* Convertion routine (space definition). */\r
+/* It's all float. The original definition is in float. */\r
+static double[9] M1 = {\r
+  +0.8189330101, +0.0329845436, +0.0482003018,\r
+  ​+0.3618667424, ​+0.9293118715, +0.2643662691,\r
+  -0.1288597137, +0.0361456387, ​+0.6338517070,\r
+}\r
+\r
+static double[9] M2 = {\r
+  +0.2104542553, +0.7936177850, - 0.0040720468,\r
+  +1.9779984951, -2.4285922050, + 0.4505937099,\r
+  +0.0259040371, +0.7827717662, - 0.8086757660,\r
+}\r
+\r
+static float[9] M1f;\r
+static float[9] M2f;\r
+static float[9] inv_M1f;\r
+static float[9] inv_M2f;\r
+static int mat_ready;\r
+\r
+/* fast approximate cube root\r
+ * origin: http://www.hackersdelight.org/hdcodetxt/acbrt.c.txt\r
+ * permissions: http://www.hackersdelight.org/permissions.htm\r
+ */\r
+static inline float\r
+_cbrtf (float x)\r
+{\r
+  union\r
+  {\r
+    float f;\r
+    uint32_t i;\r
+  } u = { x };\r
+\r
+  u.i = u.i / 4 + u.i / 16;\r
+  u.i = u.i + u.i / 16;\r
+  u.i = u.i + u.i / 256;\r
+  u.i = 0x2a5137a0 + u.i;\r
+  u.f = 0.33333333f * (2.0f * u.f + x / (u.f * u.f));\r
+  u.f = 0.33333333f * (2.0f * u.f + x / (u.f * u.f));\r
+\r
+  return u.f;\r
+}\r
+\r
+static inline void\r
+XYZ_to_Oklab_step (float *xyz, float *lab_out)\r
+{\r
+  float[3] lms;\r
+  babl_matrix_mul_vectorff (M1f, xyz, lms);\r
+  for (int i = 0; i < 3; i++)\r
+    {\r
+      lms[i] = _cbrtf (lms[i]);\r
+    }\r
+  babl_matrix_mul_vectorff (M2f, lms, lab_out);\r
+}\r
+\r
+static inline void\r
+Oklab_to_XYZ_step (float *lab, float *xyz_out)\r
+{\r
+  float[3] lms;\r
+  babl_matrix_mul_vectorff (inv_M2f, lab, lms);\r
+  for (int i = 0; i < 3; i++)\r
+    {\r
+      lms[i] = lms[i] * lms[i] * lms[i];\r
+    }\r
+  babl_matrix_mul_vectorff (inv_M1f, lms, xyz_out);\r
+}\r
+\r
+static inline void\r
+ab_to_ch_step (float *ab, float *ch_out)\r
+{\r
+  float a = ab[0], b = ab[1];\r
+\r
+  ch_out[1] = sqrt (a * a + b * b);\r
+  ch_out[2] = atan2 (b, a) * DEGREES_PER_RADIAN;\r
+\r
+  // Keep H within the range 0-360\r
+  if (ch_out[2] < 0.0)\r
+    ch_out[2] += 360;\r
+}\r
+\r
+static inline void\r
+ch_to_ab_step (float *ch, float *ab_out)\r
+{\r
+  float c = ch[0], h = ch[1];\r
+\r
+  ab_out[0] = cos (h * RADIANS_PER_DEGREE) * c;\r
+  ab_out[1] = sin (h * RADIANS_PER_DEGREE) * c;\r
+}\r
+\r
+static inline void\r
+xyz_to_Oklch_step (float *xyz, float *lch_out)\r
+{\r
+  XYZ_to_Oklab_step (xyz, lch_out);\r
+  ab_to_ch_step (lch_out + 1, lch_out + 1);\r
+}\r
+\r
+static inline void\r
+Oklch_to_XYZ_step (float *lch, float *xyz_out)\r
+{\r
+  float[3] lab = { lch[0], lch[1], lch[2] };\r
+  ch_to_ab_step (lab + 1, lab + 1);\r
+  Oklab_to_XYZ_step (lab, xyz_out);\r
+}\r
+\r
+static inline void\r
+constants ()\r
+{\r
+  /* FIXME: babl xyz is D50. Should adapt back to D65xy (0.3127, 0.3290) before\r
+   * doing M1, but babl_chromatic_adaptation_matrix is private :( */\r
+  if (mat_ready)\r
+    return;\r
+\r
+  double[9] tmp;\r
+\r
+  babl_matrix_invert (M1, tmp);\r
+  babl_matrix_to_float (tmp, inv_M1f);\r
+  babl_matrix_invert (M2, tmp);\r
+  babl_matrix_to_float (tmp, inv_M2f);\r
+\r
+  babl_matrix_to_float (M1, M1f);\r
+  babl_matrix_to_float (M2, M2f);\r
+\r
+  mat_ready = 1;\r
+}\r
+\r
+/* Convertion routine (glue and boilerplate). */\r
+static void\r
+rgba_to_laba (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;+\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+  const Babl *space = babl_conversion_get_source_space (conversion);\r
+\r
+  while (n--)\r
+    {\r
+      float xyz[3];\r
+      babl_space_to_xyzf (space, src, xyz);\r
+      XYZ_to_Oklab_step (xyz, dst);\r
+      dst[3] = src[3];\r
+\r
+      src += 4;\r
+      dst += 4;\r
+    }\r
+}\r
+\r
+static void\r
+rgba_to_lcha (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+  const Babl *space = babl_conversion_get_source_space (conversion);\r
+\r
+  while (n--)\r
+    {\r
+      float xyz[3];\r
+      babl_space_to_xyzf (space, src, xyz);\r
+      XYZ_to_Oklch_step (xyz, dst);\r
+      dst[3] = src[3];\r
+\r
+      src += 4;\r
+      dst += 4;\r
+    }\r
+}\r
+\r
+static void\r
+rgb_to_lab (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+  const Babl *space = babl_conversion_get_source_space (conversion);\r
+\r
+  while (n--)\r
+    {\r
+      float xyz[3];\r
+      babl_space_to_xyzf (space, src, xyz);\r
+      XYZ_to_Oklab_step (xyz, dst);\r
+\r
+      src += 3;\r
+      dst += 3;\r
+    }\r
+}\r
+\r
+static void\r
+rgb_to_lch (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+  const Babl *space = babl_conversion_get_source_space (conversion);\r
+\r
+  while (n--)\r
+    {\r
+      float xyz[3];\r
+      babl_space_to_xyzf (space, src, xyz);\r
+      XYZ_to_Oklch_step (xyz, dst);\r
+\r
+      src += 3;\r
+      dst += 3;\r
+    }\r
+}\r
+\r
+static void\r
+lab_to_rgb (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+  const Babl *space = babl_conversion_get_destination_space (conversion);\r
+\r
+  while (n--)\r
+    {\r
+      float xyz[3];\r
+      Oklab_to_XYZ_step (src, xyz);\r
+      babl_space_from_xyzf (space, xyz, dst);\r
+\r
+      src += 3;\r
+      dst += 3;\r
+    }\r
+}\r
+\r
+static void\r
+lch_to_rgb (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+  const Babl *space = babl_conversion_get_destination_space (conversion);\r
+\r
+  while (n--)\r
+    {\r
+      float xyz[3];\r
+      Oklch_to_XYZ_step (src, xyz);\r
+      babl_space_from_xyzf (space, xyz, dst);\r
+\r
+      src += 3;\r
+      dst += 3;\r
+    }\r
+}\r
+\r
+static void\r
+laba_to_rgba (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+  const Babl *space = babl_conversion_get_destination_space (conversion);\r
+\r
+  while (n--)\r
+    {\r
+      float xyz[3];\r
+      Oklab_to_XYZ_step (src, xyz);\r
+      babl_space_from_xyzf (space, xyz, dst);\r
+      dst[3] = src[3];\r
+\r
+      src += 4;\r
+      dst += 4;\r
+    }\r
+}\r
+\r
+static void\r
+lcha_to_rgba (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+  const Babl *space = babl_conversion_get_destination_space (conversion);\r
+\r
+  while (n--)\r
+    {\r
+      float xyz[3];\r
+      Oklch_to_XYZ_step (src, xyz);\r
+      babl_space_from_xyzf (space, xyz, dst);\r
+      dst[3] = src[3];\r
+\r
+      src += 4;\r
+      dst += 4;\r
+    }\r
+}\r
+\r
+static void\r
+lch_to_lab (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+\r
+  while (n--)\r
+    {\r
+      dst[0] = src[0];\r
+      ch_to_ab_step (src + 1, dst + 1);\r
+\r
+      src += 3;\r
+      dst += 3;\r
+    }\r
+}\r
+\r
+static void\r
+lab_to_lch (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+\r
+  while (n--)\r
+    {\r
+      dst[0] = src[0];\r
+      ab_to_ch_step (src + 1, dst + 1);\r
+\r
+      src += 3;\r
+      dst += 3;\r
+    }\r
+}\r
+\r
+static void\r
+lcha_to_laba (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+\r
+  while (n--)\r
+    {\r
+      dst[0] = src[0];\r
+      ch_to_ab_step (src + 1, dst + 1);\r
+      dst[3] = src[3];\r
+\r
+      src += 4;\r
+      dst += 4;\r
+    }\r
+}\r
+\r
+static void\r
+laba_to_lcha (const Babl *conversion, char *src_, char *dst_, long samples)\r
+{\r
+  long n = samples;\r
+  float *src = (float *)src_, *dst = (float *)dst_;\r
+\r
+  while (n--)\r
+    {\r
+      dst[0] = src[0];\r
+      ab_to_ch_step (src + 1, dst + 1);\r
+      dst[3] = src[3];\r
+\r
+      src += 4;\r
+      dst += 4;\r
+    }\r
+}\r
+/* End conversion routines. */\r
+\r
+static void\r
+conversions (void) {\r
+  constants ();\r
+\r
+  #define _pair(f1, f2, fwd, rev) do { \\r
+    babl_conversion_new(babl_format(f1), babl_format(f2), "linear", fwd, NULL); \\r
+    babl_conversion_new(babl_format(f2), babl_format(f1), "linear", rev, NULL); \\r
+  } while (0)\r
+\r
+  _pair("RGB float", "Oklab float", rgb_to_lab, lab_to_rgb);\r
+  _pair("RGB float", "Oklch float", rgb_to_lch, lch_to_rgb);\r
+\r
+  _pair("RGBA float", "Oklab alpha float", rgba_to_laba, laba_to_rgba);\r
+  _pair("RGBA float", "Oklch alpha float", rgba_to_lcha, lcha_to_rgba);\r
+\r
+  _pair("Oklab float", "Oklch float", lab_to_lch, lch_to_lab);\r
+  _pair("Oklab alpha float", "Oklch alpha float", laba_to_lcha, lcha_to_laba);\r
+  #undef _pair\r
+}\r