Last active
April 29, 2023 04:58
-
-
Save Kyant0/ee9b2e713c17da8fd6743092fd13cd85 to your computer and use it in GitHub Desktop.
Fast conversion between srgb and zcam
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <jni.h> | |
| #include <stdlib.h> | |
| #include <math.h> | |
| void srgb_to_zcam(double *jch, const double *srgb); | |
| void zcam_to_clipped_srgb(double *srgb, const double *jch); | |
| JNIEXPORT jdoubleArray JNICALL | |
| Java_com_kyant_color_NativeColor_srgbToZcam( | |
| JNIEnv *env, | |
| jobject thiz, | |
| jdoubleArray rgb | |
| ) { | |
| double *srgbArray = (*env)->GetDoubleArrayElements(env, rgb, NULL); | |
| double jch[3]; | |
| srgb_to_zcam(jch, srgbArray); | |
| jdoubleArray output = (*env)->NewDoubleArray(env, 3); | |
| (*env)->SetDoubleArrayRegion(env, output, 0, 3, jch); | |
| return output; | |
| } | |
| JNIEXPORT jdoubleArray JNICALL | |
| Java_com_kyant_color_NativeColor_zcamToSrgb( | |
| JNIEnv *env, | |
| jobject thiz, | |
| jdoubleArray jch | |
| ) { | |
| double *jchArray = (*env)->GetDoubleArrayElements(env, jch, NULL); | |
| double srgb[3]; | |
| zcam_to_clipped_srgb(srgb, jchArray); | |
| jdoubleArray output = (*env)->NewDoubleArray(env, 3); | |
| (*env)->SetDoubleArrayRegion(env, output, 0, 3, srgb); | |
| return output; | |
| } | |
| void srgb_to_zcam(double *jch, const double *srgb) { | |
| // srgb to linear srgb | |
| double lin_srgb[3]; | |
| for (int i = 0; i < 3; i++) { | |
| if (srgb[i] >= 11.0 / 280.0) { | |
| lin_srgb[i] = pow((srgb[i] + 0.055) / 1.055, 2.4); | |
| } else { | |
| lin_srgb[i] = srgb[i] / 12.923210180787855; | |
| } | |
| } | |
| // linear srgb to xyz | |
| double xyz[3]; | |
| xyz[0] = 0.412460082587137 * lin_srgb[0] + 0.35757568105816245 * lin_srgb[1] + 0.1804347694992064 * lin_srgb[2]; | |
| xyz[1] = 0.2126747300839925 * lin_srgb[0] + 0.7151513621163249 * lin_srgb[1] + 0.07217390779968255 * lin_srgb[2]; | |
| xyz[2] = 0.019334066371272045 * lin_srgb[0] + 0.11919189368605417 * lin_srgb[1] + 0.9502897860291537 * lin_srgb[2]; | |
| // xyz to iab | |
| double x_ = 1.15 * xyz[0] - 0.15 * xyz[2]; | |
| double y_ = 0.66 * xyz[1] + 0.34 * xyz[0]; | |
| double rgb_[3]; | |
| rgb_[0] = 0.41478972 * x_ + 0.579999 * y_ + 0.0146480 * xyz[2]; | |
| rgb_[1] = -0.2015100 * x_ + 1.120649 * y_ + 0.0531008 * xyz[2]; | |
| rgb_[2] = -0.0166008 * x_ + 0.264800 * y_ + 0.6684799 * xyz[2]; | |
| double rgb__[3]; | |
| for (int i = 0; i < 3; i++) { | |
| double x = pow(rgb_[i] * 203.0 / 10000.0, 2610.0 / 16384.0); | |
| rgb__[i] = pow((3424.0 / 4096.0 + 2413.0 / 128.0 * x) / (1.0 + 2392.0 / 128.0 * x), 1.7 * 2523.0 / 32.0); | |
| } | |
| double iab[3]; | |
| iab[0] = rgb__[1] - 3.7035226210190005e-11; | |
| iab[1] = 3.524000 * rgb__[0] - 4.066708 * rgb__[1] + 0.542708 * rgb__[2]; | |
| iab[2] = 0.199076 * rgb__[0] + 1.096799 * rgb__[1] - 1.295875 * rgb__[2]; | |
| // iab to jch | |
| double q = 471.4521246689395 * pow(iab[0], 1.3599419891175006); | |
| jch[0] = 100.0 * q / 132.61789415528972; | |
| double h = atan2(iab[2], iab[1]); | |
| if (h < 0.0) h += 6.283185307179586; | |
| jch[2] = h * 180.0 / 3.141592653589793; | |
| double e = 1.015 + cos(h + 1.5540062593907111); | |
| double m = 100.0 * 2.318975718747944 * pow(iab[1] * iab[1] + iab[2] * iab[2], 0.37) * pow(e, 0.068); | |
| jch[1] = 100.0 * m / 132.61789415528972 - 5.4824383851949985e-18; | |
| } | |
| void zcam_to_srgb(double *srgb, const double *jch) { | |
| // jch to iab | |
| double i = pow(jch[0] * 132.61789415528972 / 100.0 / 471.4521246689395, 1.0 / 1.3599419891175006); | |
| double h = jch[2] * 3.141592653589793 / 180.0; | |
| double m = (jch[1] + 5.4824383851949985e-18) * 132.61789415528972 / 100.0; | |
| double e = 1.015 + cos(h + 1.5540062593907111); | |
| double c_ = pow(m / 100.0 / 2.318975718747944 / pow(e, 0.068), 1.0 / 0.74); | |
| double a = c_ * cos(h); | |
| double b = c_ * sin(h); | |
| double i_ = i + 3.7035226210190005e-11; | |
| // iab to xyz | |
| double rgb__[3]; | |
| rgb__[0] = 1.0 * i_ + 0.2772100865430786 * a + 0.11609463231223774 * b; | |
| rgb__[1] = 1.0 * i_; | |
| rgb__[2] = 1.0 * i_ + 0.042585801245220344 * a - 0.75384457989992 * b; | |
| double rgb_[3]; | |
| for (int j = 0; j < 3; j++) { | |
| double x = pow(rgb__[j], 1.0 / (1.7 * 2523.0 / 32.0)); | |
| rgb_[j] = | |
| 10000.0 / 203.0 * pow((3424.0 / 4096.0 - x) / (2392.0 / 128.0 * x - 2413.0 / 128.0), 16384.0 / 2610.0); | |
| } | |
| double xyz_[3]; | |
| xyz_[0] = 1.9242264357876067 * rgb_[0] + -1.0047923125953655 * rgb_[1] + 0.03765140403061801 * rgb_[2]; | |
| xyz_[1] = 0.35031676209499907 * rgb_[0] + 0.7264811939316552 * rgb_[1] + -0.06538442294808502 * rgb_[2]; | |
| xyz_[2] = -0.09098281098284756 * rgb_[0] + -0.31272829052307394 * rgb_[1] + 1.5227665613052603 * rgb_[2]; | |
| double xyz[3]; | |
| xyz[0] = (xyz_[0] + 0.15 * xyz_[2]) / 1.15; | |
| xyz[1] = (xyz_[1] - 0.34 * xyz[0]) / 0.66; | |
| xyz[2] = xyz_[2]; | |
| // xyz to srgb | |
| double lin_srgb[3]; | |
| lin_srgb[0] = 3.240425537316315 * xyz[0] + -1.5371249343679956 * xyz[1] + -0.4985270057409716 * xyz[2]; | |
| lin_srgb[1] = -0.9692671055142337 * xyz[0] + 1.8760129261227598 * xyz[1] + 0.04155606361996135 * xyz[2]; | |
| lin_srgb[2] = 0.05564426784503105 * xyz[0] + -0.20402898209844716 * xyz[1] + 1.0572410890555897 * xyz[2]; | |
| // linear srgb to srgb | |
| for (int j = 0; j < 3; j++) { | |
| if (lin_srgb[j] >= 0.003039934639778432) { | |
| srgb[j] = 1.055 * pow(lin_srgb[j], 1.0 / 2.4) - 0.055; | |
| } else { | |
| srgb[j] = 12.923210180787855 * lin_srgb[j]; | |
| } | |
| } | |
| } | |
| int is_in_srgb_gamut(const double *rgb) { | |
| return !(rgb[0] < 0.0 || rgb[0] > 1.0 || rgb[1] < 0.0 || rgb[1] > 1.0 || rgb[2] < 0.0 || rgb[2] > 1.0); | |
| } | |
| void clamp_rgb(double *rgb) { | |
| for (int i = 0; i < 3; i++) { | |
| if (rgb[i] < 0.0) { | |
| rgb[i] = 0.0; | |
| } else if (rgb[i] > 1.0) { | |
| rgb[i] = 1.0; | |
| } | |
| } | |
| } | |
| void zcam_to_clipped_srgb(double *srgb, const double *jch) { | |
| zcam_to_srgb(srgb, jch); | |
| if (is_in_srgb_gamut(srgb)) return; | |
| const double epsilon = 1e-3; | |
| if (jch[0] < epsilon) { | |
| srgb[0] = 0.0; | |
| srgb[1] = 0.0; | |
| srgb[2] = 0.0; | |
| } else if (jch[0] > 100.0 - epsilon) { | |
| srgb[0] = 1.0; | |
| srgb[1] = 1.0; | |
| srgb[2] = 1.0; | |
| } else { | |
| double min = 0.0; | |
| double max = jch[1]; | |
| double srgb2[] = {0.0, 0.0, 0.0}; | |
| while (fabs(max - min) > epsilon) { | |
| double mid = (min + max) / 2.0; | |
| zcam_to_srgb(srgb, (double[]) {jch[0], mid, jch[2]}); | |
| if (!is_in_srgb_gamut(srgb)) { | |
| max = mid; | |
| } else { | |
| zcam_to_srgb(srgb2, (double[]) {jch[0], mid + epsilon, jch[2]}); | |
| if (is_in_srgb_gamut(srgb2)) { | |
| min = mid; | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| clamp_rgb(srgb); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment