Skip to content

Instantly share code, notes, and snippets.

@Kyant0
Last active April 29, 2023 04:58
Show Gist options
  • Select an option

  • Save Kyant0/ee9b2e713c17da8fd6743092fd13cd85 to your computer and use it in GitHub Desktop.

Select an option

Save Kyant0/ee9b2e713c17da8fd6743092fd13cd85 to your computer and use it in GitHub Desktop.
Fast conversion between srgb and zcam
#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