The Canvas tag's popularity is slowly increasing around the web. I've seen big sites use it for image rotation, graph plotting, reflection effects and much more.
However, Canvas is still limited to 2D: its drawing operations can only do typical vector graphics with so-called affine transformations, i.e. scaling, rotating, skewing and translation. Though there have been some efforts to try and add a 3D context to Canvas, these efforts are still experimental and only available for a minority of browsers through plug-ins.
So when my colleague Ross asked me if we could build a Cover Flow-like widget with JavaScript, my initial reaction was no... but really, that's just a cop out. All you need are textured rectangles drawn in a convincing perspective: a much simpler scenario than full blown 3D.
Perspective views are described by so-called projective transforms, which Canvas2D does not support. However, it does support arbitrary clipping masks as well as affine transforms of both entire and partial images. These can be used to do a fake projective transform: you cut up your textured surface into a bunch of smaller patches (which are almost-affine) and render each with a normal affine transform. Of course you need to place the patches just right, so as to cover any possible gaps. As long as the divisions are small enough, this looks convincingly 3D.
So some hacking later, I have a working projective transform renderer in JavaScript. The algorithm uses adaptive subdivision to maintain quality and can be tuned for detail or performance. At its core it's really just a lot of linear algebra, though I did have to add a bunch of tweaks to make it look seamless due to annoying aliasing effects.
Unfortunately Safari seems to be the only browser that can render it at an acceptable speed, so this technique is just a curiosity for now. The current code was mostly written for readability rather than performance though, so it's possible it could be optimized to a more useful state. Feel free to browse the code.
A real 3D Canvas in the browser would obviously rock, but you can still do some nifty things if you know the right tricks...


go steven go!
perhaps you could mash up my 40000 flickr photos using these techniques in some cool way?
Impressive!
Quite impressive!
The easiest way to get a speedup would be to constrain a majority of your matrix multiplies to 3x3 matrices. Then you could unroll all of the for-loops in your matrix multiplication and projection code.
You could also fake the subdivision. Project the four corner points. Then interpolate from left-to-right and then from top-to-bottom and fillup a table full of points. It's not going to be as accurate, but it should be much faster.
Safari was OK speed, but you
Safari was OK speed, but you obviously haven't tried it in Chrome. Wheeeeeee! :)
Fantastic job!
Nice work on Canvas
Well done! Only thing missing is a Superman reference. ;) I rather enjoy the design of your blog also, good to see angled stuff making a return. Canvas is pretty interesting, though I've hardly played with it (see this prototype), it's been a while since high school trig - but I digress. Lots of potential for fun stuff in canvas. Keep it up.
neat
Cool but calling update() in the mousemove event listener is killing the performance. You'd should only fiddle with the drag handles in the event listener, and call update() regularly like you do in demoTick().
Firefox too
Firefox (even with JIT turned off) runs the demo just as well, if not much faster than Safari for me. (Normal Safari, no SquirrelFish Extreme)
Bug in the demo
The demo does not work well for me - it runs for just a few seconds, then throws an exception.
Looking at the code, there seems to be a bug in the call to drawImage, where there are a couple of calls to Math.min that only pass in one parameter. I changed the call as follows, and the demo now runs fine:
ctx.drawImage(image,
u1 * iw,
v1 * ih,
Math.min((u4 - u1 + padu) * iw, iw - 1),
Math.min((v4 - v1 + padv) * ih, ih - 1),
dx, dy,
1 + padx, 1 + pady
);
Awesome
Really nice work on this! There are obviously a truckload of performance improvements possible here if you wanted to spend more time on it. Back in the early 90s when software-rendered 3D was the big thing in the demoscene these sort of things were optimized like crazy.
The only non-performance thing I can see is just improving your algorithm for subdividing the plane more intelligently - especially when the perspective is huge and the front corner is pulled really close to the "camera".
Otherwise - incredible! Nice work. Really impressed an "old skool" 3D nerd like me.
@Charles: Subdivision
Actually this case is handled pretty well as long as you up the subdivision limit. I left it at a low limit as a precaution, but as long as you keep the patch size big, there's no reason why you can't set it to e.g. 10. The subdivision decision is recursive and based on the area of the patch, corrected with a factor for how non-affine each patch is.
Wow, really cool
This was really great. We're looking to use Canvas in a new application to allow for easier, better navigation of a tree structure, and this is just awesome.
One question, is it possible to 'embed' any kind of user input (e.g. form elements) in the tag? Would that maybe be the next step beyond this to create the 'web browser OS'?
@Aaron: Input elements and canvas
You can't embed form elements in Canvas, and adding simulated input boxes would be near impossible to do right: DOM/JavaScript simply does not provide rich enough information to implement proper internationalized input on its own. All you get are raw keycode events.
However, you are free to layer input elements on top of a canvas. Just as well, it sometimes makes sense to overlay multiple canvases. Flot does this for interactive graphs for example: the base graph is on one canvas, while highlights for data points are on another. This allows fast repaints without having to redraw the entire graph all the time.
While Flash is powerful, it also has a bunch of issues with layering in browsers, is more separated from the DOM/JavaScript, and requires a lot more resources. Instantiating Canvases on the other hand is quick and light weight. In fact, a significant part of this site's design is rendered with Canvases (click to highlight).
Impressive
Nice work Steven, thanks for sharing the code/demo. It ran nice and smooth for me on Linux Firefox 3. I tested it on Konqueror as well but it didn't load correctly there.
I wonder what the performance would be like on IE with Explorer Canvas (most likely dismal:)
http://sourceforge.net/projects/excanvas/
It's going to be nice when Canvas is fully supported properly by all the major browers and going to make for some amazing sites/apps.
Works in Firefox 3.06
on a macbook 4.1
Very slick indeed
wowww
you perfect disagner
Excelent
Excelent work. Congratulations!
Post new comment