Toolkit to make composite (layered) images using blend modes and filters.
A Composite
is made using the compose {}
builder. We start with a very simple example:
fun main() = application {
program {
val composite = compose {
// this is only executed once
val position = Vector2(100.0, 100.0)
draw {
// code inside draw blocks is executed whenever the composite is drawn
drawer.circle(position, 100.0)
}
}
extend {
// draw the composite
composite.draw(drawer)
}
}
}
A Composite
with two layers looks like this.
fun main() = application {
program {
val composite = compose {
// this layer is drawn first
layer {
val position = Vector2(100.0, 100.0)
draw {
drawer.circle(position, 100.0)
}
}
// this layer is drawn second
layer {
val position = Vector2(150.0, 150.0)
draw {
drawer.circle(position, 100.0)
}
blend(Multiply())
}
}
extend {
// draw the composite
composite.draw(drawer)
}
}
}
Layers can be nested:
fun main() = application {
program {
val composite = compose {
layer {
layer {
// this draw is processed first
draw { }
}
layer {
// this draw is processed second
draw { }
}
val position = Vector2(100.0, 100.0)
draw {
// this draw is processed third
drawer.circle(position, 100.0)
}
}
// this layer is drawn second
layer {
val position = Vector2(150.0, 150.0)
draw {
drawer.circle(position, 100.0)
}
blend(Multiply())
}
}
extend {
// draw the composite
composite.draw(drawer)
}
}
}
An aside is a layer which output is not directly included in the composite drawing. The contents of an aside can be used in layers and post-processing.
fun main() = application {
program {
val composite = compose {
val a = aside {
val position = Vector2(250.0, 250.0)
draw {
drawer.circle(position, 100.0)
}
}
// this layer is drawn second
layer {
val position = Vector2(150.0, 150.0)
draw {
drawer.image(a)
drawer.circle(position, 100.0)
}
blend(Multiply())
}
}
extend {
// draw the composite
composite.draw(drawer)
}
}
}
fun main() = application {
program {
val composite = compose {
layer {
draw {
}
// the first Filter1to1 to apply
post(ApproximateGaussianBlur()) {
// here is code that is executed everytime the layer is drawn
}
// the second Filter1to1 to apply
post(ColorCorrection()) {
// here is code that is executed everytime the layer is drawn
}
}
}
extend {
// draw the composite
composite.draw(drawer)
}
}
}
Some filters use more than a single input in producing their output, these filters inherit from Filter2to1, Filter3to1, Filter4to1 etc.
One such filter is DirectionalBlur
which has image and direction field inputs. In the following example we use an aside to
draw a direction field which is fed into the blur filter.
fun main() = application {
program {
val composite = compose {
val directionField = aside {
draw {
// [...]
}
}
layer {
draw {
// [...]
}
post(DirectionalBlur(), directionField)
}
}
extend {
// draw the composite
composite.draw(drawer)
}
}
}
import org.openrndr.application
import org.openrndr.draw.loadImage
import org.openrndr.extra.compositor.*
import org.openrndr.extra.fx.blend.Add
import org.openrndr.extra.fx.edges.EdgesWork
import org.openrndr.extra.gui.GUI
import org.openrndr.math.Vector2
fun main() {
application {
configure {
width = 768
height = 768
}
program {
val w2 = width / 2.0
val h2 = height / 2.0
val c = compose {
draw {
drawer.fill = ColorRGBa.PINK
drawer.circle(width / 2.0, height / 2.0, 10.0)
}
layer {
blend(Add())
draw {
drawer.circle(width / 2.0, height / 2.0, 100.0)
}
post(ApproximateGaussianBlur()) {
window = 10
sigma = Math.cos(seconds * 10.0) * 10.0 + 10.0
}
}
}
extend(gui)
extend {
c.draw(drawer)
}
}
}
}