How to use an "eraser" on a colorBuffer

I find interesting that adding elements to a canvas is easier than removing them, be it on a piece of paper, with oil paint on a canvas, or with a colorBuffer.

Now that I think about it, we do have a drawer in OPENRNDR, which is used to draw things. But where is our eraser?

Today I needed to erase some pixels so I thought I would share here the approach I used.

Imports
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.BlendMode
import org.openrndr.draw.colorBuffer
import org.openrndr.drawImage
import org.openrndr.extensions.Screenshots
import org.openrndr.extra.fx.patterns.Checkers
import org.openrndr.extra.shapes.primitives.regularPolygon
import org.openrndr.shape.Circle
fun main() = application {
    program {
        // A background checkers pattern
        val checkers = colorBuffer(width, height)
        Checkers().apply(checkers, checkers)

        val img = drawImage(width, height) {
            // Draw a circle
            contour(Circle(bounds.center, 200.0).contour)

            // Erase a pentagon
            drawStyle.blendMode = BlendMode.REPLACE
            fill = ColorRGBa.TRANSPARENT
            contour(regularPolygon(5, bounds.center, 100.0, 5.0).contour)
        }
        extend {
            drawer.image(checkers)
            drawer.image(img)
        }
    }
}

The program starts by creating a checkers pattern to use as a background. It will help notice the transparency.

Next we use drawImage to create an image with transparent background (its default), then we draw a circle into it, and erase a pentagon. The erasing trick is to use the REPLACE blend mode, together with a transparent fill color.

I can’t remember if drawImage is available with the main branch of openrndr-template. If not, you can switch to the next-version branch. This method makes it easy to draw an image (instead of loading it from the disk).

Note 1

One shouldn’t use drawImage inside extend: it’s a one-time thing. If you need to update the drawing on every frame use a renderTarget instead.

Note 2

contour(Circle(bounds.center, 200.0).contour)

can be written shorter, as

circle(bounds.center, 200.0)

I chose the longer one so both shapes are drawn the same way, with drawer.contour.

Note 3

This example assumes we actually want a colorBuffer (a bitmap). We could instead create a Shape with an outer ShapeContour (the circle) and an inner one (the pentagon). That way it would be all vector data, which is better for drawing at different sizes, for pen plotters, or for querying contour locations and vertices, or for adjusting those contours programmatically.

3 Likes

Cool, I was wondering if this could be achieved for a part of a project I’m working on. It’s a thing I will implement in a middle term future but I’ll be sure to keep this in mind. Thanks for your dedication and willingness to share, as always

1 Like