<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTML5 Canvas Generative Art</title>

<style type="text/css">
	
</style>

</head>
<body>
<div id="container"> 
    <canvas id="displayCanvas" width="1024px" height="576px">
        Your browser does not support HTML5 canvas.
    </canvas>
    <form>
    <p id="caption">
        HTML5 Canvas - Morphing Fractal Curves, version 2.
        <input type="button", id="btnRegenerate" value="regenerate"/>
    	<input type="button", id="btnExport" value="export image (png)"/>
        <br><a href="http://www.rectangleworld.com">rectangleworld.com</a>
    </p>
    </form>
</div>
</body>
</html>
body {background-color:#000000; color:#333333;} 
	h4 {font-family: sans-serif; color:#333333; font-size:16px;}
	h3 {font-family: sans-serif; color:#333333;}
	p {font-family: sans-serif; color:#333333; font-size:14px;}
		#caption {position:absolute; width:1024px; text-align:center; top:520px; z-index:1}
	a {font-family: sans-serif; color:#d15423; text-decoration:none;}
	canvas {}
		#displayCanvas {position:absolute; top:10px; z-index:0;}
	div {}
		#container {width:1024px; height:576px; margin:auto;}
window.addEventListener("load", windowLoadHandler, false);

//for debug messages while testing code
var Debugger = function() { };
Debugger.log = function(message) {
	try {
		console.log(message);
	}
	catch (exception) {
		return;
	}
}

function windowLoadHandler() {
	canvasApp();
}

function canvasSupport() {
	return Modernizr.canvas;
}

function canvasApp() {
	if (!canvasSupport()) {
		return;
	}
	
	var displayCanvas = document.getElementById("displayCanvas");
	var context = displayCanvas.getContext("2d");
	var displayWidth = displayCanvas.width;
	var displayHeight = displayCanvas.height;
	
	//off screen canvas used only when exporting image
	var exportCanvas = document.createElement('canvas');
	exportCanvas.width = displayWidth;
	exportCanvas.height = displayHeight;
	var exportCanvasContext = exportCanvas.getContext("2d");
	
	//var exportImage = document.createElement('img');
	
	//buttons
	var btnExport = document.getElementById("btnExport");
	btnExport.addEventListener("click", exportPressed, false);
	
	var btnRegenerate = document.getElementById("btnRegenerate");
	btnRegenerate.addEventListener("click", regeneratePressed, false);
	
	var numCircles;
	var maxMaxRad;
	var minMaxRad;
	var minRadFactor;
	var circles;
	var iterations;
	var numPoints;
	var timer;
	var drawsPerFrame;
	var drawCount;
	var bgColor,urlColor;
	var lineWidth;
	var colorParamArray;
	var colorArray;
	var dataLists;
	var minX, maxX, minY, maxY;
	var xSpace, ySpace;
	var lineNumber;
	var twistAmount;
	var fullTurn;
	var lineAlpha;
	var maxColorValue;
	var minColorValue;
	
	init();
	
	function init() {
		numCircles = 15; //35
		maxMaxRad = 200;
		minMaxRad = 200;
		minRadFactor = 0;
		iterations = 11;
		numPoints = Math.pow(2,iterations)+1;
		drawsPerFrame = 4;
		
		fullTurn = Math.PI*2*numPoints/(1+numPoints);
		
		minX = -maxMaxRad;
		maxX = displayWidth + maxMaxRad;
		minY = displayHeight/2-50;
		maxY = displayHeight/2+50;
		
		twistAmount = 0.67*Math.PI*2;
		
		stepsPerSegment = Math.floor(800/numCircles);
		
		maxColorValue = 100;
		minColorValue = 20;
		lineAlpha = 0.10;
		
		bgColor = "#000000";
		urlColor = "#333333";
		
		lineWidth = 1.01;
		
		startGenerate();
	}
		
	function startGenerate() {
		drawCount = 0;
		context.setTransform(1,0,0,1,0,0);
		
		context.clearRect(0,0,displayWidth,displayHeight);
		
		setCircles();
		
		colorArray = setColorList(iterations);
				
		lineNumber = 0;
		
		if(timer) {clearInterval(timer);}
		timer = setInterval(onTimer,1000/60);
		
	}
	
	function setColorList(iter) {
		var r0,g0,b0;
		var r1,g1,b1;
		var r2,g2,b2;
		var param;
		var colorArray;
		var lastColorObject;
		var i, len;
		
		var maxComponentDistance = 32;
		var maxComponentFactor = 0.5;
		
		
		r0 = minColorValue + Math.random()*(maxColorValue-minColorValue);
		g0 = minColorValue + Math.random()*(maxColorValue-minColorValue);
		b0 = minColorValue + Math.random()*(maxColorValue-minColorValue);;
		
		r1 = minColorValue + Math.random()*(maxColorValue-minColorValue);
		g1 = minColorValue + Math.random()*(maxColorValue-minColorValue);
		b1 = minColorValue + Math.random()*(maxColorValue-minColorValue);
		
		
		/*
		//can also set colors explicitly here if you like.
		r1 = 90;
		g1 = 60;
		b1 = 20;
		
		r0 = 30;
		g0 = 77;
		b0 = 66;
		*/
		
		a = lineAlpha;
		
		var colorParamArray = setLinePoints(iter);
		colorArray = [];
		
		len = colorParamArray.length;
		
		for (i = 0; i < len; i++) {
			param = colorParamArray[i];
			
			r = Math.floor(r0 + param*(r1 - r0));
			g = Math.floor(g0 + param*(g1 - g0));
			b = Math.floor(b0 + param*(b1 - b0));
				
			var newColor = "rgba("+r+","+g+","+b+","+a+")";
			
			colorArray.push(newColor);
		}
		
		return colorArray;
		
	}
	
	function setCircles() {
		var i;
		var r,g,b,a;
		var grad;
		
		circles = [];
		
		for (i = 0; i < numCircles; i++) {
			maxR = minMaxRad+Math.random()*(maxMaxRad-minMaxRad);
			minR = minRadFactor*maxR;
			
			var newCircle = {
				centerX: minX + i/(numCircles-1)*(maxX - minX),
				centerY: minY + i/(numCircles-1)*(maxY - minY),
				//centerY: minY + Math.random()*(maxY - minY),
				maxRad : maxR,
				minRad : minR,
				phase : i/(numCircles-1)*twistAmount,
				pointArray : setLinePoints(iterations)
				};
			circles.push(newCircle);
		}
	}
	
	function onTimer() {
		var i;
		var cosTheta, sinTheta;
		var theta;
		
		var numCircles = circles.length;

		var linParam;
		var cosParam;
		var centerX, centerY;
		var xSqueeze = 0.75;
		var x0,y0;
		var rad, rad0, rad1;
		var phase, phase0, phase1;
		
		for (var k = 0; k < drawsPerFrame; k++) {
		
			theta = lineNumber/(numPoints-1)*fullTurn;
			
			context.globalCompositeOperation = "lighter";
			
			context.lineJoin = "miter";
			
			context.strokeStyle = colorArray[lineNumber];
			context.lineWidth = lineWidth;
			context.beginPath();
			
			//move to first point
			centerX = circles[0].centerX;
			centerY = circles[0].centerY;
			rad = circles[0].minRad + circles[0].pointArray[lineNumber]*(circles[0].maxRad - circles[0].minRad);
			phase = circles[0].phase;
			x0 = centerX + xSqueeze*rad*Math.cos(theta + phase);
			y0 = centerY + rad*Math.sin(theta + phase);
			context.moveTo(x0,y0);
			
			for (i=0; i< numCircles-1; i++) {
				//draw between i and i+1 circle
				rad0 = circles[i].minRad + circles[i].pointArray[lineNumber]*(circles[i].maxRad - circles[i].minRad);
				rad1 = circles[i+1].minRad + circles[i+1].pointArray[lineNumber]*(circles[i+1].maxRad - circles[i+1].minRad);
				phase0 = circles[i].phase;
				phase1 = circles[i+1].phase;
				
				for (j = 0; j < stepsPerSegment; j++) {
					linParam = j/(stepsPerSegment-1);
					cosParam = 0.5-0.5*Math.cos(linParam*Math.PI);
					
					//interpolate center
					centerX = circles[i].centerX + linParam*(circles[i+1].centerX - circles[i].centerX);
					centerY = circles[i].centerY + cosParam*(circles[i+1].centerY - circles[i].centerY);
					
					//interpolate radius
					rad = rad0 + cosParam*(rad1 - rad0);
					
					//interpolate phase
					phase = phase0 + cosParam*(phase1 - phase0);
					
					x0 = centerX + xSqueeze*rad*Math.cos(theta + phase);
					y0 = centerY + rad*Math.sin(theta + phase);
					
					context.lineTo(x0,y0);
					
				}
				
			}
			
			context.stroke();
					
			lineNumber++;
			if (lineNumber > numPoints-1) {
				clearInterval(timer);
				timer = null;
				break;
			}
		}
	}
		
	//Here is the function that defines a noisy (but not wildly varying) data set which we will use to draw the curves.
	//We first define the points in a linked list, but then store the values in an array.
	function setLinePoints(iterations) {
		var pointList = {};
		var pointArray = [];
		pointList.first = {x:0, y:1};
		var lastPoint = {x:1, y:1}
		var minY = 1;
		var maxY = 1;
		var point;
		var nextPoint;
		var dx, newX, newY;
		var ratio;
		
		var minRatio = 0.5;
				
		pointList.first.next = lastPoint;
		for (var i = 0; i < iterations; i++) {
			point = pointList.first;
			while (point.next != null) {
				nextPoint = point.next;
				
				dx = nextPoint.x - point.x;
				newX = 0.5*(point.x + nextPoint.x);
				newY = 0.5*(point.y + nextPoint.y);
				newY += dx*(Math.random()*2 - 1);
				
				var newPoint = {x:newX, y:newY};
				
				//min, max
				if (newY < minY) {
					minY = newY;
				}
				else if (newY > maxY) {
					maxY = newY;
				}
				
				//put between points
				newPoint.next = nextPoint;
				point.next = newPoint;
				
				point = nextPoint;
			}
		}
		
		//normalize to values between 0 and 1
		//Also store y values in array here.
		if (maxY != minY) {
			var normalizeRate = 1/(maxY - minY);
			point = pointList.first;
			while (point != null) {
				point.y = normalizeRate*(point.y - minY);
				pointArray.push(point.y);
				point = point.next;
			}
		}
		//unlikely that max = min, but could happen if using zero iterations. In this case, set all points equal to 1.
		else {
			point = pointList.first;
			while (point != null) {
				point.y = 1;
				pointArray.push(point.y);
				point = point.next;
			}
		}
				
		return pointArray;		
	}
		
	function exportPressed(evt) {
		//background - otherwise background will be transparent.
		exportCanvasContext.fillStyle = bgColor;
		exportCanvasContext.fillRect(0,0,displayWidth,displayHeight);
		
		//draw
		exportCanvasContext.drawImage(displayCanvas, 0,0,displayWidth,displayHeight,0,0,displayWidth,displayHeight);
		
		//add printed url to image
		exportCanvasContext.fillStyle = urlColor;
		exportCanvasContext.font = 'bold italic 16px Helvetica, Arial, sans-serif';
		exportCanvasContext.textBaseline = "top";
		var metrics = exportCanvasContext.measureText("rectangleworld.com");
		exportCanvasContext.fillText("rectangleworld.com", displayWidth - metrics.width - 10, 5);
		
		//we will open a new window with the image contained within:		
		//retrieve canvas image as data URL:
		var dataURL = exportCanvas.toDataURL("image/png");
		//open a new window of appropriate size to hold the image:
		var imageWindow = window.open("", "fractalLineImage", "left=0,top=0,width="+displayWidth+",height="+displayHeight+",toolbar=0,resizable=0");
		//write some html into the new window, creating an empty image:
		imageWindow.document.write("<title>Export Image</title>")
		imageWindow.document.write("<img id='exportImage'"
									+ " alt=''"
									+ " height='" + displayHeight + "'"
									+ " width='"  + displayWidth  + "'"
									+ " style='position:absolute;left:0;top:0'/>");
		imageWindow.document.close();
		//copy the image into the empty img in the newly opened window:
		var exportImage = imageWindow.document.getElementById("exportImage");
		exportImage.src = dataURL;
	}
	
	function regeneratePressed(evt) {
		startGenerate();
	}
	
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.