Check out my
new book!
HTML5 Games book
Photoshop-like Image Effects using Javascript and canvas
UPDATE: This has since evolved into the more advanced Pixastic JavaScript image processing library.


Continuing experiments in canvas-land, here are the beginnings of an image effect library. We all know IE's filters, which have provided a number of basic effects for IE users for a long time. With the canvas element, I've tried to recreate some of these effects, and even add a bunch more. The result is a small library with effects such as blur, sharpen, emboss, image flipping, grayscale, invert, etc.


Performance and support is once again a bit of a problem so I doubt whether this can find any real world use (yet). There's a long way to a client side Photoshop, at least. Still, it's fun.
All effects work in Firefox 2 and the latest Opera beta. Some work in IE and very few in Safari (all effects appear to work in Webkit nightlies, though).

http://www.nihilogic.dk/labs/imagefx/

⇓ 37 comments Brian

Interesting work, definitely. All of the effects appear to work in the latest Webkit nightlies, by the way.

March 24, 2008 at 8:48 AM
Jacob Seidelin

Webkit nightly does indeed work. Thanks!

March 24, 2008 at 11:07 AM
Anonymous

Your brightness algorithm doesn't actually preserve brightness correctly.

I think you'll find this to work better:

if( r<g && r<b )
{
l = (g<b?r+b:r+g)>>1;
}
else if( g<r && g<b )
{
l = (r<b?g+b:g+r)>>1;
}
else
{
l = (r<g?b+g:b+r)>>1;
}

(Bonus, it's also slightly faster)

March 25, 2008 at 8:41 AM
Mikael Bergkvist

We'd like to talk to you, 'we' being this site: http://www.widgetplus.com

March 25, 2008 at 9:24 AM
Jacob Seidelin

Virtuelvis, thanks for the suggestion! I'm assuming you're referring to the desaturate effect, in which case the current algorithm, while maybe not the best, gives a result consistent with what the IE filter does, so I'm keeping it for now.

March 25, 2008 at 9:49 AM
Jacob Seidelin

Mikael, I sent you a mail via the address on your site.

March 25, 2008 at 12:30 PM
Anonymous

So... how would one make use of this? Just pick the

http://www.nihilogic.dk/labs/imagefx/imagefx.js
http://www.nihilogic.dk/labs/imagefx/imagefx_invert.js

scripts, combine them in Notepad and add some more script at the end, depending what images need to be manipulated? Or inverted in this case. What if one wanted to create a Greasemonkey script that inverts all images on a webpage? What would be the exact javascript code for that? Those two combined and then what?

March 31, 2008 at 12:26 AM
Jacob Seidelin

Yes. Something like that.
Just include the base imagefx.js and whatever effect .js files you need.
Then either add a imagefx="invert" attribute to all the image tags, or do the effect programmatically by calling the jsImageFX.doImage(<ImageObject>, "invert") function for each image you want inverted. Also check the processImages function in imagefx.js.

I don't know much about Greasemonkey, so you're on your own with that one.

March 31, 2008 at 1:26 AM
Anonymous

Greasemonkey is an add-on for Firefox. You can load scripts into it and it uses those scripts on websites of your choosing. The language used in those scripts is javascript. So.. anyone who knows javascript can create custom Greasemonkey scripts to alter web content. I'm no programmer, mind you... but am trying to get this thing to invert colors of ALL images on a website I frequently visit. Is there some kind of wildcard option? The site has hundreds of small gifs, so referring them one by one isn't really a viable option. Or will that (imagefx="invert" attribute to all the image tags") thing do precisely that?

March 31, 2008 at 2:31 AM
Jacob Seidelin

Yes, in imagefx.js there is the function processImages which is run when the page has loaded. It goes through all the images on the page and if there is an imagefx="some_effect" attribute on them, it will apply some_effect to the image.

March 31, 2008 at 6:18 AM
mud

Hey, this is cool! I didn't think anyone else had been working on doing photo effect in canvas :) ... it's a weird thing to be doing, lol...

I've got the ColorMatrix + ConvolutionMatrix + DisplacementMap working in Canvas/Javascript. I'll try to get a demo ready sometime soon. It's kinda silly, cause it's so much slower than Actionscript... but someday, it's going to make sense!

April 16, 2008 at 10:38 PM
Jacob Seidelin

@michael: Cool. I'd love to see what you come up with. Nice to see that I'm not the only one doing silly stuff like this :)

April 17, 2008 at 10:40 AM
mud

http://www.colorjack.com/labs/colormatrix/

I put together a little tutorial for how to use the ColorMatrix in <canvas> -- I focused on color blindness conversion, however, it works with many other formulas that I've pointed out in the Color Matrix Library on nofunc:

http://www.nofunc.com/Color_Matrix_Library/

I hope all this helps someone, and would love it if anyone wants to write new Color Matrix conversions to be added to the library!

April 19, 2008 at 6:21 PM
Anonymous

Would be nice if this was done as a jQuery plugin - that way you could use selectors to get the images and then chain the filters on them, eg:

$('img').blur().sepia();

May 13, 2008 at 6:12 AM
Anonymous

Now, I'm by no means an ace javascript programmer (I come from the cold land of LAMP, and have been until now in essence a PHP guy), but I'm certainly impressed by what you're doing, and it has fueled a growing interest in javascript for me. Surely, this work is going to be remembered as some of the pioneering work in the field in the web-to-come.

May 31, 2008 at 10:59 AM
Jacob Seidelin

@lewis: Thanks. I've expanded upon this idea of doing image manipulation in JavaScript and am currently working on a image editing application (link )

June 4, 2008 at 11:27 AM
Anonymous

徵信博客
徵信
徵信blog
手机联系生活

June 5, 2008 at 1:17 AM
marker

Have you see this image processing library? it got 1000 diggs
http://ejohn.org/blog/processingjs/

Just the other day I was thinking about how I should be able to code up something like your pixastic.com site.

June 20, 2008 at 12:50 AM
Anonymous

Fantastic work, looks amazing.

BUT! For the past day I've been killing myself trying to get it to work in a test case. I created a test HTM doc, included imagefx.js and desaturate.js. Everything appears like it should work normally, but in the desaturate function I needed to catch an exception on the line, var oDataSrc = oCtx.getImageData(0,0,oImage.width,oImage.height);

The exception is NS_ERROR_DOM_SECURITY_ERR

I'm stumped. This is a simple local HTM file, I don't know why I'd be getting a domain error.

I've been digging and digging, but I can't come up with anything. Any ideas?

The strange part is... I only see the error in Firefox 3. IE7 works fine. BUT, your demo page works just fine in FF3. Odd.

July 8, 2008 at 9:33 PM
Anonymous

Dur. Please excuse the oversight in my last comment. Of course it works in IE7, it uses the proprietary "gray" filter.

Key item: FF3 works with your demo page, but not in my local file test page. Very strange.

July 8, 2008 at 10:44 PM
Jacob Seidelin

@adamssl: the getImageData method will throw a security error if you try to access image data that originates from somewhere other than the host the script is running on. I imagine there could be similar restrictions when using it on local files.
Have you tried running it from a web server?

July 9, 2008 at 7:43 AM
Anonymous

Jacob, thx for the reply. Like another poster, I'm trying to build a greasemonkey script to incorporate this core function. When run locally, I get the DOM error. When run as a greasemonkey script against a live page the behavior becomes... odd.
I can create a canvas, copy img source, draw shapes on the canvas using fillStyle and fillRect, and even replace the img object with my canvas, and the page is updated with my modifications.
But I can't actually manipulate the data. For any filter effect you need to use GetImageData to access the pixel data. When run locally I get the security error. When run against live pages everything seems to work EXCEPT GetImageData. When I set a variable to the context.getImageData there is no security exception thrown, but the var is always set to null.
The really odd thing is as I was seeking a work-around I found that when I try to use a context.createImageData, I catch an error reporting that "createImageData" is not a known function. Really strange. It makes me think that the problem is a scope / environment problem, and I must be working in some strange / legacy space.
Ah well. I was trying to create a grayscale'd version of the web, using the process you outlined here and greasemonkey. But I'm close to the point where I throw in the towel.

July 9, 2008 at 8:11 PM
Jacob Seidelin

Ha, I had the same idea but I wanted to invert everything and it pretty much stayed as an interesting thought. I don't know enough about GM to know why it's causing you trouble. Have you checked the Mozilla bug database if there are any bugs related GM and image data?

July 10, 2008 at 12:34 AM
Anonymous

PHEW. Got it. Sorta. It seems this is some sort of DOM protection, to prevent people from stealing image data? I don't know. But the trick was instead of declaring: var oCanvas = document.createElement('canvas');

You should instead declare: var oCanvas = unsafeWindow.document.createElement('canvas');

The good news is that it works great, as long as the image is on the exact same domain as the source page. Which means it works great on google's homepage, www.google.com, because the logo is loaded from www.google.com/.../images folder. BUT, it will fail on slashdot, where images are loaded from images.slashdot.org, which is technically a different domain.

I guess it's necessary, I suppose otherwise people could use an injection attack to run code hidden in an image file.

The end result is that with the unsafeWindow declaration, same-domain images are processed, but images with a different origin will throw the DOM_SECURITY_ERR

I've been playing with trying to create a copy of the image and then applying changes to the copy. But it seems even when I use .toDataURL(), that the origin information is maintained. Bummer.

Playing with canvas features certainly is an interesting learning exercise. :)

July 10, 2008 at 1:58 AM
Anonymous

I apologize for the high # of nearly off-topic comments. But I've been recommending this article to some other folks since it's the only site with this (great) information, and I'd like to share my observations.

All previous misguided comments aside, I finally figured out the problem between GM and canvas objects.

The problem is that any canvas defined with GM doesn't have a native window. So if you try to getImageData... you get nothing, since nothing is actually rendered. You CAN use the "fill" commands to paint on your canvas, then replace any object on the parent page with your canvas, and it'll render fine. But you need to actually get the pixel data from the canvas to apply any of the filters. You can't "get" any pixels from your canvas, since nothing is rendered in the space you're operating in.

If you add your canvas to the parent document and THEN call getImageData you hit a security error since HTML5 requires that the canvas and the effective script origin must exactly match.

You CAN use unsafeWindow so the script's canvas exists within the parent document, which then allows you to get/putImageData. BUT... it's insecure, and it's a partial work around. Off-site linked images once again violate the origin restrictions.

I suspect there are work-arounds to get around the limitations of the unsafeWindow work-around. But the script complexity is starting to make it a little to meaty to really consider using as a ubiquitous 'net filter.

Anyhow, Jacob, I apologize for hijacking your comments section. I still think this demo is a fantastic feat. I can't wait to see some of the cool things that this will grow in to.

Questions / comments can catch me at this id @gmail

July 10, 2008 at 11:04 PM
Jacob Seidelin

No problem at all, man. They're high quality comments so they're more than welcome. Thanks for sharing your troubles and solutions, partial as they may be.
As for what this can grow into, so far I've used some of the code to make my photo editing app at www.pixastic.com (but I suppose you've already seen that one). I do plan to go back some day and do some more work on this library, though.

July 11, 2008 at 11:12 AM
Anonymous

Wow this is great .. .

Thnx :)

July 22, 2008 at 10:48 AM
Anonymous

Any updates? :f

October 18, 2008 at 7:33 PM
Jacob Seidelin

No, I'm afraid not. It's high on my todo list, though. Some of the things I improved for Pixastic could be used in this library as well, so I'll have to see what things can be combined.

October 19, 2008 at 1:31 PM
Unknown

Any chance of seeing a "threshold" type effect? Or any idea on how to turn a photo to straight black and white, with the ability to do so at various levels?

Also curious to know if it's possible to convert a range of colors to transparent.

Cheers!

December 2, 2008 at 10:04 PM
Anonymous

is there anyway to use these effects with excanvas?

January 26, 2009 at 1:40 PM
Anonymous

Hello

Some Effect like fliphorizontal don't work on GIF TRANSPARENT

You must edit the filter file and add clearRect function

function(oImage)
{
if (oImage.client.hasCanvas) {
var ctx=oImage.canvas.getContext("2d");
// for gif transparent
ctx.clearRect(0, 0, oImage.width,oImage.height);
// for gif transparent
ctx.scale(-1,1);
ctx.drawImage(oImage.image, -oImage.width, 0, oImage.width, oImage.height)
oImage.useData = false;
return true;

} else if (oImage.client.isIE) {
oImage.image.style.filter += " fliph";
return true;
}
}

February 14, 2009 at 10:35 AM
Anonymous

grayscale don't work in SAFARI

April 29, 2009 at 12:26 PM
john

just how hard is it to make a freakin easy example that works? I'm having trouble with it. Modifying a really easy example would surely help

July 30, 2009 at 5:59 AM
turibe

Your blur and blurfast functions leave a row of black pixels on the top. Blurmore does as well, but not as many. You've likely noticed.

I do something like this in my own blur algorithms:

var len = o_data.length;
var index = (x + y * width) * 4;
var shift = Math.floor (kernel.length / 2);

for (var i = 0; i < kernel.length; ++i)
{
var src = index + (i - shift) * 4;
if (x < width && y < len)
{
src = index;
}

...
}

And that solves the problem. Also, the speed of your blur functions could easily be halved by using a separable-axis approach. See paragraph three:
http://en.wikipedia.org/wiki/Gaussian_blur#Mechanics

September 17, 2010 at 8:35 PM
Javascript Plugins Directory

Great Thanks, i added a link from my personal plugins list

September 27, 2010 at 8:20 AM
Unknown

Why effect not apply in Chrome

March 11, 2013 at 12:55 AM
Post a Comment