Can you make pictures ("headless" apps) on Windows with OPENRNDR?

Hi, I want to move away from p5.js and Paper.js because they are too slow, and just discovered OpenRNDR.

It seems like it is fast, like Processing, with powerful geometric maths available, like Paper.js. But I don’t care about drawing to the screen - I want to create .png or .bmp files on disk.

I see in Headless applications - OPENRNDR GUIDE this doesn’t seem possible in Windows, which is weird, because even Paper.js can run without a browser on Node.js on any operating system. But since Windows comes with WSL, can I just make pictures there using OpenRNDR? Or does it need some sort of direct hardware path that VMs don’t provide?

Hi hi, happy new year :slight_smile:

When you originally posted this question in reddit Yvee1 noticed and it produced this discussion in Slack:

  • Yvee1: https://www.reddit.com/r/OPENRNDR/comments/rsq808/can_you_make_pictures_headless_apps_on_windows/
  • edwin: headless on windows. that is a good question
  • Yvee1: I wonder if headless is needed for their use case. it seems they just want to output a picture which can be done with e.g. the SingleScreenshot extension right
  • edwin: I think there should be an added option to not make a window, run the draw loop once and exit
    • well yes the saving would then be supported by single screenshot or something similar
    • actually single screenshot already does the exiting part for us
    • which is great because I like the minimal amount of scenarios Application has to be aware of

So as I mentioned in reddit, currently

There’s no need to draw to a visible screen buffer, you can draw to a non visible RenderTarget (that’s like a PGraphics in Processing) and save it.

This should work on Linux, Windows and Mac. If you try and get stuck feel free to ask here.

If what @edwin mentioned above gets implemented, then there would be not even a window appearing but the program just starting, saving something to disk and closing.

The Headless applications - OPENRNDR GUIDE you linked above currently only works on Linux and is used (afaik) to generate screenshots and videos on GitHub when something gets committed to the repo. This is used to generate documentation media automatically.

BTW. welcome to the forum! :slight_smile:

import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.isolatedWithTarget
import org.openrndr.draw.renderTarget
import org.openrndr.extensions.Screenshots
import java.io.File
import kotlin.system.exitProcess

/**
 * Example that saves an image to disk and quits
 * See https://guide.openrndr.org/advancedDrawing/renderTargets.html
 */

fun main() = application {
    configure {
        width = 64
        height = 64
        hideWindowDecorations = true
    }

    program {
        val canvas = renderTarget(4000, 4000) {
            colorBuffer()
        }
        drawer.isolatedWithTarget(canvas) {
            clear(ColorRGBa.WHITE)
            stroke = null
            fill = ColorRGBa.PINK
            ortho(canvas)
            circle(canvas.width / 2.0, canvas.height / 2.0, 1000.0)
        }
        canvas.colorBuffer(0).saveToFile(File("/tmp/result.png"), async = false)
        exitProcess(0)
    }
}

Alternative approach if one doesn’t mind briefly seeing the result, avoiding the need for a render target.

import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extensions.Screenshots
import org.openrndr.extra.noise.Random
import org.openrndr.extra.noise.uniform

fun main() = application {
    program {
        val screenshots = extend(Screenshots()) {
            async = false
            quitAfterScreenshot = true
        }
        extend {
            drawer.clear(ColorRGBa.WHITE)
            drawer.stroke = null
            drawer.circles {
                repeat(20) {
                    fill = if (it % 2 == 0) ColorRGBa.PINK else ColorRGBa.WHITE
                    circle(drawer.bounds.uniform(100.0), Random.int(2, 10) * 10.0)
                }
            }
            screenshots.trigger()
        }
    }
}