Skip to content

Instantly share code, notes, and snippets.

@mattdesl
Last active April 27, 2023 06:04
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattdesl/73d4e93c78042f3aec5fca5fcaad0dc5 to your computer and use it in GitHub Desktop.
Save mattdesl/73d4e93c78042f3aec5fca5fcaad0dc5 to your computer and use it in GitHub Desktop.

Using data and code from: https://graphics.geometrian.com/research/spectral-primaries.html

See their reference implementation for details (MIT license): https://github.com/geometrian/simple-spectral

This converts sRGB triplet into spectral reflectance, then combines two curves with weighted geometric mean to produce a "mixed" colour.

Installation

Copy the code into a folder e.g. mixer. Install canvas-sketch-cli like so:

# move into the new folder containing weighted_mean.js
cd mixer/

# install
npm init -y
npm i canvas-sketch-cli --save-dev

Now you can run it:

npx canvas-sketch weighted_mean.js --open

Results

Results, compared with interpolating [R,G,B] values directly:

2023 03 19-15 16 44

It does not always work. Here is a failure case compared to mixbox:

2023 03 19-15 21 23

Gamma?

It's unclear to me why the demo here does not require gamma-to-linear conversion before doing the mix, and linear-to-gamma correction after. I assume that the data tables may have some sort of gamma treatment. It seems like it would be better to do all the interpolation and averaging in linear space, but I would need to generate new data from the original CIE files here:

https://github.com/geometrian/simple-spectral/tree/master/data

Grayscale

There is currently a bug where black-white interpolation will produce all-black output. I assume this is probably something that could be fixed but I haven't bothered yet.

License

I believe it may eventually be possible to license this as MIT but it does use the data tables from the paper's demo (not the MIT github code) which has no license associated. At the moment I am hesitant to mark it as MIT license because of that.

However, if some JavaScript code is used to convert the CIE data from the GitHub into the required arrays for this to work, the entire script can be licensed as MIT without issue.

Credits

const canvasSketch = require("canvas-sketch");
const Color = require("canvas-sketch-util/color");
const Random = require("canvas-sketch-util/random");
const math = require("canvas-sketch-util/math");
const mixbox = require("mixbox");
const settings = {
dimensions: [2048, 2048],
};
// Data tables from:
// https://graphics.geometrian.com/research/spectral-primaries.html
// https://github.com/geometrian/simple-spectral
// prettier-ignore
const x_bar = [0.001368, 0.002236, 0.004243, 0.007650, 0.014310, 0.023190, 0.043510, 0.077630, 0.134380, 0.214770, 0.283900, 0.328500, 0.348280, 0.348060, 0.336200, 0.318700, 0.290800, 0.251100, 0.195360, 0.142100, 0.095640, 0.057950, 0.032010, 0.014700, 0.004900, 0.002400, 0.009300, 0.029100, 0.063270, 0.109600, 0.165500, 0.225750, 0.290400, 0.359700, 0.433450, 0.512050, 0.594500, 0.678400, 0.762100, 0.842500, 0.916300, 0.978600, 1.026300, 1.056700, 1.062200, 1.045600, 1.002600, 0.938400, 0.854450, 0.751400, 0.642400, 0.541900, 0.447900, 0.360800, 0.283500, 0.218700, 0.164900, 0.121200, 0.087400, 0.063600, 0.046770, 0.032900, 0.022700, 0.015840, 0.011359, 0.008111, 0.005790, 0.004109, 0.002899, 0.002049, 0.001440, 0.001000, 0.000690, 0.000476, 0.000332, 0.000235, 0.000166, 0.000117, 0.000083, 0.000059, 0.000042];
// prettier-ignore
const y_bar = [0.000039, 0.000064, 0.000120, 0.000217, 0.000396, 0.000640, 0.001210, 0.002180, 0.004000, 0.007300, 0.011600, 0.016840, 0.023000, 0.029800, 0.038000, 0.048000, 0.060000, 0.073900, 0.090980, 0.112600, 0.139020, 0.169300, 0.208020, 0.258600, 0.323000, 0.407300, 0.503000, 0.608200, 0.710000, 0.793200, 0.862000, 0.914850, 0.954000, 0.980300, 0.994950, 1.000000, 0.995000, 0.978600, 0.952000, 0.915400, 0.870000, 0.816300, 0.757000, 0.694900, 0.631000, 0.566800, 0.503000, 0.441200, 0.381000, 0.321000, 0.265000, 0.217000, 0.175000, 0.138200, 0.107000, 0.081600, 0.061000, 0.044580, 0.032000, 0.023200, 0.017000, 0.011920, 0.008210, 0.005723, 0.004102, 0.002929, 0.002091, 0.001484, 0.001047, 0.000740, 0.000520, 0.000361, 0.000249, 0.000172, 0.000120, 0.000085, 0.000060, 0.000042, 0.000030, 0.000021, 0.000015];
// prettier-ignore
const z_bar = [0.006450, 0.010550, 0.020050, 0.036210, 0.067850, 0.110200, 0.207400, 0.371300, 0.645600, 1.039050, 1.385600, 1.622960, 1.747060, 1.782600, 1.772110, 1.744100, 1.669200, 1.528100, 1.287640, 1.041900, 0.812950, 0.616200, 0.465180, 0.353300, 0.272000, 0.212300, 0.158200, 0.111700, 0.078250, 0.057250, 0.042160, 0.029840, 0.020300, 0.013400, 0.008750, 0.005750, 0.003900, 0.002750, 0.002100, 0.001800, 0.001650, 0.001400, 0.001100, 0.001000, 0.000800, 0.000600, 0.000340, 0.000240, 0.000190, 0.000100, 0.000050, 0.000030, 0.000020, 0.000010, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000];
// prettier-ignore
const d65 = [49.975500, 52.311800, 54.648200, 68.701500, 82.754900, 87.120400, 91.486000, 92.458900, 93.431800, 90.057000, 86.682300, 95.773600, 104.865000, 110.936000, 117.008000, 117.410000, 117.812000, 116.336000, 114.861000, 115.392000, 115.923000, 112.367000, 108.811000, 109.082000, 109.354000, 108.578000, 107.802000, 106.296000, 104.790000, 106.239000, 107.689000, 106.047000, 104.405000, 104.225000, 104.046000, 102.023000, 100.000000, 98.167100, 96.334200, 96.061100, 95.788000, 92.236800, 88.685600, 89.345900, 90.006200, 89.802600, 89.599100, 88.648900, 87.698700, 85.493600, 83.288600, 83.493900, 83.699200, 81.863000, 80.026800, 80.120700, 80.214600, 81.246200, 82.277800, 80.281000, 78.284200, 74.002700, 69.721300, 70.665200, 71.609100, 72.979000, 74.349000, 67.976500, 61.604000, 65.744800, 69.885600, 72.486300, 75.087000, 69.339800, 63.592700, 55.005400, 46.418200, 56.611800, 66.805400, 65.094100, 63.382800];
// prettier-ignore
const cieE = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ];
// prettier-ignore
const y_d65 = 2113.454951;
// prettier-ignore
const y_cieE= 21.3713;
// prettier-ignore
const s_r1 = [0.327457414, 0.323750578, 0.313439461, 0.288879383, 0.239205681, 0.189702037, 0.121746068, 0.074578271, 0.044433159, 0.028928632, 0.022316653, 0.016911307, 0.014181107, 0.013053143, 0.011986164, 0.011288715, 0.010906066, 0.010400713, 0.010637360, 0.010907663, 0.011032712, 0.011310657, 0.011154642, 0.010148770, 0.008918582, 0.007685576, 0.006705708, 0.005995806, 0.005537257, 0.005193784, 0.005025362, 0.005136363, 0.005433200, 0.005819986, 0.006400573, 0.007449529, 0.008583636, 0.010395762, 0.013565434, 0.019384516, 0.032084071, 0.074356038, 0.624393724, 0.918310033, 0.949253030, 0.958187833, 0.958187751, 0.958187625, 0.955679061, 0.958006155, 0.954101573, 0.947607606, 0.938681328, 0.924466683, 0.904606025, 0.880412199, 0.847787873, 0.805779127, 0.752531854, 0.686439397, 0.618694571, 0.540264444, 0.472964416, 0.432701597, 0.405358046, 0.385491835, 0.370983585, 0.357608702, 0.348712800, 0.344880119, 0.341917877, 0.339531093, 0.337169504, 0.336172019, 0.335167443, 0.334421625, 0.334008760, 0.333915793, 0.333818455, 0.333672775, 0.333569513];
// prettier-ignore
const s_g1 = [0.331861713, 0.329688188, 0.327860022, 0.319173580, 0.294322584, 0.258697065, 0.188894319, 0.125388382, 0.078687060, 0.053143271, 0.042288146, 0.033318346, 0.029755948, 0.030331251, 0.030988572, 0.031686355, 0.034669962, 0.034551957, 0.040684806, 0.054460037, 0.080905287, 0.146348303, 0.379679643, 0.766744269, 0.876214748, 0.918491656, 0.940655563, 0.953731885, 0.961643280, 0.967200020, 0.970989746, 0.972852304, 0.973116594, 0.973351069, 0.973351116, 0.972261080, 0.973351022, 0.973148495, 0.971061306, 0.966371306, 0.954941968, 0.913578990, 0.364348804, 0.071507243, 0.041230434, 0.032423874, 0.031924630, 0.031276033, 0.032630370, 0.029530872, 0.031561761, 0.035674218, 0.041403005, 0.050604260, 0.063434300, 0.078918245, 0.099542743, 0.125595760, 0.157590910, 0.195398239, 0.231474475, 0.268852136, 0.296029164, 0.309754994, 0.317815883, 0.322990347, 0.326353848, 0.329143902, 0.330808727, 0.331482690, 0.331984550, 0.332341173, 0.332912009, 0.332919280, 0.333027673, 0.333179705, 0.333247031, 0.333259349, 0.333275050, 0.333294328, 0.333309425];
// prettier-ignore
const s_b1 = [0.340680792, 0.346561187, 0.358700493, 0.391947027, 0.466471731, 0.551600896, 0.689359611, 0.800033347, 0.876879781, 0.917928097, 0.935395201, 0.949770347, 0.956062945, 0.956615607, 0.957025265, 0.957024931, 0.954423973, 0.955047329, 0.948677833, 0.934632300, 0.908062000, 0.842341039, 0.609165715, 0.223106961, 0.114866670, 0.073822768, 0.052638729, 0.040272309, 0.032819463, 0.027606196, 0.023984891, 0.022011333, 0.021450205, 0.020828945, 0.020248311, 0.020289391, 0.018065342, 0.016455742, 0.015373260, 0.014244178, 0.012973962, 0.012064974, 0.011257478, 0.010182725, 0.009516535, 0.009388293, 0.009887619, 0.010536342, 0.011690569, 0.012462973, 0.014336665, 0.016718175, 0.019915666, 0.024929056, 0.031959674, 0.040669554, 0.052669382, 0.068625111, 0.089877232, 0.118162359, 0.149830947, 0.190883409, 0.231006403, 0.257543385, 0.276826039, 0.291517773, 0.302662506, 0.313247301, 0.320478325, 0.323636995, 0.326097309, 0.328127369, 0.329917976, 0.330907901, 0.331803633, 0.332396627, 0.332740781, 0.332820857, 0.332901731, 0.333025967, 0.333111083];
// prettier-ignore
const s_r2 = [0.328455134, 0.324039100, 0.315349590, 0.292792770, 0.246316933, 0.198108029, 0.130068113, 0.079657502, 0.047536766, 0.030925762, 0.023739245, 0.017858899, 0.014560638, 0.012790919, 0.011391265, 0.010621609, 0.010019665, 0.009843010, 0.010040448, 0.010026949, 0.009896261, 0.010490400, 0.009780279, 0.008394966, 0.007078490, 0.006339279, 0.005491672, 0.004880634, 0.004483955, 0.004185756, 0.004029708, 0.004096559, 0.004260582, 0.004472863, 0.004811227, 0.005409461, 0.006287819, 0.007615900, 0.009731549, 0.013081085, 0.019375748, 0.327707567, 0.538874667, 0.725699391, 0.951408718, 0.962637428, 0.966971579, 0.968007753, 0.967112589, 0.963775324, 0.958418605, 0.952601048, 0.944569777, 0.931722624, 0.913700042, 0.891598969, 0.860505987, 0.824225062, 0.773069484, 0.709647070, 0.642591809, 0.562173723, 0.490358422, 0.445050267, 0.414903338, 0.392213639, 0.375678397, 0.360661444, 0.349760473, 0.347712371, 0.339279547, 0.342893608, 0.338489141, 0.336913338, 0.341734672, 0.334898485, 0.334735839, 0.334317322, 0.333924136, 0.333359358, 0.333012936];
// prettier-ignore
const s_g2 = [0.330648932, 0.329150364, 0.326106934, 0.314306096, 0.285621445, 0.246483644, 0.176356833, 0.115924753, 0.072260822, 0.048849075, 0.038884781, 0.030676675, 0.026470494, 0.024892729, 0.024141373, 0.024877463, 0.026478710, 0.030160991, 0.038137818, 0.051247767, 0.089042464, 0.316275641, 0.447692245, 0.645266804, 0.827179315, 0.921339153, 0.942909702, 0.955503903, 0.963102866, 0.968404133, 0.972079059, 0.973896551, 0.975085761, 0.976143135, 0.976820260, 0.976783210, 0.976380637, 0.975499356, 0.973843427, 0.971305193, 0.966234176, 0.658916933, 0.449088083, 0.263712302, 0.038957672, 0.028189759, 0.023919124, 0.022538392, 0.022706962, 0.024634248, 0.027942452, 0.031543891, 0.036587085, 0.044729441, 0.056138794, 0.070030169, 0.089641578, 0.111148860, 0.141853788, 0.178303772, 0.212975249, 0.253865556, 0.283887105, 0.301548721, 0.311745496, 0.319037251, 0.323070178, 0.326947909, 0.329858486, 0.330452606, 0.330682210, 0.335519233, 0.330196327, 0.333260022, 0.331231570, 0.334947675, 0.331484024, 0.331632713, 0.331732129, 0.331951176, 0.332076988];
// prettier-ignore
const s_b2 = [0.340895932, 0.346810535, 0.358543476, 0.392901134, 0.468061621, 0.555408327, 0.693575054, 0.804417744, 0.880202412, 0.920225163, 0.937375974, 0.951464426, 0.958968868, 0.962316352, 0.964467362, 0.964500928, 0.963501626, 0.959995999, 0.951821734, 0.938725283, 0.901061275, 0.673233959, 0.542527476, 0.346338230, 0.165742195, 0.072321568, 0.051598626, 0.039615464, 0.032413179, 0.027410111, 0.023891233, 0.022006890, 0.020653657, 0.019384003, 0.018368514, 0.017807329, 0.017331544, 0.016884744, 0.016425024, 0.015613722, 0.014390077, 0.013375499, 0.012037251, 0.010588306, 0.009633610, 0.009172813, 0.009109297, 0.009453855, 0.010180450, 0.011590428, 0.013638943, 0.015855062, 0.018843138, 0.023547934, 0.030161164, 0.038370863, 0.049852435, 0.064626077, 0.085076729, 0.112049158, 0.144432942, 0.183960721, 0.225754473, 0.253401011, 0.273351165, 0.288749110, 0.301251423, 0.312390644, 0.320381036, 0.321835018, 0.330038236, 0.321587149, 0.331314518, 0.329826618, 0.327033724, 0.330153783, 0.333780041, 0.334049853, 0.334343601, 0.334689271, 0.334909795];
// prettier-ignore
const s_r3 = [0.325499558, 0.320381406, 0.308469037, 0.288245363, 0.250900198, 0.208049512, 0.142822037, 0.090291304, 0.055086555, 0.034487258, 0.025360654, 0.023786500, 0.018435917, 0.020584585, 0.016358498, 0.015433283, 0.013406507, 0.012352481, 0.011964661, 0.011659590, 0.011229869, 0.010655116, 0.009814057, 0.008714740, 0.007540184, 0.006377456, 0.005484956, 0.004817804, 0.004382595, 0.004172254, 0.004101861, 0.004138331, 0.004289889, 0.004619630, 0.004978489, 0.006264624, 0.006880366, 0.008266738, 0.011195008, 0.016993999, 0.031973010, 0.115735649, 0.912667746, 0.960557237, 0.971874841, 0.975183425, 0.976383571, 0.976016150, 0.976332613, 0.975524178, 0.973447797, 0.969445120, 0.963951581, 0.956231695, 0.945406940, 0.930681934, 0.910253756, 0.881563264, 0.842731316, 0.795244879, 0.740974931, 0.671260447, 0.595695790, 0.528500631, 0.476964847, 0.436575888, 0.406956401, 0.385054719, 0.369656118, 0.358701127, 0.351247023, 0.345647046, 0.342009756, 0.339184203, 0.337536748, 0.336140636, 0.335294339, 0.334707151, 0.334286544, 0.333995833, 0.333780605];
// prettier-ignore
const s_g3 = [0.328971397, 0.326105757, 0.319138530, 0.306444351, 0.279644852, 0.244314339, 0.180844467, 0.122661839, 0.077640735, 0.050761397, 0.039106761, 0.039570798, 0.032249338, 0.041747124, 0.035255927, 0.042991185, 0.039081117, 0.040859842, 0.048631057, 0.064256482, 0.092862239, 0.152844904, 0.333779721, 0.715669114, 0.853694727, 0.906496602, 0.933739871, 0.949983277, 0.959843022, 0.965805752, 0.969868483, 0.972791835, 0.974563464, 0.974732329, 0.976920722, 0.971668010, 0.976041654, 0.977343678, 0.975682218, 0.971328847, 0.957863449, 0.875520427, 0.079735041, 0.032657233, 0.021814725, 0.018591957, 0.017272418, 0.017277290, 0.016651778, 0.016751603, 0.017912024, 0.020450903, 0.023970674, 0.028941852, 0.035923777, 0.045389004, 0.058440618, 0.076605271, 0.100824530, 0.129811406, 0.161818833, 0.200765154, 0.239583667, 0.270729160, 0.291638057, 0.306316960, 0.315602301, 0.321740892, 0.325694404, 0.328204944, 0.329848517, 0.330955419, 0.331696707, 0.332202377, 0.332539111, 0.332762621, 0.332913970, 0.333015306, 0.333091990, 0.333139454, 0.333177806];
// prettier-ignore
const s_b3 = [0.345528956, 0.353512783, 0.372392404, 0.405310270, 0.469454942, 0.547636144, 0.676333494, 0.787046856, 0.867272709, 0.914751345, 0.935532585, 0.936642703, 0.949314745, 0.937668291, 0.948385575, 0.941575532, 0.947512376, 0.946787678, 0.939404282, 0.924083928, 0.895907892, 0.836499980, 0.656406221, 0.275616145, 0.138765088, 0.087125941, 0.060775172, 0.045198918, 0.035774382, 0.030021993, 0.026029656, 0.023069834, 0.021146647, 0.020648041, 0.018100789, 0.022067367, 0.017077980, 0.014389585, 0.013122774, 0.011677154, 0.010163542, 0.008743924, 0.007597213, 0.006785530, 0.006310435, 0.006224618, 0.006344011, 0.006706561, 0.007015609, 0.007724220, 0.008640180, 0.010103977, 0.012077744, 0.014826453, 0.018669283, 0.023929061, 0.031305625, 0.041831463, 0.056444151, 0.074943712, 0.097206230, 0.127974392, 0.164720533, 0.200770194, 0.231397076, 0.257107123, 0.277441256, 0.293204331, 0.304649395, 0.313093812, 0.318904293, 0.323397294, 0.326293188, 0.328612914, 0.329923415, 0.331095719, 0.331790241, 0.332275484, 0.332618565, 0.332860626, 0.333035850];
var illum = d65;
var y_illum = y_d65;
var s_r = s_r1;
var s_g = s_g1;
var s_b = s_b1;
// prettier-ignore
const pairs = [
[[0, 33, 133 ], [252, 211, 0]],
[[0, 33, 133 ], [255, 105, 0]],
[[25, 0, 89], [252, 211, 0]],
[[123, 72, 0 ],[107, 148, 4]],
[[255,255,255], [0,0,0]],
[[255,255,255], [0,255,0]],
[[255,255,255], [255,0,0]],
[[0,255,255], [0,0,255]],
]
const sketch = () => {
// random pairs
const [colorA, colorB] = Random.pick(pairs);
// fixed inputs
// const [colorA, colorB] = [
// [0, 33, 133],
// [252, 211, 0],
// ];
return ({ context, width, height }) => {
context.fillStyle = Color.parse(colorA).hex;
context.fillRect(0, 0, width / 2, height);
const bHeight = height / 3;
context.textAlign = "left";
context.textBaseline = "middle";
const fontSize = bHeight / 16;
context.font = `${fontSize}px monospace`;
ramp(context, colorA, colorB, width, bHeight, (a, b, t) => {
return math.lerpArray(a, b, t);
});
context.fillStyle = "white";
context.fillText(
"sRGB interpolation",
fontSize * 2,
bHeight / 2 - fontSize / 2
);
context.translate(0, bHeight);
ramp(context, colorA, colorB, width, bHeight, (a, b, t) => {
const specA = sRGBToSpectrum(colorA);
const specB = sRGBToSpectrum(colorB);
return spectrumToSRGB(mixSpectrums(specA, specB, t));
});
context.fillStyle = "white";
context.fillText(
"spectrum interpolation (weighted geometric mean)",
fontSize * 2,
bHeight / 2 - fontSize / 2
);
context.translate(0, bHeight);
ramp(context, colorA, colorB, width, bHeight, (a, b, t) => {
return mixbox.lerp(a, b, t);
});
context.fillStyle = "white";
context.fillText(
"mixbox interpolation (Kubelka-Munk)",
fontSize * 2,
bHeight / 2 - fontSize / 2
);
};
function ramp(context, a, b, width, height, mixer) {
for (let i = 0; i < width; i++) {
const t = i / (width - 1);
const rgb = mixer(a, b, t);
context.fillStyle = Color.parse(rgb).hex;
context.fillRect(i, 0, 1, height);
}
}
function mixSpectrums(specA, specB, t) {
if (specA.length !== specB.length)
throw new Error("spectrum length mismatch");
const mixedSpectrum = new Array(specA.length).fill(0);
for (let i = 0; i < specA.length; i++) {
const a = specA[i];
const b = specB[i];
const t0 = t;
const w1 = t0;
const w0 = 1 - t0;
const X = Math.pow(a, w0) * Math.pow(b, w1);
mixedSpectrum[i] = X;
}
return mixedSpectrum;
}
function sRGBToSpectrum(sRGB) {
// NOTE: There is no gamma-to-linear conversion here
// It produces odd results when I apply it. I wonder
// if the data tables has somehow already been treated to account for gamma?
const [r, g, b] = sRGB.map((x) => x / 0xff);
const spec = new Array(s_r.length);
// in 0..1 range
for (var i = 0; i < s_r.length; ++i) {
spec[i] = s_r[i] * r + s_g[i] * g + s_b[i] * b;
}
return spec;
}
function spectrumToSRGB(s) {
var x = 0,
y = 0,
z = 0;
for (var i = 0; i < illum.length; ++i) {
var intens = s[i] * illum[i];
x += x_bar[i] * intens;
y += y_bar[i] * intens;
z += z_bar[i] * intens;
}
// The transformation matrix is computed using the algorithm outlined on this page:
// http://brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
var r =
(3.2408302291321256 * x -
1.5373169035626748 * y -
0.4985892660203271 * z) /
y_illum;
var g =
(-0.9692293208748544 * x +
1.8759397940918867 * y +
0.04155444365280374 * z) /
y_illum;
var b =
(0.05564528732689767 * x -
0.20403272019862467 * y +
1.0572604592110555 * z) /
y_illum;
// NOTE: There is no linear-to-gamma conversion here
// It produces odd results when I apply it. I wonder
// if the data tables has somehow already been treated to account for gamma?
return [r, g, b].map((x) => Math.round(x * 0xff));
}
};
canvasSketch(sketch, settings);
////// Utils
// These are not used above but may be useful
// within the scope of Kubelka-Munk color mixing algorithms
function layer(R1, T1, R2, T2) {
const R = R1 + (T1 * T1 * R2) / (1 - R1 * R2);
const T = (T1 * T2) / (1 - R1 * R2);
return {
R,
T,
};
}
function reflectance_mix(ks) {
return 1.0 + ks - Math.sqrt(ks * ks + 2.0 * ks);
}
function saunderson_mix(ks) {
const K1 = 0.0031;
const K2 = 0.65;
const R = reflectance_mix(ks);
return ((1.0 - K1) * (1.0 - K2) * R) / (1.0 - K2 * R);
}
function R_to_KS(R) {
return Math.pow(1 - R, 2) / (2 * R);
}
function computeRT(K = 0.5, S = 0.5, h = 0.5) {
const K_S = K / S;
const a = 1 + K_S;
const b = Math.sqrt(a * a - 1);
const bsh = b * S * h;
const c = a * Math.sinh(bsh) + b * Math.cosh(bsh);
const R = Math.sinh(bsh / c);
const T = b / c;
return {
R,
T,
};
}
@warmist
Copy link

warmist commented Mar 20, 2023

I'm very interested in stuff like this. I have simple kubelka-munk implemented but i feel like mixbox and this attempt loose one dimension of data. Real pigments can be same yellow but mix differently.

On the other hand i guess having this be as simple as it is (i.e. you don't need two arrays of spectral data per pigment) makes it way more accessible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment