MozillaZine

Tricky issue -- logistics of saving/loading canvas images

Talk about add-ons and extension development.
akishore
 
Posts: 53
Joined: June 12th, 2006, 10:44 pm
Location: USA

Post Posted February 10th, 2007, 7:54 pm

Hi all,


I have been working on an extension that deals extensively (pun intended =D) with the <a href="http://www.whatwg.org/specs/web-apps/current-work/#the-canvas">canvas</a> element. Several months ago, I made a post asking about ways to save canvas data to disk, either as an image file (e.g. png) or just canvas-specific data. The key was being able to reload that canvas data back to another canvas at a later time.

It turned out that doing such a thing was pretty tricky, because the capability wasn't fully implemented in Firefox 1.5. It was *possible* using XPCOM and making your own C++ components bla bla bla (PearlCrescent's PageSaver did it), but all that stuff wasn't necessary because Firefox 2.0 was going to implement it fully. And voila, it did!


After putting that issue aside for months, I finally revisited it recently and using a Javascript shell, was able to save an image to a PNG file! I had to google around a little, because it wasn't totally trivial -- turns out you have to use a method called <a href="http://lxr.mozilla.org/seamonkey/source/toolkit/content/contentAreaUtils.js#192">saveImageURL</a> in a native Firefox file called contentAreaUtils.js to actually save the canvas data to disk, like so:

Code: Select all
var canvas = document.getElementById("canvas");
var dataurl = canvas.toDataURL();
saveImageURL(dataurl, null, "SaveImageTitle", false, false, null);


<b>BUT...</b> that only solves half the problem!


I have been struggling a lot lately with an efficient way of being able to *load* saved images back into another canvas. Specifically, there are only a few methods I can think of:

1. Save to disk as a PNG file. Then, when needing to load it, create a new HTMLImageElement with the src attribute set to the PNG file path, then use the <a href="http://www.whatwg.org/specs/web-apps/current-work/#drawimage">drawImage</a> method in CanvasRenderingContext2D.

2. Save the pixel information via <a href="http://www.whatwg.org/specs/web-apps/current-work/#getimagedata">getImageData</a> and save the ImageData <a href="http://www.whatwg.org/specs/web-apps/current-work/#imagedata">variables</a> to disk as a file. When needing to load the data, create a new ImageData object and use <a href="http://www.whatwg.org/specs/web-apps/current-work/#putimagedata">putImageData</a>.

3. ???


Here are the issues with these three methods:

1. If I want to save a bunch of images at once, I would need to save individual PNG files for each image. This is basically unacceptable because of the specifics of my extension. Basically, it will almost always be the case that multiple images (on the order of 50+) will need to be saved all at once, and I would prefer that the user did not have access to or knowledge of 50+ image files on their hard drive somewhere....

In other words, it's best if all the data could be saved as one file, and I don't see an efficient way of doing that with method 1.

One workaround is to save all the PNG data into one file (as text), and when you need to load it, create the 50+ PNGs with saveImageURL somewhere onto the hard drive (without the user having any knowledge of this), but I have a feeling that this will be grossly inefficient at load time. Anybody have any idea if this will indeed be the case?

(Just creating one image took on the order of 1-2 seconds on my computer! Creating 50+ images would be far too time-consuming I think.)

2. This would be great, except..... <i>putImageData isn't working!</i> I'm not sure if the problem lies in getImageData or in putImageData. Take a look at some sample code:

Code: Select all
var a = document.getElementById("canvas1");
var b = a.getContext("2d");
var c = b.getImageData(0, 0, a.width, a.height);

a = document.getElementById("canvas2");
b = a.getContext("2d");
b.putImageData(c, 0, 0);


This code throws the error: "NS_ERROR_DOM_SYNTAX_ERR on line 1: An invalid or illegal string was specified". ????

Going into the Javascript shell, a is correctly an HTMLCanvasElement, b is correctly a CanvasRenderingContext2D, but c is just an Object. I'm pretty sure c should be an instance of ImageData, but it doesn't say so. However, c.width, c.height and c.data all return correct values.

Alas, I can't get this to work at all. Can someone help me out?

3. I feel like there should be more/easier ways of solving this problem. Any ideas??


Sorry for the long post, and thanks for reading. Any and all help is greatly appreciated. Thanks in advance!

Aseem

Mook
 
Posts: 1752
Joined: November 7th, 2002, 9:35 pm

Post Posted February 11th, 2007, 1:37 am

How big are your images? I'm guessing 1 didn't work well because it needed to allocate a giant string to hold the data: URL in... It's base64 encoded, so it should be bloated to approximately 4/3 of its original size. It'd probably still be your best bet though (assuming you're not going to be writing things in a compile language).

Also, 2 is guaranteed to be a lot slower than 1. I had an written an image decoder for 1.7 branch once, it took about, umm, I think it was five or ten presses of the slow script dialog to render a 640x480 image. (It didn't matter if I was actually decoding real data, making up random junk, or just returning a constant color, IIRC). You want to avoid making calls as much as possible.

Also, if you have the data: urls around, you don't need to save them to disk to load them - <html:img>, <xul:image>, etc. all happily display them (in fact, they have no idea what protocol it uses, it's all the same). But then, if you're Firefox 2+ you might want to look at sqlite...
poot.

akishore
 
Posts: 53
Joined: June 12th, 2006, 10:44 pm
Location: USA

Post Posted February 12th, 2007, 1:11 pm

Mook, thanks for your response. I didn't ignore it, I just haven't had time to look into this stuff. But the advice about html:img and xul:image loading data: urls is excellent. I will check it out. Thanks!

Aseem

raphlinus
 
Posts: 1
Joined: February 20th, 2007, 10:54 pm

Post Posted February 20th, 2007, 11:19 pm

If you can persist the image objects, then the following makes sense. Otherwise, you'll probably have the best luck joining and splitting the data urls from toDataURL(), treating them as strings.

I'm pretty sure the most efficient way to store precomputed canvas images is to use one canvas as the source for a drawImage in another. The following code works for me in both Safari (2.0.4) and Firefox (2.0.0.1/Mac).

Code: Select all
<html>
<head>
    <script type="text/javascript">
    function draw()
    {
        var ctx = document.getElementById('canvas').getContext("2d");
        var can2 = document.getElementById('canvasBackBuffer');
        var ctx2 = can2.getContext("2d");

        var img = new Image();
        img.src = 'image.png';

        img.onload = function()
        {
          ctx2.drawImage(img, 0, 0);
          ctx.drawImage(can2, 0, 0);
        }
        if (img.complete) { img.onload(); }
    }
    </script>
</head>
<body>
    <input id="Button1" type="button" onclick="draw()" value="button" />
    <canvas id="canvas" width="200" height="100" style="border:solid;"></canvas>
    <canvas id="canvasBackBuffer" width="200" height="100" style="visibility:hidden"></canvas>
</body>
</html>


This code is based on a previous attempt at getImageData/putImageData (see this thread). That version there also had the problem of not drawing the image if it loads instantly (apparently when the image hits in cache). It seems to be very easy to get not-knowing-when-to-paint bugs with Canvas, and these can also be very browser-specific.

The Firefox implementation of gID/pID is not there yet. Aside from the bug mentioned above, I couldn't get it to work. Even if it did, it stores the image data as a JavaScript vector of JavaScript numbers, which is a nontrivial amount of overhead. The current Firefox should run at Cairo speeds, and some day the alpha compositing might actually happen in the graphics card.

elias_jarrouj
 
Posts: 1
Joined: March 6th, 2007, 3:30 am

Post Posted March 6th, 2007, 3:34 am

Hi,

I'm having problem saving canvas to image to maybe jpg format in javascript. I can load successfully an image but i can't save. Is there anyone could help and send me a sample code? I'm actually learning how to develop extentions form mozilla firefox. thanks.

akishore
 
Posts: 53
Joined: June 12th, 2006, 10:44 pm
Location: USA

Post Posted March 20th, 2007, 12:47 am

All,

I was able to extract the data:url's, save them to a file, and then load them again at any later time by reading the data:url from the file, then simply creating an <img> object (in the xhtml namespace) and setting its src to the data:url. I was under the impression that the data:url codes the location in memory of the canvas contents, not the actual contents themselves, but it turns out I was wrong.

Thanks very much for your help! I will update this thread really soon when our extension goes live -- it is a very cool extension that might excite you, both from a usage point of view and a development point of view.

Aseem

safarnejad
 
Posts: 1
Joined: July 1st, 2007, 10:33 am

Post Posted July 1st, 2007, 4:46 pm

The code posted above by "raphlinus" works fine when dealing with static images off the disk. But if the image is sourced from the canvas using the Canvas toDataURL() method, and then passed to the context's drawImage() method, it fails after the second draw. The exception thrown by my Firefox 2.0.0.4 is:

Error: uncaught exception: [Exception... "Security error" code: "1000" nsresult: "0x805303e8 (NS_ERROR_DOM_SECURITY_ERR)" location: "file:///C:/workarea/test.html Line: 23"]

Here's a one page test to see for yourself.
After loading the page, a triangle is drawn in the corner.
The first time you click on it, it will capture the image, double the canvas size, and redraw the captured image back on the same canvas.
The second time you click on the canvas, it will throw the exception mentioned above, even though the same lines of code are being run as in the first iteration!!


Code: Select all
<html>
<head>
<title>Test toDataURL</title>
<script type="text/javascript">
var canvas,ctx,snapshot;

   function init() {
      canvas = document.getElementById('map');
      ctx = canvas.getContext('2d');
      canvas.onmousedown=grab;
      snapshot = new Image();
      snapshot.onload = reload;
      drawtri();
   }

   function reload() {
      drawtri();
      ctx.drawImage(snapshot,0,0);
   }

   function grab(e) {
      ctx.scale(2.0,2.0);
      snapshot.src = canvas.toDataURL();
   }

   function drawtri() {
      ctx.beginPath();
      ctx.moveTo(20,20);
      ctx.lineTo(20,5);
      ctx.lineTo(5,20);
      ctx.closePath();
      ctx.stroke();
   }

</script>
</head>
<body onload="init()">
<canvas id="map" width="300" height="300"></canvas>
</body>
</html>


I would appreciate any help and clarification on this bug, as I'm completely lost on what the problem is with my code, or with Firefox.

Thanks,
Ali

Return to Extension Development


Who is online

Users browsing this forum: No registered users and 2 guests