Paul Milham
Paul Milham

Reputation: 546

Setting img.src to dataUrl Leaks Memory

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?

Upvotes: 11

Views: 8889

Answers (5)

Mourner
Mourner

Reputation: 3301

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'}));

Upvotes: 0

Venryx
Venryx

Reputation: 17999

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).

Upvotes: 0

Pierre F
Pierre F

Reputation: 1392

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;

Upvotes: 4

distributed
distributed

Reputation: 396

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)

Upvotes: 1

Paul Milham
Paul Milham

Reputation: 546

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.

Upvotes: 9

Related Questions