Wrong circle size when scaled in renderTarget

To easily create large format static generative art and export/print it, I wrote a small library in javascript (THIS is a small example of what it looks like). Because I need more speed for some things, I want to convert the library to a faster programming language. Kotlin with OpenRNDR seems like a fantastic candidate to me. Precision is very important and here lies my first problem with openrndr. Here’s the problem:

import org.openrndr.KEY_ESCAPE
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.isolatedWithTarget
import org.openrndr.draw.renderTarget
import org.openrndr.extra.color.presets.LIGHT_GRAY
import org.openrndr.extra.olive.oliveProgram
import org.openrndr.shape.Circle
import org.openrndr.shape.LineSegment
import org.openrndr.shape.Rectangle
import java.io.File

fun main() = application {

    configure {
        width = 1181
        height = 1181
        hideWindowDecorations = false
    }

    oliveProgram {

        val rt = renderTarget(width, height, 10.0) {
            colorBuffer()
        }

        val rect = Rectangle(0.0, 0.0, width.toDouble(), height.toDouble())

        keyboard.keyDown.listen {
            when {
                it.key == KEY_ESCAPE -> {
                    application.exit()
                }

                it.key == 'E'.code -> {

                    val exportRT = renderTarget(width * 10, height * 10) {
                        colorBuffer()
                    }

                    drawer.isolatedWithTarget(exportRT) {
                        drawer.image(rt.colorBuffer(0))
                    }
                    exportRT.colorBuffer(0).saveToFile(File("image.png"))
                    exportRT.detachColorAttachments()
                    exportRT.destroy()

                    println("Artwork Exported.")
                }
            }
        }

        drawer.isolatedWithTarget(rt) {

            val bg = Rectangle(0.0, 0.0, width * 1.0, height * 1.0)
            drawer.fill = ColorRGBa.LIGHT_GRAY
            drawer.stroke = null
            drawer.rectangle(bg)

            val circ = Circle(
                width * 0.5,
                height * 0.5,
                width * 0.4
            )
            drawer.fill = ColorRGBa.BLACK
            drawer.stroke = null
            drawer.circle(circ)

            val line0 = LineSegment(
                width * 0.5 - width * 0.4,
                height * 0.5,
                width * 0.5 + width * 0.4,
                height * 0.5,
            )
            drawer.stroke = ColorRGBa.fromHex("#c0f7ff")
            drawer.strokeWeight = width * 0.05
            drawer.lineSegment(line0);
        }

        extend {

            drawer.clear(ColorRGBa.WHITE)
            drawer.image(rt.colorBuffer(0), rect, rect)
        }
    }
}

This program draws a thick line and a clrcle. I expect the diameter of the circle having the same width/length as the thick line, but it is not the case. This is more noticeable when exporting (pressing ‘E’) the image and zoom in the exported result.
What am i doing wrong, or is it just a precision error ?

  • rounding the numbers brings no solution
  • the exported image, printed @ 300ppi should be 1000mm by 1000mm and then the error is (for me) very noticeable.

Hi @ElTapir! Good to see you in the forum :slight_smile:

I believe not all shapes are rendered the same way and circles in particular may produce a somewhat unexpected size. In this case, the small error gets amplified 10x because of the scaling. Until this is solved what I would do is to convert the circle into a ShapeContour or a Shape:

        drawer.isolatedWithTarget(rt) {
            fill = ColorRGBa.LIGHT_GRAY
            stroke = null
            rectangle(bounds)

            val circ = Circle(bounds.center, width * 0.4).contour
            fill = ColorRGBa.BLACK
            stroke = null
            contour(circ)

            val line0 = LineSegment(
                bounds.position(0.1, 0.5),
                bounds.position(0.9, 0.5)
            ).contour
            stroke = ColorRGBa.fromHex("#c0f7ff")
            strokeWeight = width * 0.05
            contour(line0)
        }

I also converted the LineSegment into a ShapeContour even if that didn’t bring any improvements. Just to make all shapes the same type. You can ignore my other changes :sweat_smile:

To be able to render contours, the rt will need a depthBuffer:

        val rt = renderTarget(width, height, 10.0) {
            colorBuffer()
            depthBuffer() // <--
        }

One more thing: at least on Linux saving the image is asynchronous so if I quit the program before it’s done, nothing is saved. It can be made synchronous like this:

exportRT.colorBuffer(0).saveToFile(File("/tmp/image.png"), async = false)

When doing high-res stuff and wanting to zoom in and out like in your linked example Camera2D can be convenient :slight_smile:

Does the contour approach help?

1 Like

Hello abe,

Thanks very much for your quick answer. The use of contour indeed solved my problem ( for 99.999% :smiley: ). Now I can start learning Kotlin in dept and enjoy the wonderful things of OpenRNDR. :smiley:

1 Like

Also thanks for the Camera2D tip :smiley:

1 Like

Happy to hear! Looking forward to see what you create :slight_smile: And to more questions :grin: