Using HTML5 canvas, Drag and Drop and File API to offer The Cure

By using a combination of the <canvas> element and the File API we could put together a little service that offers “The Cure” for many people.

The cure for what, you might ask? Baldness, of course. :-)

I’m going to go through the basics here to show how you can drag an image from your desktop into the web browser, drop it into a <canvas> element and then start drawing on it; to be more specific, draw something that could, for instance, be hair on the picture of a bald guy. When you are done, you also have the option to save what you have accomplished in the canvas.

The end result

You can see/try out this example in The Cure demo page. I’ve also put together a little video showing this in action, offered below through the HTML5 <video> element and the vid.ly video service:

Web browser support disclaimer

Before we begin, let me state that this will only work fully as intended in Firefox and Google Chrome (not Safari). All the canvas things should work fine in Safari, Opera and IE9, but the Drag and Drop combined with the File API is the things that won’t.

First steps

To begin with, we create a canvas element in a page and get some scripting references:

HTML

    <canvas id="my-canvas" width="700" height="600">I am canvas</canvas>

JavaScript

var canvas = document.getElementById("my-canvas"),
	context = canvas.getContext("2d"),
	img = document.createElement("img"),
	mouseDown = false,
	brushColor = "rgb(0, 0, 0)",
	hasText = true,
	clearCanvas = function () {
		if (hasText) {
			context.clearRect(0, 0, canvas.width, canvas.height);
			hasText = false;
		}
	};

This is basically just about getting a reference to the canvas element, its 2d-context we will be drawing on and some extra variables for detecting what has happened on the canvas, brush color and such. The img variable is a container image for future loading use.

Instructions and loading an image

    // Adding instructions
	context.fillText("Drop an image onto the canvas", 240, 200);
	context.fillText("Click a spot to set as brush color", 240, 220);
	
	// Image for loading	
	img.addEventListener("load", function () {
		clearCanvas();
		context.drawImage(img, 0, 0);
	}, false);

What we do here is draw some text in the canvas element to show initial instructions, and also applying a loading event to a container image we will be using to load dropped images on the canvas and then draw it into the canvas once it has loaded.

Drag and drop

// To enable drag and drop
canvas.addEventListener("dragover", function (evt) {
	evt.preventDefault();
}, false);

// Handle dropped image file - only Firefox and Google Chrome
canvas.addEventListener("drop", function (evt) {
	var files = evt.dataTransfer.files;
	if (files.length > 0) {
		var file = files[0];
		if (typeof FileReader !== "undefined" && file.type.indexOf("image") != -1) {
			var reader = new FileReader();
			// Note: addEventListener doesn't work in Google Chrome for this event
			reader.onload = function (evt) {
				img.src = evt.target.result;
			};
			reader.readAsDataURL(file);
		}
	}
	evt.preventDefault();
}, false);    

First, we need to apply the dragover event to the canvas and cancel its default behavior; yes, folks, that’s how the Drag and Drop specification and implementation works, to make the drop event to be triggered…

The second part is handling a dropped image: it gets a reference to the dropped file, and then utilizes the FileReader object to directly read out the dropped image as code (i.e. as a data URL, a base64-encoded representation of the image). When it has finished loading the dropped image into the FileReader it loads that image as a src for the image container we created before.

Drawing on the canvas

So, now when we have the basic things set up, Drag and Drop in place and drawing an image into the canvas, it’s time to add drawing features.

// Detect mousedown
canvas.addEventListener("mousedown", function (evt) {
	clearCanvas();
	mouseDown = true;
	context.beginPath();
}, false);

// Detect mouseup
canvas.addEventListener("mouseup", function (evt) {
	mouseDown = false;
	var colors = context.getImageData(evt.layerX, evt.layerY, 1, 1).data;
	brushColor = "rgb(" + colors[0] + ", " + colors[1] + ", " + colors[2] + ")";
}, false);

// Draw, if mouse button is pressed
canvas.addEventListener("mousemove", function (evt) {
	if (mouseDown) {
		context.strokeStyle = brushColor;								
		context.lineWidth = 20;
		context.lineJoin = "round";
		context.lineTo(evt.layerX+1, evt.layerY+1);
		context.stroke();
	}
}, false);    

First we detect if the mouse is being pressed and make sure that the drawing will begin a new path, i.e. not closing the drawing for a potential previous path. We have a mouseup event, and the interesting thing to mention there is a little extra side-feature: if you click somewhere, we will use the getImageData method on the canvas context to detect the color of the canvas coordinates where the mouse button was released. Here we just read out the RGB color and then set that as the new brush color for our drawing.

With the mousemove event, if the mouse button is being held down, we draw a 20 pixels wide rounded line with the chosen brush color where the mouse currently is.

Saving the canvas content

When you are happy with the creation you have made in the canvas, we have a dynamically generated button that will open the canvas content in a new window/tab (depending on your web browser’s default behavior), just as a data URL in the PNG format.

The entire code

This is what the entire code looks like:

(function () {
	var canvas = document.getElementById("my-canvas"),
		context = canvas.getContext("2d"),
		img = document.createElement("img"),
		mouseDown = false,
		brushColor = "rgb(0, 0, 0)",
		hasText = true,
		clearCanvas = function () {
			if (hasText) {
				context.clearRect(0, 0, canvas.width, canvas.height);
				hasText = false;
			}
		};
		
	// Adding instructions
	context.fillText("Drop an image onto the canvas", 240, 200);
	context.fillText("Click a spot to set as brush color", 240, 220);
	
	// Image for loading	
	img.addEventListener("load", function () {
		clearCanvas();
		context.drawImage(img, 0, 0);
	}, false);
	
	// Detect mousedown
	canvas.addEventListener("mousedown", function (evt) {
		clearCanvas();
		mouseDown = true;
		context.beginPath();
	}, false);
	
	// Detect mouseup
	canvas.addEventListener("mouseup", function (evt) {
		mouseDown = false;
		var colors = context.getImageData(evt.layerX, evt.layerY, 1, 1).data;
		brushColor = "rgb(" + colors[0] + ", " + colors[1] + ", " + colors[2] + ")";
	}, false);
	
	// Draw, if mouse button is pressed
	canvas.addEventListener("mousemove", function (evt) {
		if (mouseDown) {
			context.strokeStyle = brushColor;								
			context.lineWidth = 20;
			context.lineJoin = "round";
			context.lineTo(evt.layerX+1, evt.layerY+1);
			context.stroke();
		}
	}, false);

	// To enable drag and drop
	canvas.addEventListener("dragover", function (evt) {
		evt.preventDefault();
	}, false);

	// Handle dropped image file - only Firefox and Google Chrome
	canvas.addEventListener("drop", function (evt) {
		var files = evt.dataTransfer.files;
		if (files.length > 0) {
			var file = files[0];
			if (typeof FileReader !== "undefined" && file.type.indexOf("image") != -1) {
				var reader = new FileReader();
				// Note: addEventListener doesn't work in Google Chrome for this event
				reader.onload = function (evt) {
					img.src = evt.target.result;
				};
				reader.readAsDataURL(file);
			}
		}
		evt.preventDefault();
	}, false);
	
	// Save image
	var saveImage = document.createElement("button");
	saveImage.innerHTML = "Save canvas";
	saveImage.addEventListener("click", function (evt) {
		window.open(canvas.toDataURL("image/png"));
		evt.preventDefault();
	}, false);
	document.getElementById("main-content").appendChild(saveImage);
})();

Combining APIs

This is just an example of how you can combine a number of HTML5 APIs to interact with the user’s desktop/file system, read out file information and offering drawing and saving in the web browser. Now go code something nice. :-)

Posted in Developing,HTML5/HTML/XHTML,Technology,Web browsers |

Leave a Reply

Your email address will not be published. Required fields are marked *