lucasb-eyer / go-colorful

A library for playing with colors in go (golang).
MIT License
1.16k stars 59 forks source link

Hcl seems to be broken #14

Closed mbertschler closed 7 years ago

mbertschler commented 7 years ago

Hi and thanks for this libary, it looks pretty useful! I generated a gradient over the hues 0 to 360 with the Hcl function, and the output looks wrong. Using the Hsl or Hsv functions causes no problems. Here is my rainbow gradient:

screen shot 2017-07-27 at 00 41 51

The code I used to produce this image using a HTML file that I opened in my browser:

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "os"

    "github.com/lucasb-eyer/go-colorful"
)

func main() {
    var out = `<html><head></head><body>`
    for hue := 0; hue < 360; hue++ {
        col := colorful.Hsv(float64(hue), 1, 1)
        r, g, b := col.RGB255()
        out += fmt.Sprintf(`<div style="width:1000px;height:2px;`+
            `background-color:rgb(%d,%d,%d)"></div>`, r, g, b)
    }
    out += "</body></html>"
    err := ioutil.WriteFile(os.ExpandEnv("$HOME/tmp/rgbdebug.html"), []byte(out), 0644)
    if err != nil {
        log.Println(err)
    }
}
lucasb-eyer commented 7 years ago

Hi, thanks for the great report with example code and picture, and sorry for the long delay in answering!

This is actually Working As Intended™. The reason is that most of the colors you are trying to compute here don't actually exist. When you're asking to generate a color of hue, say 190° and both chroma and luminosity of 1.0, the corresponding RGB values would be -8.25590, 1.17914, 1.12230, meaning such a color is not representable in the RGB system (and a computer screen).

If you go for less extreme values of luminance and chroma, you will see more and more of the gradient appear. For some values, such as (0.35,0.65), the full gradient exists, and you'll get a nice picture like this:

If you really, really want to have a gradient of higher perceived chroma or luminance, though, there's an ugly workaround that you can do: clamp these non-representable colors to the closes representable one. This will not be entirely correct (converting the gradient to grayscale will show variations in brightness), but depending on your application, it is Good Enough™. I have actually specifically implemented the Clamped function for that purpose, and called it out in the README's "Blending colors" section. I'll add an extra entry to the FAQ, do you have any other suggestion on making it more prominent?

So with that, simply change your code from col.RGB255() to col.Clamped().RGB255() and your demo produces gradients for any values, here is one for (0.8, 0.8), which I also converted to grayscale so you see the non-constant lightness:

mbertschler commented 7 years ago

@lucasb-eyer thank you for this detailed answer, the behavior makes sense. I already saw the added FAQ section, maybe you could also add a warning to the comments for the functions where color values outside of the RGB range could be returned? This way you would already see that this could happen before getting frustrated and reading the FAQ.

lucasb-eyer commented 7 years ago

Good idea, thanks. Done!