Drawing on a headless window and output png

I am trying to run an app headlessly.

  • Partially because for my use case I do not need to see the drawings being made only to generate them.
  • Partially because I am trying to generate 6000, 6000 images and I do not have enough monitor space to support such a thing.

I have currently tried running as the following:
main.cpp:

#include "ofMain.h"
#include "ofApp.h"
#include "ofAppNoWindow.h"

//========================================================================
int main( ) {

	ofAppNoWindow window;  
    ofSetupOpenGL(&window, 6000, 6000, OF_WINDOW);  
    ofRunApp(new ofApp());  
}

ofApp.cpp:

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    cout << "setup has run" << endl; //Confirm execution of setup - This does print into the console.

    ofBackground(0, 0, 0, 0); // set the background color to black
    
    // Save transforamtion matrix
    ofPushMatrix();
    // Translate Canvas
    ofTranslate(ofGetWidth() / 2, ofGetHeight() /2);

    int AXIS_LENGTH = 3000;

    ofSetColor(255, 255, 255);
    // Draw Y Axis
    ofDrawLine(0, 0, 0, AXIS_LENGTH);
    ofDrawLine(0, 0, 0, -AXIS_LENGTH);

    // Draw X Axis
    ofDrawLine(0, 0, 0, AXIS_LENGTH);
    ofDrawLine(0, 0, 0, -AXIS_LENGTH);

    for(int i = 0; i <= AXIS_LENGTH; i++) {
        if (i % 10 == 0) {
            // Draw lines connecting each axis to its neighbor.
            ofDrawLine(i, 0, 0, AXIS_LENGTH - i);
            ofDrawLine(-i, 0, 0, AXIS_LENGTH - i);
            ofDrawLine(i, 0, 0, i - AXIS_LENGTH);
            ofDrawLine(-i, 0, 0, i - AXIS_LENGTH);
        }
    }

    // ofSaveScreen("output.png"); - Segmentation Fault when running headless.
    image.allocate(ofGetWidth(), ofGetHeight(), OF_IMAGE_COLOR_ALPHA);
    image.grabScreen(0, 0, ofGetWidth(), ofGetHeight());
    image.save("test.png");
    ofExit();
}

//--------------------------------------------------------------
void ofApp::update(){

}

//--------------------------------------------------------------
void ofApp::draw(){
    
}



// All other functions are empty. Omitting.

and lastly ofApp.h:

#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();

		void keyPressed(int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void mouseEntered(int x, int y);
		void mouseExited(int x, int y);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);

		ofImage image;
		
};

With this setup I do not get anything on the output, however it is the correct size. Some more info I am using:

image.allocate(ofGetWidth(), ofGetHeight(), OF_IMAGE_COLOR_ALPHA);
    image.grabScreen(0, 0, ofGetWidth(), ofGetHeight());
    image.save("test.png");
    ofExit();

because ofSaveScreen("output.png); will cause a SegFault in the code.

The line image.allocate(ofGetWidth(), ofGetHeight(), OF_IMAGE_COLOR_ALPHA); is particularly needed as otherwise the program returns that the image is not allocated.

When I execute this code the programs outputs the following in bin/data/test.png:

Replacing my main.cpp with:

#include "ofMain.h"
#include "ofApp.h"
#include "ofAppNoWindow.h"

//========================================================================
int main( ) {


	// Use ofGLFWWindowSettings for more options like multi-monitor fullscreen
	ofGLWindowSettings settings;
	settings.setSize(6000, 6000);
	
	settings.windowMode = OF_WINDOW; //can also be OF_FULLSCREEN

	auto window = ofCreateWindow(settings);

	ofRunApp(window, make_shared<ofApp>());
	ofRunMainLoop();

}

I get a better but still not correct output for the application that is 6000x2103px "I believe this is just the maximum size that my monitors are able to hold. I have attached a snipping tool shot of it as the actual file is much to large to upload.

Is there some other way I can get the value of the "screen" as I am now believing that the issues lies with the fact there is not technically a screen since I am running ofAppNoWindow. I am willing to run with the window, however I would still need to push the resolution to 6000x6000. Some guidance from anyone would be much appreciated!

Edit: Typos whoops :smiley:

Sorry to double post but I wanted to add that I believe this might be some strange thing Windows is enforcing onto the ofWindow due to the fact that if I stack my screens up in the windows settings as such:

I now get the following output:

Is it possible to trick windows into believing I simply have more screen space than I actually do? I believe that this could be a very hacky way to accomplish what I am aiming for. Perhaps some sort of virtual machine could be used? - I may be off track but just trying to find solutions!

Hey @awesamdude you can always render into an ofFbo in a windowed app, and then draw a scaled version of it to fit the monitor. I’m pretty sure that all the openGL stuff in OF is not available when ofAppNoWindow is used in main.

The rendering in the ofFbo can be saved, instead of grabbing it from the screen, with something like:

ofPixels pixels;
fbo.readToPixels(pixels);
ofSaveImage(pixels, "image.jpg");

An ofFbo can be drawn to any size with something like fbo.draw(0.f, 0.f, 1080.f, 1080.f), where the 3rd and 4th arguments specify the width and height of the drawing on the screen.

1 Like

ofFbo is what you want here.

Add
ofFbo myFbo;
to your ofApp.h

Then in setup:

myFbo.allocate(6000, 6000, GL_RGB); 
myFbo.begin();
ofClear(0, 0, 0, 255);
//draw here`
myFbo.end();
ofPixels pix;
myFbo.readToPixels(pix);
ofSaveImage(pix, "test.png");

The above won’t actually show the image.
Though I am not sure if it will work without a Window ( which provides the GL Context ). If it doesn’t try changing the app to a regular windowed one. You could always make it really small or move the window pos offscreen if needed.

2 Likes

This solution does work for building my images!

I have tested and can confirm that running with ofAppNoWindow will cause the following error:

Sam@DESKTOP-I3MCUMR MINGW64 /c/of/apps/myApps/myTestSketch
$ make RunDebug
[warning] ofInit: MSYS2 has limited support for UTF-8. using English_United States.1252
setup has run
[ error ] ofFbo: GL frame buffer object not supported by this graphics card
[ error ] ofImage: saveImage(): couldn't save ""0.png"", pixels are not allocated

I believe that the pixels are not allocated due to the fact as @theo said here:

there is not actually any window at all.

I can however when using a standard ofWindow of any size successfully generate 6000x6000 images inside the setup() function.

I also believe that calling ofExit() at the end of setup() after saving means that there is essentially no window ever opened and shown.

If anyone would like to replicate this I have provided some boiler plate below that achieves what I was working on.

main.cpp:

#include "ofMain.h"
#include "ofApp.h"

//========================================================================
int main( ) {

	// Use ofGLFWWindowSettings for more options like multi-monitor fullscreen

	for(unsigned int i = 0; i < 10; ++i) {
		ofGLWindowSettings settings;
		settings.setSize(100, 100); // <-- this size does not matter.
		
		settings.windowMode = OF_WINDOW; //can also be OF_FULLSCREEN

		auto window = ofCreateWindow(settings);

		ofRunApp(window, make_shared<ofApp>(to_string(i)));
		ofRunMainLoop();

	}

	cout << "The image was generated in " << duration.count() * .000001 << " seconds." << endl;
}

ofApp.cpp:

#include "ofApp.h"

ofApp::ofApp(string orderId) {
    this->orderId = orderId;
}

//--------------------------------------------------------------
void ofApp::setup(){
    cout << "setup has run" << endl; //Confirm execution of setup - This does print into the console.

    myFbo.allocate(6000, 6000, GL_RGBA); // <-- This line decides size of image and colorspace.
    myFbo.begin();
    ofBackground(0, 0, 0, 0); // set the background color to black
    
    // Save transforamtion matrix
    ofPushMatrix();
    // Translate Canvas
    ofTranslate(myFbo.getWidth() / 2, myFbo.getHeight() /2); // <- must reference size using ofFbo.


    // Any type of drawing can take place here as far as I can tell. This is just a simple test drawing.
    int AXIS_LENGTH = 3000;

    ofSetColor(255, 255, 255);
    // Draw Y Axis
    ofDrawLine(0, 0, 0, AXIS_LENGTH);
    ofDrawLine(0, 0, 0, -AXIS_LENGTH);

    // Draw X Axis
    ofDrawLine(0, 0, 0, AXIS_LENGTH);
    ofDrawLine(0, 0, 0, -AXIS_LENGTH);

    for(int i = 0; i <= AXIS_LENGTH; i++) {
        if (i % 10 == 0) {
            // Draw lines connecting each axis to its neighbor.
            ofDrawLine(i, 0, 0, AXIS_LENGTH - i);
            ofDrawLine(-i, 0, 0, AXIS_LENGTH - i);
            ofDrawLine(i, 0, 0, i - AXIS_LENGTH);
            ofDrawLine(-i, 0, 0, i - AXIS_LENGTH);
        }
    }
    myFbo.end();


    ofPixels pix;
    myFbo.readToPixels(pix);
    ofSaveImage(pix, orderId + ".png");

    ofExit(); // <-- ofExit() being called here means that no window ever opens.
}


//--------------------------------------------------------------
void ofApp::update(){

}

//--------------------------------------------------------------
void ofApp::draw(){
    
}

//Omitting remaining functions as all are empty.

ofApp.h:

#pragma once

#include "ofMain.h"
#include <string>

class ofApp : public ofBaseApp{

	public:

		ofApp(string orderId);

		void setup();
		void update();
		void draw();
        // Defined in ofApp.cpp but not used.
		void keyPressed(int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void mouseEntered(int x, int y);
		void mouseExited(int x, int y);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);

		ofImage image;
		string orderId;

		ofFbo myFbo;
};

Thanks again for the help in solving this @theo and @TimChi will mark this as the solution so others can find it!

Edit: Include ofApp.h

Hey @awesamdude glad you have it working! ofClear(ofColor(0, 0, 0, 0)) might be a better choice here; it will set all the colors in the ofFbo to whatever color you pass as an argument. ofBackground() will set the background color for the window, but I don’t think it will clear the memory of the ofTexture in the ofFbo.

Running the loop in main.cpp will make lots of separate apps. If you need to at some point (like if you had tons of iterations), you could make 1 app in main and then run a loop in ofApp::setup() to create and save each one before calling ofExit().

1 Like

Thank you @TimChi for the guidance on ofClear(ofColor(0, 0, 0, 0)) for my simple tests I think I just got lucky that initializes ofFbo with alpha of 0 or something along those lines.

I have gotten what you described working, however I need to pass arguments/properties for each iteration into the app. Is there a way to do this without adding a value into an ofApp.h constructor as I have done here? - I suppose you could just pass in some sort of abstraction that contains all the data and then read that data from an instance of the object in main.cpp but I feel there must be a better way? Guidance on doing this would be much appreciated!

Hey I looked at ofBackground() and ofClear() and decided to delete my previous post. They both should be just fine to use after calling ofFbo::begin(). They are very similar, and differ in subtle ways. I had always thought ofBackground() just affected the window, while ofClear() seemed commonly used with ofFbos.

I love default constructors. I think if you don’t want to use a custom constructor, you could try making an empty shared_ptr in main and then set some values for class objects before calling ofRunApp():

    // in main.cpp:
    auto window = ofCreateWindow(settings);
    auto app = std::make_shared<ofApp>();
    app->orderId = to_string(i);
    // set up other variables, or call some functions, then:
    ofRunApp(window, app);
    ofRunMainLoop();
1 Like

I see, thank you for looking at and providing insight onto that. I am wondering though as you mentioned in the previous post that you deleted that instead of creating multiple apps I should just call app::setup(); instead. I currently have that working but I don’t fully understand how/why.

I have included my main.cpp if you wouldn’t mind taking a look at it. I plan to later attempt to add threading to the application for the actual process of drawing the image. Is that something that I can and or should still implement with what you described?

Any guidance would be great! Thank you!

#include "ofMain.h"
#include "ofApp.h"

//========================================================================
int main( ) {
	// Use ofGLFWWindowSettings for more options like multi-monitor fullscreen
		ofGLWindowSettings settings;
		settings.setSize(100, 100);
		
		settings.windowMode = OF_WINDOW; //can also be OF_FULLSCREEN

		auto window = ofCreateWindow(settings);

		ofApp *app = new ofApp(to_string(1));

	for(unsigned int i = 0; i < 10; ++i) {
		app->setup();
	}
}

what I do not understand is how the code functions in this state, when I have not utilized the window I have created in any way other than creating it. Is calling ofCreateWindow()enough for th GL Context as @theo described here:

Hi! Why you are trying to call ofApp:setup a bunch of times from main?
I would let just OF do its thing and then on ofApp::setup I would loop as many times has I wanted.

1 Like

Hey I restored the deleted post since it seems relevant.

@hubris has a really great point about letting OF do its thing! ofRunApp() does a bunch of stuff, but the openGL aspects may be set up enough by ofCreateWindow() to draw into an ofFbo. You’ll have to try it and see. You could also try moving everything from ofApp::setup() into main.cpp too.

When the loop is in main, each iteration will create a separate app with an openGL context that runs on its own thread. I’m pretty sure the openGL stuff (ie rendering something) in each app can’t be multithreaded. But threading can definitely help with non-openGL related stuff, like complex calculations, or setting the colors in an ofPixels object, or independent loading of image files, etc.

1 Like

Thanks for the responses @hubris and @TimChi

My purpose for calling App::setup() was simply to generate the images, I can see the benefits of letting oF handle its thing.

In regards to this

The application does work as expected with the above shown main.cpp so I guess it can be discerned that calling ofCreateWindow() is enough to give the ofFbo context to draw. What is strange about this to me is that the App cannot be ran headlessly uising ofAppNoWindow but can be ran in this strange way where an instance of ofApp never has ofRunApp() called on it.

In regards to what you said here:

Is it not possible to run a c++ native thread which creates an entirely new App and then runs setup etc. on it? I assume it should be possible to run multiple instances of ofApp in separate threads but based on your comment it seems that might not be possible?

In regards to what you mentioned about

I think this solution would work very well for me. I will experiment with using an ofFbo in main. Does this mean however that I will have no need to use an instance of an ofApp at all?

No, you would need to create different openGL contexts. You can create multiple ofApps (see multi window examples) and you can share resources. But if one is busy, it will put everything on hold.

If you have a bunch of pre-calculations that take a long time to solve and then you do your drawcalls, you could thread all the calculations and then draw on the main thread which ever comes first, like a queue.

Otherwise you could have an app/script that starts instances of your app, so that you can have a bunch of them running in parallel. And you can use args to tweak/change settings.

Yeah, you just need a window otherwise I think it will crash.

1 Like

It may not be such a strange way, just maybe a little atypical. I think your app is sort of a mix between the two types. You need the openGL context for rendering (but not the window), and you don’t need the update/draw cycle that is provided by ofApp (along with its other methods). So plowing ahead with some experimentation is awesome! A custom class in main should work OK if you need it. And also play around with the threading to see what it can and can’t do. You should get some errors for the stuff you can’t do.

Moving ofCreateWindow() inside the loop should create a separate openGL context for each iteration. I’m thinking that each openGL context will run on its own thread and have its own rendering capabilities (like running multiple instances of an app). And you could try to parallelize the loop (with something like tbb::parallel_for(), or gcd::dispatch_apply() on a Mac), and let each thread take an iteration, make its own context, and then run its code. But you’ll definitely have to try it and see how it goes!

ofAppNoWindow() is helpful for apps that can’t have (or would not use) an openGL context, like a headless RPi that needs some OSC and Arduino functionality, but doesn’t need any of the openGL stuff.