/* babl - dynamically extendable universal pixel conversion library. * Copyright (C) 2005, 2014, 2019 Øyvind Kolås. * Copyright (C) 2014, 2019 Elle Stone * Copyright (C) 2009, Martin Nordholts * Copyright (C) 2021, Mingye Wang * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see * . */ /* * Björn Ottosson (2020). Oklab, a perceptual color space for image * processing. https://bottosson.github.io/posts/oklab/ */ #include "config.h" #include #include #include "babl-internal.h" #include "babl-matrix.h" #include "babl.h" #include "base/util.h" #define DEGREES_PER_RADIAN (180 / 3.14159265358979323846) #define RADIANS_PER_DEGREE (1 / DEGREES_PER_RADIAN) static void components (void); static void models (void); static void conversions (void); static void formats (void); int init (void); static int enable_lch = 0; // the Oklch conversions are not fully symmetric, // thus not allowing the tests to pass if we register // the code int init (void) { components (); models (); formats (); conversions (); return 0; } static void components (void) { babl_component_new ("Ok L", "doc", "Luminance, range 0.0-100.0 in float", NULL); babl_component_new ("Ok a", "chroma", "doc", "chroma component 0.0 is no saturation", NULL); babl_component_new ("Ok b", "chroma", "doc", "chroma component 0.0 is no saturation", NULL); babl_component_new ("Ok C", "chroma", "doc", "chrominance/saturation", NULL); babl_component_new ("Ok H", "chroma", "doc", "hue value range 0.0-360.0", NULL); } static void models (void) { babl_model_new ("name", "Oklab", "doc", "Oklab color model, a perceptually uniform space.", babl_component ("Ok L"), babl_component ("Ok a"), babl_component ("Ok b"), NULL); babl_model_new ( "name", "OklabA", "doc", "Oklab color model with separate alpha.", babl_component ("Ok L"), babl_component ("Ok a"), babl_component ("Ok b"), babl_component ("A"), "alpha", NULL); if (enable_lch) { babl_model_new ("name", "Oklch", "doc", "Cylindrical representation of Oklab.", babl_component ("Ok L"), babl_component ("Ok C"), babl_component ("Ok H"), NULL); babl_model_new ( "name", "OklchA", "doc", "Oklch color model with separate alpha.", babl_component ("Ok L"), babl_component ("Ok C"), babl_component ("Ok H"), babl_component ("A"), "alpha", NULL); } } static void formats (void) { babl_format_new ( "name", "Oklab float", babl_model ("Oklab"), babl_type ("float"), babl_component ("Ok L"), babl_component ("Ok a"), babl_component ("Ok b"), NULL ); babl_format_new ( "name", "Oklab alpha float", babl_model ("OklabA"), babl_type ("float"), babl_component ("Ok L"), babl_component ("Ok a"), babl_component ("Ok b"), babl_component ("A"), NULL ); if (enable_lch) { babl_format_new ( "name", "Oklch float", babl_model ("Oklch"), babl_type ("float"), babl_component ("Ok L"), babl_component ("Ok C"), babl_component ("Ok H"), NULL ); babl_format_new ( "name", "Oklch alpha float", babl_model ("OklchA"), babl_type ("float"), babl_component ("Ok L"), babl_component ("Ok C"), babl_component ("Ok H"), babl_component ("A"), NULL ); } } /* Convertion routine (space definition). */ /* It's all float. The original definition is in float. */ static double M1[9] = { +0.8189330101, +0.0329845436, +0.0482003018, +0.3618667424, +0.9293118715, +0.2643662691, -0.1288597137, +0.0361456387, +0.6338517070, }; static double M2[9] = { +0.2104542553, +0.7936177850, - 0.0040720468, +1.9779984951, -2.4285922050, + 0.4505937099, +0.0259040371, +0.7827717662, - 0.8086757660, }; static float M1f[9]; static float M2f[9]; static float inv_M1f[9]; static float inv_M2f[9]; static double inv_M1[9]; static double inv_M2[9]; static int mat_ready; /* fast approximate cube root * origin: http://www.hackersdelight.org/hdcodetxt/acbrt.c.txt * permissions: http://www.hackersdelight.org/permissions.htm */ static inline float _cbrtf (float x) { union { float f; uint32_t i; } u = { x }; u.i = u.i / 4 + u.i / 16; u.i = u.i + u.i / 16; u.i = u.i + u.i / 256; u.i = 0x2a5137a0 + u.i; u.f = 0.33333333f * (2.0f * u.f + x / (u.f * u.f)); u.f = 0.33333333f * (2.0f * u.f + x / (u.f * u.f)); return u.f; } static inline void XYZ_to_Oklab_step (double *xyz, double *lab_out) { double lms[3]; babl_matrix_mul_vector (M1, xyz, lms); for (int i = 0; i < 3; i++) { lms[i] = cbrt (lms[i]); } babl_matrix_mul_vector (M2, lms, lab_out); } static inline void XYZ_to_Oklab_stepf (float *xyz, float *lab_out) { float lms[3]; babl_matrix_mul_vectorff (M1f, xyz, lms); for (int i = 0; i < 3; i++) { lms[i] = _cbrtf (lms[i]); } babl_matrix_mul_vectorff (M2f, lms, lab_out); } static inline void Oklab_to_XYZ_stepf (float *lab, float *xyz_out) { float lms[3]; babl_matrix_mul_vectorff (inv_M2f, lab, lms); for (int i = 0; i < 3; i++) { lms[i] = lms[i] * lms[i] * lms[i]; } babl_matrix_mul_vectorff (inv_M1f, lms, xyz_out); } static inline void Oklab_to_XYZ_step (double *lab, double *xyz_out) { double lms[3]; babl_matrix_mul_vector (inv_M2, lab, lms); for (int i = 0; i < 3; i++) { lms[i] = lms[i] * lms[i] * lms[i]; } babl_matrix_mul_vector (inv_M1, lms, xyz_out); } static inline void ab_to_ch_step (double *ab, double *ch_out) { double a = ab[0], b = ab[1]; ch_out[1] = sqrt (a * a + b * b); ch_out[2] = atan2 (b, a) * DEGREES_PER_RADIAN; // Keep H within the range 0-360 if (ch_out[2] < 0.0) ch_out[2] += 360; } static inline void ab_to_ch_stepf (float *ab, float *ch_out) { float a = ab[0], b = ab[1]; ch_out[1] = sqrtf (a * a + b * b); ch_out[2] = atan2f (b, a) * DEGREES_PER_RADIAN; // Keep H within the range 0-360 if (ch_out[2] < 0.0) ch_out[2] += 360; } static inline void ch_to_ab_step (double *ch, double *ab_out) { double c = ch[0], h = ch[1]; ab_out[0] = cos (h * RADIANS_PER_DEGREE) * c; ab_out[1] = sin (h * RADIANS_PER_DEGREE) * c; } static inline void ch_to_ab_stepf (float *ch, float *ab_out) { float c = ch[0], h = ch[1]; ab_out[0] = cosf (h * RADIANS_PER_DEGREE) * c; ab_out[1] = sinf (h * RADIANS_PER_DEGREE) * c; } static inline void XYZ_to_Oklch_step (double *xyz, double *lch_out) { XYZ_to_Oklab_step (xyz, lch_out); ab_to_ch_step (lch_out + 1, lch_out + 1); } static inline void XYZ_to_Oklch_stepf (float *xyz, float *lch_out) { XYZ_to_Oklab_stepf (xyz, lch_out); ab_to_ch_stepf (lch_out + 1, lch_out + 1); } static inline void Oklch_to_XYZ_step (double *lch, double *xyz_out) { double lab[3] = { lch[0], lch[1], lch[2] }; ch_to_ab_step (lab + 1, lab + 1); Oklab_to_XYZ_step (lab, xyz_out); } static inline void Oklch_to_XYZ_stepf (float *lch, float *xyz_out) { float lab[3] = { lch[0], lch[1], lch[2] }; ch_to_ab_stepf (lab + 1, lab + 1); Oklab_to_XYZ_stepf (lab, xyz_out); } static inline void constants (void) { double tmp[9]; double D65[3] = { 0.95047, 1.0, 1.08883 }; double D50[3] = { 0.96420288, 1.0, 0.82490540 }; if (mat_ready) return; babl_chromatic_adaptation_matrix (D50, D65, tmp); babl_matrix_mul_matrix (tmp, M1, M1); babl_matrix_invert (M1, inv_M1); babl_matrix_invert (M2, inv_M2); babl_matrix_to_float (M1, M1f); babl_matrix_to_float (M2, M2f); babl_matrix_to_float (inv_M1, inv_M1f); babl_matrix_to_float (inv_M2, inv_M2f); mat_ready = 1; } /* Convertion routine (glue and boilerplate). */ static void rgba_to_laba_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_source_space (conversion); while (n--) { float xyz[3]; babl_space_to_xyzf (space, src, xyz); XYZ_to_Oklab_stepf (xyz, dst); dst[3] = src[3]; src += 4; dst += 4; } } static void rgba_to_laba (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; double *src = (double*)src_, *dst = (double*)dst_; const Babl *space = babl_conversion_get_source_space (conversion); while (n--) { double xyz[3]; babl_space_to_xyz (space, src, xyz); XYZ_to_Oklab_step (xyz, dst); dst[3] = src[3]; src += 4; dst += 4; } } static void rgba_to_lab_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_source_space (conversion); while (n--) { float xyz[3]; babl_space_to_xyzf (space, src, xyz); XYZ_to_Oklab_stepf (xyz, dst); src += 4; dst += 3; } } static void rgba_to_lab (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; double *src = (double *)src_, *dst = (double *)dst_; const Babl *space = babl_conversion_get_source_space (conversion); while (n--) { double xyz[3]; babl_space_to_xyz (space, src, xyz); XYZ_to_Oklab_step (xyz, dst); src += 4; dst += 3; } } static void rgba_to_lcha_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_source_space (conversion); while (n--) { float xyz[3]; babl_space_to_xyzf (space, src, xyz); XYZ_to_Oklch_stepf (xyz, dst); dst[3] = src[3]; src += 4; dst += 4; } } static void rgba_to_lcha (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; double *src = (double *)src_, *dst = (double *)dst_; const Babl *space = babl_conversion_get_source_space (conversion); while (n--) { double xyz[3]; babl_space_to_xyz (space, src, xyz); XYZ_to_Oklch_step (xyz, dst); dst[3] = src[3]; src += 4; dst += 4; } } static void rgba_to_lch_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_source_space (conversion); while (n--) { float xyz[3]; babl_space_to_xyzf (space, src, xyz); XYZ_to_Oklch_stepf (xyz, dst); src += 4; dst += 3; } } static void rgba_to_lch (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; double *src = (double *)src_, *dst = (double *)dst_; const Babl *space = babl_conversion_get_source_space (conversion); while (n--) { double xyz[3]; babl_space_to_xyz (space, src, xyz); XYZ_to_Oklch_step (xyz, dst); src += 4; dst += 3; } } static void rgb_to_lab_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_source_space (conversion); while (n--) { float xyz[3]; babl_space_to_xyzf (space, src, xyz); XYZ_to_Oklab_stepf (xyz, dst); src += 3; dst += 3; } } static void rgb_to_lch_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_source_space (conversion); while (n--) { float xyz[3]; babl_space_to_xyzf (space, src, xyz); XYZ_to_Oklch_stepf (xyz, dst); src += 3; dst += 3; } } static void lab_to_rgb_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_destination_space (conversion); while (n--) { float xyz[3]; Oklab_to_XYZ_stepf (src, xyz); babl_space_from_xyzf (space, xyz, dst); src += 3; dst += 3; } } static void lab_to_rgba_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_destination_space (conversion); while (n--) { float xyz[3]; Oklab_to_XYZ_stepf (src, xyz); babl_space_from_xyzf (space, xyz, dst); dst[3] = 1.0; src += 3; dst += 4; } } static void lab_to_rgba (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; double *src = (double *)src_, *dst = (double *)dst_; const Babl *space = babl_conversion_get_destination_space (conversion); while (n--) { double xyz[3]; Oklab_to_XYZ_step (src, xyz); babl_space_from_xyz (space, xyz, dst); dst[3] = 1.0; src += 3; dst += 4; } } static void lch_to_rgb_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_destination_space (conversion); while (n--) { float xyz[3]; Oklch_to_XYZ_stepf (src, xyz); babl_space_from_xyzf (space, xyz, dst); src += 3; dst += 3; } } static void laba_to_rgba_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_destination_space (conversion); while (n--) { float xyz[3]; Oklab_to_XYZ_stepf (src, xyz); babl_space_from_xyzf (space, xyz, dst); dst[3] = src[3]; src += 4; dst += 4; } } static void laba_to_rgba (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; double *src = (double *)src_, *dst = (double *)dst_; const Babl *space = babl_conversion_get_destination_space (conversion); while (n--) { double xyz[3]; Oklab_to_XYZ_step (src, xyz); babl_space_from_xyz (space, xyz, dst); dst[3] = src[3]; src += 4; dst += 4; } } static void lcha_to_rgba_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_destination_space (conversion); while (n--) { float xyz[3]; Oklch_to_XYZ_stepf (src, xyz); babl_space_from_xyzf (space, xyz, dst); dst[3] = src[3]; src += 4; dst += 4; } } static void lcha_to_rgba (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; double *src = (double *)src_, *dst = (double *)dst_; const Babl *space = babl_conversion_get_destination_space (conversion); while (n--) { double xyz[3]; Oklch_to_XYZ_step (src, xyz); babl_space_from_xyz (space, xyz, dst); dst[3] = src[3]; src += 4; dst += 4; } } static void lch_to_rgba_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; const Babl *space = babl_conversion_get_destination_space (conversion); while (n--) { float xyz[3]; Oklch_to_XYZ_stepf (src, xyz); babl_space_from_xyzf (space, xyz, dst); dst[3] = 1.0f; src += 3; dst += 4; } } static void lch_to_rgba (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; double *src = (double *)src_, *dst = (double *)dst_; const Babl *space = babl_conversion_get_destination_space (conversion); while (n--) { double xyz[3]; Oklch_to_XYZ_step (src, xyz); babl_space_from_xyz (space, xyz, dst); dst[3] = 1.0f; src += 3; dst += 4; } } static void lch_to_lab_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; while (n--) { dst[0] = src[0]; ch_to_ab_stepf (src + 1, dst + 1); src += 3; dst += 3; } } static void lab_to_lch_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; while (n--) { dst[0] = src[0]; ab_to_ch_stepf (src + 1, dst + 1); src += 3; dst += 3; } } static void lcha_to_laba_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; while (n--) { dst[0] = src[0]; ch_to_ab_stepf (src + 1, dst + 1); dst[3] = src[3]; src += 4; dst += 4; } } static void laba_to_lcha_float (const Babl *conversion, char *src_, char *dst_, long samples) { long n = samples; float *src = (float *)src_, *dst = (float *)dst_; while (n--) { dst[0] = src[0]; ab_to_ch_stepf (src + 1, dst + 1); dst[3] = src[3]; src += 4; dst += 4; } } /* End conversion routines. */ static void conversions (void) { constants (); #define _pair(f1, f2, fwd, rev) \ do \ { \ babl_conversion_new (babl_format (f1), babl_format (f2), "linear", fwd, \ NULL); \ babl_conversion_new (babl_format (f2), babl_format (f1), "linear", rev, \ NULL); \ } \ while (0) babl_conversion_new (babl_model("RGBA"), babl_model("OklabA"), "linear", rgba_to_laba, NULL); babl_conversion_new (babl_model("OklabA"), babl_model("RGBA"), "linear", laba_to_rgba, NULL); babl_conversion_new (babl_model("RGBA"), babl_model("Oklab"), "linear", rgba_to_lab, NULL); babl_conversion_new (babl_model("Oklab"), babl_model("RGBA"), "linear", lab_to_rgba, NULL); _pair ("RGB float", "Oklab float", rgb_to_lab_float, lab_to_rgb_float); _pair ("RGBA float", "Oklab alpha float", rgba_to_laba_float, laba_to_rgba_float); _pair ("RGBA float", "Oklab float", rgba_to_lab_float, lab_to_rgba_float); if (enable_lch) { babl_conversion_new (babl_model("RGBA"), babl_model("OklchA"), "linear", rgba_to_lcha, NULL); babl_conversion_new (babl_model("OklchA"), babl_model("RGBA"), "linear", lcha_to_rgba, NULL); babl_conversion_new (babl_model("RGBA"), babl_model("Oklch"), "linear", rgba_to_lch, NULL); babl_conversion_new (babl_model("Oklch"), babl_model("RGBA"), "linear", lch_to_rgba, NULL); _pair ("RGBA float", "Oklch float", rgba_to_lch_float, lch_to_rgba_float); _pair ("RGB float", "Oklch float", rgb_to_lch_float, lch_to_rgb_float); _pair ("RGBA float", "Oklch alpha float", rgba_to_lcha_float, lcha_to_rgba_float); _pair ("Oklab float", "Oklch float", lab_to_lch_float, lch_to_lab_float); _pair ("Oklab alpha float", "Oklch alpha float", laba_to_lcha_float, lcha_to_laba_float); } #undef _pair }