diff --git a/README.md b/README.md index 6366ff8..fbbba92 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,32 @@ # color-cam16 Perceptual Color Space and Language based on the CAM16 Uniform Color Space -## Color Space Background +## Defining and Manipulating Colors color-cam16 is a TypeScript library for defining and manipulating colors. Generate color sequences. Define color relationships. Determine color differences. Find analogous and complimentary colors. Ensure color contrast. Match real world colors. -The library uses a uniform color space based on human perception and human semantics. Colors can be warmer or cooler, lighter or darker, stronger or weaker. You can define a set of colors and their relationships in plain text. The library can parse a color description and calculate the relevant colors of a theme. +The library uses a uniform color space based on human perception and human semantics. You can define a set of colors by their relationships in plain text. Define colors as warmer, cooler, lighter, darker, stronger, weaker, analogous, triadic, or complimentary. Declare how many steps between colors to form color sequences. The library will parse the color description and generate a set of colors for a color theme. ## Color Space Transforms +- JuMuHu_to_color +- label_to_color + +![](https://readme-swatches.vercel.app/008000) +![](https://readme-swatches.vercel.app/0080FF) +![](https://readme-swatches.vercel.app/A080FF) ## Color Space Analysis +- delta_e +- find_closest_color +- find_strongest_color +- find_strongest_Mu +- cluster_colors ## Color Names - -## Color Language \ No newline at end of file +- label_to_name +- name_to_color +- find_closet_names + +## Color Language +- parse_colors +- rainbow_colors +- adjust_color diff --git a/src/analysis.ts b/src/analysis.ts index b6b0e2d..db47024 100644 --- a/src/analysis.ts +++ b/src/analysis.ts @@ -1,6 +1,6 @@ -import { JuMuHu, JuMuHuHex, CAM16u } from "./types" +import { JuMuHu, CAM16u } from "./types" import { c360, MuHu_to_ab } from "./utility" -import { cam16_ucs_to_hex, cam16_ucs_to_hex_ingamut } from "./transform_rev" +import { JuMuHu_to_hex, JuMuHu_to_color } from "./transform_rev" /** * Find the maximum Mu for a given Hu and Ju, using a recursive binary search @@ -12,12 +12,12 @@ import { cam16_ucs_to_hex, cam16_ucs_to_hex_ingamut } from "./transform_rev" * @param {number} n (default=0) - number of iterations so far * @returns number - the maximum Mu for the given Hu and Ju, to an accuracy of +/- 0.1 */ -export function find_largest_Mu(ucs: JuMuHu, min: number = 0, max: number = 100, n: number = 0): number { +export function find_strongest_Mu(ucs: JuMuHu, min: number = 0, max: number = 100, n: number = 0): number { if (max - min < 0.1) return min if ((n++ > 13) || (n < 0)) return 0 const half = (max + min) / 2 - const hex = cam16_ucs_to_hex({ Ju: ucs.Ju, Mu: half, Hu: ucs.Hu }) - return (hex == '') ? find_largest_Mu(ucs, min, half, n) : find_largest_Mu(ucs, half, max, n) + const hex = JuMuHu_to_hex({ Ju: ucs.Ju, Mu: half, Hu: ucs.Hu }) + return (hex == '') ? find_strongest_Mu(ucs, min, half, n) : find_strongest_Mu(ucs, half, max, n) } /** Find the approximate dMu/dJu gradient at highest chroma for a given Hu and Ju @@ -27,8 +27,8 @@ export function find_largest_Mu(ucs: JuMuHu, min: number = 0, max: number = 100, */ function gradient_dMu_dJu(Hu: number, Ju: number): number { const epsilon = 0.1 - const Mu1 = find_largest_Mu({ Hu, Ju, Mu: 0 }) - const Mu2 = find_largest_Mu({ Hu, Ju: Ju + epsilon, Mu: 0 }) + const Mu1 = find_strongest_Mu({ Hu, Ju, Mu: 0 }) + const Mu2 = find_strongest_Mu({ Hu, Ju: Ju + epsilon, Mu: 0 }) return (Mu2 - Mu1) / epsilon } @@ -87,19 +87,19 @@ export function find_strongest_Ju(Hu: number, min: number = 5, max: number = 95, } } -export function find_strongest_ucs(Hu: number): JuMuHuHex { +export function find_strongest_color(Hu: number): CAM16u { const Ju = find_strongest_Ju(Hu) - const Mu = find_largest_Mu({ Ju, Mu: 0, Hu }) - return cam16_ucs_to_hex_ingamut({ Ju, Mu, Hu }) + const Mu = find_strongest_Mu({ Ju, Mu: 0, Hu }) + return JuMuHu_to_color({ Ju, Mu, Hu }) } -export function cam16_ucs_rainbow({ Hu1 = 0, Hu2 = 360, steps = 13 }): JuMuHuHex[] { +export function rainbow_colors({ Hu1 = 0, Hu2 = 360, steps = 13 }): CAM16u[] { if (steps == 0) { - return [find_strongest_ucs(Hu1)] + return [find_strongest_color(Hu1)] } else { return Array(steps + 1).fill(0).map((_, i) => { const Hu = Hu1 + i * (Hu2 - Hu1) / steps - return find_strongest_ucs(Hu) + return find_strongest_color(Hu) }) } } @@ -114,7 +114,7 @@ export function cam16_ucs_rainbow({ Hu1 = 0, Hu2 = 360, steps = 13 }): JuMuHuHex * @param {JuMuHu} y - Object with CAM16UCS components { Ju: lightness, Mu: colorfulness, Hu: hue angle [0-360] } * @returns {number} - A value which represents the similarity of two colors (0=same, 25=very different) */ -export function deltaE_ucs(x: JuMuHu | CAM16u, y: JuMuHu | CAM16u): number { +export function delta_e(x: JuMuHu, y: JuMuHu): number { const xab = MuHu_to_ab(x) const yab = MuHu_to_ab(y) const da = xab.a - yab.a, db = xab.b - yab.b, dj = x.Ju - y.Ju; @@ -153,9 +153,9 @@ export function shortest_signed_distance(a: number, b: number): number { * @param {number} adj.steps=1 - Optional number of adjusted colors for each input color * @param {boolean} adj.contrast=false - Optional flag to indicate lightness change by contrast, ie if Cn.Ju>50 then negative Jub * @param {JuMuHu} color1,color2,color3, ... - Input color object(s) to adjust - * @returns {JuMuHuHex[]} - A flattened Array of objects holding the adjusted colors + * @returns {CAM16u[]} - A flattened Array of objects holding the adjusted colors */ -export function cam16_ucs_adjust({ Jua = 1, Jub = 0, Mua = 1, Mub = 0, Hua = 1, Hub = 0, steps = 1, contrast = false }, ...colors: JuMuHuHex[]): JuMuHuHex[] { +export function adjust_colors({ Jua = 1, Jub = 0, Mua = 1, Mub = 0, Hua = 1, Hub = 0, steps = 1, contrast = false }, ...colors: CAM16u[]): CAM16u[] { // for each of the colors in the arguments return colors.map(Cn => { // work out the starting and finishing colors @@ -164,7 +164,7 @@ export function cam16_ucs_adjust({ Jua = 1, Jub = 0, Mua = 1, Mub = 0, Hua = 1, const cf = { Ju: Cn.Ju * Jua + Jub * cc, Mu: Cn.Mu * Mua + Mub, Hu: Cn.Hu * Hua + Hub } // create array if steps > 1 if (steps == 1) { - return [cam16_ucs_to_hex_ingamut(cf)] + return [JuMuHu_to_color(cf)] } else { const dJu = (cf.Ju - cs.Ju) / (steps - 1) const dMu = (cf.Mu - cs.Mu) / (steps - 1) @@ -175,7 +175,7 @@ export function cam16_ucs_adjust({ Jua = 1, Jub = 0, Mua = 1, Mub = 0, Hua = 1, Mu: cs.Mu + i * dMu, Hu: cs.Hu + i * dHu } - return cam16_ucs_to_hex_ingamut(ct) + return JuMuHu_to_color(ct) }) } // flatten the array of arrays to a single array of colors diff --git a/src/language.js b/src/language.js index 7b42559..6977661 100644 --- a/src/language.js +++ b/src/language.js @@ -72,12 +72,12 @@ } */ var language = (function(){ -var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,7],$V1=[1,9],$V2=[1,10],$V3=[1,11],$V4=[1,8],$V5=[1,6],$V6=[1,15],$V7=[1,16],$V8=[1,17],$V9=[1,18],$Va=[1,19],$Vb=[1,20],$Vc=[1,21],$Vd=[1,22],$Ve=[1,23],$Vf=[1,24],$Vg=[1,25],$Vh=[1,26],$Vi=[1,32],$Vj=[1,6,24,25,26,27,28,29,30,31,32,34,36,38],$Vk=[2,12],$Vl=[1,6,22,24,25,26,27,28,29,30,31,32,34,36,38],$Vm=[1,37],$Vn=[1,6,22,24,25,26,27,28,29,30,31,32,34,36],$Vo=[1,58],$Vp=[2,15]; +var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,7],$V1=[1,9],$V2=[1,10],$V3=[1,11],$V4=[1,12],$V5=[1,13],$V6=[1,8],$V7=[1,6],$V8=[1,17],$V9=[1,18],$Va=[1,19],$Vb=[1,20],$Vc=[1,21],$Vd=[1,22],$Ve=[1,23],$Vf=[1,24],$Vg=[1,25],$Vh=[1,26],$Vi=[1,27],$Vj=[1,28],$Vk=[1,34],$Vl=[1,6,26,27,28,29,30,31,32,33,34,36,38,40],$Vm=[2,12],$Vn=[1,6,24,26,27,28,29,30,31,32,33,34,36,38,40],$Vo=[1,39],$Vp=[1,6,24,26,27,28,29,30,31,32,33,34,36,38],$Vq=[1,60],$Vr=[2,17]; var parser = {trace: function trace () { }, yy: {}, -symbols_: {"error":2,"Document":3,"StatementSequence":4,"Statement":5,".":6,"ColorSequence":7,"Color":8,"Assignment":9,"testcase":10,"Number":11,"Identifier":12,"is":13,"digits":14,"ColorHex":15,"hmj":16,"(":17,")":18,"Adjustment":19,"AdjSequence":20,"rainbow":21,"in":22,"steps":23,"lighter":24,"darker":25,"shaded":26,"contrast":27,"stronger":28,"weaker":29,"warmer":30,"cooler":31,"1st":32,"triadic":33,"2nd":34,"tetradic":35,"3rd":36,"by":37,"to":38,"$accept":0,"$end":1}, -terminals_: {2:"error",6:".",10:"testcase",12:"Identifier",13:"is",14:"digits",15:"ColorHex",16:"hmj",17:"(",18:")",21:"rainbow",22:"in",23:"steps",24:"lighter",25:"darker",26:"shaded",27:"contrast",28:"stronger",29:"weaker",30:"warmer",31:"cooler",32:"1st",33:"triadic",34:"2nd",35:"tetradic",36:"3rd",37:"by",38:"to"}, -productions_: [0,[3,1],[4,1],[4,3],[5,1],[5,1],[5,1],[5,2],[5,2],[5,2],[9,3],[11,1],[8,1],[8,1],[8,6],[8,2],[7,2],[7,2],[7,4],[20,4],[19,1],[19,1],[19,1],[19,1],[19,1],[19,1],[19,1],[19,1],[19,2],[19,2],[19,2],[19,2],[19,2],[19,3],[19,3],[19,3],[19,3],[19,3],[19,3],[19,3],[19,3],[19,3],[19,3],[19,3],[19,3],[19,3],[19,3],[19,2]], +symbols_: {"error":2,"Document":3,"StatementSequence":4,"Statement":5,".":6,"ColorSequence":7,"Color":8,"Assignment":9,"testcase":10,"Number":11,"Identifier":12,"is":13,"digits":14,"ColorHex":15,"ColorLabel3":16,"ColorLabel5":17,"hmj":18,"(":19,")":20,"Adjustment":21,"AdjSequence":22,"rainbow":23,"in":24,"steps":25,"lighter":26,"darker":27,"shaded":28,"contrast":29,"stronger":30,"weaker":31,"warmer":32,"cooler":33,"1st":34,"triadic":35,"2nd":36,"tetradic":37,"3rd":38,"by":39,"to":40,"$accept":0,"$end":1}, +terminals_: {2:"error",6:".",10:"testcase",12:"Identifier",13:"is",14:"digits",15:"ColorHex",16:"ColorLabel3",17:"ColorLabel5",18:"hmj",19:"(",20:")",23:"rainbow",24:"in",25:"steps",26:"lighter",27:"darker",28:"shaded",29:"contrast",30:"stronger",31:"weaker",32:"warmer",33:"cooler",34:"1st",35:"triadic",36:"2nd",37:"tetradic",38:"3rd",39:"by",40:"to"}, +productions_: [0,[3,1],[4,1],[4,3],[5,1],[5,1],[5,1],[5,2],[5,2],[5,2],[9,3],[11,1],[8,1],[8,1],[8,1],[8,1],[8,6],[8,2],[7,2],[7,2],[7,4],[22,4],[21,1],[21,1],[21,1],[21,1],[21,1],[21,1],[21,1],[21,1],[21,2],[21,2],[21,2],[21,2],[21,2],[21,3],[21,3],[21,3],[21,3],[21,3],[21,3],[21,3],[21,3],[21,3],[21,3],[21,3],[21,3],[21,3],[21,3],[21,2]], performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */, cam16) { /* this == yyval */ @@ -110,105 +110,105 @@ break; case 12: this.$ = identifiers[$$[$0]] break; -case 13: -this.$ = cam16.hex_to_cam16_ucs_ingamut($$[$0]); -break; -case 14: -this.$ = cam16.cam16_ucs_to_hex_ingamut({ Hu: $$[$0-3], Mu: $$[$0-2], Ju: $$[$0-1] }) -break; -case 15: -this.$ = cam16.cam16_ucs_adjust($$[$0], $$[$0-1])[0]; +case 13: case 14: case 15: +this.$ = cam16.label_to_color($$[$0]); break; case 16: -this.$ = cam16.cam16_ucs_adjust($$[$0], $$[$0-1]); +this.$ = cam16.JuMuHu_to_color({ Hu: $$[$0-3], Mu: $$[$0-2], Ju: $$[$0-1] }) break; case 17: -this.$ = cam16.cam16_ucs_adjust($$[$0], ...$$[$0-1]); +this.$ = cam16.adjust_colors($$[$0], $$[$0-1])[0]; break; case 18: -this.$ = cam16.cam16_ucs_rainbow({ steps: $$[$0-1] }); +this.$ = cam16.adjust_colors($$[$0], $$[$0-1]); break; case 19: -this.$ = {...$$[$0-3], steps: $$[$0-1] } +this.$ = cam16.adjust_colors($$[$0], ...$$[$0-1]); break; case 20: -this.$ = { Jub: +5 } +this.$ = cam16.rainbow_colors({ steps: $$[$0-1] }); break; case 21: -this.$ = { Jub: -5 } +this.$ = {...$$[$0-3], steps: $$[$0-1] } break; case 22: -this.$ = { Jua: 0.95, Mua: 0.95 } +this.$ = { Jub: +5 } break; case 23: -this.$ = { Jub: +75, contrast: true } +this.$ = { Jub: -5 } break; case 24: -this.$ = { Mub: +5 } +this.$ = { Jua: 0.95, Mua: 0.95 } break; case 25: -this.$ = { Mub: -5 } +this.$ = { Jub: +75, contrast: true } break; case 26: -this.$ = { Hub: +5 } +this.$ = { Mub: +5 } break; case 27: -this.$ = { Hub: -5 } +this.$ = { Mub: -5 } break; case 28: -this.$ = { Hub: +120 } +this.$ = { Hub: +5 } break; case 29: -this.$ = { Hub: +240 } +this.$ = { Hub: -5 } break; case 30: -this.$ = { Hub: +90 } +this.$ = { Hub: +120 } break; case 31: -this.$ = { Hub: +180 } +this.$ = { Hub: +240 } break; case 32: -this.$ = { Hub: +270 } +this.$ = { Hub: +90 } break; case 33: -this.$ = { Jub: +$$[$0] } +this.$ = { Hub: +180 } break; case 34: -this.$ = { Jub: -$$[$0] } +this.$ = { Hub: +270 } break; case 35: -this.$ = { Jua: 1-$$[$0]/100, Mua: 1-$$[$0]/100 } +this.$ = { Jub: +$$[$0] } break; case 36: -this.$ = { Jub: +$$[$0], contrast: true } +this.$ = { Jub: -$$[$0] } break; case 37: -this.$ = { Mub: +$$[$0] } +this.$ = { Jua: 1-$$[$0]/100, Mua: 1-$$[$0]/100 } break; case 38: -this.$ = { Mub: -$$[$0] } +this.$ = { Jub: +$$[$0], contrast: true } break; case 39: -this.$ = { Hub: +$$[$0] } +this.$ = { Mub: +$$[$0] } break; case 40: +this.$ = { Mub: -$$[$0] } +break; +case 41: +this.$ = { Hub: +$$[$0] } +break; +case 42: this.$ = { Hub: -$$[$0] } break; -case 41: case 42: +case 43: case 44: this.$ = { Jua: 0, Jub: +$$[$0] } break; -case 43: case 44: +case 45: case 46: this.$ = { Mua: 0, Mub: +$$[$0] } break; -case 45: case 46: +case 47: case 48: this.$ = { Hua: 0, Hub: +$$[$0] } break; -case 47: +case 49: this.$ = { Jua: 0, Jub: +$$[$0].Ju, Mua: 0, Mub: +$$[$0].Mu, Hua: 0, Hub: +$$[$0].Hu } break; } }, -table: [{3:1,4:2,5:3,7:4,8:5,9:6,10:$V0,12:$V1,15:$V2,16:$V3,21:$V4},{1:[3]},{1:[2,1],6:[1,12]},o($V5,[2,2]),o($V5,[2,4],{20:13,19:14,24:$V6,25:$V7,26:$V8,27:$V9,28:$Va,29:$Vb,30:$Vc,31:$Vd,32:$Ve,34:$Vf,36:$Vg,38:$Vh}),o($V5,[2,5],{20:27,19:28,24:$V6,25:$V7,26:$V8,27:$V9,28:$Va,29:$Vb,30:$Vc,31:$Vd,32:$Ve,34:$Vf,36:$Vg,38:$Vh}),o($V5,[2,6]),{8:30,9:31,11:29,12:$V1,14:$Vi,15:$V2,16:$V3},{22:[1,33]},o($Vj,$Vk,{13:[1,34]}),o($Vl,[2,13]),{17:[1,35]},{5:36,7:4,8:5,9:6,10:$V0,12:$V1,15:$V2,16:$V3,21:$V4},o($Vj,[2,17]),{22:$Vm},o($Vn,[2,20],{37:[1,38],38:[1,39]}),o($Vn,[2,21],{37:[1,40],38:[1,41]}),o($Vl,[2,22],{37:[1,42]}),o($Vl,[2,23],{37:[1,43]}),o($Vn,[2,24],{37:[1,44],38:[1,45]}),o($Vn,[2,25],{37:[1,46],38:[1,47]}),o($Vn,[2,26],{37:[1,48],38:[1,49]}),o($Vn,[2,27],{37:[1,50],38:[1,51]}),{33:[1,52],35:[1,53]},{33:[1,54],35:[1,55]},{35:[1,56]},{8:57,12:$Vo,15:$V2,16:$V3},o($Vj,[2,16]),o($Vj,$Vp,{22:$Vm}),o($V5,[2,7]),o($V5,[2,8],{19:59,24:$V6,25:$V7,26:$V8,27:$V9,28:$Va,29:$Vb,30:$Vc,31:$Vd,32:$Ve,34:$Vf,36:$Vg,38:$Vh}),o($V5,[2,9]),o([1,6,14,18,22,23,24,25,26,27,28,29,30,31,32,34,36,38],[2,11]),{11:60,14:$Vi},{8:61,12:$Vo,15:$V2,16:$V3},{11:62,14:$Vi},o($V5,[2,3]),{11:63,14:$Vi},{11:64,14:$Vi},{11:65,14:$Vi},{11:66,14:$Vi},{11:67,14:$Vi},{11:68,14:$Vi},{11:69,14:$Vi},{11:70,14:$Vi},{11:71,14:$Vi},{11:72,14:$Vi},{11:73,14:$Vi},{11:74,14:$Vi},{11:75,14:$Vi},{11:76,14:$Vi},{11:77,14:$Vi},o($Vl,[2,28]),o($Vl,[2,30]),o($Vl,[2,29]),o($Vl,[2,31]),o($Vl,[2,32]),o([1,6,22],[2,47],{19:59,24:$V6,25:$V7,26:$V8,27:$V9,28:$Va,29:$Vb,30:$Vc,31:$Vd,32:$Ve,34:$Vf,36:$Vg,38:$Vh}),o($Vl,$Vk),o($Vl,$Vp),{23:[1,78]},o($V5,[2,10],{19:59,24:$V6,25:$V7,26:$V8,27:$V9,28:$Va,29:$Vb,30:$Vc,31:$Vd,32:$Ve,34:$Vf,36:$Vg,38:$Vh}),{11:79,14:$Vi},{23:[1,80]},o($Vl,[2,33]),o($Vl,[2,41]),o($Vl,[2,34]),o($Vl,[2,42]),o($Vl,[2,35]),o($Vl,[2,36]),o($Vl,[2,37]),o($Vl,[2,43]),o($Vl,[2,38]),o($Vl,[2,44]),o($Vl,[2,39]),o($Vl,[2,45]),o($Vl,[2,40]),o($Vl,[2,46]),o($Vj,[2,18]),{11:81,14:$Vi},o($Vj,[2,19]),{18:[1,82]},o($Vl,[2,14])], +table: [{3:1,4:2,5:3,7:4,8:5,9:6,10:$V0,12:$V1,15:$V2,16:$V3,17:$V4,18:$V5,23:$V6},{1:[3]},{1:[2,1],6:[1,14]},o($V7,[2,2]),o($V7,[2,4],{22:15,21:16,26:$V8,27:$V9,28:$Va,29:$Vb,30:$Vc,31:$Vd,32:$Ve,33:$Vf,34:$Vg,36:$Vh,38:$Vi,40:$Vj}),o($V7,[2,5],{22:29,21:30,26:$V8,27:$V9,28:$Va,29:$Vb,30:$Vc,31:$Vd,32:$Ve,33:$Vf,34:$Vg,36:$Vh,38:$Vi,40:$Vj}),o($V7,[2,6]),{8:32,9:33,11:31,12:$V1,14:$Vk,15:$V2,16:$V3,17:$V4,18:$V5},{24:[1,35]},o($Vl,$Vm,{13:[1,36]}),o($Vn,[2,13]),o($Vn,[2,14]),o($Vn,[2,15]),{19:[1,37]},{5:38,7:4,8:5,9:6,10:$V0,12:$V1,15:$V2,16:$V3,17:$V4,18:$V5,23:$V6},o($Vl,[2,19]),{24:$Vo},o($Vp,[2,22],{39:[1,40],40:[1,41]}),o($Vp,[2,23],{39:[1,42],40:[1,43]}),o($Vn,[2,24],{39:[1,44]}),o($Vn,[2,25],{39:[1,45]}),o($Vp,[2,26],{39:[1,46],40:[1,47]}),o($Vp,[2,27],{39:[1,48],40:[1,49]}),o($Vp,[2,28],{39:[1,50],40:[1,51]}),o($Vp,[2,29],{39:[1,52],40:[1,53]}),{35:[1,54],37:[1,55]},{35:[1,56],37:[1,57]},{37:[1,58]},{8:59,12:$Vq,15:$V2,16:$V3,17:$V4,18:$V5},o($Vl,[2,18]),o($Vl,$Vr,{24:$Vo}),o($V7,[2,7]),o($V7,[2,8],{21:61,26:$V8,27:$V9,28:$Va,29:$Vb,30:$Vc,31:$Vd,32:$Ve,33:$Vf,34:$Vg,36:$Vh,38:$Vi,40:$Vj}),o($V7,[2,9]),o([1,6,14,20,24,25,26,27,28,29,30,31,32,33,34,36,38,40],[2,11]),{11:62,14:$Vk},{8:63,12:$Vq,15:$V2,16:$V3,17:$V4,18:$V5},{11:64,14:$Vk},o($V7,[2,3]),{11:65,14:$Vk},{11:66,14:$Vk},{11:67,14:$Vk},{11:68,14:$Vk},{11:69,14:$Vk},{11:70,14:$Vk},{11:71,14:$Vk},{11:72,14:$Vk},{11:73,14:$Vk},{11:74,14:$Vk},{11:75,14:$Vk},{11:76,14:$Vk},{11:77,14:$Vk},{11:78,14:$Vk},{11:79,14:$Vk},o($Vn,[2,30]),o($Vn,[2,32]),o($Vn,[2,31]),o($Vn,[2,33]),o($Vn,[2,34]),o([1,6,24],[2,49],{21:61,26:$V8,27:$V9,28:$Va,29:$Vb,30:$Vc,31:$Vd,32:$Ve,33:$Vf,34:$Vg,36:$Vh,38:$Vi,40:$Vj}),o($Vn,$Vm),o($Vn,$Vr),{25:[1,80]},o($V7,[2,10],{21:61,26:$V8,27:$V9,28:$Va,29:$Vb,30:$Vc,31:$Vd,32:$Ve,33:$Vf,34:$Vg,36:$Vh,38:$Vi,40:$Vj}),{11:81,14:$Vk},{25:[1,82]},o($Vn,[2,35]),o($Vn,[2,43]),o($Vn,[2,36]),o($Vn,[2,44]),o($Vn,[2,37]),o($Vn,[2,38]),o($Vn,[2,39]),o($Vn,[2,45]),o($Vn,[2,40]),o($Vn,[2,46]),o($Vn,[2,41]),o($Vn,[2,47]),o($Vn,[2,42]),o($Vn,[2,48]),o($Vl,[2,20]),{11:83,14:$Vk},o($Vl,[2,21]),{20:[1,84]},o($Vn,[2,16])], defaultActions: {}, parseError: function parseError (str, hash) { if (hash.recoverable) { @@ -690,68 +690,72 @@ case 1:/* skip comma */ break; case 2:return 10; break; -case 3:return 24; +case 3:return 26; break; -case 4:return 25; +case 4:return 27; break; -case 5:return 26; +case 5:return 28; break; -case 6:return 27; +case 6:return 29; break; -case 7:return 28; +case 7:return 30; break; -case 8:return 29; +case 8:return 31; break; -case 9:return 30; +case 9:return 32; break; -case 10:return 31; +case 10:return 33; break; case 11:return 'complimented'; break; -case 12:return 33; +case 12:return 35; break; -case 13:return 35; +case 13:return 37; break; -case 14:return 21; +case 14:return 23; break; -case 15:return 32; +case 15:return 34; break; -case 16:return 34; +case 16:return 36; break; -case 17:return 36; +case 17:return 38; break; -case 18:return 16; +case 18:return 18; break; -case 19:return 37; +case 19:return 39; break; -case 20:return 38; +case 20:return 40; break; case 21:return 13; break; case 22:return 'and'; break; -case 23:return 22; +case 23:return 24; break; -case 24:return 23; +case 24:return 25; break; -case 25:return 17; +case 25:return 19; break; -case 26:return 18; +case 26:return 20; break; case 27:return 6; break; case 28:return ','; break; -case 29:return 14; +case 29:return 16; +break; +case 30:return 17; +break; +case 31:return 15; break; -case 30:return 15; +case 32:return 14; break; -case 31:return 12; +case 33:return 12; break; } }, -rules: [/^(?:\s+)/,/^(?:,)/,/^(?:testcase)/,/^(?:lighter)/,/^(?:darker)/,/^(?:shaded)/,/^(?:contrast)/,/^(?:stronger)/,/^(?:weaker)/,/^(?:warmer)/,/^(?:cooler)/,/^(?:complimented)/,/^(?:triadic)/,/^(?:tetradic)/,/^(?:rainbow)/,/^(?:1st)/,/^(?:2nd)/,/^(?:3rd)/,/^(?:hmj)/,/^(?:by)/,/^(?:to)/,/^(?:is)/,/^(?:and)/,/^(?:in)/,/^(?:steps?)/,/^(?:\()/,/^(?:\))/,/^(?:\.)/,/^(?:,)/,/^(?:[+-]?[0-9]+(\.[0-9]+)?)/,/^(?:#[0-9a-fA-F]{6})/,/^(?:[a-zA-Z][[a-zA-Z0-9]*)/], -conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":true}} +rules: [/^(?:\s+)/,/^(?:,)/,/^(?:testcase)/,/^(?:lighter)/,/^(?:darker)/,/^(?:shaded)/,/^(?:contrast)/,/^(?:stronger)/,/^(?:weaker)/,/^(?:warmer)/,/^(?:cooler)/,/^(?:complimented)/,/^(?:triadic)/,/^(?:tetradic)/,/^(?:rainbow)/,/^(?:1st)/,/^(?:2nd)/,/^(?:3rd)/,/^(?:hmj)/,/^(?:by)/,/^(?:to)/,/^(?:is)/,/^(?:and)/,/^(?:in)/,/^(?:steps?)/,/^(?:\()/,/^(?:\))/,/^(?:\.)/,/^(?:,)/,/^(?:[0-9][A-Za-z][0-9])/,/^(?:[0-9]{2}[A-Za-z][0-9]{2})/,/^(?:#[0-9a-fA-F]{6})/,/^(?:[+-]?[0-9]+(\.[0-9]+)?)/,/^(?:[a-zA-Z][[a-zA-Z0-9]*)/], +conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],"inclusive":true}} }); return lexer; })(); diff --git a/src/language.json b/src/language.json index 52e4dd5..b0c5bc5 100644 --- a/src/language.json +++ b/src/language.json @@ -36,8 +36,10 @@ ["\\)", "return ')';"], ["\\.", "return '.';"], [",", "return ',';"], - ["[+-]?[0-9]+(\\.[0-9]+)?", "return 'digits';"], + ["[0-9][A-Za-z][0-9]", "return 'ColorLabel3';"], + ["[0-9]{2}[A-Za-z][0-9]{2}", "return 'ColorLabel5';"], ["#[0-9a-fA-F]{6}", "return 'ColorHex';"], + ["[+-]?[0-9]+(\\.[0-9]+)?", "return 'digits';"], ["[a-zA-Z][[a-zA-Z0-9]*", "return 'Identifier';"] ] }, @@ -68,14 +70,16 @@ ], "Color": [ [ "Identifier", "$$ = identifiers[$1]" ], - [ "ColorHex", "$$ = cam16.hex_to_cam16_ucs_ingamut($1);" ], - [ "hmj ( Number Number Number )", "$$ = cam16.cam16_ucs_to_hex_ingamut({ Hu: $3, Mu: $4, Ju: $5 })" ], - [ "Color Adjustment","$$ = cam16.cam16_ucs_adjust($2, $1)[0];" ] + [ "ColorHex", "$$ = cam16.label_to_color($1);" ], + [ "ColorLabel3", "$$ = cam16.label_to_color($1);" ], + [ "ColorLabel5", "$$ = cam16.label_to_color($1);" ], + [ "hmj ( Number Number Number )", "$$ = cam16.JuMuHu_to_color({ Hu: $3, Mu: $4, Ju: $5 })" ], + [ "Color Adjustment","$$ = cam16.adjust_colors($2, $1)[0];" ] ], "ColorSequence" :[ - [ "Color AdjSequence","$$ = cam16.cam16_ucs_adjust($2, $1);" ], - [ "ColorSequence AdjSequence","$$ = cam16.cam16_ucs_adjust($2, ...$1);" ], - [ "rainbow in Number steps","$$ = cam16.cam16_ucs_rainbow({ steps: $3 });" ] + [ "Color AdjSequence","$$ = cam16.adjust_colors($2, $1);" ], + [ "ColorSequence AdjSequence","$$ = cam16.adjust_colors($2, ...$1);" ], + [ "rainbow in Number steps","$$ = cam16.rainbow_colors({ steps: $3 });" ] ], "AdjSequence": [ ["Adjustment in Number steps", "$$ = {...$1, steps: $3 }"] diff --git a/src/parser.ts b/src/parser.ts index 6f05518..06bde43 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,13 +1,13 @@ -import { JuMuHuHex } from './types' -import { cam16_ucs_adjust } from './analysis' -import { cam16_ucs_to_hex_ingamut } from './transform_rev' -import { hex_to_cam16_ucs_ingamut } from './transform_fwd' +import { CAM16u } from './types' +import { adjust_colors, rainbow_colors } from './analysis' +import { JuMuHu_to_color, label_to_color } from './transform_rev' // functions passed to language.js const cam16 = { - cam16_ucs_adjust, - cam16_ucs_to_hex_ingamut, - hex_to_cam16_ucs_ingamut + JuMuHu_to_color, + label_to_color, + adjust_colors, + rainbow_colors, } ///////////////////////////////////////////////////////////////////////////////// @@ -24,7 +24,7 @@ const cam16 = { // OPTION 2 Buildtime generation of parse with command 'jison language.json' import { parse as jison_parse } from './language' -export function parse_colors(text: string): JuMuHuHex[] { +export function parse_colors(text: string): CAM16u[] { return jison_parse(text, cam16) } diff --git a/src/transform_fwd.ts b/src/transform_fwd.ts index a617872..ccd59d1 100644 --- a/src/transform_fwd.ts +++ b/src/transform_fwd.ts @@ -1,4 +1,4 @@ -import { sRGB, XYZ, CAM16, CAM16u, JMh, JuMuHuHex } from './types' +import { sRGB, XYZ, JMhQCs, JMh, CAM16u, JuMuHu } from './types' import { M16, A_w, D_RGB, N_bb, c, z, F_L_4, N_c, N_cb, n } from './utility' import { degrees, radians, is_hex_code, gamma_inverse, elem_mul, adapt } from './utility' import { h_to_Hu, JuMuHu_to_label } from './utility'; @@ -40,9 +40,9 @@ export function hex_to_srgb(hex: string): sRGB { /** * Convert XYZ value to CAM16 components * @param XYZ - Array[3 x 1] where XYZ tristimulus values (under standard illuminant D65, normalized so that the luminance of the display white is Yw = 100Y) -* @returns CAM16 - Object with CAM16 components { J: lightness[0-100] , Q: brightness[0-198], C: chroma[0-113], M: colorfulness[0-100], s: saturation[0-86], h: hue angle [0-360] } +* @returns JMhQCs - Object with CAM16 components { J: lightness[0-100] , Q: brightness[0-198], C: chroma[0-113], M: colorfulness[0-100], s: saturation[0-86], h: hue angle [0-360] } */ - export function xyz_to_cam16(XYZ: XYZ): CAM16 { + export function xyz_to_JMhQCs(XYZ: XYZ): JMhQCs { const [R_a, G_a, B_a] = elem_mul(M16(XYZ), D_RGB).map(adapt), a = R_a + (-12 * G_a + B_a) / 11, // redness-greenness @@ -83,11 +83,10 @@ export function JMH_to_JuMuHu({ J , M, h }:JMh) { * @param {XYZ} XYZ - Array[3 x 1] where XYZ tristimulus values (under standard illuminant D65, normalized so that the luminance of the display white is Yw = 100Y) * @returns - Object with CAM16 components Ju: lightness , Mu: colorfulness, a: magenta-teal, b: yellow-blue } */ -export function xyz_to_cam16_ucs(XYZ: XYZ): CAM16u { - const { J, Q, C, M, s, h } = xyz_to_cam16(XYZ) +export function xyz_to_JuMuHu(XYZ: XYZ): JuMuHu { + const { J, Q, C, M, s, h } = xyz_to_JMhQCs(XYZ) const { Ju, Mu, Hu } = JMH_to_JuMuHu({ J, M, h }) return { - J, Q, C, M, s, h, Ju, Mu, Hu }; } @@ -95,12 +94,11 @@ export function xyz_to_cam16_ucs(XYZ: XYZ): CAM16u { /** * Convert CSS RGB Hex value to CAM16 components * @param {string} hex - String RGB hex value #RRGGBB eg '#800000' = Maroon - * @returns CAM16 - Object with CAM16 components { J: lightness , Q: brightness, C: chroma, M: colorfulness, s: saturation, h: hue angle [0-360] } + * @returns JMhQCs - Object with CAM16 components { J: lightness , Q: brightness, C: chroma, M: colorfulness, s: saturation, h: hue angle [0-360] } */ -export function hex_to_cam16(hex: string): CAM16 { +export function hex_to_JMhQCs(hex: string): JMhQCs { const XYZ = srgb_to_xyz(hex_to_srgb(hex)) - return xyz_to_cam16(XYZ) - + return xyz_to_JMhQCs(XYZ) } @@ -109,9 +107,9 @@ export function hex_to_cam16(hex: string): CAM16 { * @param {string} hex - String RGB hex value #RRGGBB eg '#800000' = Maroon * @returns - Object with CAM16 UCS components { Ju: lightness , Mu: colorfulness, a: magenta-teal, b: yellow-blue } */ -export function hex_to_cam16_ucs(hex: string): CAM16u { +export function hex_to_JuMuHu(hex: string): JuMuHu { const XYZ = srgb_to_xyz(hex_to_srgb(hex)) - return xyz_to_cam16_ucs(XYZ) + return xyz_to_JuMuHu(XYZ) } /** @@ -119,8 +117,8 @@ export function hex_to_cam16_ucs(hex: string): CAM16u { * @param {string} hex - String RGB hex value #RRGGBB eg '#800000' = Maroon * @returns - Object with CAM16 UCS components and original hex { Ju: lightness , Mu: colorfulness, Hu: hue angle, hex: sRGB color, inGamut: true } */ -export function hex_to_cam16_ucs_ingamut(hex: string): JuMuHuHex { - const { Ju, Mu, Hu } = hex_to_cam16_ucs(hex) +export function hex_to_color(hex: string): CAM16u { + const { Ju, Mu, Hu } = hex_to_JuMuHu(hex) return { Ju, Mu, Hu, hex, inGamut: true, diff --git a/src/transform_rev.ts b/src/transform_rev.ts index f9dd2a0..6110c84 100644 --- a/src/transform_rev.ts +++ b/src/transform_rev.ts @@ -1,9 +1,10 @@ -import { sRGB, XYZ, JMh, JuMuHu, JuMuHuHex } from './types' +import { sRGB, XYZ, JMh, JuMuHu, CAM16u } from './types' import { JMH_whitepoint, XYZ_blackpoint, XYZ_outOfGamut, EPSILONXYZ, EPSILONRGB } from './constants' -import { M16_inv, D_RGB_inv, XYZ_w, A_w, N_bb, c, z, F_L_4, N_c, N_cb, n } from './utility' +import { M16_inv, D_RGB_inv, XYZ_w, A_w, N_bb, c, z, F_L_4, N_c, N_cb, n, label_to_JuMuHu, is_hex_code, is_ucs_label3, is_ucs_label5 } from './utility' import { radians, elem_mul, unadapt, gamma } from './utility' import { Hu_to_h, JuMuHu_to_label } from './utility'; -import { find_largest_Mu } from './analysis'; +import { find_strongest_Mu } from './analysis'; +import { hex_to_color } from './transform_fwd' //////////////////////////////////////////////////////////////////////////////////// // Inverse Path CAM16 components to RGB hex value // @@ -14,7 +15,7 @@ import { find_largest_Mu } from './analysis'; * @param JMh Object with CAM16 components { J: lightness[0-100], M: colorfulness[0-52], h: hue angle [0-360] } * @returns - Array[3 x 1] where XYZ tristimulus values (under standard illuminant D65, normalized so that the luminance of the display white is Yw = 100Y) */ -export function cam16_to_xyz({ J, M, h }: JMh): XYZ { +export function JMh_to_xyz({ J, M, h }: JMh): XYZ { const h_rad = radians(h), cos_h = Math.cos(h_rad), @@ -48,7 +49,7 @@ export function cam16_to_xyz({ J, M, h }: JMh): XYZ { * @param JuMuHu - Object with CAM16UCS components { Ju: lightness, Mu: colorfulness, Hu: hue angle [0-360] } * @returns Object with CAM16 components { J: lightness[0-100], M: colorfulness[0-52], h: hue angle [0-360] } */ -export function cam16_ucs_to_JMh({ Ju, Mu, Hu }: JuMuHu): JMh { +export function JuMuHu_to_JMh({ Ju, Mu, Hu }: JuMuHu): JMh { const M = (Math.exp(Mu * 0.0228) - 1) / 0.0228; const J = Ju / (1.7 - 0.007 * Ju); const h = Hu_to_h(Hu) @@ -61,8 +62,8 @@ export function cam16_ucs_to_JMh({ Ju, Mu, Hu }: JuMuHu): JMh { * @param JuMuHu - Object with CAM16UCS components { Ju: lightness, Mu: colorfulness, Hu: hue angle [0-360] } * @returns XYZ - Array[3 x 1] where XYZ tristimulus values (under standard illuminant D65, normalized so that the luminance of the display white is Yw = 100Y) */ -export function cam16_ucs_to_xyz({ Ju, Mu, Hu }: JuMuHu): XYZ { - return cam16_to_xyz(cam16_ucs_to_JMh({ Ju, Mu, Hu })); +export function JuMuHu_to_xyz({ Ju, Mu, Hu }: JuMuHu): XYZ { + return JMh_to_xyz(JuMuHu_to_JMh({ Ju, Mu, Hu })); } @@ -108,40 +109,55 @@ export function srgb_to_hex([R, G, B]: sRGB): string { * @param JuMuHu - Object with CAM16UCS components { Ju: lightness, Mu: colorfulness, h: hue angle [0-360] } * @returns - String RGB hex value #RRGGBB eg '#800000' = Maroon, Returns '' when out of Gamut. */ -export function cam16_ucs_to_hex(cam16u: JuMuHu): string { - const xyz1 = cam16_ucs_to_xyz(cam16u) +export function JuMuHu_to_hex(cam16u: JuMuHu): string { + const xyz1 = JuMuHu_to_xyz(cam16u) const rgb1 = xyz_to_srgb(xyz1) return srgb_to_hex(rgb1) } +/** + * Convert CAM16 Space components to hex color + * @param JHh - Object with CAM16UCS components { J: lightness, M: colorfulness, h: hue angle [0-360] } + * @returns - String RGB hex value #RRGGBB eg '#800000' = Maroon + */ + export function JMh_to_hex(cam16: JMh): string { + return srgb_to_hex(xyz_to_srgb(JMh_to_xyz(cam16))) +} + + /** Convert CAM16 Uniform Color Space components to hex color, label and ingamut flag - * @param {JuMuHu} cam16u - Object with CAM16UCS components { Ju: lightness, Mu: colorfulness, h: hue angle [0-360] } - * @returns JuMuHuHex - Object with hex color, inGamut flag, labels and cam16u details + * @param {JuMuHu} JuMuHu - Object with CAM16UCS components { Ju: lightness, Mu: colorfulness, h: hue angle [0-360] } + * @returns CAM16u - Object with hex color, inGamut flag, labels and cam16u details */ -export function cam16_ucs_to_hex_ingamut(cam16u: JuMuHu): JuMuHuHex { +export function JuMuHu_to_color(JuMuHu: JuMuHu): CAM16u { const result = { inGamut: true, - hex: cam16_ucs_to_hex(cam16u), - hexMu: cam16u.Mu, + hex: JuMuHu_to_hex(JuMuHu), + hexMu: JuMuHu.Mu, hexLabel1: '', hexLabel2: '', - ...cam16u + ...JuMuHu } if (result.hex == '') { result.inGamut = false - result.hexMu = find_largest_Mu(cam16u) - result.hex = cam16_ucs_to_hex({ Ju: result.Ju, Mu: result.hexMu, Hu: result.Hu }) + result.hexMu = find_strongest_Mu(JuMuHu) + result.hex = JuMuHu_to_hex({ Ju: result.Ju, Mu: result.hexMu, Hu: result.Hu }) } result.hexLabel1 = JuMuHu_to_label({ Ju: result.Ju, Mu: result.hexMu, Hu: result.Hu }) result.hexLabel2 = JuMuHu_to_label({ Ju: result.Ju, Mu: result.hexMu, Hu: result.Hu }, true) return result } -/** - * Convert CAM16 Space components to hex color - * @param JHh - Object with CAM16UCS components { J: lightness, M: colorfulness, h: hue angle [0-360] } - * @returns - String RGB hex value #RRGGBB eg '#800000' = Maroon + +/** Convert color label (either hex or ucs) to the structured color + * @param {string} s - hex or ucs label (3 or 5 char) representing the color + * @returns {CAM16u} - Object with hex color, inGamut flag, labels and cam16u details */ -export function cam16_to_hex(cam16: JMh): string { - return srgb_to_hex(xyz_to_srgb(cam16_to_xyz(cam16))) + export function label_to_color(s: string): CAM16u { + if (is_hex_code(s)) return hex_to_color(s) + if (is_ucs_label3(s)) return JuMuHu_to_color(label_to_JuMuHu(s)) + if (is_ucs_label5(s)) return JuMuHu_to_color(label_to_JuMuHu(s)) + + throw new Error('Bad Input: Must be of form "#DEFACE", "5Z8" or "50Z80"') + } diff --git a/src/types.d.ts b/src/types.d.ts index 1d7b0c8..1fe4db2 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -38,36 +38,25 @@ // Utility Types for different color spaces export type sRGB = [number, number, number] // where R, G, B range from [0 to 1] export type XYZ = [number, number, number] // tristimulus values (under standard illuminant D65, normalized so that the luminance of the display white is Yw = 100Y) -export type CAM16 = { // Object with CAM16 components +export type JMh = { // Object with CAM16 components J:number, // J: lightness[0-100] - Q:number, // Q: brightness[0-198] - C:number, // C: chroma[0-113] M:number, // M: colorfulness[0-100] - s:number, // s: saturation[0-86] h:number // h: hue angle [0-360] } -export type CAM16u = { // CAM16UCS - Object with CAM16 Universal Color Space components +export type JMhQCs = { // Object with CAM16 components J:number, // J: lightness[0-100] + M:number, // M: colorfulness[0-100] + h:number // h: hue angle [0-360] Q:number, // Q: brightness[0-198] C:number, // C: chroma[0-113] - M:number, // M: colorfulness[0-100] s:number, // s: saturation[0-86] - h:number, // h: hue angle [0-360] - Ju:number, // Ju: uniform lightness[0-100] - Mu:number, // Mu: uniform colorfulness[0-100] - Hu:number // Hu: uniform hue angle [0-360] -} -export type JMh = { // Object with CAM16 components - J:number, // J: lightness[0-100] - M:number, // M: colorfulness[0-100] - h:number // h: hue angle [0-360] } export type JuMuHu = { // Object with CAM16 components Ju:number, // Ju: uniform lightness[0-100] Mu:number, // Mu: uniform colorfulness[0-100] Hu:number // Hu: uniform hue angle [0-360] } -export type JuMuHuHex = { // Object with CAM16 components +export type CAM16u = { // Object with CAM16 UCS components Ju:number, // Ju: uniform lightness[0-100] Mu:number, // Mu: uniform colorfulness[0-100] Hu:number, // Hu: uniform hue angle [0-360] diff --git a/src/utility.ts b/src/utility.ts index 3b92dd6..f6e3a17 100644 --- a/src/utility.ts +++ b/src/utility.ts @@ -1,4 +1,4 @@ -import { XYZ, sRGB, JuMuHu, CAM16u } from "./types" +import { XYZ, sRGB, JuMuHu } from "./types" import { DEGREES, RADIANS, standard_whitepoints, iheH } from "./constants" // Utility Functions @@ -12,6 +12,8 @@ export const degrees = (angle:number) => mod(angle * DEGREES, 360) export const radians = (angle:number) => mod(angle, 360) * RADIANS export const elem_mul = ([a,b,c]:XYZ,[x,y,z]:XYZ): XYZ => [a*x, b*y, c*z] export const is_hex_code = (s:string) => /^#?[0-9a-fA-F]{6}$/.test(s) +export const is_ucs_label3 = (s:string) => /^[0-9][a-zA-Z][0-9]$/.test(s) +export const is_ucs_label5 = (s:string) => /^[0-9]{2}[a-zA-Z][0-9]{2}$/.test(s) export const gamma = (x:number) => (x <= 0) ? 0 : (x > 0.0031308) ? (1.055 * Math.pow(x, 0.4166666666666667) - 0.055) @@ -122,22 +124,61 @@ export function Hu_to_label(Hu:number): string { } /** - * Convert from uniform space to hex label [A-Za-z] - * @param {number} Hu - Uniform space hue has a range [0..360] where 0=Red, 90=Yellow, 180=Green, 270=Blue, 360=Red. (note 360 range rather than 400 range of paper) - * @returns string - hue label as a single character string. warm colors capitals (D=magenta, J=red, Y=yellow), cool colors lower (a=lemon, f=green, n=cyan, y=blue) + * Convert from hue label [A-Za-z] to uniform space hue Hu + * @param {string} s - hue label as a single character string. warm colors capitals (A=purple, I=red, Y=yellow), cool colors lower (a=lemon, g=green, n=cyan, y=blue) + * @returns number - uniform space hue has a range [0..360] where 0=Red, 90=Yellow, 180=Green, 270=Blue, 360=Red. (note 360 range rather than 400 range of paper) */ +export function label_to_Hu(s:string): number { + const code = s.charCodeAt(0) // note I(73)=0, i(105)=180 + return (64 { @@ -11,37 +11,37 @@ describe('Color Analysis Functions', () => { }); - test('deltaE_ucs measures small difference in round trip ucs to hex to ucs conversion', () => { + test('delta_e measures small difference in round trip ucs to hex to ucs conversion', () => { deltaE_testdata.forEach( ({ ucs1, dE }) => { - const hex = cam16_ucs_to_hex(ucs1) - const ucs2 = hex_to_cam16_ucs(hex) - const deltaE2 = deltaE_ucs(ucs1, ucs2) + const hex = JuMuHu_to_hex(ucs1) + const ucs2 = hex_to_JuMuHu(hex) + const deltaE2 = delta_e(ucs1, ucs2) expect(deltaE2).toBeLessThan(dE) }) }); - test('deltaE_ucs measures a small difference between two adjacent reds with large hue difference', () => { + test('delta_e measures a small difference between two adjacent reds with large hue difference', () => { const ucs1 = { Ju: 65, Mu: 42, Hu: 1 } // warmer red const ucs2 = { Ju: 65, Mu: 42, Hu: 358 } // cooler red - const deltaE2 = deltaE_ucs(ucs1, ucs2) + const deltaE2 = delta_e(ucs1, ucs2) expect(deltaE2).toBeLessThan(3) }); - test('deltaE_ucs measures a large difference across the color wheel', () => { + test('delta_e measures a large difference across the color wheel', () => { const ucs1 = { Ju: 30, Mu: 40, Hu: 291 } // deep blue const ucs2 = { Ju: 90, Mu: 36, Hu: 111 } // bright yellow - const deltaE2 = deltaE_ucs(ucs1, ucs2) + const deltaE2 = delta_e(ucs1, ucs2) expect(deltaE2).toBeGreaterThan(25) }); - test('deltaE_ucs measures a large difference between black and white', () => { + test('delta_e measures a large difference between black and white', () => { const ucs1 = { Ju: 0, Mu: 0, Hu: 0 } // deep blue const ucs2 = { Ju: 100, Mu: 0, Hu: 180 } // bright yellow - const deltaE2 = deltaE_ucs(ucs1, ucs2) + const deltaE2 = delta_e(ucs1, ucs2) expect(deltaE2).toBeGreaterThan(25) }); - test('find_largest_Mu to return highest Mu a range of colors', () => { + test('find_strongest_Mu to return highest Mu a range of colors', () => { const search_largest_Mu_data = [ { ucs: { Ju: 65, Mu: 10, Hu: 290}, Mu: 31.152 }, { ucs: { Ju: 85, Mu: 10, Hu: 156}, Mu: 49.511 }, @@ -50,13 +50,13 @@ describe('Color Analysis Functions', () => { { ucs: { Ju: 0, Mu: 10, Hu: 156}, Mu: 0.488 }, ] search_largest_Mu_data.forEach( ({ ucs, Mu }) => { - expect(find_largest_Mu(ucs)).toBeCloseTo(Mu) + expect(find_strongest_Mu(ucs)).toBeCloseTo(Mu) }) }); - test('find_largest_Mu doesnt iterrate past 13', () => { + test('find_strongest_Mu doesnt iterrate past 13', () => { const ucs = { Ju: 85, Mu: 10, Hu: 156} - expect(find_largest_Mu(ucs, 0, 10000)).toEqual(0) + expect(find_strongest_Mu(ucs, 0, 10000)).toEqual(0) }); test('find_strongest_Ju to return ideal Ju for the highest chromatic color at a given Hu', () => { @@ -74,7 +74,7 @@ describe('Color Analysis Functions', () => { }) }); - test('find_strongest_ucs to return ucs for the highest chromatic color at a given Hu', () => { + test('find_strongest_color to return ucs for the highest chromatic color at a given Hu', () => { const search_strongest_ucs_data = [ { Hu: 0, hex: '#ff545a' }, { Hu: 56, hex: '#ffac4b' }, @@ -85,7 +85,7 @@ describe('Color Analysis Functions', () => { { Hu: 307, hex: '#9c2cff' }, ] search_strongest_ucs_data.forEach( ({ Hu, hex }) => { - expect(find_strongest_ucs(Hu).hex).toEqual(hex) + expect(find_strongest_color(Hu).hex).toEqual(hex) }) }); @@ -113,7 +113,7 @@ describe('Color Analysis Functions', () => { }) }); - test('cam16_ucs_adjust a color based on linear transform of components ', () => { + test('adjust_colors a color based on linear transform of components ', () => { const signed_distance_data = [ { adj: { Jub: 20 }, color: { Hu: 30, Mu: 15, Ju: 35 }, result: [{ Ju: 55 }] }, { adj: { Jua: 0, Jub: 20 }, color: { Hu: 30, Mu: 15, Ju: 35 }, result: [{ Ju: 20 }] }, @@ -121,7 +121,7 @@ describe('Color Analysis Functions', () => { { adj: { Hua: 0, Hub: 350, steps: 3 }, color: { Hu: 30, Mu: 15, Ju: 70 }, result: [{ Hu: 30 }, { Hu: 10 }, { Hu: -10 }] }, ] signed_distance_data.forEach( ({ adj, color, result }) => { - expect(cam16_ucs_adjust(adj, cam16_ucs_to_hex_ingamut(color))).toMatchObject(result) + expect(adjust_colors(adj, JuMuHu_to_color(color))).toMatchObject(result) }) }); diff --git a/test/parser.spec.ts b/test/parser.spec.ts index 7b23de1..c07bd14 100644 --- a/test/parser.spec.ts +++ b/test/parser.spec.ts @@ -35,6 +35,21 @@ describe('Color Language Parser Functions', () => { }) }); + test('should parse color labels into js Objects with cam16 ucs components and hex', () => { + const definitions = [ + { text: 'testcase 5I9', result: { Ju: 50, Hu: 0, Mu: 45 } }, + { text: 'testcase 8I2', result: { Ju: 80, Hu: 0, Mu: 10 } }, + { text: 'testcase 56I22', result: { Ju: 56, Hu: 0, Mu: 11 } }, + { text: 'testcase 5i9', result: { Ju: 50, Hu: 180, Mu: 45 } }, + { text: 'testcase 2v4', result: { Ju: 20, Hu: 270, Mu: 20 } }, + { text: 'testcase 2V4', result: { Ju: 20, Hu: 90, Mu: 20 } }, + ] + definitions.forEach(({ text, result }) => { + expect(parse_colors(text)).toMatchObject(result); + + }) + }); + // test('should parse color assignments and use of identifiers', () => { // const definitions = [ // { text: 'primary is hmj(10,10,50). secondary is hmj(180,20,40). primary lighter by 8', result: [{ Ju: 58 }] }, diff --git a/test/transform_fwd.spec.ts b/test/transform_fwd.spec.ts index 948836d..16a6447 100644 --- a/test/transform_fwd.spec.ts +++ b/test/transform_fwd.spec.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from '@jest/globals' import { hexsrgb_testdata, rgb_extremeties } from './transform_testdata' -import { marsBrownHex, marsBrownRGB, marsBrownXYZ, marsBrownCam16, marsBrownCam16ucs } from './transform_testdata' -import { hex_to_srgb, srgb_to_xyz, hex_to_cam16, hex_to_cam16_ucs } from '../src/index' -import { xyz_to_cam16, xyz_to_cam16_ucs, cam16_ucs_to_hex } from '../src/index' +import { marsBrownHex, marsBrownRGB, marsBrownXYZ, marsBrownCam16, marsBrownJuMuHu } from './transform_testdata' +import { hex_to_srgb, srgb_to_xyz, hex_to_JMhQCs, hex_to_JuMuHu } from '../src/index' +import { xyz_to_JMhQCs, xyz_to_JuMuHu, JuMuHu_to_hex } from '../src/index' // helper functions const approxArr = (arr: number[]) => arr.map((x: number): string => x.toFixed(12)) @@ -20,26 +20,27 @@ describe('Forward Color Transform Functions', () => { expect(srgb_to_xyz(marsBrownRGB)).toEqual(marsBrownXYZ); }); - test('xyz_to_cam16 converts #ad6242 Mars Brown correctly', () => { - expect(xyz_to_cam16(marsBrownXYZ)).toEqual(marsBrownCam16); + test('xyz_to_JMhQCs converts #ad6242 Mars Brown correctly', () => { + expect(xyz_to_JMhQCs(marsBrownXYZ)).toEqual(marsBrownCam16); }); - test('xyz_to_cam16_ucs converts #ad6242 Mars Brown correctly', () => { - expect(xyz_to_cam16_ucs(marsBrownXYZ)).toEqual(marsBrownCam16ucs); + + test('xyz_to_JuMuHu converts #ad6242 Mars Brown correctly', () => { + expect(xyz_to_JuMuHu(marsBrownXYZ)).toEqual(marsBrownJuMuHu); }); - test('hex_to_cam16 converts #ad6242 Mars Brown correctly', () => { - expect(hex_to_cam16(marsBrownHex)).toEqual(marsBrownCam16); + test('hex_to_JMhQCs converts #ad6242 Mars Brown correctly', () => { + expect(hex_to_JMhQCs(marsBrownHex)).toEqual(marsBrownCam16); }); - test('hex_to_cam16_ucs converts #ad6242 Mars Brown correctly', () => { - expect(hex_to_cam16_ucs(marsBrownHex)).toEqual(marsBrownCam16ucs); + test('hex_to_JuMuHu converts #ad6242 Mars Brown correctly', () => { + expect(hex_to_JuMuHu(marsBrownHex)).toEqual(marsBrownJuMuHu); }); test('round trip conversion of extremeties of sRGB space', () => { rgb_extremeties.forEach(hex1 => { - const ucs = hex_to_cam16_ucs(hex1) - const hex2 = cam16_ucs_to_hex(ucs) + const ucs = hex_to_JuMuHu(hex1) + const hex2 = JuMuHu_to_hex(ucs) expect(hex2).toEqual(hex1); }) }); diff --git a/test/transform_rev.spec.ts b/test/transform_rev.spec.ts index 26883b2..75684e9 100644 --- a/test/transform_rev.spec.ts +++ b/test/transform_rev.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from '@jest/globals' import { hexsrgb_testdata } from './transform_testdata' -import { marsBrownHex, marsBrownRGB, marsBrownXYZ, marsBrownCam16, marsBrownCam16ucs } from './transform_testdata' -import { srgb_to_hex, srgb_to_xyz, hex_to_cam16, hex_to_cam16_ucs } from '../src/index' +import { marsBrownHex, marsBrownRGB, marsBrownXYZ, marsBrownCam16, marsBrownJuMuHu } from './transform_testdata' +import { srgb_to_hex, srgb_to_xyz, hex_to_JMhQCs, hex_to_JuMuHu } from '../src/index' describe('Reverse Color Transform Functions', () => { @@ -30,12 +30,12 @@ describe('Reverse Color Transform Functions', () => { expect(srgb_to_xyz(marsBrownRGB)).toEqual(marsBrownXYZ); }); - test('hex_to_cam16 converts #ad6242 Mars Brown correctly', () => { - expect(hex_to_cam16(marsBrownHex)).toEqual(marsBrownCam16); + test('hex_to_JMhQCs converts #ad6242 Mars Brown correctly', () => { + expect(hex_to_JMhQCs(marsBrownHex)).toEqual(marsBrownCam16); }); - test('hex_to_cam16_ucs converts #ad6242 Mars Brown correctly', () => { - expect(hex_to_cam16_ucs(marsBrownHex)).toEqual(marsBrownCam16ucs); + test('hex_to_JuMuHu converts #ad6242 Mars Brown correctly', () => { + expect(hex_to_JuMuHu(marsBrownHex)).toEqual(marsBrownJuMuHu); }); }) \ No newline at end of file diff --git a/test/transform_testdata.ts b/test/transform_testdata.ts index cd1cbd5..16c8b10 100644 --- a/test/transform_testdata.ts +++ b/test/transform_testdata.ts @@ -49,7 +49,6 @@ export const HuLabel_testdata = [ { Hu: 90, label: 'V' }, { Hu: 180, label: 'i' }, { Hu: 270, label: 'v' }, - { Hu: 360, label: 'I' }, { Hu: 305, label: 'A' }, { Hu: 118, label: 'Z' }, { Hu: 125, label: 'a' }, diff --git a/test/utility.spec.ts b/test/utility.spec.ts index 26c2d78..cda50db 100644 --- a/test/utility.spec.ts +++ b/test/utility.spec.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from '@jest/globals' import { hHu_testdata, HuLabel_testdata } from './transform_testdata' -import { fp, c360, lerp, h_to_Hu, Hu_to_h, Hu_to_label } from '../src/index' +import { fp, c360, lerp, is_hex_code, is_ucs_label3, is_ucs_label5 } from '../src/index' +import { h_to_Hu, Hu_to_h, Hu_to_label, label_to_Hu } from '../src/index' describe('Utility Functions', () => { test('fp should convert floating point to string with 2 decimals', () => { @@ -25,6 +26,42 @@ describe('Utility Functions', () => { testdata.forEach(d => expect(lerp(... d.args)).toBe(d.expectedresult)) }); + test('is_hex_code should test syntax correctly', () => { + const testdata:Array<{ s:string, expectedresult:boolean }> = [ + { s: '#FFFFFF', expectedresult: true }, + { s: '#aaffdd', expectedresult: true }, + { s: '202020', expectedresult: true }, + { s: '#GFFFFF', expectedresult: false }, + { s: '#fff', expectedresult: false }, + ] + testdata.forEach(d => expect(d.s+' '+is_hex_code(d.s)).toBe(d.s+' '+d.expectedresult)) + }); + + test('is_ucs_label3 should test syntax correctly', () => { + const testdata:Array<{ s:string, expectedresult:boolean }> = [ + { s: '8a3', expectedresult: true }, + { s: '3F3', expectedresult: true }, + { s: '99b22', expectedresult: false }, + { s: '#8a3', expectedresult: false }, + { s: '#GFFFFF', expectedresult: false }, + { s: '#fff', expectedresult: false }, + ] + testdata.forEach(d => expect(d.s+' '+is_ucs_label3(d.s)).toBe(d.s+' '+d.expectedresult)) + }); + + test('is_ucs_label5 should test syntax correctly', () => { + const testdata:Array<{ s:string, expectedresult:boolean }> = [ + { s: '8a3', expectedresult: false }, + { s: '3F3', expectedresult: false }, + { s: '99b22', expectedresult: true }, + { s: '99b100', expectedresult: false }, + { s: '#8a3', expectedresult: false }, + { s: '#GFFFFF', expectedresult: false }, + { s: '#fff', expectedresult: false }, + ] + testdata.forEach(d => expect(d.s+' '+is_ucs_label5(d.s)).toBe(d.s+' '+d.expectedresult)) + }); + test('h_to_Hu converts from hue to uniform hue space correctly', () => { hHu_testdata.forEach(d => { expect(h_to_Hu(d.h).toFixed(1)).toEqual(d.Hu.toFixed(1)) @@ -43,5 +80,10 @@ describe('Utility Functions', () => { }) }); + test('label_to_Hu converts from letter to uniform hue space correctly', () => { + HuLabel_testdata.forEach(d => { + expect(Math.round(label_to_Hu(d.label))).toEqual(d.Hu) + }) + }); })