{"action":"closed","issue":{"url":"https://api.github.com/repos/chalk/ansi-styles/issues/11","labels_url":"https://api.github.com/repos/chalk/ansi-styles/issues/11/labels{/name}","comments_url":"https://api.github.com/repos/chalk/ansi-styles/issues/11/comments","events_url":"https://api.github.com/repos/chalk/ansi-styles/issues/11/events","html_url":"https://github.com/chalk/ansi-styles/issues/11","id":90515378,"number":11,"title":"256/16 million color support","user":{"login":"Qix-","id":885648,"avatar_url":"https://avatars.githubusercontent.com/u/885648?v=3","gravatar_id":"","url":"https://api.github.com/users/Qix-","html_url":"https://github.com/Qix-","followers_url":"https://api.github.com/users/Qix-/followers","following_url":"https://api.github.com/users/Qix-/following{/other_user}","gists_url":"https://api.github.com/users/Qix-/gists{/gist_id}","starred_url":"https://api.github.com/users/Qix-/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Qix-/subscriptions","organizations_url":"https://api.github.com/users/Qix-/orgs","repos_url":"https://api.github.com/users/Qix-/repos","events_url":"https://api.github.com/users/Qix-/events{/privacy}","received_events_url":"https://api.github.com/users/Qix-/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":25,"created_at":"2015-06-23T23:24:36Z","updated_at":"2016-01-09T14:26:06Z","closed_at":"2016-01-09T14:26:06Z","body":"Yes, [it's time](https://twitter.com/sindresorhus/status/613460287270842368).\r\n\r\nI spent the greater part of last night trying to figure out the best way to tackle compatibility with ANSI 256/16m (truecolor) support to where colors would degrade nicely. I started [writing my own](https://github.com/Qix-/ink) engine but alas I figured this may be better suited as a PR for an existing solution (i.e. Node's biggest package, Chalk). Now that `supports-color` [will soon support more colors](https://github.com/sindresorhus/supports-color/pull/25), this is a good time to look at it.\r\n\r\nThere are a few issues issues I foresee with an upgrade such as this:\r\n\r\n- What happens to 256/16m colors on terminals that only support 16 colors (_\"basic\"_ support)? 16m colors on terminals that only support 256 colors?\r\n- How do we accurately and easily wrap color operations into an already expressive API, with full backwards compatibility?\r\n\r\n## Color Operations\r\nThe way my engine does it (and the way that seems to work fine) is to use the [`color-js`](https://www.npmjs.com/package/color-js) package to perform color operations on full color values. This includes modifying lightness, saturation, and per-channel values.\r\n\r\nThese color values can then be converted to 8bpc RGB color values, which can then be used to degrade into the lower forms.\r\n\r\n### Tests\r\nFirst of all, let's define our domain scaling function. This function takes a value, along with the upper/lower bound of that value's domain, and then applies it to a lower/upper bound of another domain.\r\n\r\n~~~coffeescript\r\napplyDomain = (val, lb, ub, tlb, tub)->\r\n  Math.round ((val - lb) / (ub - lb)) * (tub - tlb) + tlb\r\n~~~\r\n\r\nFrom there, degrading colors becomes somewhat of a simple task.\r\n\r\nFor each of the three [levels](https://github.com/sindresorhus/supports-color/pull/25#discussion_r33014946), I'll be decoding an image using [png-js](https://github.com/devongovett/png.js) and running the pixel data through each of the functions.\r\n\r\nThe original image:\r\n![applepride](https://cloud.githubusercontent.com/assets/885648/8319567/806bb6c6-19cd-11e5-9a6a-0ab623c52716.png)\r\n\r\n#### 16m (8bpc)\r\nThis one is simple. Provided a 0..255 domain of RGB values, we can then build up a pretty lossless result in the terminal.\r\n\r\n> color-js provides RGB values in floating point numbers in the domain 0.0..1.0. As you might expect, this is easily converted to the 0..255 domain using `applyDomain n, 0, 1, 0, 255`.\r\n\r\n![screen shot 2015-06-23 at 5 10 51 pm](https://cloud.githubusercontent.com/assets/885648/8319329/d5bffda6-19ca-11e5-8ca8-1e2bb294fabb.png)\r\n\r\n#### 16m (8bpc) to ANSI 256\r\nThanks to [this formula](https://github.com/jbnicolai/ansi-256-colors/blob/ef02d765885057e592cd1e9fa400003ada486f91/index.js#L30) (thanks @jbnicolai) we know the per-channel domain of ANSI-256 colors is 0..5 with a translation of +16.\r\n\r\n~~~coffeescript\r\ntoAnsi256 = (r8, g8, b8)->\r\n  r = applyDomain r8, 0, 255, 0, 5 # red, from 0..255 to 0..5\r\n  g = applyDomain g8, 0, 255, 0, 5 # green, from 0..255 to 0..5\r\n  b = applyDomain b8, 0, 255, 0, 5 # blue, from 0.255 to 0..5\r\n  return (36 * r) + (6 * g) + b + 16\r\n~~~\r\n\r\nAs you can see, it's quite accurate.\r\n\r\n![screen shot 2015-06-23 at 5 05 56 pm](https://cloud.githubusercontent.com/assets/885648/8319331/df6a7cdc-19ca-11e5-9bd2-106d66240ec8.png)\r\n\r\n#### 16m (8bpc) to 16 colors\r\nThis one was a little trickier, but really achieves the same effect.\r\n\r\n~~~coffeescript\r\ntoAnsi16 = (r8, g8, b8)->\r\n  r = applyDomain r8, 0, 255, 0, 1 # red, from 0..255 to 0..1 (rounded)\r\n  g = applyDomain g8, 0, 255, 0, 1 # green, from 0..255 to 0..1 (rounded)\r\n  b = applyDomain b8, 0, 255, 0, 1 # blue, from 0..255 to 0..1 (rounded)\r\n  return (b << 2) | (g << 1) | r\r\n~~~\r\n\r\nthis yields predictable results.\r\n\r\n~~~\r\ncoffee> toAnsi16 = require './ansi16'\r\n[Function]\r\ncoffee> toAnsi16 255, 255, 255\r\n7\r\ncoffee> toAnsi16 255, 0, 0\r\n1\r\ncoffee> toAnsi16 255, 0, 126\r\n1\r\ncoffee> toAnsi16 255, 0, 127\r\n1\r\ncoffee> toAnsi16 255, 0, 128\r\n5\r\ncoffee> toAnsi16 255, 127, 128\r\n5\r\ncoffee> toAnsi16 30, 185, 128\r\n6\r\n~~~\r\n\r\nAs you can see, it's also pretty accurate (~~minus orange, which is a tricky color to get right with the standard 16 color palette~~).\r\n\r\n![screen shot 2015-06-23 at 5 03 16 pm](https://cloud.githubusercontent.com/assets/885648/8319267/fee3305a-19c9-11e5-826f-e1d4def0a5d7.png)\r\n\r\nThough by using color-js and the lightness value bound to the domain 0..1 (rounded), then we can select the brightness scheme using the extended `100m`-`107m` bright background codes to get a better result.\r\n\r\n![screen shot 2015-06-23 at 5 43 10 pm](https://cloud.githubusercontent.com/assets/885648/8319723/561efd68-19cf-11e5-9f5b-1a0a62e98bac.png)\r\n\r\nMuch better - and with only the basic functionality (and yes, [even Windows supports those](https://github.com/joyent/node/blob/master/deps/uv/src/win/tty.c#L1233-L1238)).\r\n\r\n### Internal structure\r\nWith the domain functions being pretty accurate, we can then start making assertions about color values. This means we can store color-js `Color` objects as the main source for color, perform operations on them, and then degrade them down to corresponding command line colors.\r\n\r\nFor instance, here is my prototype library doing this with a nice shade of Purple. There isn't a ton of logic about the 16 color palette, but I'm sure something could be thrown in there for lightness governing the bold (`1m`) code.\r\n\r\n![screen shot 2015-06-23 at 5 18 26 pm](https://cloud.githubusercontent.com/assets/885648/8319424/f48fa1ea-19cb-11e5-9766-b7d1d2e31926.png)\r\n\r\n\r\n\r\n---\r\n\r\nAs you can see in the above prototype, we now have a *lot* of control over the kinds of colors we can output to the console. We can do advanced things like shift by hue, lighten/darken by lightness/value, and even start theming console colors with CSS (if we so choose) - all while being completely degradable by non-supporting terminals.\r\n\r\nExisting programs can upgrade their color suites to be more colorful, and if we so choose [supports-color](https://github.com/sindresorhus/supports-color) could even [include or modify an environment variable](https://github.com/Qix-/supports-color/blob/256-color/index.js#L55) to allow users to set this in their RC files to enable system-wide 256 or 16 million color terminal applications.\r\n\r\nThoughts? I'm fully prepared to make a complete PR for this."}}