Last week, I explored the HTML Canvas element for the first time. In that exploration, I created a "finger painting" demo for the iPhone that would post drawing commands to the server where the image would be re-created as a PNG in ColdFusion. That was a nice approach because it gave me some flexibility in how the ColdFusion image was created (using anti-aliasing and a thicker pen stroke). But, I wanted to see if there was a way to extract image data from the canvas without having to track every single drawing action performed by the user.
As it turns out, the HTML canvas element has a toDataURL( mimeType ) method. This method takes the current canvas display and returns a base64-encoded data URL of the pixel data for the given mime-type. This sounded like something that would play very nicely with ColdFusion's ImageReadBase64() method, which can create a new image object from a base64-encoded data URL.
Taking my iPhone drawing demo from last week, I refactored it to use the toDataURL() method. Now, rather than storing every single drawing command, I simply export the base64 image data and store it in a hidden form field before I post the form to the ColdFusion server.
As you can see, clicking on the "Export Graphic" button stores the base64 image data to the form field, "imageData". Because the base64 data contains all information about the image, including the height and width, I no longer need to post the canvas dimensions along with the form submission.
Once we post to the ColdFusion server, the code server-side has been greatly simplified; we are no longer re-creating the image step-by-step, we are simply creating an image object based on the submitted base64-encoded image data URL:
<!--- Param the base64 encoded image data value. ---> <cfparam name="form.imageData" type="string" default="" /> <!--- Create a new ColdFusion image with the given dimensions. ---> <cfset image = imageReadBase64( form.imageData ) /> <!--- Now that we have drawn the image, write it to the browser as a PNG file. ---> <!DOCTYPE HTML> <html> <head> <title>iPhone Touch Events With jQuery</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <body> <h1> Your iPhone Touch Drawing </h1> <p> <cfimage action="writetobrowser" source="#image#" style="border: 1px solid ##999999 ;" /> </p> <p> <a href="./index.cfm">Draw Another Image</a> </p> </body> </html>
As you can see, the ColdFusion image is created with a single line of code - one call to imageReadBase64(). This is extremely simple; but, at the same time, as I mentioned above, we have much less flexibility as to how the image is rendered. In my previous experiment, the re-created ColdFusion image had "nicer" anti-aliasing and a thicker pen stroke. Of course, "nicer" is a highly subjective term.
Using this approach, the following drawing:
... gets exported and re-created as this:
As you can see, the re-created ColdFusion image is a bit jagged in its lines.
The Canvas element is a lot of fun to play with; of course, keep in mind that the Canvas element is not yet supported by all the major browsers (think: IE). As such, it can only really be used in research and development - probably not as a mission-critical solution.
Want to use code from this post? Check out the license.
FYI, I think for your iPhone UA check you also want to match "ipod" - that will then work with the ipod touch.
Ah, good call! For some reason, I had it in my mind that iPhone would cover all the apple devices - clearly not thinking through that one very effectively :)
Have you seen this project? http://code.google.com/p/explorercanvas/ It brings the canvas element to IE. Haven't had a chance to try it yet, but seems promising.
I have not seen that - thanks for posting it here. I'll be sure to check it out.
This is awesome. Is there a way to implement this on any other touchscreen device, Example BB Storm?
That's a good question, and one that I don't know the answer to. The canvas is a "new" HTML element, so theoretically, if the Storm supports it in the browser, it should work. The biggest question would be which kind of "event" to support. This currently supports Mouse and "Touch" events. I don't know how proprietary the "touch" events are, or if they are common to most mobile smart devices.
hum - that should work for signature capture
I've got an idea along those lines for a fun little project.
Everytime I see one of these iPhone-specific pages, I'm going to try to port it to my Nexus one.
The event object for Android is identical to a browser (specifically, event.pageX, event.pageY) so no code modification required to detect the events.
I added a new function:
var isAndroid = (new RegExp( "Android", "i")).test(navigator.userAgent);
var isTouch = (isIPhone || isAndroid);
onTouchStart should use isTouch in place of isIPhone. The duplicate unbind in onTouchEnd was not required for android and actually messed it up somewhat.
Just seen a very similar concept here:
Which they say:
"As it works on webkit, he made sure it worked on the mobile Android and iPhone browsers. No multi-touch as yet, but the touch UI still makes a nice input mechanism."
Very cool stuff ... just need to find something to actually use it for :)
Oh cool - that's great to know; looks like the "touch" events are becoming standard for these mobile smart devices.
Yeah, I've seen Harmony come across on Twitter - really some stunning visuals! I especially love the way it seems to be cognizant of where existing lines are (and works with them).
This does not work on the Android Nexus because it does not support the todataURL function.
Does anyone have a work around for this?
Wondering how to do the server-side part in PHP or Ruby or Python?
Can't help you on that, sorry. ColdFusion makes working with images pretty easy, but I wouldn't know how to handle that in other languages.
This is a great script. I'm close to getting it working on my end here but being that I am a complete novice, I am hitting a wall.
Is this by design?
In the form:
a href="#">Export Graphic
// Get a reference to the export link. var exportGraphic = $( "a" );
You ALWAYS save me time. Yes, ALWAYS!
Thanks for another great starter point.
I am working with JCANVAS which helps manipulate html5 canvas using cool query syntax ( http://calebevans.me/projects/jcanvas/ ) but am running into a problem when posting the canvas content for cold fusion to save/read as a base64 image...
apparently there is something wrong with the base64 that is done with testCanvas.toDataURL("image/png"); because cold fusion is unable to read the submitted content as an image and keeps dumping a weird error "codecLib error" (An exception occurred while trying to read the image.)
Has anybody faced this before or worked around it? any tips? how can i troubleshoot a string to see if it's properly structured as a base64 image?