How to correctly release PImage memory

Hi all, I need to display many PImages simultaneously on the screen. The original image files are big and I resize the PImages when loaded. After 2 minutes I need to load and display other many PImages. Which is the right way to dispose a PImage and release its memory? Because after more or less 1 hour I get OutOfMemoryException. This is my simplified code:

List<PImage> images = new ArrayList<PImage>();

void setup()
{
    size(displayWidth, 200, OPENGL);    
    loadImages(dataPath("mediagallery"));
}

void draw()
{
    background(0);

    float x = 0;
    for (PImage img : images)
    {
        image(img, x, 0);
        x += img.width;
    }
}

void loadImages(String path)
{
    images.clear();

    File dir = new File(path);
    for (File file : dir.listFiles())
    {
        PImage img = loadImage(file.getPath());
        img.resize(0, height);
        images.add(img);
    }
}

void keyPressed()
{
    if (key == ' ') loadImages(dataPath("mediagallery"));
}

In Processing 1.5.1 PImage has the delete() method. There's something similar? Thanks!

Answers

  • Hey Paulo,

    When I moved to P2, and it was still in beta, I came across a similar issue. After many hours on VisualVM (not sure if that was the profiling tool I used) I discovered that whenever I did a loadImage, processing would create a texture behind the scenes and that was not being released causing the images to stay in memory. I don't really remember how I worked around this, but forcing a garbage collection can be a good start:

    loadImages(String path) { images.clear(); System.gc(); ...

  • edited August 2014

    Hi, using VisualVM you can see Java Heap Memory. This goes down forcing the garbage collector. The problem is that the memory used by the process (you can see it with Activity Monitor on MAC OSX) still remains big. What I tried is:

    Object cacheObj = getCache(img);
    if (cacheObj instanceof Texture)
    {
        Texture texture = (Texture)cacheObj;
        texture.disposeSourceBuffer();
    }
    removeCache(img);
    

    As you discovered this is the texture not released. This code improve the situation but don't solve the memory issue.

  • edited August 2015

    Processing's PGraphics will try to setCache() any PImage into a WeakHashMap when we draw it via image()!
    When you clear() your List, sooner or later those PImage references got removed from that WeakHashMap too.
    However, that'll only happen if there's no other fields or data containers storing references for them! :-@

    The most correct procedure is bug-hunt your code for any possible extra hanging PImage references!
    The other option is manually removing all WeakHashMap's cache entries via removeCache().

    An even lazier fix is simply display PImage objects via set() in place of image(). :-j
    However, you'd lose any alpha and transformations by doing so.
    But if you don't use any fancy effects, you should definitely use set()! It's faster too! $-)

    /**
     * RemoveCache (v2.3)
     * by GoToLoop (2014/Aug)
     *
     * forum.processing.org/two/discussion/6898/
     * how-to-correctly-release-pimage-memory
     * 
     * github.com/processing/processing/blob/master/core/src/
     * processing/core/PGraphics.java#L802
     */
    
    import java.util.List;
    final List<PImage> images = new ArrayList<PImage>();
    
    import java.io.FilenameFilter;
    static final FilenameFilter IMAGE_FILTER = new FilenameFilter() {
      final String[] EXTS = {
        ".png", ".jpg", ".jpeg", ".gif"
      };
    
      @ Override boolean accept(final File dir, String name) {
        name = name.toLowerCase();
        for (final String ext: EXTS)  if (name.endsWith(ext))  return true;
        return false;
      }
    };
    
    static final String  FOLDER = "mediagallery";
    static final boolean USE_SET_FOR_DISPLAY = false;
    
    void setup() {
      size(displayWidth - 100, 200, P3D);
      smooth(4);
      noLoop();
    
      loadImages(dataFile(FOLDER), g, false);
    }
    
    void draw() {
      background(0);
    
      int x = 0;
      for (PImage img: images) {
        if (USE_SET_FOR_DISPLAY)  set(x, 0, img);
        else                      image(img, x, 0);
    
        x += img.width;
      }
    }
    
    void keyPressed() {
      final int k = keyCode;
      if (k == ' ')  mousePressed();
    }
    
    void mousePressed() {
      loadImages(dataFile(FOLDER), g, !USE_SET_FOR_DISPLAY);
      redraw();
    }
    
    void loadImages(File dir, PGraphics pg, boolean forceRemoval) {
      File[] pics = dir.listFiles(IMAGE_FILTER);
      java.util.Arrays.sort(pics);
    
      println("\n" + dir + ENTER);
      println(pics);
    
      if (forceRemoval)  forceCacheRemoval(pg);
      images.clear();
    
      for (File f: pics) {
        PImage img = loadImage(f.getPath());
        img.resize(0, height);
        images.add(img);
      }
    }
    
    void forceCacheRemoval(PGraphics pg) {
      for (PImage img: images) {
        Object cache = pg.getCache(img);
    
        if (cache instanceof Texture)
          ((Texture) cache).disposeSourceBuffer();
    
        pg.removeCache(img);
      }
    }
    
  • edited August 2014

    Hi GoToLoop, I'm trying to follow your suggestions, bug-hunt and manually removing cache calling removeCache(). I cannot use set() to display PImages because I'm using alpha. Because of I have to display a lot of images I thought to use a PGraphics to draw them, and then call the image() function passing the same PGraphics, like this:

    PGraphics offscreen;
    
    void setup() {
       size(displayWidth, 200,OPENGL);
      offscreen = createGraphics(width, height, OPENGL);
    
      loadImages(dataFile(FOLDER), false);
    }
    
    void draw() {
       background(0);
       image(offscreen, 0, 0);
    }
    
    void loadImages(File dir) {
      File[] pics = dir.listFiles(IMAGE_FILTER);
      java.util.Arrays.sort(pics);
    
      println("\n" + dir + ENTER);
      println(pics);
    
      images.clear();
    
    int x = 0;
     offscreen.beginDraw();
     offscreen.background(0);
      for (File f: pics) {
        PImage img = loadImage(f.getPath());
        img.resize(0, height);
        offscreen.image(img, x, 0);
       x += img.width;
        img = null;
      }
    offscreen.endDraw();
    }
    

    It is ok to use PGraphics in this case? And if I want to release it's memory can I call simply removeCache(offscreen)? And how I can release PImages passed through the method offscreen.image()? Thanks in advance

  • At the very least you would need to

    removeCache(offscreen);
    offscreen = null; // remove reference so the image can be garbage collected
    

    You would have to change draw to

    void draw() {
       background(0);
       if(offscreen != null)
          image(offscreen, 0, 0);
    }
    

    As an alternative to setting offscreen to null you could use the reference to create a new PGraphics e.g.

    removeCache(offscreen);
    offscreen = createGraphics(width, height, OPENGL); // replaces old image reference
    
  • Sure, this is what I do. I call manually removeCache() when I need and I remove all PImage references, I pass all my code many times, but I still have OutOfMemory exception:

    Caused by: java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at java.nio.DirectByteBuffer.(Unknown Source) at java.nio.ByteBuffer.allocateDirect(Unknown Source) at processing.opengl.PGL.allocateDirectIntBuffer(PGL.java:1933) at processing.opengl.PGL.updateIntBuffer(PGL.java:1960) at processing.opengl.Texture.updatePixelBuffer(Texture.java:831) at processing.opengl.Texture.set(Texture.java:348) at processing.opengl.Texture.set(Texture.java:311) at processing.opengl.PGraphicsOpenGL.initCache(PGraphicsOpenGL.java:6163) at processing.opengl.PGraphicsOpenGL.getTexture(PGraphicsOpenGL.java:6115) at processing.opengl.PGraphicsOpenGL$TexCache.getTexture(PGraphicsOpenGL .java:6918) at processing.opengl.PGraphicsOpenGL.flushPolys(PGraphicsOpenGL.java:2464) at processing.opengl.PGraphicsOpenGL.flush(PGraphicsOpenGL.java:2415) at processing.opengl.PGraphicsOpenGL.beginDraw(PGraphicsOpenGL.java:1682) at processing.opengl.PGraphicsOpenGL.loadPixels(PGraphicsOpenGL.java:5434) at processing.opengl.PGraphicsOpenGL.initCache(PGraphicsOpenGL.java:6162) at processing.opengl.PGraphicsOpenGL.getTexture(PGraphicsOpenGL.java:6115) at processing.opengl.PGraphicsOpenGL$TexCache.getTexture(PGraphicsOpenGL .java:6918) at processing.opengl.PGraphicsOpenGL.flushPolys(PGraphicsOpenGL.java:2464) at processing.opengl.PGraphicsOpenGL.flush(PGraphicsOpenGL.java:2415) at processing.opengl.PGraphicsOpenGL.beginDraw(PGraphicsOpenGL.java:1682) at processing.opengl.PGraphicsOpenGL.loadPixels(PGraphicsOpenGL.java:5434) at processing.opengl.PGraphicsOpenGL.initCache(PGraphicsOpenGL.java:6162) at processing.opengl.PGraphicsOpenGL.getTexture(PGraphicsOpenGL.java:6115) at processing.opengl.PGraphicsOpenGL$TexCache.getTexture(PGraphicsOpenGL .java:6918) at processing.opengl.PGraphicsOpenGL.flushPolys(PGraphicsOpenGL.java:2464) at processing.opengl.PGraphicsOpenGL.flush(PGraphicsOpenGL.java:2415) at processing.opengl.PGraphicsOpenGL.endDraw(PGraphicsOpenGL.java:1712) at processing.core.PApplet.handleDraw(PApplet.java:2406) at processing.opengl.PJOGL$PGLListener.display(PJOGL.java:862) at jogamp.opengl.GLDrawableHelper.displayImpl(GLDrawableHelper.java:665) at jogamp.opengl.GLDrawableHelper.display(GLDrawableHelper.java:649) at javax.media.opengl.awt.GLCanvas$10.run(GLCanvas.java:1289) at jogamp.opengl.GLDrawableHelper.invokeGLImpl(GLDrawableHelper.java:1119) at jogamp.opengl.GLDrawableHelper.invokeGL(GLDrawableHelper.java:994) at javax.media.opengl.awt.GLCanvas$11.run(GLCanvas.java:1300) at java.awt.event.InvocationEvent.dispatch(Unknown Source) at java.awt.EventQueue.dispatchEventImpl(Unknown Source) at java.awt.EventQueue.access$200(Unknown Source) at java.awt.EventQueue$3.run(Unknown Source) at java.awt.EventQueue$3.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source) at java.awt.EventQueue.dispatchEvent(Unknown Source) at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.run(Unknown Source)

    I still think that the problem is the memory allocated with the Texture class, that is never (o almost never) released. Is is possible to force it?

  • edited August 2014

    I still think that the problem is the memory allocated with the Texture class,...

    I've included your anti-Texture disposeSourceBuffer() method in my example now!

    void forceCacheRemoval() {
      for (PImage img: images) {
        Object cache = getCache(img);
        if (cache instanceof Texture)
          ((Texture) cache).disposeSourceBuffer();
    
        removeCache(img);
      }
    }
    

    Dunno whether it makes any difference though! :-/

  • edited August 2014

    I think not, the problem persists. My finally solution is to use Processing 1.5.1 with GLGraphics library, that offers better performance with images and memory.

  • edited August 2014

    Old Processing 1.5.1 is the last version which provided deployment as a packaged ".jar" file for 3 OSes!
    And b/c it uses an older OpenGL version, it is more compatible w/ more systems!
    If we can still manage to write compatible code for that venerable version, it is a better choice indeed! :-bd

  • I'm having some troubles with GLGraphics / OpenGL, my textures have fuzzy borders. Is it possible to increase their borders smooth? Using some parameters like:

    gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)

  • maybe related: when using image(img, 0,0) over an offscreen renderer, img appears modified afterwards. I mean, the frame img was carrying before been used on myPGraphics.image(img, 0,0); is not the same after. why this could be happening? thnks

  • in processing 2.2.1

Sign In or Register to comment.