Setup to draw exactly one frame with orx-olive, orx-gui and PresentationMode.MANUAL does not work

Hello I’m struggling a little bit with setting up my sketch in a way that I want to.

I’m using orx-olive, orx-gui.

In my setup, I load in an image, and process every pixel. For every pixel I generate 5 semi-transparent rectangles with a positional offset relative to the pixels positions, and width, height defined by a gaussian distribution.

In my draw, I iterate over this List, and actually draw the rectangles.

Now I’ve added parameters for the deviation of the gaussian with orx-gui.

I want to achieve the following:

  • I want to call the draw exactly ONCE
  • when I update something manually in my code, I want to call draw exactly ONCE again (orx-olive)
  • when I update a parameter with orx-gui, I want to draw exactly once again.

Seems like a plausible use-case.

Now the problems:

  1. I’ve set PresentationMode.MANUAL, but when I spawn the window, the draw function is not called once, but several times. Even when I don’t move the window.
    This settles after a while, but it is wasted time depending on the resolution of the input image.

  2. After I’ve registered orx-gui parameters, the draw function is called even more often than before. There must be something going on internally that triggers the draw function again, and again. Even when I don’t change the parameters.

  3. The parameter changes through orx-gui don’t effect the setup of the rectangles. These rectangles are constructed in my setup, not in the draw. And it seems that the parameter changes are only respected in the draw… so nothing changes until I manually reload.

I tried to execute my drawing functionaly in the setup, but I just get a black windows.

Here is my code:


import org.openrndr.PresentationMode
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.loadImage
import org.openrndr.extra.gui.GUI
import org.openrndr.extra.noise.gaussian
import org.openrndr.extra.noise.random
import org.openrndr.extra.olive.Reloadable
import org.openrndr.extra.olive.oliveProgram
import org.openrndr.extra.parameters.Description
import org.openrndr.extra.parameters.DoubleParameter
import org.openrndr.math.Vector2
import org.openrndr.shape.Rectangle

fun main() = application {
    oliveProgram {
        val parameters = @Description("Gaussian Parameters") object : Reloadable(){
            @DoubleParameter("Position spread", 1.0, 100.0, precision = 2, order = 0)
            var positionSpread = 1.0

            @DoubleParameter("Size spread", 1.0, 20.0, precision = 2, order = 1)
            var sizeSpread = 12.0
        }

        val gui = GUI()
        gui.add(parameters)
        extend(gui)

        val image = loadImage("data/images/ramp2.png") // Replace with your image path
        val pixels = image.shadow
        pixels.download()

        val scale = 1024.0 / image.width
        val outputWidth = (image.width * scale).toInt()
        val outputHeight = (image.height * scale).toInt()

        window.size = Vector2(outputWidth.toDouble(), outputHeight.toDouble())
        window.presentationMode = PresentationMode.MANUAL
        window.requestDraw()

        val splatterData = mutableListOf<Pair<ColorRGBa, List<Rectangle>>>()
        for (y in 0 until image.height) {
            for (x in 0 until image.width) {
                val pixelColor = pixels[x, y]
                val positions = (0 until 5).map {
                    val x1 = (x + gaussian(0.0, parameters.positionSpread)) * scale
                    val y1 = (y + gaussian(0.0, parameters.positionSpread)) * scale
                    Rectangle.fromCenter(
                        Vector2(x1, y1),
                        gaussian(1.0, parameters.sizeSpread) * scale,
                        gaussian(1.0, parameters.sizeSpread) * scale
                    )
                }
                splatterData.add(pixelColor to positions)
            }
        }

        extend {
                drawer.clear(ColorRGBa.BLACK)
                splatterData.forEach { (color, rectangles) ->
                    drawer.fill = color.opacify(random(0.01, 0.1)) // Use pixel color with some transparency
                    drawer.stroke = null
                    drawer.rectangles(rectangles)
                }
        }
    }
}

Does anyone have any idea how I can approach this?

Helloo @Empyreans ! Welcome to the forum! Two signups in one day :slight_smile:

So… I’m not sure. I think there are several things going on. One is that the olive mode somehow restarts the program after about 5 seconds. That would mean two redraws. Another thing I suspect is that the GUI is updated to load the last known state. This might not be an issue in your program, but I think it is in my version:

import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.extra.gui.GUI
import org.openrndr.extra.noise.gaussian
import org.openrndr.extra.noise.random
import org.openrndr.extra.olive.Once
import org.openrndr.extra.olive.Reloadable
import org.openrndr.extra.olive.oliveProgram
import org.openrndr.extra.parameters.Description
import org.openrndr.extra.parameters.DoubleParameter
import org.openrndr.math.Vector2
import org.openrndr.shape.Rectangle

fun main() = application {
    oliveProgram {
        val parameters = @Description("Gaussian Parameters") object : Reloadable() {
            @DoubleParameter("Position spread", 1.0, 100.0, precision = 2, order = 0)
            var positionSpread = 1.0

            @DoubleParameter("Size spread", 1.0, 20.0, precision = 2, order = 1)
            var sizeSpread = 12.0
        }

        var changeCount = 0
        val gui = GUI()
        val image = loadImage("theImage.jpg")
        val pixels = image.shadow
        pixels.download()

        val scale = 1024.0 / image.width
        val outputWidth = (image.width * scale).toInt()
        val outputHeight = (image.height * scale).toInt()

        window.size = Vector2(outputWidth.toDouble(), outputHeight.toDouble())

        val rt by Once {
            persistent {
                renderTarget(width, height) {
                    colorBuffer()
                    depthBuffer()
                }
            }
        }

        val splatterData = mutableListOf<Pair<ColorRGBa, List<Rectangle>>>()

        fun redraw() {
            println("redraw on frame $frameCount")

            splatterData.clear()
            for (y in 0 until image.height) {
                for (x in 0 until image.width) {
                    val pixelColor = pixels[x, y]
                    val positions = (0 until 5).map {
                        val x1 = (x + gaussian(0.0, parameters.positionSpread)) * scale
                        val y1 = (y + gaussian(0.0, parameters.positionSpread)) * scale
                        Rectangle.fromCenter(
                            Vector2(x1, y1),
                            gaussian(1.0, parameters.sizeSpread) * scale,
                            gaussian(1.0, parameters.sizeSpread) * scale
                        )
                    }
                    splatterData.add(pixelColor to positions)
                }
            }

            // This could be drawn one by one inside the isolated target below, but batching is much faster
            val staticBatch = drawer.rectangleBatch {
                splatterData.forEach { (color, rectangles) ->
                    fill = color.opacify(random(0.01, 0.1)) // Use pixel color with some transparency
                    stroke = null
                    rectangles(rectangles)
                }
            }

            drawer.isolatedWithTarget(rt) {
                clear(ColorRGBa.BLACK)
                rectangles(staticBatch)
            }
        }

        extend(gui) {
            add(parameters)
            onChange { name, value ->
                println("onChange $frameCount $seconds $changeCount")
                changeCount++
                if (changeCount > 2 || (frameCount > 0 && changeCount == 1)) {
                    redraw()
                }
            }
        }
        extend {
            drawer.image(rt.colorBuffer(0))
        }
    }
}

At some point I will ask why the program is restarted after five seconds. I see this in the console:

INFO [DefaultDispatcher-worker-1] o.o.e.o.Olive  ↘ loading script took 6574ms

So in my version I hacked things to make sure it only runs once until you touch the GUI. But that means that for a few seconds when starting the program there’s nothing to see.

Ah, another change is that I didn’t use PresentationMode.MANUAL but a render target. Drawing the colorBuffer in the render target on every frame is fast to do. What’s slow is the updating, so I put that into a redraw() method which is called when making changes to the GUI or saving a modified program.

ps. I used batched rectangle drawing to make it faster.

This is amazing, and it works. Thank you so much.

Maybe I’ll join you on your next Creative Code Stammtisch, as I live close to Berlin :slight_smile:

Off topic, but do you still have recordings of your shader workshop? I’d be very interested, also to attend to workshops by you.

2 Likes

Happy to hear it works for you :slight_smile:

Since you are near Berlin: this weekend I’m having an exhibition where I’ll show 3 works made with OPENRNDR and an older one made with Processing:

Workshops: I never recorded them. I have considered making an online version of In the Mood for Shaders. And I do plan to make an OPENRNDR introduction workshop. I’ll post about them in Mastodon and my newsletter (link above).

Continuing with the off topic, I would be happy to give OPENRNDR workshops anywhere in the EU, but haven’t made an effort yet to find opportunities.

1 Like

:slight_smile: