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/
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/
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 SeidelinWebkit nightly does indeed work. Thanks!
March 24, 2008 at 11:07 AM AnonymousYour brightness algorithm doesn't actually preserve brightness correctly.
March 25, 2008 at 8:41 AM Mikael BergkvistI 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)
We'd like to talk to you, 'we' being this site: http://www.widgetplus.com
March 25, 2008 at 9:24 AM Jacob SeidelinVirtuelvis, 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 SeidelinMikael, I sent you a mail via the address on your site.
March 25, 2008 at 12:30 PM AnonymousSo... how would one make use of this? Just pick the
March 31, 2008 at 12:26 AM Jacob Seidelinhttp://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?
Yes. Something like that.
March 31, 2008 at 1:26 AM AnonymousJust 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.
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 SeidelinYes, 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 mudHey, 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...
April 16, 2008 at 10:38 PM Jacob SeidelinI'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!
@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 mudhttp://www.colorjack.com/labs/colormatrix/
April 19, 2008 at 6:21 PM AnonymousI 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!
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:
May 13, 2008 at 6:12 AM Anonymous$('img').blur().sepia();
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徵信博客
June 5, 2008 at 1:17 AM marker徵信
徵信blog
手机联系生活
Have you see this image processing library? it got 1000 diggs
June 20, 2008 at 12:50 AM Anonymoushttp://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.
Fantastic work, looks amazing.
July 8, 2008 at 9:33 PM AnonymousBUT! 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.
Dur. Please excuse the oversight in my last comment. Of course it works in IE7, it uses the proprietary "gray" filter.
July 8, 2008 at 10:44 PM Jacob SeidelinKey item: FF3 works with your demo page, but not in my local file test page. Very strange.
@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.
July 9, 2008 at 7:43 AM AnonymousHave you tried running it from a web server?
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.
July 9, 2008 at 8:11 PM Jacob SeidelinI 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.
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 AnonymousPHEW. 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');
July 10, 2008 at 1:58 AM AnonymousYou 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. :)
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.
July 10, 2008 at 11:04 PM Jacob SeidelinAll 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
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.
July 11, 2008 at 11:12 AM AnonymousAs 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.
Wow this is great .. .
July 22, 2008 at 10:48 AM AnonymousThnx :)
Any updates? :f
October 18, 2008 at 7:33 PM Jacob SeidelinNo, 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 UnknownAny 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?
December 2, 2008 at 10:04 PM AnonymousAlso curious to know if it's possible to convert a range of colors to transparent.
Cheers!
is there anyway to use these effects with excanvas?
January 26, 2009 at 1:40 PM AnonymousHello
February 14, 2009 at 10:35 AM AnonymousSome 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;
}
}
grayscale don't work in SAFARI
April 29, 2009 at 12:26 PM johnjust 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 turibeYour 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.
September 17, 2010 at 8:35 PM Javascript Plugins DirectoryI 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
Great Thanks, i added a link from my personal plugins list
September 27, 2010 at 8:20 AM UnknownWhy effect not apply in Chrome
March 11, 2013 at 12:55 AM