Personal and Professional Blog of Rich Hauck

Perlin Noise in HTML5 Canvas

January 31, 2012

I was experimenting with Perlin Noise for an underwater effect on a website. I posted the results here, since there doesn’t seem to be an example online of what I wanted.

Not surprisingly, I canned the idea, as it’s simply too processor-intensive for aesthetics. It made for an interesting exercise in canvas, though.

Coming from an ActionScript background, I really wish I better knew how the “offset” array was implemented in the ActionScript call, as it’s not a part of Perlin’s original class:

perlinNoise(baseX:Number, baseY:Number, numOctaves:uint, randomSeed:int, stitch:Boolean, fractalNoise:Boolean, channelOptions:uint = 7, grayScale:Boolean = false, offsets:Array = null):void
Generates a Perlin noise image.

Fortunately, I didn’t have to port the original code to JavaScript, as I found it on GitHub. In the ActionScript world, I’d increment the offset over time to create an organic-looking water effect. For HTML5 canvas, I found this could be replicated by incrementing the z argument (zOff) in “classical” noise:

var result = perlin.noise(xVal, yVal, zOff);

Here’s the result.

Categories: ActionScript, art, canvas, Flash, HTML5

Brownian Motion in HTML5 Canvas

January 20, 2012

Brownian Motion

Ya know, cause I couldn’t find it anywhere (Lovingly stolen from Processing documentation). View the example.

I’m hoping to convert Craig Reynolds’ Boids to JavaScript/Canvas at some point in the future, too.

window.addEventListener('load', init, false);
function init(){
	var canvas = document.getElementById("canvas");
	if (canvas.getContext('2d')){
  		main(canvas);
	} else {
    	console.log("Canvas tag is not supported");
	}
}
function main(canvas){
	var canvas = canvas;
	var ctx = canvas.getContext("2d");
	const CANVAS_WIDTH = canvas.width;
	const CANVAS_HEIGHT = canvas.height;

	var num = 10;
	var range = 200;

	var ax = [];
	var ay = [];

	for(var i = 0; i < num; i++) {
	  ax[i] = CANVAS_WIDTH/2;
	  ay[i] = CANVAS_HEIGHT/2;
	}

	setInterval(draw, 30);

	function draw(){
		// Shift all elements 1 place to the left
		  for(var i = 1; i < num; i++) {
		    ax[i-1] = ax[i];
		    ay[i-1] = ay[i];
		  }
		  // Put a new value at the end of the array
		  var r2x = range*2;
		  ax[num-1] += (Math.random()*r2x)-range;
		  ay[num-1] += (Math.random()*r2x)-range;

		  // Constrain all povars to the screen
		  ax[num-1] = constrain(ax[num-1], 0, CANVAS_WIDTH);
		  ay[num-1] = constrain(ay[num-1], 0, CANVAS_HEIGHT);

		  // Draw a line connecting the povars
		  for(var i = 1; i < num; i++) {

			var alpha = i/num;
			ctx.globalAlpha = i/num;

			ctx.moveTo(ax[i-1], ay[i-1]);
			ctx.lineTo(ax[i], ay[i]);
			ctx.stroke();
		  }
	}
	function constrain(val, min, max){
		if(val > max){
			val = max;
		}else if(val < min){
			val = min;
		}
		return val;
	}
}
Categories: canvas, HTML5

Revisiting Interactive Media

January 16, 2012
fibonacci

Playing around with the Fibonacci number in Canvas. Click image for example. Code below.

I recently read a CNN op-ed piece by my former instructor, Doug Rushkoff, on how coding should be taught in every school as a way to be more competitive in the global industry. I couldn’t agree more.

For the past two years I’ve taught an Interactive Media course at Harrisburg Area Community College that has traditionally been taught in Adobe Flash. This semester, I decided to revisit the medium in which to teach interactive media, especially now that web-based Flash is being replaced with web standards technology like HTML, CSS, and JavaScript.

I haven’t completely replaced Flash in the course, as it remains a valuable tool for complex animation, video, and games. I have, however, decided to begin with the fundamentals of programming, and feel that the Flash IDE has too many distractions to serve as the starting medium.

I gave a lot of thought towards Processing, provided it’s easy-to-approach IDE, its large community, and its excellent documentation. In the end, though, I decided on HTML5 Canvas, as my students will likely end up using javascript instead of Java.

To a more selfish end, it’s forced me to better grasp canvas and JavaScript. I purposely chose a medium I wanted to learn, as I’m already comfortable with Flash and doubt I will be revisiting Java.

I’m going to try and start posting more code, such as this for the Fibonacci example:

window.addEventListener('load', init, false);
var canvas;
/**
* Detects for context 2d
*/
function init(){
	canvas = document.getElementById("myCanvas");
	if (canvas.getContext('2d')){
  		main(canvas);
		initSliders();
	} else {
    	console.log("Canvas not supported");
	}
}
/**
* Initializes sliders
*/
function initSliders(){
	var radRange = document.getElementById("radRange");
	radRange.addEventListener('change', function(){showOutput(radRange, "rangeOuput")}, false);
	document.getElementById("rangeOuput").innerHTML = radRange.value;

	var distRange = document.getElementById("distRange");
	distRange.addEventListener('change',  function(){showOutput(distRange, "distOuput")}, false);
	document.getElementById("distOuput").innerHTML = distRange.value;

	/**
	* Displays output to text fields.
	* @param slider		id of range input
	* @param field		id of span to write value.
	*/
	function showOutput(slider, field){
		document.getElementById(field).innerHTML = slider.value;
	}
}
/**
* Main
* @param canvas target canvas
*/
function main(canvas){
	var ctx = canvas.getContext("2d");

	var max = 200; 						// number of items on screen
	var i = 0; 							// initialize for counter
	const fib = 1/1.618033989;			// Fibonacci Sequence
	var radius = 0; 					// initial radius
	var lastInputRadius;				// remembers previous state to determine if positioning needs changed
	var lastInputDistance;				// remembers previous state to determine if positioning needs changed

	var circles = [];		 			// Create Array to hold items

	const CANVAS_RIGHT = canvas.width;
	const CANVAS_BOTTOM = canvas.height;

	var interval = setInterval(draw, 30);

	/**
	* Draws pattern to screen
	*/
	function draw() {
		var inputRadius = document.getElementById("rangeOuput").innerHTML;
		var inputDistance = document.getElementById("distOuput").innerHTML;
		if(i < max){
			// create shape
			radius += inputRadius*fib;
			lastInputRadius = inputRadius;
			lastInputDistance = inputDistance;
		    var dist = i*inputDistance; //represents distance from object
		    var x = Math.cos(radius*Math.PI/180)*dist + CANVAS_RIGHT/2;
		    var y = Math.sin(radius*Math.PI/180)*dist + CANVAS_BOTTOM/2;
			var hex = rgbToHex(255-i,0,i);
		    circles[i] = new Circle(ctx, x, y, (i/max)*100, hex);
		    i++;
		}else{
			// if input has changed, reposition shape
			if(lastInputRadius != inputRadius || lastInputDistance != inputDistance){
				radius += inputRadius*fib;
				clear();
				for(var j = 0; j < max; j++){
					radius += inputRadius*fib;
				    var dist = j*inputDistance; //represents distance from object
				    var x = Math.cos(radius*Math.PI/180)*dist + CANVAS_RIGHT/2;
				    var y = Math.sin(radius*Math.PI/180)*dist + CANVAS_BOTTOM/2;
					var hex = rgbToHex(255-j,0,j);
					circles[j].render(x, y, (j/max)*100, hex);
				}
			}
		}
		lastInputRadius = inputRadius;
		lastInputDistance = inputDistance;
	}
	/**
	* Draws circle.
	*/
	function Circle(ctx, x, y, r, hex){
		this.ctx = ctx;
		this.centerX = x;
		this.centerY = y;
		this.radius = r;

		this.render = function render(x, y, r, hex){
			this.centerX = x;
			this.centerY = y;
			this.radius = r;

			this.ctx.beginPath();
			this.ctx.arc(this.centerX, this.centerY, this.radius, 0, 2*Math.PI, false);
			this.ctx.fillStyle = hex;
			this.ctx.fill();
			this.ctx.lineWidth = 2;
			this.ctx.strokeStyle = "black";
			this.ctx.stroke();
		}
		this.render(this.centerX, this.centerY, this.radius, hex);
	}
	/**
	* Converts RGB values to hexidecimal
	* @param r 0-255 red value
	* @param g 0-255 green value
	* @param b 0-255 blue value
	*/
	function rgbToHex(r, g, b) {
		/**
		* Converts numeric value to base-16
		* @param c		target value
		*/
		function componentToHex(c) {
		    var hex = c.toString(16);
		    return hex.length == 1 ? "0" + hex : hex;
		}

	    return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
	}
	/**
	* Clears the canvas
	*/
	function clear(){
		ctx.clearRect(0, 0, CANVAS_RIGHT, CANVAS_BOTTOM);
	}
}

It’s been difficult not having a definitive API documentation like ActionScript and Processing have. The best I’ve found so far has been HTML5 Canvas Tutorials accompanied by a lot of O’Reilly JavaScript books. Coming from an ActionScript background, I’m still wrapping my head on how every function is an object, and how privatization, and event listening isn’t quite so baked in. I’m know the code above could be optimized more–eliminating globals, calculating x and y-positions once in draw(), etc., but it’s just a sketch.

I haven’t quite figured out how to structure my experiments on this blog, but my goal this year is posting more content, and spending less time examining distractions and vices.

Categories: canvas, HTML5, webdesign

About Me

Rich HauckI'm a designer, developer, and teacher based in Harrisburg, Pa. I run Hauck Interactive, Inc.




Archives