New: the Color Module

I’m very excited about this new feature to GLC. To be fair, I was already proud of color management in GLC and did a lot of work on that aspect of it. It can handle pretty much any color string you can throw at it – 3- and 6-digit hex strings, rgb and rgba strings and named CSS color strings. It can also handle 8-digit hex strings, which native canvas cannot handle. And it can animate between any two colors in any of these formats. It does that by breaking down the colors into their component r, g, b and a channels, interpolating between them, and converting the result back into a color string. Not rocket science, but lots of details coded in there to make sure it all works smoothly.

However, I felt there was a need for a bit more flexibility in the area of creating colors. Sometimes I want a random color, or I need to calculate the values of the color channels mathematically, and convert that into a string (ironically, so that it can be converted back into a number to be animated – might be some room for some future optimization there). And, I had a feature request for hsv/hsb colors. So color improvements is something I’ve had on the list for a while.

About a year and a half ago, I’d created a JavaScript library called clrs, which did just about all of what I wanted to be in GLC. I was hoping I could just “require” it in there and have it work, but when I took a closer look I found that there was a lot of stuff in there that was inapplicable, a few things that were missing, and it actually just wasn’t compatible with GLC. But all of that was easily fixable. I ripped out a bunch of stuff, added some stuff, and refactored what was left. And so now we have a GLC color module.

You access the module with glc.color. However, like other GLC features, I’ve added an alias to the template:

color = glc.color;

So now you can just type “color” to get at the methods. Here are all the methods:

- color.rgb(r, g, b)
- color.rgba(r, g, b, a)
- color.gray(shade)
- color.randomRGB()
- color.randomRGB(min, max)
- color.randomGray()
- color.randomGray(min, max)
- color.num(number)
- color.hsv(h, s, v)
- color.animHSV(h, s, v)
- color.randomHSV(minH, maxH, minS, maxS, minV, maxV)

Let’s go through them.

color.rgb(255, 128, 0) does the same thing as "rgb(255, 128, 0)". The big difference is that using the latter, if you calculate one or more of the channel values mathematically, you’ll need to round those values to integers, then insert them into a string, like so:

fillStyle: "rgb(" + Math.round(red) + ", " + Math.round(green) + ", " + Math.round(blue) + ")"

As opposed to:

fillStyle: color.rgb(red, green, blue)

The same with color.rgba

To create a gray color, just pass a single value from 0-255 to the color.gray method:

fillStyle: color.gray(128)

Again, this saves you from rounding and stringifying it.

The random methods can be called with no parameters and create a completely random rgb color or random gray value:

strokeStyle: color.randomGray(),
fillStyle: color.randomRGB()

Or, you can pass in min and max values. For color.randomRGB(min, max), this ensures that each of the component channels will have a value between min and max. So you can create darker random colors:

fillStyle: color.randomRGB(0, 128)

Or lighter ones, or any range in between. The same goes for color.randomGray(min, max)

The last of the rgb methods is color.num(number). This just lets you pass in an integer that will be converted to a color string. This would most often be used with hex-formatted integers, like the following example, but any integer would work. Note, this only works for 24-bit numbers, no alpha.

fillStyle: color.num(0xff8000)

Now we get down to the hsv based functions. color.hsv(h, s, v) does exactly what you’d think it would do – creates a color based on hue, saturation and value (the same as hsb – hue, saturation and brightness). Hue is in terms of a number between 0 and 360 and saturation and value are both percentages in the range of 0 to 1.

fillStyle: color.hsv(30, 1, 1)

An important thing to note here is that this function takes your h, s and v values and immediately converts them to an rgb string. So, if you expect to animate between hsv values directly, you’ll be disappointed. Take this example:

function onGLC(glc) {
    glc.loop();
    glc.size(200, 200);
    glc.setDuration(5);
    glc.setMode("single");
    glc.setEasing(false);
    var list = glc.renderList,
        width = glc.w,
        height = glc.h,
        color = glc.color;

    list.addCircle({
        x: 100,
        y: 100,
        radius: 100,
        fillStyle: [color.hsv(0, 1, 1), color.hsv(360, 1, 1)]
    })
}

We’re animating the hue value from 0 to 360, so you might expect to see the circle go through all the colors of the spectrum. But instead, we get:

hsv1

The problem is color.hsv(0, 1, 1) and color.hsv(1, 1, 1) both convert into "#ff0000". So when GLC goes to animate them, that's what it animates between. The hue value is completely lost in the translation.

To satisfy that problem, there's another hsv function called color.animHSV(startH, endH, startS, endS, startV, endV). This allows you to specify a start and end value for h, s and v. These will be preserved across an animation, so you can actually animate across the spectrum. Here's that last example fixed to show this in use:

function onGLC(glc) {
    glc.loop();
    glc.size(200, 200);
    glc.setDuration(5);
    glc.setMode("single");
    glc.setEasing(false);
    var list = glc.renderList,
        width = glc.w,
        height = glc.h,
        color = glc.color;

    list.addCircle({
        x: 100,
        y: 100,
        radius: 100,
        fillStyle: color.animHSV(0, 360, 1, 1, 1, 1)
    });
}       

With this, hue, saturation, and value will be recalculated and interpolated on every frame, and you'll get an animation across the whole spectrum.

hsv2

Here, you'll notice I've set the duration to 5, the mode to bounce, and turned off easing, allowing you to see the transition more clearly.

Of course, you can use the same function to animate between a smaller portion of the spectrum:

function onGLC(glc) {
    glc.loop();
    glc.size(200, 200);
    var list = glc.renderList,
        width = glc.w,
        height = glc.h,
        color = glc.color;

    list.addCircle({
        x: 100,
        y: 100,
        radius: 100,
        fillStyle: color.animHSV(20, 60, 1, 1, 1, 1)
    })
}

Here, we are animating between hues of 20 (orangey-red) and 60 (yellowish).

hsv3

Of course, you can also animate with saturation and value as well.

And here's a more advanced use of hsv, to show what's possible:

function onGLC(glc) {
    glc.loop();
    var list = glc.renderList,
        width = glc.w,
        height = glc.h,
        color = glc.color;

    var res = 25;
    for(var y = 0; y < height; y += res) { 
        for(var x = 0; x < width; x += res) { 
            var dx = width / 2 - x - res / 2,
                dy = height / 2 - y - res / 2,
                dist = Math.sqrt(dx * dx + dy * dy);
            list.addCircle({
                translationX: x,
                translationY: y,
                x: res / 2,
                y: res / 2,
                radius: res / 2,
                fillStyle: color.animHSV(20, 60, 1, 1, 1, 1),
                phase: dist * 0.005
            })
        }
    }
}       

Here, we're creating a grid of 25-pixel wide circles, using the same hue range as before. I'm also calculating a dist variable, which is the distance from the center of each circle to the center of the canvas. This is then used to affect the phase value, causing the circles to cycle through their colors at different offsets based on how far from the center they are.

hsv4

Pretty neat! It would be much more difficult to make a nice animation between color values like that using just rgb.

Finally, there is color.randomHSV(minH, maxH, minS, maxS, minV, maxV). This requires minimum and maximum values for each of h, s and v. If you want a fixed value for any one of these, just pass in the same value for min and max. This can create some really nice random, yet coordinated color palettes.

function onGLC(glc) {
    glc.loop();
    var list = glc.renderList,
        width = glc.w,
        height = glc.h,
        color = glc.color;

    var res = 50;
    for(var y = 0; y < height; y += res) { 
        for(var x = 0; x < width; x += res) { 
            list.addCircle({
                translationX: x,
                translationY: y,
                x: res / 2,
                y: res / 2,
                radius: res / 2,
                fillStyle: color.randomHSV(90, 180, 1, 1, 1, 1),
            })
        }
    }
}       

hsv5

Here, the hue is random between 90 and 180, while both saturation and value are 1.

Or, you can keep the hue the same, and randomize the saturation and/or value, giving you some monochromatic type palettes.

function onGLC(glc) {
    glc.loop();
    var list = glc.renderList,
        width = glc.w,
        height = glc.h,
        color = glc.color;

    var res = 50;
    for(var y = 0; y < height; y += res) { 
        for(var x = 0; x < width; x += res) { 
            list.addCircle({
                translationX: x,
                translationY: y,
                x: res / 2,
                y: res / 2,
                radius: res / 2,
                fillStyle: color.randomHSV(240, 240, 0, 1, 0.5, 1),
            })
        }
    }
}       

hsv6

So, a whole lot of new stuff there. Hope you find it useful.

Update 11/24/15:

Per a request, there are now versions of the hsv functions that include an alpha channel.

fillStyle: color.hsva(h, s, v, a)

and

fillStyle: color.animHSVA(startH, endH, startS, endS, startV, endV, startA, endA)

Leave a Reply