Mar 102010
 

Here’s a simple paint program I made using canvas. It’s a pretty blatent ripoff of another one that a friend showed me today, but unfortunately I don’t have the URL handy. (Sorry, I’ll update in the morning!) The framework makes it pretty simple for you to add your own tools. Click on for source.

Update: Harmony was the inspiration I was looking for!

JPLT.Class.create("JPLT.PaintTool", JPLT.Object,
	function() {
		this.isPainting = false;
		this.x = 0;
		this.y = 0;
		this.d = 0;
		this.a = 0;
		this.isPainting = false;
	},
	
	{
		mouseMoved: function(e) {
			var x2 = e.clientX;
			var y2 = e.clientY;
			this.oldx = this.x;
			this.oldy = this.y;
			this.dy = y2-this.y;
			this.dx = x2-this.x;
			this.d = Math.sqrt(Math.pow(this.dx,2)+Math.pow(this.dy,2));		
			this.a = Math.atan2(this.dy,this.dx);
			this.x = x2;
			this.y = y2;
		},
	
		mousePressed: function() {
			this.isPainting = true;
		},
	
		mouseReleased: function() {
			this.isPainting = false;
		},
	
		paint: function(ctx) {
		}
	}
);

JPLT.Class.create("JPLT.PaintTool.InkBlob", JPLT.PaintTool,
	function() {
		this.superConstruct()
		this.radius = 1;
	},
	{
		mouseMoved:function(e) {
			this.superCall("mouseMoved",e);
			
			if (this.radius > 1) {
				this.radius = Math.max(this.radius-this.d/5,1);				
			}
		},
		
		mouseReleased:function(e) {
			this.superCall("mouseReleased",e);
			
			this.radius = 1;
		},
		
		paint: function(ctx) {
			if (this.isPainting) {
				ctx.fillStyle = "rgba(0,0,0,0.3)";
				ctx.beginPath();
				ctx.arc(this.x, this.y, this.radius, 0, 2*Math.PI, false);
				ctx.fill();
				this.radius += 0.5;
			}
		}
	}
);

JPLT.Class.create("JPLT.PaintTool.Ribbon", JPLT.PaintTool,
	function() {
		this.superConstruct();
	},
	{	
		paint: function(ctx) {
			if (this.isPainting) {
				ctx.save();
				ctx.strokeStyle = "rgba(0,0,0,0.3)";
				ctx.translate(this.x,this.y);
				ctx.rotate(this.a);
				ctx.beginPath();
				ctx.moveTo(-10,-10);
				ctx.lineTo(10,10);
				ctx.stroke();
				ctx.restore();
			}
		}
	}
);

JPLT.Class.create("JPLT.PaintTool.Slinky", JPLT.PaintTool,
	function() {
		this.superConstruct();
	},
	{
		paint:function(ctx) {
			if (this.isPainting) {
				ctx.save();
				ctx.strokeStyle = "rgba(0,0,0,0.3)";
				ctx.beginPath();
				ctx.arc(this.x,this.y,10,this.a,this.a+Math.PI,true);
				ctx.stroke();
				ctx.restore();				
			}
		}
	}
);

JPLT.Class.create("JPLT.PaintTool.Line", JPLT.PaintTool,
	function() {
		this.superConstruct();
	},
	{
		paint:function(ctx) {
			if (this.isPainting) {
				ctx.save();
				ctx.strokeStyle = "rgba(0,0,0,0.3)";
				ctx.beginPath();
				ctx.moveTo(this.x-this.dx,this.y-this.dy);
				ctx.lineTo(this.x,this.y);
				ctx.stroke();
				ctx.restore();
			}
		}
	}
);

JPLT.Class.create("JPLT.Paint", JPLT.Object,
	function() {
		this.width = window.innerWidth;
		this.height = window.innerHeight;
		
		this.currentTool = new JPLT.PaintTool.InkBlob();
		
		this.createElement();
		this.run();
	},
	{
		tools: {
			'inkblob': new JPLT.PaintTool.InkBlob(),
			'ribbon': new JPLT.PaintTool.Ribbon(),
			'slinky': new JPLT.PaintTool.Slinky(),
			'line': new JPLT.PaintTool.Line()
		},
		
		createElement: function() {
			var body = document.documentElement || document.body;

			this.element = document.createElement("canvas");
			this.element.width = this.width;
			this.element.height = this.height;
			this.element.style.position = "absolute";
			this.element.addEventListener("mousemove", this.delegate(this.mouseMoved), true);
			this.element.addEventListener("mousedown", this.delegate(this.mousePressed), true);
			this.element.addEventListener("mouseup", this.delegate(this.mouseReleased), true);			
			body.appendChild(this.element);
			
			var div = document.createElement("div");
			div.style.textAlign = "center";
			div.style.position = "absolute";
			div.style.zIndex = 1;
			div.style.width = this.width;
			
			var select = document.createElement("select");
			select.addEventListener("change", this.delegate(this.changeTool), true);
			
			for (var tool in this.tools) {
				var option = document.createElement("option");
				option.value = tool;
				option.text = tool;
				select.add(option,null);
			}
			div.appendChild(select);
			
			var clearButton = document.createElement("button");
			clearButton.innerHTML = "clear";
			clearButton.addEventListener("click", this.delegate(this.clear), true);
			div.appendChild(clearButton);
			
			var saveButton = document.createElement("button");
			saveButton.innerHTML = "save";
			saveButton.addEventListener("click", this.delegate(this.save), true);
			div.appendChild(saveButton);

			body.appendChild(div);
		},
		
		mouseMoved: function(e) {
			this.currentTool.mouseMoved(e);
		},
		
		mousePressed: function(e) {
			this.currentTool.mousePressed(e);
		},
		
		mouseReleased: function(e) {
			this.currentTool.mouseReleased(e);
		},
		
		context: function() {
			return this.element.getContext("2d");
		},

		run: function() {
			if (!this.timer) {
				this.timer = window.setInterval(this.delegate(this.paint), this.delay);	
			}
		},
		
		stop: function() {
			window.clearInterval(this.timer);			
			this.timer = null;
		},
		
		changeTool: function(e) {
			var newTool = e.target.value;
			
			this.log("Changed tool to " + newTool);
			this.currentTool = this.tools[newTool];
		},
		
		clear: function() {
			var ctx = this.context();
			ctx.clearRect(0,0,this.width,this.height);
		},
		
		save: function() {
			window.open(this.element.toDataURL());
		},
		
		paint: function() {
			try {
				var ctx = this.context();
				this.currentTool.paint(ctx);
			}
			catch (e) {
				this.stop();
				throw(e);
			}
		}
	}
);