Skip to content

Instantly share code, notes, and snippets.

@Sam-Izdat
Last active June 19, 2024 06:38
Show Gist options
  • Save Sam-Izdat/43e630ea683a07c935863ba32e26d476 to your computer and use it in GitHub Desktop.
Save Sam-Izdat/43e630ea683a07c935863ba32e26d476 to your computer and use it in GitHub Desktop.
import {g, tm} from './util';
let ready = false;
let setup = () => ti.addToKernelScope({
g, tm,
ColorTransform, TransferFunction,
convert_to_xyz, convert_from_xyz, convert_color_space
});
export class ColorTransform {
static convert = (cst, v) => convert_color_space(cst, v); // [ColorTransform, vec] -> vec
static Variant = class {
static UNKNOWN = 1<<0;
static NONCOLOR = 1<<1;
static CIE_XYZ = 1<<2;
static CIE_XYY = 1<<3;
static SRGB = 1<<4;
static SRGB_LIN = 1<<5;
static REC709 = 1<<6;
static REC2020 = 1<<7;
static REC2020_LIN = 1<<8;
static DCI_P3 = 1<<9;
static DCI_P3_LIN = 1<<10;
static DISPLAY_P3 = 1<<11;
static ACESCG = 1<<12;
static ACESCC = 1<<13;
static ACESCCT = 1<<14;
static ACES2065_1 = 1<<15;
static LMS = 1<<16;
static SUPPORTED = this.CIE_XYZ |
this.SRGB | this.SRGB_LIN | this.REC709 |
this.REC2020 | this.REC2020_LIN |
this.DCI_P3 | this.DCI_P3_LIN |
this.ACESCG | this.LMS;
};
constructor(cs_from=ColorTransform.Variant.SRGB, cs_to=ColorTransform.Variant.ACESCG) {
return (async () => {
// Dummy kernel for ti.Static for now.
this.init = ti.classKernel(this, () => {});
this.init();
if (!ready) { setup(); ready=true; }
g.assert_bitwise_and(cs_from, ColorTransform.Variant.SUPPORTED, 'unsupported input color space');
g.assert_bitwise_and(cs_to, ColorTransform.Variant.SUPPORTED, 'unsupported output color space');
this.cs_from = cs_from;
this.cs_to = cs_to;
return this;
})();
};
static mat_xyz_to_srgb = [
[3.24096994190452134, -1.53738317757009346, -0.498610760293003284],
[-0.969243636280879826, 1.87596750150772067, 0.0415550574071756125],
[0.0556300796969936084, -0.203976958888976564, 1.05697151424287856]];
static mat_srgb_to_xyz = [
[0.412390799265959481, 0.357584339383877964, 0.180480788401834288],
[0.212639005871510358, 0.715168678767755927, 0.072192315360733715],
[0.0193308187155918507, 0.119194779794625988, 0.950532152249660581]];
// NOTE: Includes "D60"/D65 white point conversion
static mat_srgb_to_acescg = [
[ 0.6130974024, 0.3395231462, 0.04737945141],
[ 0.07019372247, 0.916353879, 0.01345239847],
[ 0.02061559288, 0.1095697729, 0.8698146341]];
// NOTE: Includes "D60"/D65 white point conversion
static mat_acescg_to_srgb = [
[ 1.705050993, -0.6217921206,-0.083258872],
[-0.1302564175, 1.140804737, -0.01054831907],
[-0.02400335681,-0.1289689761, 1.152972333]];
static mat_aces_rrt_sat = [
[0.9708890, 0.0269633, 0.00214758],
[0.0108892, 0.9869630, 0.00214758],
[0.0108892, 0.0269633, 0.96214800]];
static mat_aces_odt_sat = [
[0.949056, 0.0471857, 0.00375827],
[0.019056, 0.9771860, 0.00375827],
[0.019056, 0.0471857, 0.93375800]];
// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
static mat_srgb_to_ap1_tm = [
[0.59719, 0.35458, 0.04823],
[0.07600, 0.90834, 0.01566],
[0.02840, 0.13383, 0.83777]];
// ODT_SAT => XYZ => D60_2_D65 => sRGB
static mat_ap1_to_srgb_tm = [
[ 1.60475, -0.53108, -0.07367],
[-0.10208, 1.10813, -0.00605],
[-0.00327, -0.07276, 1.07602]];
// NOTE: Includes "D60"/D65 white point conversion
static mat_srgb_to_aces2065_1 = [
[ 0.439632982, 0.382988698, 0.17737832],
[ 0.0897764431, 0.813439429, 0.0967841284],
[ 0.0175411704, 0.111546553, 0.870912277]];
// NOTE: Includes "D60"/D65 white point conversion
static mat_aces2065_1_to_srgb = [
[ 2.52168619, -1.13413099, -0.387555198],
[-0.276479914, 1.37271909, -0.0962391736],
[-0.015378065, -0.152975336, 1.1683534]];
static mat_srgb_to_displayp3 = [
[ 0.822461969, 0.177538031, 1.15772692e-10],
[ 0.0331941989, 0.966805801, 1.95085037e-11],
[ 0.0170826307, 0.0723974405, 0.910519929]];
static mat_displayp3_to_srgb = [
[ 1.22494018, -0.224940176, -4.77534979e-11],
[-0.0420569547, 1.04205695, 3.37864801e-11],
[-0.0196375546,-0.0786360454, 1.0982736]] ;
// NOTE: No chromatic adaptation
static mat_srgb_to_dcip3 = [
[0.868579739716132409, 0.128919138460847047, 0.00250112182302054368],
[0.0345404102543194426, 0.961811386361919975, 0.0036482033837605824],
[0.0167714290414502718, 0.0710399977868858352, 0.912188573171663893]];
// NOTE: No chromatic adaptation
static mat_dcip3_to_srgb = [
[ 1.15751640619975871, -0.154962378073857756, -0.00255402812590095854],
[-0.0415000715306859699, 1.04556792307969925, -0.00406785154901328463],
[-0.0180500389562539583,-0.0785782726530290654, 1.09662831160928302]];
// NOTE: No chromatic adaptation
static mat_dcip3_to_xyz = [
[ 0.445169815564552417, 0.277134409206777664, 0.172282669815564564],
[ 0.209491677912730539, 0.721595254161043636, 0.0689130679262258258],
[-3.63410131696985616e-17, 0.0470605600539811521, 0.907355394361973415]];
// NOTE: No chromatic adaptation
static mat_xyz_to_dcip3 = [
[2.7253940304917328, -1.01800300622718496, -0.440163195190036463],
[-0.795168025808764195, 1.689732054843624, 0.0226471906084774533],
[0.0412418913957000325, -0.0876390192158623825, 1.10092937864632191]];
static mat_srgb_to_rec2020 = [
[ 0.627403896, 0.329283039, 0.0433130657],
[ 0.0690972894, 0.919540395, 0.0113623156],
[ 0.0163914389, 0.0880133077, 0.895595253]];
static mat_rec2020_to_srgb = [
[ 1.660491, -0.587641139,-0.0728498633],
[-0.124550475, 1.1328999, -0.00834942258],
[-0.0181507633,-0.100578898, 1.11872966]];
static mat_rec2020_to_xyz = [
[0.636958048301291, 0.144616903586208, 0.168880975164172],
[0.262700212011267, 0.677998071518871, 0.059301716469862],
[4.99410657446607e-17, 0.0280726930490874, 1.06098505771079]];
static mat_xyz_to_rec2020 = [
[1.71665118797127, -0.355670783776393, -0.25336628137366],
[-0.666684351832489, 1.61648123663494, 0.0157685458139111],
[0.0176398574453108, -0.0427706132578085, 0.942103121235474]];
// NOTE: No chromatic adaptation
static mat_acescg_to_xyz = [
[0.662454181108505, 0.134004206456433, 0.156187687004908],
[0.272228716780915, 0.674081765811148, 0.0536895174079371],
[-0.00557464949039411, 0.00406073352898283, 1.010339100313]];
// NOTE: No chromatic adaptation
static mat_xyz_to_acescg = [
[1.64102337969433, -0.32480329418479, -0.236424695237612],
[-0.663662858722983, 1.61533159165734, 0.0167563476855301],
[0.0117218943283754, -0.00828444199623741, 0.988394858539022]];
// NOTE: For CIE XYZ color
static mat_d60_to_d65 = [
[0.987224008703017, -0.00611322860685692, 0.0159532883359127],
[-0.0075983718116624, 1.00186148473965, 0.00533003579138895],
[0.00307257705853153, -0.0050959615111306, 1.0816806030658]];
// NOTE: For CIE XYZ color
static mat_d65_to_d60 = [
[1.01303491464999, 0.00610525782320722, -0.0149709436265875],
[0.00769823012541507, 0.998163352118278, -0.00503203853511188],
[-0.00284131743243907, 0.00468515672253722, 0.924506137457663]];
// NOTE: For CIE XYZ color
static mat_d65_to_dci = [
[0.976578896646979768, -0.0154362646984919742, -0.016686021704209866],
[-0.0256896658505145926, 1.02853916787996963, -0.00378517365630504153],
[-0.00570574587417104179, 0.0110778657389971485, 0.871176159390377409]];
// NOTE: For CIE XYZ color
static mat_dci_to_d65 = [
[1.02449672775257752, 0.0151635410224165156, 0.0196885223342066827],
[0.0256121933371584198, 0.97258630562441342, 0.00471635229242730096],
[0.0063842306500876874, -0.012268082736730219, 1.14794244517367791]];
static mat_xyz_to_lms = [
[ 0.8951, 0.2664,-0.1614],
[-0.7502, 1.7135, 0.0367],
[ 0.0389,-0.0685, 1.0296]];
static mat_lms_to_xyz = [
[ 0.986993, -0.147054, 0.159963],
[ 0.432305, 0.51836, 0.0492912],
[ -0.00852866, 0.0400428, 0.968487]];
// For OKLAB's XYZ to LMS
static mat_oklab_m1 = [
[ 0.8189330101, 0.3618667424, -0.1288597137],
[ 0.0329845436, 0.9293118715, 0.0361456387],
[ 0.0482003018, 0.2643662691, 0.6338517070]];
// For OKLAB's non-linear L'M'S' to OKLAB
static mat_oklab_m2 = [
[ 0.2104542553, 0.7936177850, -0.0040720468],
[ 1.9779984951, -2.4285922050, 0.4505937099],
[ 0.0259040371, 0.7827717662, -0.8086757660]];
// Inverse of OKLAB M1
static mat_oklab_m1_inv = [
[ 1.22701385, -0.55779998, 0.28125615],
[-0.04058018, 1.11225687, -0.07167668],
[-0.07638128, -0.42148198, 1.58616322]];
// Inverse of OKLAB M2
static mat_oklab_m2_inv = [
[ 1. , 0.39633779, 0.21580376],
[ 1.00000001, -0.10556134, -0.06385417],
[ 1.00000005, -0.08948418, -1.29148554]];
}
export class TransferFunction {
static srgb_eotf = (v) => {
let s1 = v / 12.92;
let s2 = ti.pow((v + 0.055) / 1.055, 2.4);
return tm.where_lte(v, 0.04045, s1, s2);
}
static srgb_oetf = (v) => {
let s1 = v * 12.92;
let s2 = ti.pow(v, 1. / 2.4) * 1.055 - 0.055;
return tm.where_lte(v, 0.0031308, s1, s2);
}
static rec709_eotf = (v) => {
let s1 = v / 4.5;
let s2 = ti.pow((v + 0.099) / 1.099, 2.2);
return tm.where_lte(v, 0.081, s1, s2);
}
static rec709_oetf = (v) => {
let s1 = v * 4.5;
let s2 = ti.pow(v, 1. / 2.2) * 1.099 - 0.099;
let cond = 1. - ti.step(0.018, v);
return tm.where_lte(v, 0.018, s1, s2);
}
static rec2020_eotf = (v) => {
let a = 1.09929682680944;
let b = 0.08124285829;
let s1 = v / 4.5;
let s2 = ti.pow((v + a - 1.) / a, 1./ 0.45);
return tm.where_lte(v, b, s1, s2)
}
static rec2020_oetf = (v) => {
let a = 1.09929682680944;
let b = 0.018053968510807;
let s1 = v * 4.5;
let s2 = a * ti.pow(v, .45) - (a - 1.);
return tm.where_lte(v, b, s1, s2);
}
static dcip3_eotf = (v) => {
return ti.pow(ti.abs(v), 2.6) * ti.sign(v);
}
static dcip3_oetf = (v) => {
return ti.pow(ti.abs(v), 1./2.6) * ti.sign(v);
}
static log_c_eotf = (v) => {
let offset = 0.00937677;
let x = tm.where_gt(v, 0.1496582,
ti.pow(10.0, (v - 0.385537) / 0.2471896) * 0.18 - offset,
(x / 0.9661776 - 0.04378604) * 0.18 - offset);
return x;
}
static log_c_oetf = (v) => {
let offset = 0.00937677;
let x = tm.where_gt(v, 0.02 - offset,
(((tm.log10((v + offset) / 0.18)) * 0.2471896) + 0.385537),
((((v + offset) / 0.18) + 0.04378604) * 0.9661776));
return x;
}
static s_log_eotf = (v) => {
return ti.pow(10.0, ((v - 0.616596 - 0.03) / 0.432699)) - 0.037584;
}
static s_log_oetf = (v) => {
return (0.432699 * tm.log10(v + 0.037584) + 0.616596) + 0.03;
}
}
export var convert_to_xyz = (c, v) => { // [ColorTransform, vec] -> vec
if (ti.Static(c.cs_from == ColorTransform.Variant.SRGB_LIN)) {
return tm.mm(ColorTransform.mat_srgb_to_xyz, v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.SRGB)) {
return tm.mm(ColorTransform.mat_srgb_to_xyz, TransferFunction.srgb_eotf(v));
} else if (ti.Static(c.cs_from == ColorTransform.Variant.REC709)) {
return tm.mm(ColorTransform.mat_srgb_to_xyz, TransferFunction.rec709_eotf(v));
} else if (ti.Static(c.cs_from == ColorTransform.Variant.REC2020_LIN)) {
return tm.mm(ColorTransform.mat_rec2020_to_xyz, v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.REC2020)) {
return tm.mm(ColorTransform.mat_rec2020_to_xyz, TransferFunction.rec2020_eotf(v));
} else if (ti.Static(c.cs_from == ColorTransform.Variant.DCI_P3_LIN)) {
return tm.mm(ColorTransform.mat_dci_to_d65, tm.mm(ColorTransform.mat_dcip3_to_xyz, v));
} else if (ti.Static(c.cs_from == ColorTransform.Variant.DCI_P3)) {
return tm.mm(ColorTransform.mat_dci_to_d65, tm.mm(ColorTransform.mat_dcip3_to_xyz, TransferFunction.dcip3_eotf(v)));
} else if (ti.Static(c.cs_from == ColorTransform.Variant.LMS)) {
return tm.mm(ColorTransform.mat_lms_to_xyz, v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.ACESCG)) {
return tm.mm(ColorTransform.mat_d60_to_d65, tm.mm(ColorTransform.mat_acescg_to_xyz, v));
}
}
export var convert_from_xyz = (c, v) => { // [ColorTransform, vec] -> vec
if (ti.Static(c.cs_to == ColorTransform.Variant.SRGB_LIN)) {
return tm.mm(ColorTransform.mat_xyz_to_srgb, v);
} else if (ti.Static(c.cs_to == ColorTransform.Variant.SRGB)) {
return TransferFunction.srgb_oetf(tm.mm(ColorTransform.mat_xyz_to_srgb, v));
} else if (ti.Static(c.cs_to == ColorTransform.Variant.REC709)) {
return TransferFunction.rec709_oetf(tm.mm(ColorTransform.mat_xyz_to_srgb, v));
} else if (ti.Static(c.cs_to == ColorTransform.Variant.REC2020_LIN)) {
return tm.mm(ColorTransform.mat_xyz_to_rec2020, v);
} else if (ti.Static(c.cs_to == ColorTransform.Variant.REC2020)) {
return TransferFunction.rec2020_oetf(tm.mm(ColorTransform.mat_xyz_to_rec2020, v));
} else if (ti.Static(c.cs_to == ColorTransform.Variant.DCI_P3_LIN)) {
return tm.mm(ColorTransform.mat_xyz_to_dcip3, tm.mm(ColorTransform.mat_d65_to_dci, v));
} else if (ti.Static(c.cs_to == ColorTransform.Variant.DCI_P3)) {
return TransferFunction.dcip3_oetf(tm.mm(ColorTransform.mat_xyz_to_dcip3, tm.mm(ColorTransform.mat_d65_to_dci, v)));
} else if (ti.Static(c.cs_to == ColorTransform.Variant.LMS)) {
return tm.mm(ColorTransform.mat_xyz_to_lms, v);
} else if (ti.Static(c.cs_to == ColorTransform.Variant.ACESCG)) {
return tm.mm(ColorTransform.mat_xyz_to_acescg, tm.mm(ColorTransform.mat_d65_to_d60, v));
}
}
export var convert_color_space = (c, v) => { // [ColorTransform, vec] -> vec
if (ti.Static(c.cs_from == ColorTransform.Variant.SRGB && c.cs_to == ColorTransform.Variant.SRGB_LIN)) {
return TransferFunction.srgb_eotf(v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.SRGB_LIN && c.cs_to == ColorTransform.Variant.SRGB)) {
return TransferFunction.srgb_oetf(v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.SRGB_LIN && c.cs_to == ColorTransform.Variant.REC709)) {
return TransferFunction.rec2020_oetf(v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.REC2020 && c.cs_to == ColorTransform.Variant.REC2020_LIN)) {
return TransferFunction.rec2020_eotf(v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.REC2020_LIN && c.cs_to == ColorTransform.Variant.REC2020)) {
return TransferFunction.rec2020_oetf(v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.DCI_P3 && c.cs_to == ColorTransform.Variant.DCI_P3_LIN)) {
return TransferFunction.dcip3_eotf(v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.DCI_P3_LIN && c.cs_to == ColorTransform.Variant.DCI_P3)) {
return TransferFunction.dcip3_oetf(v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.ACESCG && c.cs_to == ColorTransform.Variant.SRGB_LIN)) {
return tm.mm(ColorTransform.mat_acescg_to_srgb, v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.SRGB_LIN && c.cs_to == ColorTransform.Variant.ACESCG)) {
return tm.mm(ColorTransform.mat_srgb_to_acescg, v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.ACESCG && c.cs_to == ColorTransform.Variant.SRGB)) {
return TransferFunction.srgb_oetf(tm.mm(ColorTransform.mat_acescg_to_srgb, v));
} else if (ti.Static(c.cs_from == ColorTransform.Variant.SRGB && c.cs_to == ColorTransform.Variant.ACESCG)) {
return tm.mm(ColorTransform.mat_srgb_to_acescg, TransferFunction.srgb_eotf(v));
} else if (ti.Static(c.cs_to == c.cs_from)) {
return v;
} else if (ti.Static(c.cs_to == ColorTransform.Variant.CIE_XYZ)) {
return convert_to_xyz(c, v);
} else if (ti.Static(c.cs_from == ColorTransform.Variant.CIE_XYZ)) {
return convert_from_xyz(c, v);
} else {
return convert_from_xyz(c, convert_to_xyz(c, v));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment