Last active
April 27, 2023 06:04
Revisions
-
mattdesl revised this gist
Mar 19, 2023 . 1 changed file with 4 additions and 0 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -41,6 +41,10 @@ It's unclear to me why the demo here does not require gamma-to-linear conversion 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. -
mattdesl revised this gist
Mar 19, 2023 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -50,7 +50,7 @@ However, if some JavaScript code is used to convert the CIE data from the GitHub ### Credits - Thanks to [@OneDayOfCrypto](https://twitter.com/OneDayOfCrypto) for his help answering questions on Twitter - I went down this rabbit hole after seeing [their own implementation](https://twitter.com/OneDayOfCrypto/status/1636091072790003717) and learning about it - Most of this code is adapted from the paper, demo, and source of [Spectral Primary Decomposition for Rendering with sRGB Reflectance](https://graphics.geometrian.com/research/spectral-primaries.html) by Ian Mallett and Cem Yuksel - Also see Lars Wander's [recent repo](https://github.com/lwander/open-km) on dummy K-S coefficients for Kubelka-Munk mixing - Thanks to Scott Burns' website for related resources: http://scottburns.us/subtractive-color-mixture/ -
mattdesl revised this gist
Mar 19, 2023 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -54,4 +54,5 @@ However, if some JavaScript code is used to convert the CIE data from the GitHub - Most of this code is adapted from the paper, demo, and source of [Spectral Primary Decomposition for Rendering with sRGB Reflectance](https://graphics.geometrian.com/research/spectral-primaries.html) by Ian Mallett and Cem Yuksel - Also see Lars Wander's [recent repo](https://github.com/lwander/open-km) on dummy K-S coefficients for Kubelka-Munk mixing - Thanks to Scott Burns' website for related resources: http://scottburns.us/subtractive-color-mixture/ - Also see Jose's sketchbook for additional resources: http://www.joesfer.com/?p=244 - And thanks to ChatGPT 4, who was used as an educational companion to untangle some of the complexities and jargon in academic writing around Kubelka-Munk theory. -
mattdesl revised this gist
Mar 19, 2023 . 1 changed file with 5 additions and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -50,4 +50,8 @@ However, if some JavaScript code is used to convert the CIE data from the GitHub ### Credits - Thanks to [@OneDayOfCrypto](https://twitter.com/OneDayOfCrypto) for his help answering questions on Twitter. - Most of this code is adapted from the paper, demo, and source of [Spectral Primary Decomposition for Rendering with sRGB Reflectance](https://graphics.geometrian.com/research/spectral-primaries.html) by Ian Mallett and Cem Yuksel - Also see Lars Wander's [recent repo](https://github.com/lwander/open-km) on dummy K-S coefficients for Kubelka-Munk mixing - Thanks to Scott Burns' website for related resources: http://scottburns.us/subtractive-color-mixture/ - Also see Jose's sketchbook for additional resources: http://www.joesfer.com/?p=244 -
mattdesl revised this gist
Mar 19, 2023 . 1 changed file with 6 additions and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -45,4 +45,9 @@ https://github.com/geometrian/simple-spectral/tree/master/data 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 Thanks to onedayofcrypto for his help on twitter! -
mattdesl revised this gist
Mar 19, 2023 . 1 changed file with 21 additions and 0 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -6,6 +6,27 @@ 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: ```sh # 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:  -
mattdesl created this gist
Mar 19, 2023 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,27 @@ 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. Results, compared with interpolating `[R,G,B]` values directly:  It does not always work. Here is a failure case compared to mixbox:  ### 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 ### 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. 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,243 @@ 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, }; }