Drawing on a Retina / HiDPI Screen

Hello everyone,

I recently started playing with OpenRNDR and I haven’t been able to figure out how to make it use a high density screen properly. In processing there is a function called PixelDensity() and usually calling pixelDensity(displayDensity()) in the setup() does the trick. Is there a way to do this in OpenRNDR ?
The way I can see it isn’t using the proper density is because a 800x800 window almost uses the entire height of the 2560x1600 screen and the shapes drawn look a little bit blurry on the edges.

Thank you to anybody who can help !

Hi @Seyka ! Welcome to the forum :slight_smile:

I don’t have such a display so I can just guess…
What do you get when you println(width) and println(height)?

Would knowing the density help somehow?


This gives me 1.0.

Can you share a screenshot showing that blurryness?

Hi @abe ! Thank you for your reply.

The println(displays.first().contentScale) gives me 2.0.
The println(width) / println(height) returns 800 (as set in the configure{...}).

But after some research, it seem Processing doesn’t do as much as I thought it did. The windows are still the same size in Processing and OpenRNDR. I think the “blurriness” I notice is due to the antialiasing being different (and I’m probably just not used to it yet). It seems both frameworks do a few things in a few different ways, one way not being necessarily better than the other.

I wrote the same square packing program in Processing and and OpenRNDR to compare.
You probably need to save the screenshots to be able to zoom in without everything being blurry.
(here the windows are set to 400x400)

I uploaded the files to a Drive since I’m not allowed to use more than 1 embedded file in my post :
Google Drive link

Here is a breakdown of the screenshots :

  • “openrndr vs processing dense.png” : OpenRNDR on the left and processing on the right (with pixelDensity(2)).
  • “openrndn vs processing normal.png” : The same comparison with pixelDensity(1) in processing.
  • “openrndr.png” : Close-up of Openrndr (some light and asymmetrical antialiasing).
  • “processing dense.png” : Close-up of Processing (with pixelDensity(2)) (some symmetrical antialiasing).
  • “processing normal.png” : Close-up of Processing (with pixelDensity(1)) (no antialiasing).

Making a Point() is also different in each case :

  • Processing without high density makes a point that is 2 pixels wide.
  • Processing with high density makes a 1 pixel wide point but adds some antialiasing around it.
  • OpenRNDR actually makes a 1 pixel wide point without any antialiasing added.

[only speculations here]
The way the antialiasing is handle probably has something to do with the way each frameworks handles floating point coordinates. I used floating point coordinates in both programs. The fact that the antialiasing from processing is more symmetrical may be due to the framework rounding coordinates before drawing the shapes.

In the end, both frameworks actually make slightly “blurry” squares.
I wanted to have perfect squares without any antialiasing

  • It seems the way to go about this on processing is to use pixelDensity(1). It won’t use the full potential of a high density screen but at least it won’t add any antialiasing.
  • I’m not sure if there is a way to handle this in OpenRNDR. Maybe disabling antialiasing and forcing the screen density to 1.0 would be a solution ?

If your artwork is vectors, then you could try using OPENRNDR’s SVG capabilities, save the the output to a file and then convert it to whatever output format you want using a vector graphics tool. That may, and I’m only guessing, give you more control over anti-aliasing in the final output.

And if it’s not vectors … I think there is something unexpected going on. I’m not able to draw 1px wide lines or rectangles. I tried an old trick from Flash times, which was to round to int position + 0.5, but also didn’t help.

I only found this way to draw pixel sharp rectangles:

import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.extra.noise.Random
import org.openrndr.math.Vector3

fun main() = application {
    configure {
        width = 800
        height = 800

    program {
        val maxSquares = 100
        val squares = vertexBuffer(vertexFormat {
        }, 8 * maxSquares)

        var actualSquares = 0
        squares.put {
            repeat(33) {
                val x = Random.int(0, width - 150).toDouble()
                val y = Random.int(0, height - 150).toDouble()
                val w = Random.int(1, 4) * 50.0
                val h = Random.int(1, 4) * 50.0
                addSquare(x, y, w, h)

        extend {
            drawer.fill = ColorRGBa.PINK
            drawer.vertexBuffer(squares, DrawPrimitive.LINES, 0, actualSquares * 8)

private fun BufferWriter.addSquare(x: Double, y: Double, w: Double, h: Double) {
    val a = Vector3(x, y, 0.0)
    val b = Vector3(x + w, y, 0.0)
    val c = Vector3(x + w, y + h, 0.0)
    // Without the 0.5 offsets there's a missing pixel
    val d = Vector3(x - 0.5, y + h + 0.5, 0.0)
    write(a, b, a, d, b, c, d, c)

Basically creating a vertex buffer and using plain OPENGL line drawing. I’ll ask if there are other approaches as this is not really intuitive :slight_smile: