// The reference for understanding CIECAM02 is:// http://www.springer.com/cda/content/document/cda_downloaddocument/9781441961891-c1.pdfimport*asciebasefrom"ciebase";import*asciecam02from"ciecam02";import{merge}from"mout/object";var{rgb, workspace, illuminant}=ciebase,{cfs}=ciecam02,xyz=ciebase.xyz(workspace.sRGB,illuminant.D65);varviewingConditions={whitePoint: illuminant.D65,adaptingLuminance: 40,backgroundLuminance: 20,surroundType: "average",discounting: false};// By default, 7 correlates are returned when converting from XYZ to CAM.// For the purpose of this example, we will limit ourselves to the JCh correlates.// (J is the lightness, C the chroma and h the hue.)varcam=ciecam02.cam(viewingConditions,cfs("JCh")),gamut=ciecam02.gamut(xyz,cam),{min, max}=Math;functionhexToCam(hex){returncam.fromXyz(xyz.fromRgb(rgb.fromHex(hex)));}functioncamToHex(CAM){returnrgb.toHex(xyz.toRgb(cam.toXyz(CAM)));}functioncrop(v){returnmax(0,min(1,v));}varcamSand=hexToCam("e0cda9"),// {J: 77.82, C: 16.99, h: 81.01}camOrange=merge(camSand,{C: 90}),// {J: 77.82, C: 90.00, h: 81.01}[isInside,rgbOrange]=gamut.contains(camOrange);// [false, [1.09, 0.73, -0.7]]if(!isInside){// The gamut.limit function interpolates between an inside and an outside point// and return an inside point as close as possible to the boundary.// (The gamut is the set of CAM values that maps to valid RGB coordinates.)letcamOrange1=gamut.limit(camSand,camOrange),// {J: 77.82, C: 55.23, h: 81.01}// The alternative method is to simply crop the RGB coordinatescamOrange2=cam.fromXyz(xyz.fromRgb(rgbOrange.map(crop)));// {J: 74.43, C: 67.60, h: 81.30}console.log([camOrange1,camOrange2].map(camToHex));// #ffc447 #ffb900}else{console.log(rgb.toHex(rgbOrange));}
import{map}from"mout/object";var{hq}=ciecam02,ucs=ciecam02.ucs();functionucsLimit(camIn,camOut,prec=1e-3){// UCS is based on the JMh correlatesvar[ucsIn,ucsOut]=[camIn,camOut].map(v=>ucs.fromCam(cam.fillOut(cfs("JMh"),v)));while(ucs.distance(ucsIn,ucsOut)>prec){letucsMid=lerp(ucsIn,ucsOut,0.5),[isInside,]=gamut.contains(ucs.toCam(ucsMid));if(isInside){ucsIn=ucsMid;}else{ucsOut=ucsMid;}}returncam.fillOut(map(camIn,v=>true),ucs.toCam(ucsIn));}// The hue notation is a different writting of the hue quadrant,// of the form a(p?b)? where a and b are in {R, Y, G, B} (a ≠ b)// and p is in ]0, 100[. apb = b(100-p)a, ab = a50b.functionhue(N){returnhq.toHue(hq.fromNotation(N));}vartopChroma=max(...["f00","0f0","00f"].map(v=>hexToCam(v).C)),camRed={J: 60,h: hue("R")},camYellow={J: 90,h: hue("Y")},camGreen={J: 90,h: hue("G")},camBlue={J: 70,h: hue("B")};varhexCodes=[camRed,camYellow,camGreen,camBlue].map(function(CAM){CAM=merge(CAM,{C: topChroma+1});CAM=ucsLimit(gamut.spine(CAM.J/100),CAM);returncamToHex(CAM);});