How to Transition an Image from B&W to Color with Canvas
videos

How to Transition an Image from B&W to Color with Canvas

Tutorial Details
  • Technologies Discussed: Canvas, CSS3
  • Difficulty: Moderate
  • Format: 8 Minute Screencast

Recently, in the CodeCanyon forums, a question was brought up: “How do I transition an image from black and white, to color — using only one image?” Unfortunately, at this point in time, it’s not possible with CSS. However, if we’re creative with JavaScript and canvas, we can create a solution relatively easily. I’ll show you how in today’s video tutorial!

Choose 720p for maximum clarity.
Subscribe to our YouTube and Blip.tv channels to watch more screencasts.

Final Source

<!DOCTYPE html> 
 
<html lang="en"> 
<head> 
   <meta charset="utf-8"> 
   <title>untitled</title> 
	<style> 
		/* Setup...not important. */
		.img-wrap {
			width: 500px;
			margin: 100px auto;
			position: relative;
			cursor: pointer;
		}
		
		/* Handles animation of b*w to color */
		canvas {
			position: absolute;
			left: 0;
			top: 0;
			opacity: 1;
			-webkit-transition: all 1s;
			-moz-transition: all 1s;
			-o-transition: all 1s;
			-ms-transition: all 1s;
			transition: all 1s;
		}
		
		canvas:hover {
			opacity: 0;
		}
		
		/* If you MUST have IE support */
		#cvs-src {
		   filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
		}
		
		#cvs-src:hover {
			filter: none;
		}
	</style>  
</head> 
<body> 
 
<div class="img-wrap"> 
	<img id="cvs-src" src="your-image.jpg" /> 
	<canvas width=500 height=500></canvas> 
</div> 
 
<script> 
	(function() {
		var supportsCanvas = !!document.createElement('canvas').getContext;
		supportsCanvas && (window.onload = greyImages);
		
		function greyImages() {
			var ctx = document.getElementsByTagName("canvas")[0].getContext('2d'),
				img = document.getElementById("cvs-src"),
				imageData, px, length, i = 0,
				grey;
			
			ctx.drawImage(img, 0, 0);
			
			// Set 500,500 to the width and height of your image.
			imageData = ctx.getImageData(0, 0, 500, 500);
			px = imageData.data;
			length = px.length;
					
			for ( ; i < length; i+= 4 ) {
				grey = px[i] * .3 + px[i+1] * .59 + px[i+2] * .11;
				px[i] = px[i+1] = px[i+2] = grey;
			}
			
			ctx.putImageData(imageData, 0, 0);		
		}
	})();
</script> 
 
</body> 
</html>	

Conclusion

So what do you think? Would you use this technique in your own projects? Can you think of a better way that doesn’t involve using a server-side language or sprites? Let me know in the comments!

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://pantso.gr Pantso

    Great tutorial but not something you can say it’s relatively easily ! Why not do it with simple CSS just by using a sprite? And by using a sprite (a new image with double height, the colored picture on top and the b/w picture below it) you can even add a transition effect from top to bottom with javascript to make it look cooler !

    • http://www.jeffrey-way.com Jeffrey Way
      Author

      Because there are situations when you don’t have access to a sprite. Or, if the developer doesn’t know how to generate one with a server-side language.

      Also – you can create the same effect that you mentioned with this script. Rather than starting with two images, we’re creating the second with canvas.

      • http://zyglobe.com Evan Smith

        I agree. If I could just set up an effect with canvas once, I’d much rather do that than have to remember to sprite every image I upload in the future. It delegates minor processing to the client side and saves some capacity/bandwidth on our end too (since you’d be doubling your image size site using sprites).

  • http://gregbabula.com Greg Babula

    Great video and an awesome effect, thanks!

  • David

    Thanks for yet another great tutorial – was looking for a way to do this without sprites, so again a big Thank You. Great work!

  • http://shay.co/ Shay Ben Moshe

    Impractical.
    Much easier to create a grey image and fade it…

    • Patrick

      With a bazillion of user uploaded files?

    • DED

      I disagree. This is practical. It just takes more programming skill. What’s wrong with that? Essentially this is image processing 101, it is very basic technique for pixel manipulation.
      You have to keep in mind that whenever we use those things that seem so simple, like CSS transitions, a lot of code is being executed in the background. Using those simple techniques disguises that. I’m not saying there is anything wrong with using them, but it is good to remember that a browser is just a computer program and everything that happens in it is still code running. Here, Jeffery is demonstrating taking control of the code. That isn’t impractical.
      If you don’t approve of this technique in this circumstance, think about how else the same technique can be used where transitions aren’t available. Now you know how to accomplish that idea.

      • http://www.justforthealofit.com/ TheAL

        I don’t think this takes more programming skills, as most non Canvas solutions today require a decent amount of javascript and css foresight anyhow (look up most such solutions, and you’ll see most don’t account for some factor, be it disabled javascript, multiples images on page, multiple images in same parent, etc). The only downside to this is relying on HTML5 and CSS3, which aren’t ubiquitous enough yet. Tutorials like this are still valuable, though. It’s all worth learning to at least know it.

      • DED

        TheAl, you are talking about a completely different point than I am. For starters, using CSS is not programming, it is a different skill set that has some similarities. What I’m talking about is actual programming. Canvas is bringing a level of image manipulation and programming to the browser that wasn’t there natively before. We had to use Flash, Java Applets etc. to do this kind of manipulation.
        To someone who hasn’t been exposed to that kind of programming, this solution may look complicated or even seem difficult, but to someone who has been programming for awhile it is a welcome level of control and allows a greater freedom of imagination. The issue of browser compatibility is another issue all together.

  • http://johmanx.com Jan-Marten de Boer

    Hmm. They are creepy, aren’t they, Jeffrey? :P

  • http://www.bramme.net Bram Van der Sype

    I’d still rather do this with Javascript, CSS and an image sprite (with the b&w and color version next to each other). It’s a cool effect, but I don’t feel people should be using this technique at this time, not when so many people still use browsers that don’t support canvas.

  • http://www.jeffrey-way.com Jeffrey Way
    Author

    Hey guys – Just to clarify: yes, if you can use a sprite, definitely do so. That would be my first choice as well. But, if you need an alternate solution, this works too.

  • http://vtimbuc.net Valeriu Timbuc

    Hey Jeffrey! I’ve a question, what program do you use to expand the html? Ex: div.class and this will expand to html.

    • http://www.jeffrey-way.com Jeffrey Way
      Author

      It’s called Sparkup. Also look for Zen Coding.

      • http://vtimbuc.net Valeriu Timbuc

        Thanks, That’s a lot easier and faster to code..

  • http://andrewburgess.ca Andrew Burgess

    That’s a pretty cool technique, Jeffrey; it’s unbelievable what you can do with canvas!

    I don’t think you mentioned this in the screencast (or I might have missed it), but for the call to ctx.getImageData to work, you need to be running this on a server. I gave this a try and was getting a security error, and a bit of googling brought this up: http://blog.project-sierra.de/archives/1577 . As I understand it, the image data and the script need to be on the same domain, otherwise you get an error . . . and running it locally doesn’t count as the same domain.

    • http://www.jeffrey-way.com Jeffrey Way
      Author

      Correct. I thought I mentioned that — but maybe I accidentally edited out. Anyhow yes, guys, Andrew is right. Make sure you’re running this on a local server.

    • DED

      Andrew, you mean ‘locally’ as in from a file? Because, running this ‘locally’ as in ‘localhost’ will work.

  • http://www.pryde-design.co.uk Andrew Pryde

    The main problem with this method is the fact that browsers don’t allow you to access image data across domains so the image must be hosted on the same domain as the script. That is too large a constraint imho… I was going to bundle this up as a jQuery plugin and release it but with such a large caveat it doesn’t seem worthwhile.

    - @Prydie

    • http://www.jeffrey-way.com Jeffrey Way
      Author

      It’s definitely a problem. Any readers know a work-around??

      • http://www.shiftedwork.de/blog Daniel S

        Yes, you can build a proxy script with php, which uses CURL to access an image from another server.

        Btw: Great Tut :)

      • http://www.pryde-design.co.uk Andrew Pryde

        I’m looking into using the following code for the jQuery plugin:

        http://www.maxnov.com/getimagedata/

        - @Prydie

  • http://www.3rddesign.com 3rddesign

    I will gonna try this tutorial. I know this is not going to be easy but I will try my best to follow this one. All in all the video is great and I am amazed with the effects.

  • Edward Longman

    Aulthough I have to agree the IE filter values are plain stupid,
    For a change the IE way is actually the simplest and the most logical, shame you can’t get a transition effect.

    Anyone agree?

  • pelumini

    Nice one, Jeff. But not all IE, ie8 and below you mean?

  • http://www.thecodebakery.com Web Technology News

    In order of the solution.

    1) Sprite it
    2) PHP script to download original, greyscale with GD and cache image then use JS to create a layer
    3) Maybe this solution

    This would be my last choice.

    • http://www.powerdev.ca Aleks

      Unobtrusive solution would be your last choice? What about dynamically loaded images, slideshows or featured images, you can’t sprite that. PHP script? what if you can’t touch the client’s code? Smells like a lack of practical experience from a mile away. This is a best solution by far, just replace css transition with javascript and for IE use xcanvas.

      Thank you Jeffrey for this awesome tutorial!!!

  • THE DOUBL

    Hi jeffrey. It’s a very good tut. But I have one question. I know it’s not the correct place but i would like to know how to perfom the numbers of page on the bottom of the page or elsewhere as on nettuts.(1,2,3 … last page).
    Thanks

    • Oliver

      *Facepalm*

      • Happy

        Now now… don’t be mean XD

  • http://csillanas.hu/ honlapkeszites

    Very nice effect! Thank you!

  • Joel

    I’ve used a js library before that can achieve a similar effect, it’s at http://www.pixastic.com/lib/

    I have an example of it in use on a half-built test project if you care to see :p, http://www.blackboxdynamics.ca/desaturate

    In any case, another stunning article by Jeffrey!

  • http://www.webadvanced.com Reza Assar

    I read on a forum that you can set headers to fix the cross domain issue but haven’t had any luck with this so far. I’m working on an enterprise site and all images are hosted on a cdn :(

  • Albert Bakker

    Cool, thanks!

  • http://www.valleybiblemodesto.org Danny

    Great tut as always! thanks so much!

  • w1sh

    That’s pretty smooth.

  • Nate

    Awesome tutorial Jeffrey; and a very very clean solution. :) This is the type of forefront coding tutorials I like. :)

    By the way, what’s up with all the complaints. I find this solution to be A LOT cleaner that CSS sprites. Why, besides transition that someone mentioned, is this solution worse than sprites? As a developer, it’s much easier to deal just with one image that have to worry about two images…or am I the only one?

    P.S. Jeffery, at 5:45, 500 * 500 = 250,000 and not 25,000. ;)

  • Leonardo

    why not “px[i] * .3 + px[i+1] * .3 + px[i+2] * .3″?

    • DED

      Short answer to why not use p[0] * .3, p[1] * .3, p[2] * .3:
      Because your eyes do not have the same sensitivity to all three color channels. There are other algorithms to do this for different screen types etc., but it they all work about the same.

  • Alistair

    Never before has there been such a good time to bring Lionel back into colour.

    Brilliant as ever Jeff, very tidy tutorial.

    • http://www.jeffrey-way.com Jeffrey Way
      Author

      :)

  • http://www.computeriskomputer.blogspot.com Nanang Gunawan

    Good post….

    thanks…. :D

  • http://elgard-et-elgard.com/ Laue

    Hmmmm this reminds me a confusion about desaturation vs. grayscaling by J. Padolsey there:

    http://james.padolsey.com/javascript/grayscaling-in-non-ie-browsers/

    !

  • Jack F

    There’s one bit I don’t get. At the end when you do:

    ctx.putImageData(imageData,0,0);

    You don’t seem to have edited the imageData variable at all, just the value of px. Before the for loop you set px=imageData.data but after the for loop you don’t seem to set imageData to equal the new value of px.

    Am I missing something ?

  • http://nxqd3051990.blogspot.com nXqd

    I have to say wow. I’m much more enjoy your screencast and i makes me hating the code in this post.
    I have one question, how about the performance if it’s compared with another grayscale image loaded and do the transition ?

    Thanks for great post :)

    nXqd.

  • Tobi

    How do you have tabs in finder?

  • http://proistorikos.gr proistorikos

    Once again u provide us an amazing tut jeffrey thank you! i like it more than sprites..

  • Adem

    Hey Jeff, awesome tutorial as always!

    One side note though: Shouldn’t your meta-tag be self-closing?

  • http://www.vancouverrealestatelink.ca/ Vancouver

    Always amazed at the things you can do with these graphic design tools these days. I’m still a very basis user but am learning slowly but surely.

  • Jack Franklin

    Hey JW,

    Great video, but there’s one bit that confuses me:

    imageData = ctx.getImageData(0, 0, 500, 500);
    px = imageData.data;
    length = px.length;

    for ( ; i < length; i+= 4 ) {
    grey = px[i] * .3 + px[i+1] * .59 + px[i+2] * .11;
    px[i] = px[i+1] = px[i+2] = grey;
    }

    ctx.putImageData(imageData, 0, 0);

    You set px=imageData.data, then you loop through and set the pixels to the grayscale value, I understand that. What I don’t get is the call ctx.putImageData(imageData, 0,0). You’ve never actually reassigned imageData.data to equal px after editing all the pixels, so surely imageData is the same before and after the for loop – except it’s clearly not as your example works fine!

    If someone could clear up my confusion that would be hugely appreciated.

    • Joel Besada

      The px array is actually only referencing to the data array in the image data, so any changes done to the px array will be reflected on imageData.data. That’s just how arrays in javascript work (the same goes for objects).

  • http://www.ixperience.nl Michel Bos

    Greatly done. But the only thing I’m missing here is the flexibility. You’re setting the width and height of the image fixed… What if we have a bunch (i.e. 20) images that we want to fade? — The option of PHP and GD doesn’t seem logical to me either, when you have a website with 100 visitors a day it should do the trick, but imagine you have 100 visitors per minute…. takes a huge server load then…

  • http://duskydesigns.nl dusk

    The one at webdesignerwall does rougly the same, only it uses dynamic widths. Id say check that one. It gives a better insight in making it dynamically.

    http://webdesignerwall.com/demo/html5-grayscale/

    • http://jameskupczak.com James

      This is a great tutorial. Flexible image widths is a must though, that’s why I like the version on Web Designer Wall.

      Unfortunately that version throw javascript errors at you in IE. :( Wish I knew how to fix that.

  • http://jenswebstek.nl Jenski

    What about the alpha channel? Since it’s not being put ‘back’ into the ctx, does this imply that we’re creating a canvas-img without an alpha chan?

  • John

    I had never seen that kind of function/variable assignment.
    There is a name for that, so I can search for study?

    supportsCanvas && (window.onload = greyImages);

  • http://www.rickgrossman-blog.com D. Lukow

    Great idea but can the canvas size be changed???? How??

  • http://www.studentaffterall.com zatax

    It is possible to change the gray to black, I can not find how! Thank you!

  • http://www.jeffadams.co.uk Jeff Adams

    Neat tutorial – I’ll be honest it’s not really something I need but since it was Lionel Ritchie I thouoght it was hilarious!

  • aleks

    Hey is there any possibility of you adding a tutorial on how to use the jquery function fadein with dinamicli created images from ajax?

    • http://www.adrianflorescu.info/en Florescu Adrian

      I also have something similar, but I load new images with AJAX. Can we have this effect on images loaded by AJAX? Thanks for the tutorial!

  • http://www.fyianlai.com Ian

    Fantastic tutorial. You can achieve the same hover transition effect for IE through the use of jQuery’s fadeIn / fadeOut actually. That, coupled with Modernizr and YepNope.js should allow for optimum cross-browser compatibility.

  • http://www.pryde-design.co.uk Andrew Pryde

    I have created a jQuery plugin based on this tutorial which can be found at the following URL:

    http://www.pryde-design.co.uk/2011/08/greyscale-jquery-plugin/

    It’s up on Github too so feel free to fork it and help me improve it.

    Cheers,

    - @Prydie

  • http://www.ews.hu Honlapkészítés

    Nice design and effects all support conversion rates if done properly. But beware – it all depends on your users / visitors. The site should be made for them and not for yourself….

  • The Learner

    How can you apply this to a class instead of an id? Is it possible?

  • Beardsella

    ATM i cant use this code in multiple places on one page (its probably me being slow) has any one got a fix or any ideas or suggestions?

  • Beardsella

    ATM i cant use this code in multiple places on one page (its probably me being slow) has any one got a fix or any ideas or suggestions?

  • ram

    Totally agree with this..

    But what does it mean by “Break the Rules”?

  • Roco elite

    hey Jeffy,
    can you tell me how can we make this code usable for 2 images in same html document
    thanks in advance and
    thanks for this useful info video

  • Harry Wiles

    Just thought i’d mention that the demo url is 404ing….