/* Copyright 2010 Aaron J. Radke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cc.drx /** * A general color class and constructor */ object Color{ //--constructors implicit object ParsableColor extends Parsable[Color]{ def apply(color:String):Color = Color(color) } private def numeric(str:String):Color = Try{ val s = if(str startsWith "#") str drop 1 else if(str startsWith "0x") str drop 2 else str s.size match { case 3 => val r = s(0); val g = s(1); val b = s(2); argb(Parse[Int](s"0xFF$r$r$g$g$b$b")) case 4 => val a = s(0); val r = s(1); val g = s(2); val b = s(3); argb(Parse[Int](s"0x$a$a$r$r$g$g$b$b")) case 6 => rgb(Parse[Int](str)) case _ => argb(Parse[Int](str)) } } getOrElse Black //TODO implement a css like `color-mod` that uses "red s(-10%)" like notation with blend and whiteness def apply(str:String):Color = { val s = str.trim val es = s.split("opacity") if(es.size == 1) (drxColorNames++catColorNames).getOrElse(s, //first check without changing the case cssColorNames.getOrElse(s.toLowerCase, //then check the lowercase version name in the CSS names numeric(s) //then just convert it as a number ) ) else if (es.size == 2) Color(es(0)) opacity Parse(es(1)) else Black } def apply(argb:Int):Color = new Color(argb) def rgb(r:Int,g:Int,b:Int):Color = new Color(0xFF000000 | r<<16 | g<<8 | b) def rgb(r:Int,g:Int,b:Int,a:Int):Color = new Color(a<<24 | r<<16 | g<<8 | b) def rgb(rgb:Int,a:Int):Color = new Color(a<<24 | (rgb & 0x00FFFFFF)) def rgb(rgb:Int):Color = new Color(0xFF000000 | (rgb & 0x00FFFFFF)) def rgba(rgba:Int):Color = rgb( (0xFFFFFF00 & rgba) >> 8 , 0x000000FF & rgba) def argb(argb:Int):Color = new Color(argb) def mix(colors:Iterable[Color]):Color = { val N = colors.size if(N==0) Black //the void else Color.rgb( colors.map{_.r}.sum/N, colors.map{_.g}.sum/N, colors.map{_.b}.sum/N, colors.map{_.a}.sum/N ) } //colorbrewer2.org "9-class Set1" val Red = Color(0xffe41a1c) val Blue = Color(0xff377eb8) val Green = Color(0xff4daf4a) val Purple = Color(0xff984ea3) val Orange = Color(0xffff7f00) val Yellow = Color(0xffffff33) val Brown = Color(0xffa65628) val Pink = Color(0xfff781bf) val Grey = Color(0xff999999) val Black = Color(0xff000000) val Charcoal = Color(0xff333333) val White = Color(0xffFFFFFF) val Cyan = Color(0xff00ffff) val Magenta = Color(0xffff00ff) val Transparent = Color(0x00000000) //transparent black lazy val drxColorNames = Map[String,Color]( //pluss drx color names "Red" -> Red, "Blue" -> Blue, "Green" -> Green, "Purple" -> Purple, "Orange" -> Orange, "Yellow" -> Yellow, "Brown" -> Brown, "Pink" -> Pink, "Grey" -> Grey, "Charcoal" -> Charcoal, "Cyan" -> Cyan, "Magenta" -> Magenta ) lazy val catColorNames = Map[String,Color]( //pluss drx color names "0" -> cat10(0), "1" -> cat10(1), "2" -> cat10(2), "3" -> cat10(3), "4" -> cat10(4), "5" -> cat10(5), "6" -> cat10(6), "7" -> cat10(7), "8" -> cat10(8), "9" -> cat10(9) ) //--- from http://dev.w3.org/csswg/css-color-4/#named-colors lazy val cssColorNames = Map[String,Color]( "aliceblue" -> Color(0xfff0f8ff), "antiquewhite" -> Color(0xfffaebd7), "aqua" -> Color(0xff00ffff), "aquamarine" -> Color(0xff7fffd4), "azure" -> Color(0xfff0ffff), "beige" -> Color(0xfff5f5dc), "bisque" -> Color(0xffffe4c4), "black" -> Color(0xff000000), "blanchedalmond" -> Color(0xffffebcd), "blue" -> Color(0xff0000ff), "blueviolet" -> Color(0xff8a2be2), "brown" -> Color(0xffa52a2a), "burlywood" -> Color(0xffdeb887), "cadetblue" -> Color(0xff5f9ea0), "chartreuse" -> Color(0xff7fff00), "chocolate" -> Color(0xffd2691e), "coral" -> Color(0xffff7f50), "cornflowerblue" -> Color(0xff6495ed), "cornsilk" -> Color(0xfffff8dc), "crimson" -> Color(0xffdc143c), "cyan" -> Color(0xff00ffff), "darkblue" -> Color(0xff00008b), "darkcyan" -> Color(0xff008b8b), "darkgoldenrod" -> Color(0xffb8860b), "darkgray" -> Color(0xffa9a9a9), "darkgreen" -> Color(0xff006400), "darkgrey" -> Color(0xffa9a9a9), "darkkhaki" -> Color(0xffbdb76b), "darkmagenta" -> Color(0xff8b008b), "darkolivegreen" -> Color(0xff556b2f), "darkorange" -> Color(0xffff8c00), "darkorchid" -> Color(0xff9932cc), "darkred" -> Color(0xff8b0000), "darksalmon" -> Color(0xffe9967a), "darkseagreen" -> Color(0xff8fbc8f), "darkslateblue" -> Color(0xff483d8b), "darkslategray" -> Color(0xff2f4f4f), "darkslategrey" -> Color(0xff2f4f4f), "darkturquoise" -> Color(0xff00ced1), "darkviolet" -> Color(0xff9400d3), "deeppink" -> Color(0xffff1493), "deepskyblue" -> Color(0xff00bfff), "dimgray" -> Color(0xff696969), "dimgrey" -> Color(0xff696969), "dodgerblue" -> Color(0xff1e90ff), "firebrick" -> Color(0xffb22222), "floralwhite" -> Color(0xfffffaf0), "forestgreen" -> Color(0xff228b22), "fuchsia" -> Color(0xffff00ff), "gainsboro" -> Color(0xffdcdcdc), "ghostwhite" -> Color(0xfff8f8ff), "gold" -> Color(0xffffd700), "goldenrod" -> Color(0xffdaa520), "gray" -> Color(0xff808080), "green" -> Color(0xff008000), "greenyellow" -> Color(0xffadff2f), "grey" -> Color(0xff808080), "honeydew" -> Color(0xfff0fff0), "hotpink" -> Color(0xffff69b4), "indianred" -> Color(0xffcd5c5c), "indigo" -> Color(0xff4b0082), "ivory" -> Color(0xfffffff0), "khaki" -> Color(0xfff0e68c), "lavender" -> Color(0xffe6e6fa), "lavenderblush" -> Color(0xfffff0f5), "lawngreen" -> Color(0xff7cfc00), "lemonchiffon" -> Color(0xfffffacd), "lightblue" -> Color(0xffadd8e6), "lightcoral" -> Color(0xfff08080), "lightcyan" -> Color(0xffe0ffff), "lightgoldenrodyellow" -> Color(0xfffafad2), "lightgray" -> Color(0xffd3d3d3), "lightgreen" -> Color(0xff90ee90), "lightgrey" -> Color(0xffd3d3d3), "lightpink" -> Color(0xffffb6c1), "lightsalmon" -> Color(0xffffa07a), "lightseagreen" -> Color(0xff20b2aa), "lightskyblue" -> Color(0xff87cefa), "lightslategray" -> Color(0xff778899), "lightslategrey" -> Color(0xff778899), "lightsteelblue" -> Color(0xffb0c4de), "lightyellow" -> Color(0xffffffe0), "lime" -> Color(0xff00ff00), "limegreen" -> Color(0xff32cd32), "linen" -> Color(0xfffaf0e6), "magenta" -> Color(0xffff00ff), "maroon" -> Color(0xff800000), "mediumaquamarine" -> Color(0xff66cdaa), "mediumblue" -> Color(0xff0000cd), "mediumorchid" -> Color(0xffba55d3), "mediumpurple" -> Color(0xff9370db), "mediumseagreen" -> Color(0xff3cb371), "mediumslateblue" -> Color(0xff7b68ee), "mediumspringgreen" -> Color(0xff00fa9a), "mediumturquoise" -> Color(0xff48d1cc), "mediumvioletred" -> Color(0xffc71585), "midnightblue" -> Color(0xff191970), "mintcream" -> Color(0xfff5fffa), "mistyrose" -> Color(0xffffe4e1), "moccasin" -> Color(0xffffe4b5), "navajowhite" -> Color(0xffffdead), "navy" -> Color(0xff000080), "oldlace" -> Color(0xfffdf5e6), "olive" -> Color(0xff808000), "olivedrab" -> Color(0xff6b8e23), "orange" -> Color(0xffffa500), "orangered" -> Color(0xffff4500), "orchid" -> Color(0xffda70d6), "palegoldenrod" -> Color(0xffeee8aa), "palegreen" -> Color(0xff98fb98), "paleturquoise" -> Color(0xffafeeee), "palevioletred" -> Color(0xffdb7093), "papayawhip" -> Color(0xffffefd5), "peachpuff" -> Color(0xffffdab9), "peru" -> Color(0xffcd853f), "pink" -> Color(0xffffc0cb), "plum" -> Color(0xffdda0dd), "powderblue" -> Color(0xffb0e0e6), "purple" -> Color(0xff800080), "rebeccapurple" -> Color(0xff663399), "red" -> Color(0xffff0000), "rosybrown" -> Color(0xffbc8f8f), "royalblue" -> Color(0xff4169e1), "saddlebrown" -> Color(0xff8b4513), "salmon" -> Color(0xfffa8072), "sandybrown" -> Color(0xfff4a460), "seagreen" -> Color(0xff2e8b57), "seashell" -> Color(0xfffff5ee), "sienna" -> Color(0xffa0522d), "silver" -> Color(0xffc0c0c0), "skyblue" -> Color(0xff87ceeb), "slateblue" -> Color(0xff6a5acd), "slategray" -> Color(0xff708090), "slategrey" -> Color(0xff708090), "snow" -> Color(0xfffffafa), "springgreen" -> Color(0xff00ff7f), "steelblue" -> Color(0xff4682b4), "tan" -> Color(0xffd2b48c), "teal" -> Color(0xff008080), "thistle" -> Color(0xffd8bfd8), "tomato" -> Color(0xffff6347), "turquoise" -> Color(0xff40e0d0), "violet" -> Color(0xffee82ee), "wheat" -> Color(0xfff5deb3), "white" -> Color(0xffffffff), "whitesmoke" -> Color(0xfff5f5f5), "yellow" -> Color(0xffffff00), "yellowgreen" -> Color(0xff9acd32) ) lazy val q1c9 = Vector(Red,Blue,Green,Purple,Orange,Yellow,Brown,Pink,Grey) lazy val q1c8 = Vector(Red,Blue,Green,Purple,Orange,Brown,Pink,Grey) lazy val q1c24 = q1c8 ++ q1c8.map(_.darker) ++ q1c8.map(_.darker.darker) // private def colorList(hexList:String):Vector[Color] = hexList.trim.split("""\s+""").toVector.map{hex => Color((hex base 16).toInt) alpha 0xff} private def colorList(hexList:String):Vector[Color] = hexList.filter(!_.isWhitespace).grouped(6).map{hex => Color((hex base 16).toInt) alpha 0xff}.toVector lazy val cat10 = colorList(""" 1f77b4 ff7f0e 2ca02c d62728 9467bd 8c564b e377c2 7f7f7f bcbd22 17becf """) lazy val cat20 = colorList(""" 1f77b4 aec7e8 ff7f0e ffbb78 2ca02c 98df8a d62728 ff9896 9467bd c5b0d5 8c564b c49c94 e377c2 f7b6d2 7f7f7f c7c7c7 bcbd22 dbdb8d 17becf 9edae5 """) lazy val cat20b = colorList(""" 393b79 5254a3 6b6ecf 9c9ede 637939 8ca252 b5cf6b cedb9c 8c6d31 bd9e39 e7ba52 e7cb94 843c39 ad494a d6616b e7969c 7b4173 a55194 ce6dbd de9ed6 """) lazy val cat20c = colorList(""" 3182bd 6baed6 9ecae1 c6dbef e6550d fd8d3c fdae6b fdd0a2 31a354 74c476 a1d99b c7e9c0 756bb1 9e9ac8 bcbddc dadaeb 636363 969696 bdbdbd d9d9d9 """) /** "perceptually uniform" colormaps * https://github.com/d3/d3-scale * https://raw.githubusercontent.com/d3/d3-scale/master/src/viridis.js * https://bids.github.io/colormap/ * https://github.com/BIDS/colormap/blob/master/colormaps.py * */ // type ColorMap = Vector[Color] lazy val maps = Map( "viridis" -> viridis, "magma" -> magma, "plasma" -> plasma, "inferno" -> inferno) lazy val viridis = colorList("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725") lazy val magma = colorList("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf") lazy val inferno = colorList("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4") lazy val plasma = colorList("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921") /**ansi color map * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors * code i bgr color * ---------------- * 3 0 000 black * 3 1 001 red * 3 2 010 green * 3 3 011 yellow * 3 4 100 blue * 3 5 101 purple(magenta) * 3 6 110 aqua (cyan) * 3 7 111 white */ private def permute8(on:Int, off:Int)(key:Int => String) = (0 to 7).map{i => def f(b:Int) = if(i bit b) on else off Color.rgb( f(0), f(1), f(2) ) -> key(i) }.toMap lazy val ansi8:Map[Color,String] = permute8(170,0){i=>s"${i}"} lazy val ansi16:Map[Color,String] = ansi8 ++ permute8(255,85){i=>s"${i};1"} /* TODO add ansi-256 color mode from https://en.wikipedia.org/wiki/ANSI_escape_code#Colors lazy val ansi256:Map[Color,String] = ansi8 ++ permute8(255,85){i=>s"${i};1"} In 256-color mode (ESC[38;5;<fgcode>m and ESC[48;5;<bgcode>m), the color-codes are the following:[citation needed] 0x00-0x07: standard colors (as in ESC [ 30–37 m) 0x08-0x0F: high intensity colors (as in ESC [ 90–97 m) 0x10-0xE7: 6 × 6 × 6 = 216 colors: 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) 0xE8-0xFF: grayscale from black to white in 24 steps */ 2017-07-30("use the more general and clear cat20 % i instead","v0.1.20") def catColor(i:Int) = q1c24(i % q1c24.size) object HCL{ // def hcl(H:Double,C:Double,L:Double,a:Int=0xff):HCL = HCL(Vec(H.rad)*C tz L, a) def hsl(H:Angle,S:Double,L:Double,a:Int=0xff):HCL = { val C = if(L<=0.5) S*2*L else S*2*(1-L) HCL(H,C,L,a) } def apply(H:Angle, C:Double, L:Double,a:Int):HCL = HCL(Vec.polar(C,H) tz L, a) def apply(H:Angle, C:Double, L:Double):HCL = HCL(Vec.polar(C,H) tz L, 0xff) def apply(color:Color):HCL ={ //variables match en.wikipedia.org/wiki/HSL_and_HSV val R = color.r/255.0 val G = color.g/255.0 val B = color.b/255.0 val M = (R max G) max B //strongest color value val m = (R min G) min B //weakest color val C = M-m //chroma (relative size of the "chromacity plane" of the cube projeted on a hexagon) //warp the hexagon of rgb&cym to a circle val H = ((if (C == 0.0) 0.0 //rather undefined (it could really be any hue) else if(C == R) ((G-B)/C) % 6.0 else if(C == G) ((B-R)/C) + 2.0 else ((R-G)/C) + 4.0 )*60.0).deg //convert to degrees for hue val L = (M+m)/2 //Lightness: average of largest and smallet color components /* val Y = 0.3*R + 0.59*G + 0.11*B //Luma: gamma corrected RGB for percieved luminance val V = M //Value: largest component of color val S_hsv = if(V==0.0) 0.0 else C/V //Value adjusted/validated to 0.0 to 1.0 val S_hsl = if(L==0.0 || L == 1.0) 0.0 else C/(1 - (2*L-1).abs) //Lightness adjusted/validated on 0.0 to 1.0 scale */ HCL(Vec.polar(C,H) tz L, color.a) } } case class HCL(vec:Vec,a:Int=0){ lazy val H:Angle = vec.heading //heading as a positive number in deg lazy val C:Double = vec.range //0 to 1 ?? lazy val L:Double = vec.z //0 to 1 ?? lazy val rgb:Color = { val Hp = H.norm.deg/60.0 val X = C*(1 - (Hp % 2 - 1).abs) val (r,g,b) = if (Hp < 1) (C, X, 0.0) else if(Hp < 2) (X, C, 0.0) else if(Hp < 3) (0.0, C, X) else if(Hp < 4) (0.0, X, C) else if(Hp < 5) (X, 0.0, C) else (C, 0.0, X) val m = L - C/2 Color.rgb( ((r+m)*255).round.toInt, ((g+m)*255).round.toInt, ((b+m)*255).round.toInt, a ) } def lerp(that:HCL)(amt:Double):HCL = HCL( (this.vec lerp that.vec)(amt), // hcl vec (this.a lerp that.a )(amt) //alpha channel ) } /* case class CYMK(c:Int,y:Int,m:Int,k:Int){ lazy val rbg:Color = ??? } */ } trait Colorizer[A,B]{ def apply(c:Color, a:A):B } object Colorizer{ implicit object ColorizerAnsi extends Colorizer[String,String]{ def apply(c:Color, a:String):String = c.ansi(a) } } //TODO make color a trait that RGB and HCL and others extend, however this may have large impacts class Color(val argb:Int) extends AnyVal{ def apply[A,B](a:A)(implicit c:Colorizer[A,B]):B = c(this,a) def toAwt:java.awt.Color = new java.awt.Color(r, g, b, a) def cssHex:String = "#" + hex.takeRight(6) def hex = f"0x$argb%08X" override def toString = s"Color($hex)" def alpha(a:Int):Color = Color(a<<24 | (0x00ffffff & argb) ) def opacity(ratio:Double):Color = alpha((0xFF * ratio).toInt.sat(0,0xFF)) def a:Int = argb >> 24 & 0xFF def r:Int = argb >> 16 & 0xFF def g:Int = argb >> 8 & 0xFF def b:Int = argb & 0xFF def ~(that:Color):Bound[Color] = Bound(this,that) def invert:Color = Color.rgb(255-r, 255-g, 255-b, a) def complement:Color = { val rgb = Seq(r,g,b) val m = rgb.min + rgb.max Color.rgb(m-r, m-g, m-b,a) } /**rgb as a drx.Vec*/ def vec:Vec = Vec(r,g,b) /**relative distance metric used for closeness https://en.wikipedia.org/wiki/Color_difference */ def dist(that:Color):Double = { // val weight = Vec(1,1,1) //euclidean rgb // val weight = Vec(2,4,3) //rgb perception weighted euclidan val weight = { val r = (this.r + that.r)/2d def q(n:Double) = 2d + n/256d Vec(q(r),4,q(255-r)) //red blue scaling } val D = this.vec - that.vec math.sqrt(D.sq * weight) } def closest(colors:Iterable[Color]):Color = colors minBy dist def cssName:String = closestName(Color.cssColorNames) def drxName:String = closestName(Color.drxColorNames) def closestName(nameMap:Map[String,Color]):String = nameMap.minBy{case (_,c) => dist(c)}._1 // FIXME TODO use cssName table //https://en.wikipedia.org/wiki/ANSI_escape_code#Colors private def ansiCode:String = Color.ansi16(closest(Color.ansi16.keys)) private def ansiWrap(code:String)(body:String) = s"\u001b[${code}m${body}\u001b[0m" def ansi(body:String):String = ansiWrap(s"3${ansiCode}")(body) def ansi(body:String,bg:Color):String = ansiWrap(s"4${bg.ansiCode};3${ansiCode}")(body) //3:forground 4:background //TODO move this to a generic logarithmic scaling function in bounds or scales private def adjust(k:Double, min:Int=0, max:Int=0xFF)(x:Int):Int = ((0.7 ** k)*x).round.toInt.sat(min,max) def map(f:Int => Int):Color = Color.rgb( f(r), f(g), f(b), a) def brighter(k:Double):Color = this map adjust(-k, 30, 0xFF) def darker(k:Double):Color = this map adjust( k, 0, 0xFF) def brighter:Color = brighter(1.0) def darker:Color = darker(1.0) def isFullyTransparent:Boolean = a == 0x00 def isTransparent:Boolean = a != 0xFF //warning: this method name can be misleading is it completely transparent or partially? def isOpaque:Boolean = a == 0xFF //opaque by definition is fullyOpaque or not even partially transparent def moreTransparent:Color = moreTransparent(1.0) def moreTransparent(k:Double=1.0):Color = alpha(adjust(k, 0, 0xFF)(a)) def moreOpaque:Color = moreOpaque(1.0) def moreOpaque(k:Double=1.0):Color = alpha(adjust(-k, 0, 0xFF)(a)) 2017-07-30("use d3js like darker instead","v0.1.3") def darken(v:Int):Color = this map {c => (c - v).sat(0,0xFF)} def lerp(that:Color)(amt:Double):Color = Color.rgb( (this.r lerp that.r)(amt), (this.g lerp that.g)(amt), (this.b lerp that.b)(amt), (this.a lerp that.a)(amt) ) def hcl = Color.HCL(this) //private def lerpHSL(that:Color,amt:Double):Color = ??? //--convenience functions to construct styles def fill = Style.Fill(this) def stroke = Style.Stroke(this) }