Aligning text / a single char on a grid for ASCII effect

I came up with some changes:

import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.extra.olive.oliveProgram
import org.openrndr.math.Vector2
import org.openrndr.math.map

class CenteredText(private val drawer: Drawer, fontPath: String, fontSize: Double) {
    private val font = loadFont(fontPath, fontSize)
    private val writer = Writer(drawer)
    fun draw(text: String, pos: Vector2) {
        drawer.fontMap = font
        drawer.text(text, pos + Vector2(-writer.textWidth(text) / 2, font.height / 2))
    }
}

fun main() = application {
    configure {
        width = 1024
        height = 1024
    }
    oliveProgram {
        var img = loadImage("/tmp/a.png")
        val pixels = img.shadow
        pixels.download()

        val chars = listOf(
            "%/*+-. ",
            "╕╒╛╘╪╞╡╧╤",
            "Ñs@#W$9876543210?!abc;:+=-,._",
            "+"
        )
        val charSet = chars[2] // for debugging purposes

        val centeredText = CenteredText(drawer, "data/fonts/default.otf", 64.0)

        val tilesX = 16
        val tilesY = 16

        val tileW = img.width.toDouble() / tilesX
        val tileH = img.height.toDouble() / tilesY

        val pgr = renderTarget(img.width, img.height) {
            colorBuffer()
        }

        extend {
            drawer.isolatedWithTarget(pgr) {
                clear(ColorRGBa.BLACK)
                fill = ColorRGBa.WHITE
                translate(tileW / 2, tileH / 2)
                for (x in 0 until tilesX) {
                    for (y in 0 until tilesY) {
                        val px = (x * tileW).toInt()
                        val py = (y * tileH).toInt()
                        val color = pixels[px, py]
                        val brightness = color.luminance
                        val selector = brightness.map(0.0, 1.0, 0.0, charSet.length.toDouble()).toInt()
                            .coerceAtMost(charSet.length - 1)
                        centeredText.draw(charSet[selector].toString(), Vector2(x * tileW, y * tileH))
                    }
                }
            }
            drawer.image(pgr.colorBuffer(0), 0.0, 0.0, width.toDouble(), height.toDouble())
        }
    }
}

One thing I changed is not to create a new Writer instance for every character, by creating a class and keeping one instance of it. I made it specific for centering, but you could tweak it to accept an align vector as you did, which is a cool idea. I saw this old post about aligning text, maybe there’s others.

One thing: it does centering of characters assuming the height values, which are equal for all characters, since this is a bitmap font and I don’t think it stores a bounding box of the actual drawing (so it’s the same size for . than for 8).

Maybe this is not a goal, but if you would like to center the drawing so that a . could be drawn in the center of a O, I think one would need an intermediate step of using the contours of the characters. Then you could get the bounding box, center it and either draw it directly (this would be heavier, because it’s harder to draw contours than bitmaps), or if real time performance is very important (to do this effect with video, for example) then you could create your own “font map”: a texture with all the characters drawn in the exact centers of tiles you can use.

Does this help you get closer?

Good point about imageMode and textAlign. An alternative to imageMode is imageFit, which is quite flexible in drawing images to fit or cover certain areas. For aligning I also wrote this: Aligning text in drawer.writer - #2 by abe I don’t remember if there’s some built-in feature to achieve this, but it would be nice to add.

1 Like