11

Below I've created a simple test case that shows that when an img tag's src is set to different dataUrls, it leaks memory. It looks like the image data is never unloaded after the src is changed to something else.

<!DOCTYPE html>
<html>
  <head>
    <title>Leak Test</title>
    <script type="text/javascript">
      canvas = null;
      context = null;
      image = null;
      onLoad = function(event)
      {
        canvas = document.getElementById('canvas');
        context = canvas.getContext('2d');
        image = document.getElementById('image');
        setTimeout(processImage, 1000);
      }

      processImage = function(event)
      {
        var imageData = null;
        for (var i = 0; i < 500; i ++)
        {
          context.fillStyle = "rgba(" + Math.floor(Math.random() * 256) + "," + Math.floor(Math.random() * 256) + "," + Math.floor(Math.random() * 256) + "," + Math.random() +")";
          context.fillRect(0, 0, canvas.width, canvas.height);
          imageData = canvas.toDataURL("image/jpeg", .5);
          image.src = imageData;
        }
        setTimeout(processImage, 1000); 
      }
    </script>
  </head>
  <body onload="onLoad(event)">
    <canvas id="canvas"></canvas>
    <img id="image"></img>
  </body>
</html>

If you load this html page, RAM usage builds over time and is never cleaned up. This issue looks very similar: Rapidly updating image with Data URI causes caching, memory leak . Is there anything I can do to prevent this memory leak?

Community
  • 1
  • 1
Paul Milham
  • 526
  • 5
  • 13
  • What browser is this? You could check if there exist an issue. GC is handled internally by the browser so the only thing you can is to rewrite the logic. –  Oct 10 '13 at 20:49
  • This happens on both Chrome and Firefox, so I'm assuming it's according to the spec or something. – Paul Milham Oct 10 '13 at 22:26
  • Did they fix the bug yet? – BrightIntelDusk Mar 18 '14 at 16:23
  • You can check the bug status at https://code.google.com/p/chromium/issues/detail?id=309543&thanks=309543&ts=1382344039 . It looks like it's marked as fixed. – Paul Milham Mar 19 '14 at 17:43
  • By the way, this memory leak has also been reported for WebKit (e.g. Safari and Safari on iOS): https://bugs.webkit.org/show_bug.cgi?id=31253 – Pierre F Jul 17 '18 at 08:11

5 Answers5

9

I ended up doing a work around for the issue. The memory bloat only happens when the image.src is changed, so I just bypassed the Image object altogether. I did this by taking the dataUrl, converting it into binary (https://gist.github.com/borismus/1032746) then parsing it using jpg.js (https://github.com/notmasteryet/jpgjs). Using jpg.js I can then copy the image back to my canvas, so the Image element is completely bybassed thus negating the need to set its src attribute.

Paul Milham
  • 526
  • 5
  • 13
  • 1
    Do you have any sample code out there you can direct me to? I'm trying to implement things as you have described and am obviously not playing well with jpg.js. – JayhawksFan93 Jan 29 '15 at 22:41
  • @JayhawksFan93 look at [https://github.com/notmasteryet/jpgjs/blob/master/example.html](https://github.com/notmasteryet/jpgjs/blob/master/example.html). You need the `decodeImage` method only with _jpeg_ part of the switch if your image is in JPEG format. – Zsolt Apr 01 '15 at 06:48
  • I am experiencing something similar. I am using `drawImage` repeatedly on the `canvas` to display a video. I notice the memory start to climb right away. I noticed you have the `canvas` tagged in your OP. Will this work on canvas or just in actual element? – Ronnie Aug 27 '15 at 17:40
  • I should note, it climbs right away and then crashes after a few minutes..somewhere between 1gb and 1.5gb of memory – Ronnie Aug 27 '15 at 17:52
  • I've never tried drawing video onto a canvas using canvas 2d, but it seems to be fairly common so I'd be surprised if it leaked. Can you paste code that reproduces the issue? – Paul Milham Aug 28 '15 at 19:36
  • `jpgjs` works, but I prefer this alternative library, which improves on the jpgjs base: https://github.com/eugeneware/jpeg-js – Venryx Jun 25 '20 at 20:37
4

Panchosoft's answer solved this for me in Safari.

This workaround avoids the memory increase by bypassing the leaking Image object.

// Methods to address the memory leaks problems in Safari
var BASE64_MARKER = ';base64,';
var temporaryImage;
var objectURL = window.URL || window.webkitURL;

function convertDataURIToBlob(dataURI) {
    // Validate input data
    if(!dataURI) return;

    // Convert image (in base64) to binary data
    var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
    var base64 = dataURI.substring(base64Index);
    var raw = window.atob(base64);
    var rawLength = raw.length;
    var array = new Uint8Array(new ArrayBuffer(rawLength));

    for(i = 0; i < rawLength; i++) {
        array[i] = raw.charCodeAt(i);
    }

    // Create and return a new blob object using binary data
    return new Blob([array], {type: "image/jpeg"});
}

then, in the processImage rendering loop:

// Destroy old image
if(temporaryImage) objectURL.revokeObjectURL(temporaryImage);

// Create a new image from binary data
var imageDataBlob = convertDataURIToBlob(imageData);

// Create a new object URL
temporaryImage = objectURL.createObjectURL(imageDataBlob);

// Set the new image
image.src = temporaryImage;
Pierre F
  • 1,332
  • 15
  • 33
1

I'm also experiencing this issue and I do believe it's a browser bug. I see this happening in FF and Chrome as well. At least Chrome once had a similar bug that was fixed. I think it's not gone or not completely gone. I see a constant increase in memory when I set img.src repeatedly to unique images. I have filed a bug with Chromium, if you want to put some weight in :) https://code.google.com/p/chromium/issues/detail?id=309543&thanks=309543&ts=1382344039 (The bug triggering example does not necessarily generate a new unique image every time around, but at at least it does with a high probability)

distributed
  • 366
  • 4
  • 13
  • Starred the issue. Thanks! Also note that I was able to work around the bug using javascript, but it's less than ideal because it takes a lot more CPU than letting the browser handle it. – Paul Milham Oct 22 '13 at 22:06
  • Cool, thanks. I noted your solution. It's nice and pretty creative :) It's cool that these workarounds are possible today, but still a shame that we can fail at something simple as mundane as displaying an image. – distributed Oct 26 '13 at 08:51
0

Some solutions not mentioned in the other answers:

For browser

jpeg-js

Similar to jpgjs mentioned by @PaulMilham, but with additional features and a nicer API (imo).

For NodeJS/Electron

sharp

General purpose image-processing library for NodeJS, with functionality to both read and write jpeg, png, etc. images (as files, or just in memory).

Since my program is in Electron, I ended up using sharp, as jpeg-js mentioned it as a more performant alternative (due to its core being written in native code).

Venryx
  • 15,624
  • 10
  • 70
  • 96
0

Setting the source to a fixed minimal dataURI after handling the image seems to fix the issue for me:

const dummyPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=';

img.onload = () => {
    // ... process the image
    URL.revokeObjectURL(img.src);
    img.onload = null;
    img.src = dummyPng;
};
img.src = URL.createObjectURL(new window.Blob([new Uint8Array(data)], {type: 'image/png'}));
Mourner
  • 3,171
  • 24
  • 21