--- /dev/null
+/* 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