Matthew Kaney at ITP

Color

Visual Language

Color Visualizer

For my color composition, I decided to play around with Two.js, a javascript graphics library. The visualization is a sort of spiky line graph that moves up and down, and as the lines move up and down, the height of the segments above the middle of the page determines the hue offset, saturation, or brightness.

My color composition is located at this link. Use the left and right arrow keys to change between hue, saturation, and brightness modes, and any other keys to change the base hue.

Code

Instead of drawing every frame (like Processing), Two.js allows you to create vector shapes and then modify the properties of those shapes over time. So, for this, I started by creating two sets of points: one set that ran along the midline of the screen at random distances, and a second set running along the bottom of the window, directly below the first set.

// Points is a 2d array: Row 0 holds all the points along the bottom
// of the screen. Row 1 holds the points that move up and down above
// the mid-line.
points = new Array();
points[0] = new Array();
points[1] = new Array();

// From left to right, pick random values between 0 and the width of
// the animated figure. Create two points at this horizontal position.
for(var i = 0; i <= width - 5; i += Math.random() * 10 + 5) {
	// These points are relative to the center of the screen
	points[0].push(new Two.Vector(i - width / 2, two.height / 2));
	points[1].push(new Two.Vector(i - width / 2, 0));
}

// Add the final two points, so the figure is the exact width of
// the variable 'width'.
points[0].push(new Two.Vector(width / 2, two.height / 2));
points[1].push(new Two.Vector(width / 2, 0));

Then, I created a Two.js object that manages all of the graphics, a container object, “group”, that centers the content

// Create a new Two.js object to hold all the artwork.
two = new Two({fullscreen: true}).appendTo(document.getElementById('home'));

// This group is a container for the art. Move it to the center of
// the screen.
group = new Two.Group();
two.add(group);
group.translation.set(two.width / 2, two.height / 2);

// Now, loop through each pair of vertical points in our points
// array. (That is, points[0][x] and points[1][x])
for(var i = 1; i < points[0].length; ++i) {
	// Make a polygon with this vertical pair of points and the
	// previous vertical pair of points.
	var poly = new Two.Polygon([points[0][i-1], points[1][i-1],
				    points[1][i], points[0][i]]);

	// Now, add this polygon to the container group
	group.add(poly);
}

From there, I keep track of a variable ‘mode’ that is set to 0, 1, or 2, for hue, saturation, and lightness. On arrow key presses, I switched out the mode, and updated the basic hue.

var mode;
var hue;

//This is triggered whenever the user presses a key
function switchMode (event) {
	// First, if left or right is pressed, adjust mode up or down
	if(event != null && event.keyCode == 39) {
		mode = (mode + 1) % 3;
	} else if(event != null && event.keyCode == 37) {
		mode = (mode + 2) % 3;
	}

	// Now, pick the default background color, based on the color mode
	if(mode == 0) {
		// For hue mode, pick a random hue
		hue = Math.random() * 280;
		backgroundColor = hue + ', 60%, 50%';
		var label = "hue";
	} else if(mode == 1) {
		// For saturation mode, medium gray
		hue = Math.random() * 360;
		backgroundColor = '0, 0%, 50%';
		label = "saturation";
	} else {
		// For brightness mode, black
		hue = Math.random() * 360;
		backgroundColor = '0, 0%, 0%';
		label = "brightness";
	}

	// Now, set the background to the background color
	document.getElementById('home').style.background = 'hsl(' + backgroundColor + ')';
}

With a random hue set, the only thing remaining was to update the position and color of the animated graphics. This code is called once per frame:

// Wobble all the points in the array points[1] up and down.
for(var i = 0; i < points[1].length; ++i) {
	// First, use a sine wave to move points up and down
	points[1][i].y = (Math.sin(frameCount * 0.05 + points[1][i].x * 80) *
			 (maxLength - minLength) / 2 + (maxLength + minLength) / 2) * -1;

	// Now, scale the movement of those points based on their distance from
	// the horizontal center, giving us nice high peaks in the middle, tapering
	// off to the sides.
	points[1][i].y *= Math.abs(Math.abs(points[1][i].x / (width / 2)) - 1);
}

// Because we've modified the points, their associated shapes have already updated.

// All that we need to do now is modify the colors for each polygon.
for(var polyName in group.children) {
	// Iterate through each polygon in the group.
	var poly = group.children[polyName];

	// The polygon has two heights: the height of its left side and its
	// right side. Calculate the average height.
	var avgLength = ((-poly.vertices['1'].y - poly.vertices['2'].y) / 2) / maxLength;

	// Now, use that average height to compute either hue offset, saturation,
	// or lightness, depending on the color mode.
	if(mode == 0) {
		var color = 'hsl(' + (hue + avgLength * 80) + ', 60%, 50%)';
	} else if(mode == 1) {
		var color = 'hsl(' + hue + ', ' + avgLength * 100 + '%, 50%)';
	} else {
		var color = 'hsl(' + hue + ', 60%, ' + avgLength * 100 + '%)';
	}

	// Fill and stroke the polygon with this color.
	poly.fill = color;
	poly.stroke = color;
}