javascript memory leak with HTML5 getImageData
Asked Answered
B

1

8

I've been working on trying to make some visual effects for a javascript game I'm creating, and I noticed a piece of code that I'm using to modulate the color of my sprites makes my browsers memory usage go up and up, seemingly without limit.

You can see the code and the memory leak here: http://timzook.tk/javascript/test.html

This memory leak only happens in my updateimage() function when I call getImageData from my canvas context every frame (via setInterval) in order to make a new ImageData object to recolor. I would have thought that javascript's garbage collector would be destroying the old one, but if not I have no idea how to destroy it manually. Any help figuring out why it does this or how to fix it would be appreciated.

My question is very similar to this one: What is leaking memory with this use of getImageData, javascript, HTML5 canvas However, I NEED my code to run every frame in the functioned called by setInterval, his solution of moving it outside the setInterval function isn't an option for me, and I can't leave a comment asking if he found some other method of solving it.

Note to people testing it out: Since this example uses getImageData, it can't be tested out locally just by throwing it in a .html file, a web server is required. Also it obviously uses HTML5 elements so some browsers won't work with it.

Edit: *SOLVED* Thanks, the solution below fixed it. I didn't realize that you could use a canvas element the same way as you could use an image in drawImage(), I restructured my code so it now uses significantly less memory. I uploaded this changed code to the page linked above, if anyone wants to see it.

Bohunk answered 3/10, 2011 at 16:42 Comment(0)
S
11

You aren't getting a memory leak from your calls to getImageData(). The source of your problem is this line:

TempImg.src = ImgCanvas.toDataURL("image/png");

Effectively, every time that line of code is executed the browser "downloads" another image and stores it in memory. So, what you actually end up with is a cache that is growing very quickly. You can easily verify this by opening the site in Chrome and checking the resources tab of the developer tools (ctrl+shift+i).

You can work around this by making a TempImgCanvas and storing your image data on that canvas instead of keeping an image object updated after every call to your updateimage() loop.

I have to step away for a while, but I can work up an example in a few hours when I get back, if you would like.


Edit: I restructured things a bit and eliminated your caching issue. You just have to make two changes. The first is replacing your updateimage() function with this one:

function updateimage() {    
    var TempImgData = ImgContext.getImageData(0, 0, ImgCanvas.width, ImgCanvas.height);
    var NewData = TempImgData.data;
    var OrigData = ImgData.data;

    //Change image color
    var len = 4*ImgData.width*ImgData.height-1;
    for(var i=0;i<=len;i+=4) {
        NewData[i+0] = OrigData[i+0] * color.r;
        NewData[i+1] = OrigData[i+1] * color.g;
        NewData[i+2] = OrigData[i+2] * color.b;
        NewData[i+3] = OrigData[i+3];
    }

    //Put changed image onto the canvas
    ImgContext.putImageData(TempImgData, 0, 0);
}

The second is updating the last line in draw() to read as follows:

drawImg(ImgCanvas, Positions[i].x, Positions[i].y, Positions[i].x+Positions[i].y);

Using this updated code, we simply refer to the original base (white) image data and calculate new values based on that every time we go through the updateimage() function. When you call getImageData() you receive a copy of the image data on the canvas, so if you edit the canvas or the data, the other one remains untouched. You were already grabbing the original base image data to begin with, so it made sense to just use that instead of having to re-acquire it every time we updated.

Also, you'll notice that I modified your loop that changes the image color slightly. By obtaining a handle to the actual data array that you want to access/modify, you save yourself having to resolve the array location from the parent object every time you go through the loop. This technique actually results in a pretty nice performance boost. Your performance was fine to start with, but it can't hurt to be more efficient since it's pretty straight-forward.

Serried answered 3/10, 2011 at 20:37 Comment(5)
+1 Most browsers (probably all) cache all images that have been downloaded. This is done so images don't have to be redownloaded if they're used multiple times.Andel
Thanks, I'll be back in a few hours as well, but this looks like a very promising solution. I hadn't thought to try to mess with that bit of code ever since I got it working.Bohunk
@Bohunk No problem! With the changes I just posted in my edit, I'm not seeing the increase in memory consumption that we had before.Serried
I used some of the changes you posted except I also realized I could move "var TempImgData = ImgContext.getImageData(0, 0, ImgCanvas.width, ImgCanvas.height)" up into my setupimage() function instead of calling it each time. It works great now, thanks a lot :) By the way, I use chrome and the developer tools as well, but when I go to the resources tab I don't see anything that specifically shows how much memory images and things are using, how did you find that information?Bohunk
@Bohunk Ah, yes that line can definitely be moved as you found out. The important thing there is that we have a properly-formatted image data array to store our information in. With regard to the Chrome resource information, it wasn't so much that I saw a certain amount of memory being used but rather that an infinite stream of images was being cached. If you load up the original file and check the resources tab, in the file tree on the left under Frames->(test.html)->Images you'll see a perpetual stream of images being loaded for the page. Also, welcome to SO! :)Serried

© 2022 - 2024 — McMap. All rights reserved.