...

Text file src/github.com/lucasb-eyer/go-colorful/README.md

Documentation: github.com/lucasb-eyer/go-colorful

     1go-colorful
     2===========
     3
     4[![go reportcard](https://goreportcard.com/badge/github.com/lucasb-eyer/go-colorful)](https://goreportcard.com/report/github.com/lucasb-eyer/go-colorful)
     5
     6A library for playing with colors in Go. Supports Go 1.13 onwards.
     7
     8Why?
     9====
    10I love games. I make games. I love detail and I get lost in detail.
    11One such detail popped up during the development of [Memory Which Does Not Suck](https://github.com/lucasb-eyer/mwdns/),
    12when we wanted the server to assign the players random colors. Sometimes
    13two players got very similar colors, which bugged me. The very same evening,
    14[I want hue](http://tools.medialab.sciences-po.fr/iwanthue/) was the top post
    15on HackerNews' frontpage and showed me how to Do It Right™. Last but not
    16least, there was no library for handling color spaces available in go. Colorful
    17does just that and implements Go's `color.Color` interface.
    18
    19What?
    20=====
    21Go-Colorful stores colors in RGB and provides methods from converting these to various color-spaces. Currently supported colorspaces are:
    22
    23- **RGB:** All three of Red, Green and Blue in [0..1].
    24- **HSL:** Hue in [0..360], Saturation and Luminance in [0..1]. For legacy reasons; please forget that it exists.
    25- **HSV:** Hue in [0..360], Saturation and Value in [0..1]. You're better off using HCL, see below.
    26- **Hex RGB:** The "internet" color format, as in #FF00FF.
    27- **Linear RGB:** See [gamma correct rendering](http://www.sjbrown.co.uk/2004/05/14/gamma-correct-rendering/).
    28- **CIE-XYZ:** CIE's standard color space, almost in [0..1].
    29- **CIE-xyY:** encodes chromacity in x and y and luminance in Y, all in [0..1]
    30- **CIE-L\*a\*b\*:** A *perceptually uniform* color space, i.e. distances are meaningful. L\* in [0..1] and a\*, b\* almost in [-1..1].
    31- **CIE-L\*u\*v\*:** Very similar to CIE-L\*a\*b\*, there is [no consensus](http://en.wikipedia.org/wiki/CIELUV#Historical_background) on which one is "better".
    32- **CIE-L\*C\*h° (HCL):** This is generally the [most useful](http://vis4.net/blog/posts/avoid-equidistant-hsv-colors/) one; CIE-L\*a\*b\* space in polar coordinates, i.e. a *better* HSV. H° is in [0..360], C\* almost in [-1..1] and L\* as in CIE-L\*a\*b\*.
    33- **CIE LCh(uv):** Called `LuvLCh` in code, this is a cylindrical transformation of the CIE-L\*u\*v\* color space. Like HCL above: H° is in [0..360], C\* almost in [-1..1] and L\* as in CIE-L\*u\*v\*.
    34- **HSLuv:** The better alternative to HSL, see [here](https://www.hsluv.org/) and [here](https://www.kuon.ch/post/2020-03-08-hsluv/). Hue in [0..360], Saturation and Luminance in [0..1].
    35- **HPLuv:** A variant of HSLuv. The color space is smoother, but only pastel colors can be included. Because the valid colors are limited, it's easy to get invalid Saturation values way above 1.0, indicating the color can't be represented in HPLuv beccause it's not pastel.
    36
    37For the colorspaces where it makes sense (XYZ, Lab, Luv, HCl), the
    38[D65](http://en.wikipedia.org/wiki/Illuminant_D65) is used as reference white
    39by default but methods for using your own reference white are provided.
    40
    41A coordinate being *almost in* a range means that generally it is, but for very
    42bright colors and depending on the reference white, it might overflow this
    43range slightly. For example, C\* of #0000ff is 1.338.
    44
    45Unit-tests are provided.
    46
    47Nice, but what's it useful for?
    48-------------------------------
    49
    50- Converting color spaces. Some people like to do that.
    51- Blending (interpolating) between colors in a "natural" look by using the right colorspace.
    52- Generating random colors under some constraints (e.g. colors of the same shade, or shades of one color.)
    53- Generating gorgeous random palettes with distinct colors of a same temperature.
    54
    55What not (yet)?
    56===============
    57There are a few features which are currently missing and might be useful.
    58I just haven't implemented them yet because I didn't have the need for it.
    59Pull requests welcome.
    60
    61- Sorting colors (potentially using above mentioned distances)
    62
    63So which colorspace should I use?
    64=================================
    65It depends on what you want to do. I think the folks from *I want hue* are
    66on-spot when they say that RGB fits to how *screens produce* color, CIE L\*a\*b\*
    67fits how *humans perceive* color and HCL fits how *humans think* colors.
    68
    69Whenever you'd use HSV, rather go for CIE-L\*C\*h°. for fixed lightness L\* and
    70chroma C\* values, the hue angle h° rotates through colors of the same
    71perceived brightness and intensity.
    72
    73How?
    74====
    75
    76### Installing
    77Installing the library is as easy as
    78
    79```bash
    80$ go get github.com/lucasb-eyer/go-colorful
    81```
    82
    83The package can then be used through an
    84
    85```go
    86import "github.com/lucasb-eyer/go-colorful"
    87```
    88
    89### Basic usage
    90
    91Create a beautiful blue color using different source space:
    92
    93```go
    94// Any of the following should be the same
    95c := colorful.Color{0.313725, 0.478431, 0.721569}
    96c, err := colorful.Hex("#517AB8")
    97if err != nil {
    98    log.Fatal(err)
    99}
   100c = colorful.Hsv(216.0, 0.56, 0.722)
   101c = colorful.Xyz(0.189165, 0.190837, 0.480248)
   102c = colorful.Xyy(0.219895, 0.221839, 0.190837)
   103c = colorful.Lab(0.507850, 0.040585,-0.370945)
   104c = colorful.Luv(0.507849,-0.194172,-0.567924)
   105c = colorful.Hcl(276.2440, 0.373160, 0.507849)
   106fmt.Printf("RGB values: %v, %v, %v", c.R, c.G, c.B)
   107```
   108
   109And then converting this color back into various color spaces:
   110
   111```go
   112hex := c.Hex()
   113h, s, v := c.Hsv()
   114x, y, z := c.Xyz()
   115x, y, Y := c.Xyy()
   116l, a, b := c.Lab()
   117l, u, v := c.Luv()
   118h, c, l := c.Hcl()
   119```
   120
   121Note that, because of Go's unfortunate choice of requiring an initial uppercase,
   122the name of the functions relating to the xyY space are just off. If you have
   123any good suggestion, please open an issue. (I don't consider XyY good.)
   124
   125### The `color.Color` interface
   126Because a `colorful.Color` implements Go's `color.Color` interface (found in the
   127`image/color` package), it can be used anywhere that expects a `color.Color`.
   128
   129Furthermore, you can convert anything that implements the `color.Color` interface
   130into a `colorful.Color` using the `MakeColor` function:
   131
   132```go
   133c, ok := colorful.MakeColor(color.Gray16{12345})
   134```
   135
   136**Caveat:** Be aware that this latter conversion (using `MakeColor`) hits a
   137corner-case when alpha is exactly zero. Because `color.Color` uses pre-multiplied
   138alpha colors, this means the RGB values are lost (set to 0) and it's impossible
   139to recover them. In such a case `MakeColor` will return `false` as its second value.
   140
   141### Comparing colors
   142In the RGB color space, the Euclidian distance between colors *doesn't* correspond
   143to visual/perceptual distance. This means that two pairs of colors which have the
   144same distance in RGB space can look much further apart. This is fixed by the
   145CIE-L\*a\*b\*, CIE-L\*u\*v\* and CIE-L\*C\*h° color spaces.
   146Thus you should only compare colors in any of these space.
   147(Note that the distance in CIE-L\*a\*b\* and CIE-L\*C\*h° are the same, since it's the same space but in cylindrical coordinates)
   148
   149![Color distance comparison](doc/colordist/colordist.png)
   150
   151The two colors shown on the top look much more different than the two shown on
   152the bottom. Still, in RGB space, their distance is the same.
   153Here is a little example program which shows the distances between the top two
   154and bottom two colors in RGB, CIE-L\*a\*b\* and CIE-L\*u\*v\* space. You can find it in `doc/colordist/colordist.go`.
   155
   156```go
   157package main
   158
   159import "fmt"
   160import "github.com/lucasb-eyer/go-colorful"
   161
   162func main() {
   163	c1a := colorful.Color{150.0 / 255.0, 10.0 / 255.0, 150.0 / 255.0}
   164	c1b := colorful.Color{53.0 / 255.0, 10.0 / 255.0, 150.0 / 255.0}
   165	c2a := colorful.Color{10.0 / 255.0, 150.0 / 255.0, 50.0 / 255.0}
   166	c2b := colorful.Color{99.9 / 255.0, 150.0 / 255.0, 10.0 / 255.0}
   167
   168	fmt.Printf("DistanceRgb:       c1: %v\tand c2: %v\n", c1a.DistanceRgb(c1b), c2a.DistanceRgb(c2b))
   169	fmt.Printf("DistanceLab:       c1: %v\tand c2: %v\n", c1a.DistanceLab(c1b), c2a.DistanceLab(c2b))
   170	fmt.Printf("DistanceLuv:       c1: %v\tand c2: %v\n", c1a.DistanceLuv(c1b), c2a.DistanceLuv(c2b))
   171	fmt.Printf("DistanceCIE76:     c1: %v\tand c2: %v\n", c1a.DistanceCIE76(c1b), c2a.DistanceCIE76(c2b))
   172	fmt.Printf("DistanceCIE94:     c1: %v\tand c2: %v\n", c1a.DistanceCIE94(c1b), c2a.DistanceCIE94(c2b))
   173	fmt.Printf("DistanceCIEDE2000: c1: %v\tand c2: %v\n", c1a.DistanceCIEDE2000(c1b), c2a.DistanceCIEDE2000(c2b))
   174}
   175```
   176
   177Running the above program shows that you should always prefer any of the CIE distances:
   178
   179```bash
   180$ go run colordist.go
   181DistanceRgb:       c1: 0.3803921568627451	and c2: 0.3858713931171159
   182DistanceLab:       c1: 0.32048458312798056	and c2: 0.24397151758565272
   183DistanceLuv:       c1: 0.5134369614199698	and c2: 0.2568692839860636
   184DistanceCIE76:     c1: 0.32048458312798056	and c2: 0.24397151758565272
   185DistanceCIE94:     c1: 0.19799168128511324	and c2: 0.12207136371167401
   186DistanceCIEDE2000: c1: 0.17274551120971166	and c2: 0.10665210031428465
   187```
   188
   189It also shows that `DistanceLab` is more formally known as `DistanceCIE76` and
   190has been superseded by the slightly more accurate, but much more expensive
   191`DistanceCIE94` and `DistanceCIEDE2000`.
   192
   193Note that `AlmostEqualRgb` is provided mainly for (unit-)testing purposes. Use
   194it only if you really know what you're doing. It will eat your cat.
   195
   196### Blending colors
   197Blending is highly connected to distance, since it basically "walks through" the
   198colorspace thus, if the colorspace maps distances well, the walk is "smooth".
   199
   200Colorful comes with blending functions in RGB, HSV and any of the LAB spaces.
   201Of course, you'd rather want to use the blending functions of the LAB spaces since
   202these spaces map distances well but, just in case, here is an example showing
   203you how the blendings (`#fdffcc` to `#242a42`) are done in the various spaces:
   204
   205![Blending colors in different spaces.](doc/colorblend/colorblend.png)
   206
   207What you see is that HSV is really bad: it adds some green, which is not present
   208in the original colors at all! RGB is much better, but it stays light a little
   209too long. LUV and LAB both hit the right lightness but LAB has a little more
   210color. HCL works in the same vein as HSV (both cylindrical interpolations) but
   211it does it right in that there is no green appearing and the lighthness changes
   212in a linear manner.
   213
   214While this seems all good, you need to know one thing: When interpolating in any
   215of the CIE color spaces, you might get invalid RGB colors! This is important if
   216the starting and ending colors are user-input or random. An example of where this
   217happens is when blending between `#eeef61` and `#1e3140`:
   218
   219![Invalid RGB colors may crop up when blending in CIE spaces.](doc/colorblend/invalid.png)
   220
   221You can test whether a color is a valid RGB color by calling the `IsValid` method
   222and indeed, calling IsValid will return false for the redish colors on the bottom.
   223One way to "fix" this is to get a valid color close to the invalid one by calling
   224`Clamped`, which always returns a nearby valid color. Doing this, we get the
   225following result, which is satisfactory:
   226
   227![Fixing invalid RGB colors by clamping them to the valid range.](doc/colorblend/clamped.png)
   228
   229The following is the code creating the above three images; it can be found in `doc/colorblend/colorblend.go`
   230
   231```go
   232package main
   233
   234import "fmt"
   235import "github.com/lucasb-eyer/go-colorful"
   236import "image"
   237import "image/draw"
   238import "image/png"
   239import "os"
   240
   241func main() {
   242    blocks := 10
   243    blockw := 40
   244    img := image.NewRGBA(image.Rect(0,0,blocks*blockw,200))
   245
   246    c1, _ := colorful.Hex("#fdffcc")
   247    c2, _ := colorful.Hex("#242a42")
   248
   249    // Use these colors to get invalid RGB in the gradient.
   250    //c1, _ := colorful.Hex("#EEEF61")
   251    //c2, _ := colorful.Hex("#1E3140")
   252
   253    for i := 0 ; i < blocks ; i++ {
   254        draw.Draw(img, image.Rect(i*blockw,  0,(i+1)*blockw, 40), &image.Uniform{c1.BlendHsv(c2, float64(i)/float64(blocks-1))}, image.Point{}, draw.Src)
   255        draw.Draw(img, image.Rect(i*blockw, 40,(i+1)*blockw, 80), &image.Uniform{c1.BlendLuv(c2, float64(i)/float64(blocks-1))}, image.Point{}, draw.Src)
   256        draw.Draw(img, image.Rect(i*blockw, 80,(i+1)*blockw,120), &image.Uniform{c1.BlendRgb(c2, float64(i)/float64(blocks-1))}, image.Point{}, draw.Src)
   257        draw.Draw(img, image.Rect(i*blockw,120,(i+1)*blockw,160), &image.Uniform{c1.BlendLab(c2, float64(i)/float64(blocks-1))}, image.Point{}, draw.Src)
   258        draw.Draw(img, image.Rect(i*blockw,160,(i+1)*blockw,200), &image.Uniform{c1.BlendHcl(c2, float64(i)/float64(blocks-1))}, image.Point{}, draw.Src)
   259
   260        // This can be used to "fix" invalid colors in the gradient.
   261        //draw.Draw(img, image.Rect(i*blockw,160,(i+1)*blockw,200), &image.Uniform{c1.BlendHcl(c2, float64(i)/float64(blocks-1)).Clamped()}, image.Point{}, draw.Src)
   262    }
   263
   264    toimg, err := os.Create("colorblend.png")
   265    if err != nil {
   266        fmt.Printf("Error: %v", err)
   267        return
   268    }
   269    defer toimg.Close()
   270
   271    png.Encode(toimg, img)
   272}
   273```
   274
   275#### Generating color gradients
   276A very common reason to blend colors is creating gradients. There is an example
   277program in [doc/gradientgen.go](doc/gradientgen/gradientgen.go); it doesn't use any API
   278which hasn't been used in the previous example code, so I won't bother pasting
   279the code in here. Just look at that gorgeous gradient it generated in HCL space:
   280
   281!["Spectral" colorbrewer gradient in HCL space.](doc/gradientgen/gradientgen.png)
   282
   283### Getting random colors
   284It is sometimes necessary to generate random colors. You could simply do this
   285on your own by generating colors with random values. By restricting the random
   286values to a range smaller than [0..1] and using a space such as CIE-H\*C\*l° or
   287HSV, you can generate both random shades of a color or random colors of a
   288lightness:
   289
   290```go
   291random_blue := colorful.Hcl(180.0+rand.Float64()*50.0, 0.2+rand.Float64()*0.8, 0.3+rand.Float64()*0.7)
   292random_dark := colorful.Hcl(rand.Float64()*360.0, rand.Float64(), rand.Float64()*0.4)
   293random_light := colorful.Hcl(rand.Float64()*360.0, rand.Float64(), 0.6+rand.Float64()*0.4)
   294```
   295
   296Since getting random "warm" and "happy" colors is quite a common task, there
   297are some helper functions:
   298
   299```go
   300colorful.WarmColor()
   301colorful.HappyColor()
   302colorful.FastWarmColor()
   303colorful.FastHappyColor()
   304```
   305
   306The ones prefixed by `Fast` are faster but less coherent since they use the HSV
   307space as opposed to the regular ones which use CIE-L\*C\*h° space. The
   308following picture shows the warm colors in the top two rows and happy colors
   309in the bottom two rows. Within these, the first is the regular one and the
   310second is the fast one.
   311
   312![Warm, fast warm, happy and fast happy random colors, respectively.](doc/colorgens/colorgens.png)
   313
   314Don't forget to initialize the random seed! You can see the code used for
   315generating this picture in `doc/colorgens/colorgens.go`.
   316
   317### Getting random palettes
   318As soon as you need to generate more than one random color, you probably want
   319them to be distinguishible. Playing against an opponent which has almost the
   320same blue as I do is not fun. This is where random palettes can help.
   321
   322These palettes are generated using an algorithm which ensures that all colors
   323on the palette are as distinguishible as possible. Again, there is a `Fast`
   324method which works in HSV and is less perceptually uniform and a non-`Fast`
   325method which works in CIE spaces. For more theory on `SoftPalette`, check out
   326[I want hue](http://tools.medialab.sciences-po.fr/iwanthue/theory.php). Yet
   327again, there is a `Happy` and a `Warm` version, which do what you expect, but
   328now there is an additional `Soft` version, which is more configurable: you can
   329give a constraint on the color space in order to get colors within a certain *feel*.
   330
   331Let's start with the simple methods first, all they take is the amount of
   332colors to generate, which could, for example, be the player count. They return
   333an array of `colorful.Color` objects:
   334
   335```go
   336pal1, err1 := colorful.WarmPalette(10)
   337pal2 := colorful.FastWarmPalette(10)
   338pal3, err3 := colorful.HappyPalette(10)
   339pal4 := colorful.FastHappyPalette(10)
   340pal5, err5 := colorful.SoftPalette(10)
   341```
   342
   343Note that the non-fast methods *may* fail if you ask for way too many colors.
   344Let's move on to the advanced one, namely `SoftPaletteEx`. Besides the color
   345count, this function takes a `SoftPaletteSettings` object as argument. The
   346interesting part here is its `CheckColor` member, which is a boolean function
   347taking three floating points as arguments: `l`, `a` and `b`. This function
   348should return `true` for colors which lie within the region you want and `false`
   349otherwise. The other members are `Iteration`, which should be within [5..100]
   350where higher means slower but more exact palette, and `ManySamples` which you
   351should set to `true` in case your `CheckColor` constraint rejects a large part
   352of the color space.
   353
   354For example, to create a palette of 10 brownish colors, you'd call it like this:
   355
   356```go
   357func isbrowny(l, a, b float64) bool {
   358    h, c, L := colorful.LabToHcl(l, a, b)
   359    return 10.0 < h && h < 50.0 && 0.1 < c && c < 0.5 && L < 0.5
   360}
   361// Since the above function is pretty restrictive, we set ManySamples to true.
   362brownies := colorful.SoftPaletteEx(10, colorful.SoftPaletteSettings{isbrowny, 50, true})
   363```
   364
   365The following picture shows the palettes generated by all of these methods
   366(sourcecode in `doc/palettegens/palettegens.go`), in the order they were presented, i.e.
   367from top to bottom: `Warm`, `FastWarm`, `Happy`, `FastHappy`, `Soft`,
   368`SoftEx(isbrowny)`. All of them contain some randomness, so YMMV.
   369
   370![All example palettes](doc/palettegens/palettegens.png)
   371
   372Again, the code used for generating the above image is available as [doc/palettegens/palettegens.go](https://github.com/lucasb-eyer/go-colorful/blob/master/doc/palettegens/palettegens.go).
   373
   374### Sorting colors
   375TODO: Sort using dist fn.
   376
   377### Using linear RGB for computations
   378There are two methods for transforming RGB<->Linear RGB: a fast and almost precise one,
   379and a slow and precise one.
   380
   381```go
   382r, g, b := colorful.Hex("#FF0000").FastLinearRgb()
   383```
   384
   385TODO: describe some more.
   386
   387### Want to use some other reference point?
   388
   389```go
   390c := colorful.LabWhiteRef(0.507850, 0.040585,-0.370945, colorful.D50)
   391l, a, b := c.LabWhiteRef(colorful.D50)
   392```
   393
   394### Reading and writing colors from databases
   395
   396The type `HexColor` makes it easy to store colors as strings in a database. It
   397implements the [https://godoc.org/database/sql#Scanner](database/sql.Scanner)
   398and [database/sql/driver.Value](https://godoc.org/database/sql/driver.Value)
   399interfaces which provide automatic type conversion.
   400
   401Example:
   402
   403```go
   404var hc HexColor
   405_, err := db.QueryRow("SELECT '#ff0000';").Scan(&hc)
   406// hc == HexColor{R: 1, G: 0, B: 0}; err == nil
   407```
   408
   409FAQ
   410===
   411
   412### Q: I get all f!@#ed up values! Your library sucks!
   413A: You probably provided values in the wrong range. For example, RGB values are
   414expected to reside between 0 and 1, *not* between 0 and 255. Normalize your colors.
   415
   416### Q: Lab/Luv/HCl seem broken! Your library sucks!
   417They look like this:
   418
   419<img height="150" src="https://user-images.githubusercontent.com/3779568/28646900-6548040c-7264-11e7-8f12-81097a97c260.png">
   420
   421A: You're likely trying to generate and display colors that can't be represented by RGB,
   422and thus monitors. When you're trying to convert, say, `HCL(190.0, 1.0, 1.0).RGB255()`,
   423you're asking for RGB values of `(-2105.254  300.680  286.185)`, which clearly don't exist,
   424and the `RGB255` function just casts these numbers to `uint8`, creating wrap-around and
   425what looks like a completely broken gradient. What you want to do, is either use more
   426reasonable values of colors which actually exist in RGB, or just `Clamp()` the resulting
   427color to its nearest existing one, living with the consequences:
   428`HCL(190.0, 1.0, 1.0).Clamp().RGB255()`. It will look something like this:
   429
   430<img height="150" src="https://user-images.githubusercontent.com/1476029/29596343-9a8c62c6-8771-11e7-9026-b8eb8852cc4a.png">
   431
   432[Here's an issue going in-depth about this](https://github.com/lucasb-eyer/go-colorful/issues/14),
   433as well as [my answer](https://github.com/lucasb-eyer/go-colorful/issues/14#issuecomment-324205385),
   434both with code and pretty pictures. Also note that this was somewhat covered above in the
   435["Blending colors" section](https://github.com/lucasb-eyer/go-colorful#blending-colors).
   436
   437### Q: In a tight loop, conversion to Lab/Luv/HCl/... are slooooow!
   438A: Yes, they are.
   439This library aims for correctness, readability, and modularity; it wasn't written with speed in mind.
   440A large part of the slowness comes from these conversions going through `LinearRgb` which uses powers.
   441I implemented a fast approximation to `LinearRgb` called `FastLinearRgb` by using Taylor approximations.
   442The approximation is roughly 5x faster and precise up to roughly 0.5%,
   443the major caveat being that if the input values are outside the range 0-1, accuracy drops dramatically.
   444You can use these in your conversions as follows:
   445
   446```go
   447col := // Get your color somehow
   448l, a, b := XyzToLab(LinearRgbToXyz(col.LinearRgb()))
   449```
   450
   451If you need faster versions of `Distance*` and `Blend*` that make use of this fast approximation,
   452feel free to implement them and open a pull-request, I'll happily accept.
   453
   454The derivation of these functions can be followed in [this Jupyter notebook](doc/LinearRGB Approximations.ipynb).
   455Here's the main figure showing the approximation quality:
   456
   457![approximation quality](doc/approx-quality.png)
   458
   459More speed could be gained by using SIMD instructions in many places.
   460You can also get more speed for specific conversions by approximating the full conversion function,
   461but that is outside the scope of this library.
   462Thanks to [@ZirconiumX](https://github.com/ZirconiumX) for starting this investigation,
   463see [issue #18](https://github.com/lucasb-eyer/go-colorful/issues/18) for details.
   464
   465### Q: Why would `MakeColor` ever fail!?
   466A: `MakeColor` fails when the alpha channel is zero. In that case, the
   467conversion is undefined. See [issue 21](https://github.com/lucasb-eyer/go-colorful/issues/21)
   468as well as the short caveat note in the ["The `color.Color` interface"](README.md#the-colorcolor-interface)
   469section above.
   470
   471Who?
   472====
   473
   474This library was developed by Lucas Beyer with contributions from
   475Bastien Dejean (@baskerville), Phil Kulak (@pkulak) and Christian Muehlhaeuser (@muesli).
   476
   477It is now maintained by makeworld (@makeworld-the-better-one).
   478
   479
   480## License
   481
   482This repo is under the MIT license, see [LICENSE](LICENSE) for details.

View as plain text