Testing CompositionDrawer clip modes

I thought I could share this simple program I just wrote to test clip modes.

package apps2.simpleTests

import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.color.rgba
import org.openrndr.math.Vector2
import org.openrndr.shape.ClipMode
import org.openrndr.shape.drawComposition

fun main() {
    application {
        configure {
            width = 400
            height = 900
        }
        program {
            val svgs = ClipMode.values().mapIndexed() { n, mode ->
                val x = width * 0.7
                val y = 50 + n * 100.0
                val r = 40.0
                drawComposition {
                    fill = rgba(0.9, 0.9, 0.9, 0.7)
                    circle(x - 50.0, y, r)
                    circle(x + 50.0, y, r)
                    lineSegment(x - 50.0, y, x + 50.0, y)
                    clipMode = mode
                    fill = ColorRGBa.PINK.opacify(0.8)
                    circle(x, y, r)
                }
            }

            extend {
                drawer.clear(ColorRGBa.WHITE)
                svgs.forEachIndexed { n, svg ->
                    drawer.composition(svg)
                    drawer.fill = ColorRGBa.BLACK
                    drawer.text(ClipMode.values()[n].name, Vector2(20.0, 20.0 + n * 100.0))
                }
            }
        }
    }
}

ps. It would be nice to add the texts to the compositions instead of later during the drawing phase. Adding it works, but then it fails to draw the composition I think because text rendering is not implemented yet.

3 Likes

Thank you for this.

I mentioned in a different thread I’m interested in generating SVGs for use with a pen plotter. I’d like to find development patterns I can use to be more intentional in how paths layer on top of each other since a pen plotter will plot everything with an outline even if its otherwise occluded or drawn over.

Today my workflow involves post processing with vector drawing programs (Inkscape and Illustrator) where I will clean up some overdraws or restack things by hand. This quickly becomes tedious.

I was aware of OPENRNDRs boolean ops but they all fell short of what I needed. REVERSE_DIFFERENCE is a new mode that I was not aware of, it seems to have been added just three weeks ago and is not part of the 3.44 release.

Anyway I gave it a try and it works just like Illustrator’s “Trim” pathfinding operation which is pretty much all I use Illustrator for so this is a great addition.

However it still does have one problem (AI’s Trim also shares). When the shapes are split, new edges are created to close up the shapes, the edges are still stacked on each other.

Screen Shot 2021-01-09 at 4.12.43 PM

As you can see, I moved the right grey circle away from the pink shape, the shared edges are overlapping.

I like to use a lot of colors in my plots and lets say I outlined the pink shape with a dark color and then outlined the grey circle with a lighter color, the dark color would show through on the overlapping parts. I can work around this by carefully choosing colors and draw order but I’m a programmer and I can’t help think there is a way to do this in code vs by hand.

I had some thoughts of trying to keep metadata about drawing layers and then walking through the whole shape tree before writing my SVG and splitting paths at whatever points another higher-in-stack path covers it.

Basically, flattening the composition to only parts that are viewable.

I might end up with a ton of really short paths but its a start. Please suggest alternatives if you have ideas!

Again, thanks for the post and showing me REVERSE_DIFFERENCE, it already saved me a ton of time on plots where color layering isn’t too important.

Hi! This is what I came up with. It seems to work at least in one case :slight_smile:


import org.openrndr.KEY_SPACEBAR
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.dialogs.saveFileDialog
import org.openrndr.shape.*
import org.openrndr.svg.saveToFile

fun main() {
    application {
        program {
            val svg = drawComposition {
                fill = null
                circle(-100.0, 0.0, 90.0)
                circle(100.0, 0.0, 90.0)
                clipMode = ClipMode.REVERSE_DIFFERENCE // Draw behind
                circle(0.0, 0.0, 100.0)
            }
            val deduped = svg.dedupe()

            extend {
                drawer.apply {
                    clear(ColorRGBa.PINK)
                    translate(bounds.position(0.5, 0.3))
                    composition(svg)
                    translate(bounds.position(0.0, 0.4))
                    composition(deduped)
                }
            }
            keyboard.keyDown.listen {
                when (it.key) {
                    KEY_SPACEBAR -> saveFileDialog(supportedExtensions = listOf("svg")) { file ->
                        deduped.saveToFile(file)
                    }
                }

            }
        }
    }
}

/**
 * For a Composition, filter out bezier segments contained in longer bezier segments.
 * The goal is to avoid drawing lines multiple times with a plotter.
 */
private fun Composition.dedupe(): Composition {
    val segments = this.findShapes().map { it.shape.contours.map { it.segments }.flatten() }.flatten()
    val deduped = segments.filter { curr ->
        segments.none { other -> other.contains(curr, 1.0) }
    }.map { it.contour }
    return drawComposition {
        contours(deduped)
    }
}

/**
 * Simple test to see if a segment contains a different Segment.
 * Compares start, end and two points at 1/3 and 2/3.
 * Returns false when comparing a Segment to itself.
 */
private fun Segment.contains(other: Segment, error: Double = 0.5): Boolean = this !== other &&
        this.on(other.start, error) != null &&
        this.on(other.end, error) != null &&
        this.on(other.position(1.0 / 3), error) != null &&
        this.on(other.position(2.0 / 3), error) != null

Looks like this in Inkscape when running Extensions > Colors > Randomize...

2021-01-11-163148_457x273_scrot

Improvements and bug reports welcome!

And here one case where it doesn’t work yet :slight_smile: I’m adding a main circle and then 30 circles using UNION, DIFFERENCE and REVERSE_DIFFERENCE. There shouldn’t be open curves there.
Soo… the code above can serve a starting point for an improved version :slight_smile:
now.Behind-2021-01-11-17.04.27

ps. Something else for me to figure out later: some of the shapes exist 5 or 6 times overlapping each other. That’s why some shapes look “bold”. That means that by adding the same shape multiple times with one of the three ClipModes above repeats the shape. Is that what’s expected? Should something else happen instead?
now.Behind-2021-01-11-17.09.12

1 Like

That repetition is a bug I think, I can’t come up with a reason why it should be repeated so often at least :slight_smile: