Home > Back-end >  Save PApplet frame as image on disk not working
Save PApplet frame as image on disk not working

Time:06-21

I am making a map with unfolding with processing.core.PApplet. I'm using pure Java instead of Processing (Java 8).

My goal is to save the map as an image on my hard drive, but when I try to save it as a PNG, it is a black image. Same blank result happens when trying to save it as a TIF or JPG.

The map itself has no event dispatcher. You cannot interact with it, no clicking, zooming nor scrolling. It should have no problem saving as an image.

My Main code is quite simple:

import processing.core.PApplet;


public class Main {
    public static void main(String args[]) {
        PApplet sketch = new ChoroplethMap();
        sketch.init();
        sketch.save("test.jpg");
    }
}

I've also tried the saveFrame() method as well to no avail. From other forums, it seems like these methods have no issues in Processing but I've found no information on them in pure Java 8.

I've also tried using BufferedImages and streams but it yields the same result: a black image with no map.

Here's my map code, it is pretty much copied from the unfolding library Choropleth map example:

import java.util.HashMap;
import java.util.List;

import processing.core.PApplet;

import de.fhpotsdam.unfolding.UnfoldingMap;
import de.fhpotsdam.unfolding.data.Feature;
import de.fhpotsdam.unfolding.data.GeoJSONReader;
import de.fhpotsdam.unfolding.examples.SimpleMapApp;
import de.fhpotsdam.unfolding.examples.interaction.snapshot.MapSnapshot;
import de.fhpotsdam.unfolding.marker.Marker;
import de.fhpotsdam.unfolding.utils.MapUtils;

/**
 * Visualizes population density of the world as a choropleth map. Countries are shaded in proportion to the population
 * density.
 * 
 * It loads the country shapes from a GeoJSON file via a data reader, and loads the population density values from
 * another CSV file (provided by the World Bank). The data value is encoded to transparency via a simplistic linear
 * mapping.
 */
public class ChoroplethMap extends PApplet {

    UnfoldingMap map;

    HashMap<String, DataEntry> dataEntriesMap;
    List<Marker> countryMarkers;

    public void setup() {
        size(800, 600, OPENGL);
        smooth();

        map = new UnfoldingMap(this, 50, 50, 700, 500);
        map.zoomToLevel(2);
        map.setBackgroundColor(240);
        //MapUtils.createDefaultEventDispatcher(this, map);

        // Load country polygons and adds them as markers
        List<Feature> countries = GeoJSONReader.loadData(this, "data/countries.geo.json");
        countryMarkers = MapUtils.createSimpleMarkers(countries);
        map.addMarkers(countryMarkers);

        // Load population data
        dataEntriesMap = loadPopulationDensityFromCSV("data/countries-population-density.csv");
        println("Loaded "   dataEntriesMap.size()   " data entries");

        // Country markers are shaded according to its population density (only once)
        shadeCountries();
    }

    public void draw() {
        background(255);

        // Draw map tiles and country markers
        map.draw();
    }

    public void shadeCountries() {
        for (Marker marker : countryMarkers) {
            // Find data for country of the current marker
            String countryId = marker.getId();
            DataEntry dataEntry = dataEntriesMap.get(countryId);

            if (dataEntry != null && dataEntry.value != null) {
                // Encode value as brightness (values range: 0-1000)
                float transparency = map(dataEntry.value, 0, 700, 10, 255);
                marker.setColor(color(255, 0, 0, transparency));
            } else {
                // No value available
                marker.setColor(color(100, 120));
            }
        }
    }

    public HashMap<String, DataEntry> loadPopulationDensityFromCSV(String fileName) {
        HashMap<String, DataEntry> dataEntriesMap = new HashMap<String, DataEntry>();

        String[] rows = loadStrings(fileName);
        for (String row : rows) {
            // Reads country name and population density value from CSV row
            String[] columns = row.split(";");
            if (columns.length >= 3) {
                DataEntry dataEntry = new DataEntry();
                dataEntry.countryName = columns[0];
                dataEntry.id = columns[1];
                dataEntry.value = Float.parseFloat(columns[2]);
                dataEntriesMap.put(dataEntry.id, dataEntry);
            }
        }

        return dataEntriesMap;
    }

    class DataEntry {
        String countryName;
        String id;
        Integer year;
        Float value;
    }

}

I feel like I'm not initializing the PApplet in my main class the right way. Is there anything else I need to do besides calling the init() method?

I'm open to over suggestions besides trying to fix save() or saveFrame(), something like using BufferedImages or streams to write to a file.

Thanks, and have a nice day.

CodePudding user response:

Does the PApplet launch ? If it does, could you get away by calling save()/saveFrame() from within the PApplet ?

E.g.

 public void draw() {
        background(255);

        // Draw map tiles and country markers
        map.draw();
        save("test.jpg");
        noLoop();// alternatively just exit(); if you don't need the PApplet window anymore.
    }

Does it make a difference if you call sketch.loadPixels()l before sketch.save() ?

I also wonder if you've got any guarantees that the sketch finished initialising and rendering at least a frame when you call save. (AFAIK rendering happens on a separate thread (the animation thread)).

To test this idea you can use registerMethod("draw",this);

Register a built-in event so that it can be fired for libraries, etc. Supported events include: ...draw – at the end of the draw() method (safe to draw)

e.g. (not tested, but hopefully illustrates the idea):

public class Main {
    
    PApplet sketch;

    public Main(){
        sketch = new ChoroplethMap();
        sketch.init();
        // add a callback triggered after draw() in a sketch completed 
        // (as soon as a frame is ready)
        // sketch will search for method named "draw" in "this" instance
        sketch.registerMethod("draw", this);
        //stop rendering after the first frame
        sketch.noLoop();
    }

    // called after sketch's draw() completes
    public void draw(){
        sketch.save("test.jpg");
    }

    public static void main(String args[]) {
        new Main();
    }

}

Might be offtopic, but in the past I remember launching the sketch with PApplet.main():

import processing.core.PApplet;


public class Main {
    public static void main(String args[]) {
        PApplet.main(ChoroplethMap.class.getCanonicalName());
    }
}

This is probably relevant only if sketch.init() doesn't launch the sketch already.

  • Related