I have been playing around with SVG Filters a lot over the last few weeks and I have to say that the SVG Filters are a) really freekin’ cool and b) powerful in ways yet undiscovered. When these things land cross-browser, it will really polish off the modern web-app paradigm, adding a level of depth to applications that are usually only seen in computer games and higher-end computer applications.
Such as: bloom/glow effects pioneered by RFC movies like Tron
Paul Irish and Boaz Sender alerted me to the awesomeness that is SVG Filters after an SVG hack weekend with the W3C at the Microsoft Research Center in Boston. Paul wrote a great demonstration of how CSS can be used to apply SVG Filters to a
<video> element. If your browser kicks ass (probably Firefox 3.7+), you should totally check this out: http://paulirish.com/2010/svg-filters-on-html5-video/
If you’re down with the Fox.next, feel free to play-with/steal-and-patent the code: http://code.bocoup.com/processing-js/vijax/vijax.xhtml
<audio> beat detection will not work unless you have the Processing.js Team’s build of Firefox, which you can get at: Mozilla’s Audio Data API page. If you have problems with this build, please /join #processing.js on irc.mozilla.org and ask for Humph, Corban, F1LT3R or NotMasterYet: who can help get you up and running on your OS of choice.
Back To The Filters
The usual by-product of awesome-source is the discovery of glitches in the matrix. I found a couple of holes in the SVG Filter tapestry, which will hopefully be woven tighter in an not-too-distant iteration of SVG (hopefully before SVG 1.1 has passed us by). The first issue is elem.style. There does not seem to be any correlation between the CSSStyleObject and the DOM element’s style object. EG: To use an SVG Filter with CSS you would do something like this:
document.getElementById('#blurMe').style.filter = null;
document.getElementById('#blurMe').setAttribute('style', '' );
Here’s an example page demonstrating the issue: SVG Filters In-Accessible from Elem.style
The other gotcha I stumbled across with SVG Filters was the in-ability to use multiple DOM elements as sources in one SVG Filter. For example: if we wanted to rebuild Adobe Photoshop in the browser, or even Flash… we would need to come up with a fast way of applying feBlend SVG Filters to layers upon layers of images: or DOM elements.
Currently: an SVG filter can only use a single DOM element as a “SourceGraphic”. This super-inflexibility is very frustrating and the hack-around I came up with to use multiple-DOM-element-backgrounds, as SourceGraphics in an feBlend-stack (similar to Photoshop’s “Layers Window”) is a) uglier than this and b) computationally expensive to the nth degree.
If you want to blend multiple images with SVG filters today, you can do this:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"> <defs> <filter id="srcLoadedOverlay"> <feImage xlink:href="no1.png" result="img1"> <feImage xlink:href="no2.png" result="img2"> <feImage xlink:href="no3.png" result="img3"> <feBlend in="img1" in2="img2" result="blend1" mode="multiply"> <feBlend in="blend1" in2="img3" mode="lighten"> </filter> </defs> </svg>
This loads images into the SVG DOM and applies the appropriate blend. All good. Except… what if I want to replace images with dynamic DOM elements like animated Canvases from the good-old-fashioned HTML DOM? One might imagine doing something along these lines:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"> <defs> <filter id="srcLoadedOverlay"> <feImage xlink:href="url(#canvas0)" result="img1"> <feImage xlink:href="url(#canvas1)" result="img2"> <feImage xlink:href="url(#canvas2)" result="img3"> <feBlend in="img1" in2="img2" result="blend1" mode="multiply"> <feBlend in="blend1" in2="img3" mode="lighten"> </filter> </defs> </svg>
Alas: this is impossible, leading to the inevitably ugly combination of CSS, SVG & XHTML…
<div style="width:100px;height:100px;overflow:hidden;"> <canvas id="canvas0" style="left:0px;top:0px;position:absolute;"></canvas> <div style="left:-200px;top:-200px; filter:url(#domLoadedOverlay); width:300px;height:300px;border:1px solid black;position:relative;"> <canvas id="canvas1" style="left:100px;top:100px;position:absolute;"></canvas> <canvas id="canvas2" style="left:200px;top:200px;position:absolute;"></canvas> </div> </div> <filter id="domLoadedOverlay"> <feOffset dx="100" dy="100" result="origin1"> <feBlend in="origin1" in2="SourceGraphic" mode="multiply"> <feOffset dx="100" dy="100" result="origin2"> <feBlend in="origin2" in2="SourceGraphic" mode="lighten"> </filter>
This work-around creates a floating div containing the elements to be blended, that is locked into a container with overflow:none. The sources are then laid out diagonally and filtered in succession. Now these elements are technically in the same DOM element, and can be filtered together. However, this uses a HUGE area to calculate the result, which is so just not cloud.
If you want to blend 2 elements of 100px x 100px, you are effectively squaring the area and then multiplying that size by the number of feBlends. That’s somewhere in the order of 80,000 pixels being processed to provide you with a 10,000 pixel image. NOT GOOD PEOPLE! This should be more in the order of 20,000 pixels. Apply that logic to a 10 layer animation and you are processing 1Mil. pixels to get a return of 10,000 pixels, where a 100Thou. pixel calculation should suffice.
I should point out that I have no idea how many operations are actually involved when blending one layer with another, but the fact remains, if you square the size of anything before you process it, you are going to make a big performance payment.
If you want to see this work-around in action, check out the following source: SVG Dynamic DOM Source Issue