GLC Tumblr

Just thought I’d note that I set up a GLC tumbr account:

http://gifloopcoder.tumblr.com/

You can submit your own gifs to the page as well, and I’d love to see a nice range of stuff up there. Just make sure that the gif is under 2MB – the tumblr limit, not mine. And as far as I can tell, tumblr won’t credit the poster, so if you want credit, credit yourself in the description. It’s also nice to include or link to the code that created the gif (pastebin.com is nice for this) but not required.

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)

Single Mode

The default mode of GLC is “bounce”. This causes the animated properties of your objects to go from their starting values to their ending values, and then back again. All nice and smooth. Pretty hard to create a non-smoothly-looping animation with that default setup.

But once you switch over to “single” mode, things become more difficult. Most simple animations will look horrible because they’ll animate from one state to another, and then jump back to the initial state. Very jarring. But there are some tricks to making decent single mode animations, and once you get it, I’d say these animations are even superior to ones created in bounce mode.

The biggest single point to understand, is that for each object in the render list, what’s on screen at the start of the animation point has to match what’s on screen in that same location at the end of the animation.

Now at first glance, that seems impossible. You’re changing something from state A to state B. How could it be at state A when it ends at state B? Well, one example would just be to rotate something 360 degrees. Like this:

list.addRect({
    x: 100,
    y: 100,
    w: 100,
    h: 100,
    rotation: [0, 360]
});

single01

Since 360 degrees is the same as 0 degrees, we’re in the same state, and we have a smoothly running single mode animation.

Actually, because we have a square there, we can even rotate it to any multiple of 90 degrees and still have it wind up in the same visible state it starts at. It won’t technically BE in the same state, because 90 isn’t 0, but what you see on screen in that location will be the same.

list.addRect({
    x: 100,
    y: 100,
    w: 100,
    h: 100,
    rotation: [0, 90]
});

single02

But there are other tricks. Consider this: if an object starts in some state where you can’t see it, and ends in some other state where you can’t see it, that’s the same too. So we could have it start and end off screen.

list.addRect({
    x: [-50, 250],
    y: 100,
    w: 100,
    h: 100
});

single03

But off screen isn’t the only way something can be invisible. We could set the alpha down to zero or change the alpha channel of the color down to zero, or simply have the color change to match the background. In all of these cases, you won’t be able to see the object.

list.addRect({
    x: [-50, 100],
    y: 100,
    w: 100,
    h: 100,
    globalAlpha: [1, 0]
});

single04

In this last example, the square starts off invisible, because it’s off screen to the left. It ends invisible because its alpha is 0. So it’s all nice and smooth. You could reverse this and have it start off with zero alpha, and fade in as it moves off screen.

Or, you could reduce the size of the object to zero.

list.addRect({
    x: [-50, 100],
    y: 100,
    w: [100, 0],
    h: [100, 0],
});

single05

How about hiding it by moving it behind something?

list.addRect({
    x: [-50, 100],
    y: 100,
    w: 50,
    h: 50
});
list.addRect({
    x: 100,
    y: 100,
    w: 75,
    h: 200
});

single06

Here, there’s another rect in the middle of the screen. The first rect is hiding behind it so you don’t notice it snapping out of existence and returning to the starting point.

This last one is key because it gives us the starting point to the most powerful method of single mode animations – multiple objects. In this example, we have one object start where the other one ends.

list.addRect({
    x: [-50, 100],
    y: 100,
    w: 50,
    h: 50
});
list.addRect({
    x: [100, 250],
    y: 100,
    w: 50,
    h: 50
});

single07

Here we have the first square starting off screen to the left and moving to a position of 100, 100. And we have another square starting at 100, 100 and moving off screen to the right. At the end of the animation the first square winks out of existence and jumps back to its starting position on the left. At the same instant, the second square jumps back into the middle of the screen. But because these two things match up, you never see the change.

Now because we have easing on, it does stop there briefly in the middle of the screen. But that’s only due to the easing. If we turn off easing, we have a nice smooth transition.

single08

Realize that it doesn’t matter what the first square does or looks like before it reaches its end point. And it doesn’t matter what that second square changes into or where it goes after that initial state. As long as A’s end matches B’s start, we’re golden.

list.addRect({
    x: [-50, 100],
    y: 100,
    w: [20, 50],
    h: [200, 50],
    fillStyle: ["red", "black"]
});
list.addRect({
    x: [100, 250],
    y: [100, -50],
    w: 50,
    h: 50,
    fillStyle: ["black", "green"],
    rotation: [0, 360]
});

single09

Here, the first square starts out as a red rectangle. The second one turns green as it spins out through the top right corner. But at that transition point, they are the same, so it’s smooth.

Just like juggling, once you get two objects down, you can move to three.

list.addRect({
    x: [-50, 100],
    y: 100,
    w: [20, 50],
    h: [200, 50],
    fillStyle: ["red", "black"]
});
list.addRect({
    x: [100, 250],
    y: [100, 50],
    w: 50,
    h: 50,
    fillStyle: ["black", "green"],
    rotation: [0, 360]
});
list.addRect({
    x: 250,
    y: [50, 150],
    w: [50, 0],
    h: [50, 0],
    fillStyle: "green",
    globalAlpha: [1, 0]
});

single10

Here the second rectangle doesn’t go off screen, but it joins a third one that moves down as it shrinks and fades away.

Now we have two merge points where one object is swapping over with another. And we can chain as many as we want this way. But the first and last objects need to start and end invisible. Unless… they merge with each other.

list.addRect({
    x: [50, 150],
    y: 50,
    w: 50,
    h: 50,
});
list.addRect({
    x: 150,
    y: [50, 150],
    w: 50,
    h: 50,
});
list.addRect({
    x: [150, 50],
    y: 150,
    w: 50,
    h: 50,
});
list.addRect({
    x: 50,
    y: [150, 50],
    w: 50,
    h: 50,
});

single11

Here we have four squares. The first one moves left to right, where it joins with the second one, which moves down. This meets with the third, which moves back to the left, meeting the fourth that moves up. The fourth square ends where the first starts, so everything joins together.

And we can do whatever we want with any of these shapes as long as the end of one matches the start of the next one, all the way around.

list.addRect({
    x: [50, 150],
    y: 50,
    w: 50,
    h: 50,
    fillStyle: ["red", "blue"]
});
list.addRect({
    x: 150,
    y: [50, 150],
    w: 50,
    h: 50,
    fillStyle: ["blue", "green"],
    rotation: [0, 45]
});
list.addRect({
    x: [150, 50],
    y: 150,
    w: [50, 25],
    h: [50, 75],
    fillStyle: ["green", "orange"],
    rotation: [45, 360]
});
list.addRect({
    x: 50,
    y: [150, 50],
    w: [25, 50],
    h: [75, 50],
    fillStyle: ["orange", "red"]
});

single12

So, it takes a bit more work, thinking and planning than bounce mode, but the results are well worth it.

New Object: Cube

I added a new object to GLC earlier today. The cube. I actually had this one done before I released GLC at all, but held it back because it’s not as feature-complete as it could be. But in the end, I decided that even if it’s not all that it could be, it’s still a fun piece to have there.

Basically, the cube draws a wire-frame 3d cube. You can control its x, y and z position, its size and its rotation on the x, y and z-axes. You can’t fill it, but it respects all of the stroke properties – lineWidth, strokeStyle, etc. and can even be drawn with a shadow.

cubes

Not much more to say about it, but you can check out the docs for it, and have fun.

Max Colors and File Size

Two new features today, and you can see them both right here:

1.1

(Click to enlarge that.)

First, in the control panel, you’ll see a new slider below duration and fps. It’s Max Colors. Up to now, gifs were encoded with 256 colors. This was hard coded in one of the gif encoding modules that GLC uses. I didn’t write these, but did tweak them a bit to work with the system. They are mostly over my head, but I did figure out where this value was being set and went in there with a machete and hacked out a code trail to it and exposed that with a slider. The end result is that you can specify the maximum number of colors your final animated gif will have, anywhere from 2 to 256.

Note, sometimes those sliders can be difficult to get to land on an exact number. But once one is in focus, you can tweak it by single values using the cursor left and right keys on your keyboard.

Limiting the number of colors in the gif can have a big impact on the size of your output. Often at no visible loss of quality. Realize that changing this slider will not affect what you see in the Canvas panel AT ALL. Also, if you generate a gif, the slider won’t change that gif you’re looking at either. You’ll have to re-generate a new gif.

The other change, very much related to the first, is down at the bottom of the output panel. GLC will now estimate the gif size before you even save it. In my tests on Windows anyway, this estimation has actually been spot on. Different file systems may or may not make a difference, so I’ll leave the word “estimated” in there.

The combo of these two changes makes it possible to tweak your animation for size even more than you could with just fps and duration controls, and see the results before you even save the file. This can be vital if you’re trying to generate gifs for Tumblr, for example, which has a 2MB limit.

Factory Sample

factory

function onGLC(glc) {
    glc.loop();
    glc.size(300, 300);
    glc.setDuration(1.5);
    glc.setMode("single");
    glc.setEasing(false);

    var list = glc.renderList,
        width = glc.w,
        height = glc.h;

    var size = 50;
    for(var x = -size / 2; x < width / 2; x += size) {
        list.addRect({
            translationX: x,
            x: [0, size],
            y: size / 2,
            w: size * 0.8,
            h: size * 0.8
        })
    }
    for(var x = size / 2; x < width / 2 + size * 1.5; x += size) {
        console.log(x);
        list.addRect({
            translationX: x,
            x: [0, -size],
            y: height -size / 2,
            w: size * 0.8,
            h: size * 0.8
        })
    }
    list.addRect({
        translationX: width / 2 + size / 2,
        x: 0,
        y: [size / 2, height - size / 2],
        w: size * 0.8,
        h: size * 0.8,
        rotation: [0, 180]
    });
    list.addRect({
        translationX: width / 2 + size * 1.5,
        x: 0,
        y: [-size / 2, height / 2],
        w: size * 0.8,
        h: size * 0.8,
        rotation: [90, 0]
    });
    for(var x = width / 2 + size * 1.5; x <= width + size / 2; x += size) {
        console.log(x);
        list.addRect({
            translationX: x,
            x: [0, size],
            y: height / 2,
            w: size * 0.8,
            h: size * 0.8
        })
    }

    list.addRect({
        x: 0,
        y: size,
        w: width / 2,
        h: 10,
        drawFromCenter: false
    })
    list.addRect({
        x: width - size * 2 + 5,
        y: height / 2 + size / 2,
        w: size * 2,
        h: 10,
        drawFromCenter: false
    })
    list.addRect({
        x: 0,
        y: height - size - 10,
        w: width / 2 - 5,
        h: 10,
        drawFromCenter: false
    })

    list.addGear({
        x: 0,
        y: height / 2,
        radius: width * .3,
        rotation: [0, 90],
        teeth: 16
    })
    list.addGear({
        x: width,
        y: height * 0.75,
        radius: width * .15,
        rotation: [0, 90],
        teeth: 12
    })
}       

Flower Sample

flower

function onGLC(glc) {
	glc.loop();
	glc.size(300, 300);

	var list = glc.renderList,
		width = glc.w,
		height = glc.h;

	for(var i = 5; i < width; i += 1) {
		list.addStar({
			x: width / 2,
			y: height / 2,
			outerRadius: i,
			innerRadius: [i * 0.25, i * 0.76],
			lineWidth: 0.25,
			stroke: true,
			fill: false,
			rotation: [0, 360/12],
			phase: -i / width,
			points: 12
		})
	}
}		

Gear Hub Sample

hub

function onGLC(glc) {
    glc.loop();
    glc.size(300, 300);

    var list = glc.renderList,
        width = glc.w,
        height = glc.h;

    function gray(i) {
        var shade = Math.floor(255 - i / width * 256).toString(16);
        return "#" + shade + shade + shade;
    }

    for(var i = width / 2 - 10; i > 10; i -= 20) {
        list.addGear({
            x: width / 2,
            y: height / 2,
            radius: i,
            toothHeight: i * 0.2,
            rotation: [0, 90],
            phase: -i / width,
            fillStyle: gray(i), 
            phase: i / width,
            shadowColor: "#70000000",
            shadowOffsetX: 10,
            shadowOffsetY: 10,
            shadowBlur: 10,
            teeth: Math.round(i * 0.125)
        })
    }
}

Creating a grid layout

Sometimes you want to create a bunch of objects laid out in a grid. There are a few ways to do this. Here’s the easiest way I found.

We’ll start with the basic template. I’ll add a size variable and a double for loop that goes from 0 to height on the y-axis and 0 to width on x.

function onGLC(glc) {
    glc.loop();
    // glc.playOnce();
    // glc.size(400, 400);
    // glc.setDuration(5);
    // glc.setFPS(20);
    // glc.setMode("single");
    // glc.setEasing(false);
    var list = glc.renderList,
        width = glc.w,
        height = glc.h;

        var size = 50;
        for(var y = 0; y < height; y += size) {
        for(var x = 0; x < width; x += size) {

        }
    }
}


The size variable represents the size of one cell in the grid. Here, I’m making square cells, but you could have a different variable for each axis and make non-square cells as well.

The loop starts x and y at 0 and increments them by size each time. Now we can add an object.

var size = 50;
for(var y = 0; y < height; y += size) {
    for(var x = 0; x < width; x += size) {
        list.addCircle({
            translationX: x,
            translationY: y,
        });
    }
}

Using translationX and translationY moves the origin of the canvas to the top left of each grid cell on each iteration. Now you can just draw your object assuming that it’s in a size x size rectangle. No fancy math needed to calculate the x and y positions. Here, I’ll center the circle within that rectangle. And animate it’s radius to a max of size / 2

var size = 50;
for(var y = 0; y < height; y += size) {
    for(var x = 0; x < width; x += size) {
        list.addCircle({
            translationX: x,
            translationY: y,
            x: size / 2,
            y: size / 2,
            radius: [size / 4, size / 2]
        });
    }
}

Here’s what this gives us.

grid

Pretty simple. No fancy math needed. Now we can make things a bit more interesting by messing with the phase property.

var size = 50;
for(var y = 0; y < height; y += size) {
    for(var x = 0; x < width; x += size) {
        list.addCircle({
            translationX: x,
            translationY: y,
            x: size / 2,
            y: size / 2,
            radius: [size / 4, size / 2],
            phase: x / width + y / height
        });
    }
}


gridphase

And then, simply by changing the size variable to a smaller value, like 20, and no other changes, we get this:

gridphase2