User-friendly image saving from the canvas

After the previous few canvas exercises, I wanted a simple way to allow users to save images from a canvas tag to their local machine. Ideally, without any server-side dependencies. While the whole right-click-the-canvas-and-open-a-png-in-a-new-tab is a handy pro-tip, I really wanted a nicer and easy user experience. Something I wouldn’t have to explain to my mom how to use. I figured there were a few solutions out there, so I began poking around. I ended up with something of my own :)

For starters, it’s really simple to get the bytecode for an image from the canvas tag. No magic necessary; canvas.toDataURL() to the rescue. This function accepts a MIME-type as an optional parameter (think: ‘image/png’), and returns a gianormous base64 encoded string containing all the pixel data.

Some quick google searches revealed that you can take this string and change its MIME-type to trick the browser into thinking it is a file for download. Some of the more popular canvas-to-image utilities (mainly the aptly named canvas2image) do this. While canvas2image is a lot more robust (it let’s you save to a bunch of different image formats), the whole gist of it’s approach focuses on client side mime-type switching like:

var data = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
window.location.href = data;

This is sooooo close to what I was after! However, I just can’t get down with not being able to set a filename for the downloaded file. Safari and Chrome do a decent job of not freaking out the user by saving the files with the name ‘Download’. No file extension or anything, but it’s readable. However, I knew I needed a different solution after testing out how FireFox handled it:

Yikes. To the average user, it looks like this thing is about to eat their hard drive! What the heck is '...z6zSHmu/vFec+...'?! Well, us smarties know its just the image bytecode, but the average joe would be left wondering if it’s time to cancel that operation. It would be much better to have it say something like mypicture.png.

Needless to say, image bytecode is orders of magnitude above what I am able to comprehend. After laughably trying in vain to pass a filename into the byte-code string, I gave up on a client-side only approach. Since my hackish toying, I don’t even think file names are stored in the byte-code. I ran a diff of the byte code between a file called ‘Download’ and a close renamed ‘Download.png’ and both files were exactly the same.

Anyway, to the server side we go! I kludged together simple php script to accept the bytecode string from the toDataURL() function, and decode it back into an image. All while setting the files’ MIME type as an image, making it a download, and giving it a custom filename. It looks like this:

	# we are a PNG image
	header('Content-type: image/png');
	# we are an attachment (eg download), and we have a name
	header('Content-Disposition: attachment; filename="' . $_POST['name'] .'"');
	#capture, replace any spaces w/ plusses, and decode
	$encoded = $_POST['imgdata'];
	$encoded = str_replace(' ', '+', $encoded);
	$decoded = base64_decode($encoded);
	#write decoded data
	echo $decoded;

Ok. Server side down. Let’s move it back to the client side. Enter the CanvasSaver object. Now we can more easily implement the client side portion of this process.

function CanvasSaver(url) {
	this.url = url;
	this.savePNG = function(cnvs, fname) {
		if(!cnvs || !url) return;
		fname = fname || 'picture';
		var data = cnvs.toDataURL("image/png");
		data = data.substr(data.indexOf(',') + 1).toString();
		var dataInput = document.createElement("input") ;
		dataInput.setAttribute("name", 'imgdata') ;
		dataInput.setAttribute("value", data);
		dataInput.setAttribute("type", "hidden");
		var nameInput = document.createElement("input") ;
		nameInput.setAttribute("name", 'name') ;
		nameInput.setAttribute("value", fname + '.png');
		var myForm = document.createElement("form");
		myForm.method = 'post';
		myForm.action = url;
		document.body.appendChild(myForm) ;
		myForm.submit() ;
		document.body.removeChild(myForm) ;
	this.generateButton = function (label, cnvs, fname) {
		var btn = document.createElement('button'), scope = this;
		btn.innerHTML = label;['class'] = 'canvassaver';
		btn.addEventListener('click', function(){scope.savePNG(cnvs, fname);}, false);
		return btn;

When creating a new CanvasSaver, I pass in the URL to the saveme.php file. Then, I generate a simple button to allow a user to save a PNG from the canvas with the generateButtion() method. I could skip the button generation, and opt for manually calling the savePNG function arbitrarily, but the button is nice enough.

Here is an example of implementing the CanvasSaver in a project:

var cs = new CanvasSaver('')
cs.generateButton('save an image!', cnvs, 'myimage');

And how about a working demo:

Hopefully, I’ll be refining this over the next few days. It needs more work on the implementation side of things, but it’s working. And it seems more user-friendly then client-side only saving. Hopefully you find this useful!

It’s been a good experiment. When exploring a new technology, reinventing the wheel is underrated

Update: Fork, clone and push away on github: CanvasSaver

67 Responses to 'User-friendly image saving from the canvas'

  1. Nice stuff Jon! Very interesting and cool. Definitely worth re-inventing the wheel to learn a new technology.

  2. Solenoid says:

    The part about no file naming on client side is a real deal breaker for me, but since you went ahead and did the research on this I won’t have to, thanks.

  3. ANNA says:

    Thanks very match.. This is exactly what I was lookig for!!!

  4. GrooN says:

    Hi, interesting article.
    It’s a shame that there isn’t a way to let the user download it client-side. Sending all the data to the host before being able to download it is a waste of, not only bandwidth, but also time for the client.

  5. João says:


  6. Delapouite says:

    Hi Jonathan.

    Did you have a look at the new download attribute introduced recently in Chrome that let you choose the file name?

  7. Jacques says:

    Found the error
    In your php it says
    header(‘Content-type: image/png’);
    but i’m sending jpegs so ive changed ot to
    header(‘Content-type: image/jpg’);
    now it works

  8. Jacques says:

    No it was not that sorry. still have the same problem Jon

  9. Bob says:

    Great stuff!

  10. Russ says:

    This is exactly what I’m after! But is there a way of getting something like an Adobe Edge animation or even Flash to sit in the canvas and capture that?

  11. Russ says:

    This is exactly what I was looking for! But is there a way of placing an Adobe Edge animation or even Flash on the canvas and capturing that?

  12. duncan says:

    Help – File stops at 393,216 bytes ???

    That’s really really good. one thing though – after experimenting with this, if i make the canvas bigger (or the image way more complex) the file always stops saving at exactly 393,216 bytes. if this is enough to include the whole image then I have a working file – if the image is more than 393,216 bytes then i only get some of a file and it’s not usable (for example, on my mac I can see in the bounds of the file in the quicklook preview but the bottom whatever % will be blank because its stopped at 393,216 bytes). Any ideas why this would be ????

  13. Erick says:

    So I made a little program where you can take multiple pictures thanks to “getusermedia” using chrome. Im at the point where i try to save the images taken using “” etc.. YES, an array… the problem is that php throws a $_FILES error 4 which is: “No File uploaded”… I could let $_POST handle the encoded data, i just need to add more data to the post_max_limit in the php.ini but i heard that it can be a bad idea because DOS attacks.. so please point a finger … How do I save an array of images to php? thank you..

  14. Kendall says:

    Doesn’t work on Android 2.3 stock browser

  15. Djordje says:

    This .php script doesn’t work for me. When I use
    var cs = new CanvasSaver(‘;) everything works fine, but when I download code for script and use
    var cs = new CanvasSaver(‘saveme.php’)I can’t open the image.

  16. Igor says:

    Great job, man! Very useful stuff. Tnx!

  17. knospe says:

    First of all, thank you Jonathan for this great example.

    I have been playing around with fabric JavaScript library and have used your example to save canvas as an image. I have stumbled upon a problem that was already described by duncan and here is my solution for it;

    To get to save the image larger than 393,216 B in Chrome, set the type of the input for imgdata to “hidden” -> in canvassaver.js by

    var dataInput = document.createElement(“input”);
    dataInput.setAttribute(“name”, ‘imgdata’);
    dataInput.setAttribute(“value”, data);

    set value for the “type” attribute -> dataInput.setAttribute(“type”, ‘hidden’);

    If this attribute is not set, the default value “text” is used. Each browser has a different limitation on this input type. That is why for example image that was saved looks ok in Firefox, but in Chrome gets like cut off at the bottom.

    I hope this helps.

    • Jonathan Greene says:

      thanks for the input. i’ll give that a while on a large image. cheers.

    • Jeff says:

      Thank you so much, knospe! Had this same problem with 393,216 byte size in chrome and after days of troubleshooting I found your simple solution. Thank you so much.

  18. Ingmar says:

    Hi Jonathan,

    Thank you very much for this! I’m really happy I found your script, this was exactly what I was looking for!

    One question: your script doesn’t seem to work on Android. The image is saved, but it’s empty (0 bytes).

    Might you know how this can be solved?

    With kind regards,


  19. Borja says:

    Awesome stuff!!Very great examples :)

  20. Jonathan Greene says:

    Hey Guys, not sure why I never linked this before. But I just updated the article with the git hub repo address. Thanks!

  21. Thanks for this great article. I encountered a gotcha that confounded me for hours that I’ll pass along. Initially, the script wasn’t working for “large” images …. so I checked, and re-checked my httpd.conf and php.ini for the ususal suspects :
    * LimitRequestBody
    * upload_max_filesize
    * post_max_size

    All of them were set to large thresholds, and still, I was getting a blank $_POST array even though the data was correctly posting from the browser to my server according firebug.

    Turns out the issue was the suhosin security settings. So, for those running php with the suhosin security patch (pretty much all of you), you’ll need to take a look at suhosin.ini (debian -> /etc/php5/conf.d/suhosin.ini). You’re looking for the following field :

    ; = 1000000

    That limits the value of any non-file-upload post variable (such as content in an input field) to under 1MB. My canvas “screenshot” data was about 1.2MB of base64 encoded data, which exceeded that default threshold. After adjusting the threshold upward (to 3 MB), my problems diappeared! Hope this helps someone…

  22. spacestud says:

    The script is really nice !, saved me :)

  23. ppyyo says:

    While trying the example locally (.js and .php files in the same folder as example.html) with firefox I couldn’t save the image but the saveme.php. Could you anyone tell me why? Has this to do with permissions? Thanks in advance.

  24. Ivo Ilic says:

    I keep getting this warning in the JS console
    “Resource interpreted as Document but transferred with MIME type image/png”
    Anyone know how I can get rid of this/ what this is?

  25. Madan says:

    Thank Jonathan for great solution and knospe for helping to save large files saved life

  26. John says:

    Just what I needed for a project I am working on. Excellent article and code. Thanks.

  27. calzinispaiati says:

    how do I give a color background to the canvas?


    • calzinispaiati says:

      Is there a way to save it in jpg? I can not change the color to the canvas, or rather it can change, but when it does the screenshot, reads another canvas generated by a js that can not be edited. I think I would save it in jpg chance to have a white background.


  28. Fred says:

    Thank you! This has worked so far (in Chrome)–a relief to be able to save the images with a halfway-decent filename.

  29. okta via says:

    thank you ….
    This is really what I needed and was very helpful to me.

  30. Laura says:

    Very nice!

    I found it preferable, myself, to replace the this.generateButton code with this:

    this.generateEvent = function(cnvs, fname) { this.savePNG(cnvs, fname); };

    so that I could call it directly from anywhere in the javascript:

    var cs = new CanvasSaver(‘saveme.php’)
    cs.generateEvent($(“#canvas”)0, ‘canvas’);

    • Laura says:

      Note that where the blog software has interpreted a footnote, there is supposed to be an array index of 0. Not that it really matters; do it however you normally reference your canvas.

  31. Akash says:

    Finally got a perfect script i wanted. Thanks.

  32. arie says:

    I don’t know how to integrate this code with Google chart API code..PLEASE SOMEBODY..!!!!HELP ME…!! T___T

  33. Echeban says:

    Can you write the PHP code in C# ?
    I wrote a JS script inside a ASP.NET page using WebGL to create an image.
    I am able to save it as PNG but it does not have the extension PNG (same problem everybody is having) so my users are freaking out about this extensionless file being saved in their client.
    I do not know PHP and even less how to insert PHP code inside C#
    Please help me.

  34. Tejas Suthar says:

    Thanks man!
    It’s working perfect as expected. Earlier I did exported with canvas2Image library. But it was not working with safari.

    Now its working with safari as well.

    Many thanks for your library. It’s just working awesome.

    – Tejas Suthar

  35. Somehow I can’t get this example to work. The data is sent to PHP, then the PNG downloads, but when finished, the PNG file is 0kb.

  36. shhasan says:

    Hello Jonathan. Thank you for this amazing article. Unfortunately the script that you’ve posted isn’t working for me. The file being downloaded isn’t the image but the saveme.php file. I’ve also tested using CanvasSaver GitHub package and I get the same issue there as well. How do I solve this issue ? All help is greatly appreciated.

    • shhasan says:

      I was able to find an alternative. HTML download attribute. I’m using something like this: = ‘filename.png’;

  37. cyberhood says:

    i tried using this and it worked in xampp and a free server… but image doesn’t download in actual server. instead it shows huge lot of letters.

  38. cyberhood says:

    i tried using this and it worked in xampp and a free server… but image doesn’t download in actual server. instead it shows huge lot of letters.
    how can i get it working? please help. thank you

  39. swati says:

    Can anybody write the same save.php code in python/django please?

  40. swati says:

    Can anybody write the sane save.php file code in python/django?

  41. Aron says:

    Thank You – Bookmarked.

  42. Anthony says:

    I get this error.

    “does not allow request data with POST requests, or the amount of data provided in the request exceeds the capacity limit.”

    I am sending a full screen canvass image. Ideas?

  43. Anthony says:

    Spent the morning trouble shooting this. Had to increase the limits in PHP to allow for uploads and to turn off extra security in apache as mod sec had a 1MB limit on the size of post fields. Thanks for this! Maybe include a server config info box — as this was a few hours to get this sorted out.

    • Jonathan Greene says:

      Yep, you’ll need to boost PHP’s post max and upload max settings.

      You can do it in the php.ini file:

      or in the .htaccess file:
      php_value post_max_size XXM
      php_value upload_max_filesize XXM

      Where XX is the number of MBs you’d like the new limit to be.

  44. Anthony says:

    How do I call the savePNG function from, say, a link? I don’t want to generate the button, I’d rather just call the script myself. Ideas?

    • Jonathan Greene says:

      You can call the ‘savePNG’ method on the CanvasSaver instance directly instead of generating a button like so:

      var mycs = new CanvasSaver('/path/to/saveme.php');
      mycs.savePNG(cnvs, "name_of_image");

      You could event chain it all together if you’d like:

      new CanvasSaver('/path/to/saveme.php').savePNG(cnvs, "name_of_image");

      Where ‘cnvs’ is your canvas instance.

  45. bernte says:

    after saving the image and reloading manually the page i am getting an undefined index for name and imgdata on save.php

    you should change it to this

    • bernte says:

      if (!empty($_POST[‘name’])){

      # we are a PNG image
      header(‘Content-type: image/png’);

      # we are an attachment (eg download), and we have a name
      header(‘Content-Disposition: attachment; filename=”‘ . $_POST[‘name’] .'”‘);

      #capture, replace any spaces w/ plusses, and decode
      $encoded = $_POST[‘imgdata’];
      $encoded = str_replace(‘ ‘, ‘+’, $encoded);
      $decoded = base64_decode($encoded);

      #write decoded data
      echo $decoded;


  46. bernte says:

    and change in canvassaver.js[‘class’] = ‘canvassaver’;
    btn.className = ‘canvassaver’;

    because the class hasn’t set ;-)

    btw.. nice script!!

  47. Elvira says:

    Thank you! Just what I wanted.

  48. Himaja says:


    Great work, thanks for providing such an easy solution.

    I need to save the image to the server instead of providing a download option, can you let me know if that’s possible? I’m sorry, I’m not very proficient in PHP so I’m not sure how i can do this.

    Thanks in advance!

  49. Eolia says:

    Or simply change:

    function saveFile (strData) {
    var link = document.createElement(‘a’); = ‘image.png’;
    link.href = strData;;

  50. BestCortez says:

    I see you don’t monetize your page, don’t waste
    your traffic, you can earn additional bucks every month because
    you’ve got high quality content. If you want to
    know how to make extra bucks, search for: Ercannou’s essential tools best adsense alternative

  51. depobola.Me says:

    Trusted Online Sbobet Casino Real estate agent
    Sbobet Casino is a type of online casino gambling game that has mushroomed in Indonesia.
    Players no longer need to maneuver from their benches to
    play casino gambling, because now with the advancement
    in technology the gambling industry has now entered a new phase known as online
    gambling. Present Sbobet service provider as the best wagering promoter in Asia with the web betting method certainly
    provides more advantages because it is more accessible to you
    wherever and anytime you need it.

    Using live streaming technology broadcast live from the Philippines casino with a betting
    system directly from the screen of your device, Bola tangkas Casino
    uses a top quality server and casino program that has passed studies and meets international
    standards with an official license from PAGCOR First Cigayan.

    When talking about gambling, of course sportsbook and online
    casino games are the prima donna in this business. Most soccer fans and Sbobet Casino spend more time in these two game categories,
    especially on the soccer markets such as Asian Handicap
    for the gambling category of poker and poker online games for casino.

    Uniquely in Indonesia, which is one of the countries that prohibits gambling activities,
    Sgd777 gambling companies cannot touch their customers in Indonesia directly.
    Therefore, the service provider needs an agent that provides for a liaison.
    Therefore Depobola exists as an agent of Sbobet Casino, providing you
    access to the creation of an official Sbobet Casino online bank account which you can use
    to join all the games provided on the official website.
    A new variety of the best games that you can find and you can play here.
    From gambling to baccarat, blackjack, roulette, to the most popular games in the

    Why Depobola?
    Until now there has been millions of established members who registered through Depobola, along with its easy and convenient
    registration, through the official Sbobet broker you can also get various benefits that are unable to be obtained from other places,
    – The financial transactions such as debris and withdrawals can be
    achieved in a short time or even simply a matter of minutes.
    Accommodating various types of deposit methods, players can even make deposits through
    OVO services!
    – We provide complete game variants, not only online games from the Sbobet site but also other services such as Nova88, Isin4D, 338A,
    Tangkas and Poker, as well as much other types of products.

    – Depobola customer service is always online 24
    hours a day every few days. So, no matter when a player
    is actively playing or facing difficulties, our customer
    service will always be ready to help users provide
    the best solution.
    – All kinds of products offered are all of
    international standard and are ensured to have the best quality software in its school.

    – Money that players get in the game will be paid whatever
    the amount without the minimum or maximum limits.
    – Prepared with informative features such as play guides, match predictions to the latest information related to the game.

    – Member identity is guaranteed safe and may not be leaked to people.

    – To sign up for the online soccer gambling game and Sbobet Casino Online,
    as well as other exciting games with us, all you need to do is click the “register” button located
    over a homepage of this site.

    Register yourself now, become a member of us and enjoy the most exciting
    online gambling experience with millions of other members worldwide only with us all.
    Have a nice play!

  52. Lensamovie says:

    Watch the Best Movie associated with All Time
    What movie do you want to watch today? Sniper movies may be required about your list.

    This will be the most exciting shooting battle, where every sniper
    actions is always interesting in order to watch.

  1. […] User-friendly image saving from the canvas – greenethumb Share this:TwitterFacebook This entry was posted in Uncategorized by zeezali52. Bookmark the permalink. […]

  2. […] Special thanks to: Lars Jung, for the js QR Generator and CanvasSaver by greenethumb. […]

  3. […] we couldn’t find any way to ensure the correct download behavior across browsers without sending the image data to a server-side script that returns it with headers telling the browser to download. The final task was showing a […]

Leave a Reply

Your email address will not be published. Required fields are marked *