[orx-fx] Add CompositeFilter

This commit is contained in:
Edwin Jakobs
2022-09-03 13:14:22 +02:00
parent ff12413f1f
commit 63fd81a670
3 changed files with 186 additions and 0 deletions

View File

@@ -59,6 +59,7 @@ kotlin {
dependencies {
implementation(project(":orx-color"))
implementation(project(":orx-fx"))
implementation(project(":orx-noise"))
}
}
}

View File

@@ -0,0 +1,122 @@
package org.openrndr.extra.fx.composite
import org.openrndr.draw.ColorBuffer
import org.openrndr.draw.Filter
import org.openrndr.draw.createEquivalent
import org.openrndr.draw.isEquivalentTo
/**
* @param first the filter that is applied first
* @param second the filter that is applied second
* @param firstSource a function that maps source color buffers for the [first] filter
* @param secondSource a function that maps source color buffers for the [second] filter
* @param firstParameters a function that sets parameters for the [first] filter
* @param secondParameters a function that sets parameters for the [second] fillter
* @param useIntermediateBuffer should an intermediate buffer be maintained? when set to false the [first] filter will
* write to the target color buffer
*/
class CompositeFilter<F0 : Filter, F1 : Filter>(
val first: F0,
val second: F1,
private val firstSource: (List<ColorBuffer>) -> List<ColorBuffer>,
private val secondSource: (List<ColorBuffer>, ColorBuffer) -> List<ColorBuffer>,
private val firstParameters: F0.() -> Unit,
private val secondParameters: F1.() -> Unit,
private val useIntermediateBuffer: Boolean = false
) : Filter() {
private var intermediate: ColorBuffer? = null
override fun apply(source: Array<ColorBuffer>, target: Array<ColorBuffer>) {
val firstSource = firstSource(source.toList()).toTypedArray()
if (!useIntermediateBuffer) {
first.firstParameters()
first.apply(firstSource, target)
second.secondParameters()
val secondSource = secondSource(source.toList(), target.first()).toTypedArray()
second.apply(secondSource, target)
} else {
val li = intermediate
if (li != null && !li.isEquivalentTo(target.first())) {
li.destroy()
intermediate = null
}
if (intermediate == null) {
intermediate = target.first().createEquivalent()
}
first.firstParameters()
first.apply(firstSource, arrayOf(intermediate!!))
val secondSource = secondSource(source.toList(), intermediate!!).toTypedArray()
second.secondParameters()
second.apply(secondSource, target)
}
}
override fun destroy() {
intermediate?.destroy()
super.destroy()
}
}
class CompositeFilterBuilder<F0 : Filter, F1 : Filter>(val first: F0, val second: F1) {
private var firstSourceFunction: (inputs: List<ColorBuffer>) -> List<ColorBuffer> = { inputs -> inputs }
private var secondSourceFunction: (inputs: List<ColorBuffer>, intermediate: ColorBuffer) -> List<ColorBuffer> =
{ inputs, intermediate -> listOf(intermediate) + inputs.drop(1) }
private var firstParametersFunction: (F0.() -> Unit) = {}
private var secondParametersFunction: (F1.() -> Unit) = {}
/** Supply the function that sets the source color buffers for the [first] filter */
fun firstSource(function: (source: List<ColorBuffer>) -> List<ColorBuffer>) {
firstSourceFunction = function
}
/** Supply the function that sets the source color buffers for the [second] filter */
fun secondSource(function: (source: List<ColorBuffer>, intermediate: ColorBuffer) -> List<ColorBuffer>) {
secondSourceFunction = function
}
/**
* Supply the function that sets the filter parameters for the [first] filter
*/
fun firstParameters(function: (F0.() -> Unit)) {
firstParametersFunction = function
}
/**
* Supply the function that sets the filter parameter the [second] filter
*/
fun secondParameters(function: (F1.() -> Unit)) {
secondParametersFunction = function
}
/**
* Should an intermediate color buffer be used?
*/
var useIntermediateBuffer = true
fun build(): CompositeFilter<F0, F1> {
return CompositeFilter(
first,
second,
firstSourceFunction,
secondSourceFunction,
firstParametersFunction,
secondParametersFunction,
useIntermediateBuffer
)
}
}
/**
* Create a composite filter that first applies [this] filter and then the [next] filter.
*/
fun <F0 : Filter, F1 : Filter> F0.then(
next: F1,
builder: CompositeFilterBuilder<F0, F1>.() -> Unit = {}
): CompositeFilter<F0, F1> {
val compositeFilterBuilder = CompositeFilterBuilder(this, next)
compositeFilterBuilder.builder()
return compositeFilterBuilder.build()
}

View File

@@ -0,0 +1,63 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.ColorType
import org.openrndr.draw.colorBuffer
import org.openrndr.draw.loadImage
import org.openrndr.extra.fx.Post
import org.openrndr.extra.fx.blur.DirectionalBlur
import org.openrndr.extra.fx.composite.then
import org.openrndr.extra.fx.grain.FilmGrain
import org.openrndr.extra.noise.*
import org.openrndr.math.smoothstep
import kotlin.math.cos
fun main() {
application {
program {
extend(Post()) {
// -- create a color buffer and fill it with random direction vectors
val direction = colorBuffer(width, height, type = ColorType.FLOAT32)
val s = direction.shadow
val n = simplex2D.bipolar().fbm().scaleShiftInput(0.01, 0.0, 0.01, 0.0).withVector2Output()
val ng = simplex2D.unipolar().scaleShiftInput(0.005, 0.0, 0.005, 0.0)
for (y in 0 until height) {
for (x in 0 until width) {
val a = smoothstep(0.4, 0.6, cos((x + y) * 0.01) * 0.5 + 0.5)
val nv = n(2320, x.toDouble(), y.toDouble()) * smoothstep(0.45, 0.55, ng(1032, x.toDouble(), y.toDouble()))
s[x, y] = ColorRGBa(nv.x, nv.y, 0.0, 1.0)
}
}
s.upload()
val directional = DirectionalBlur()
// -- create a bidirectional composite filter by using a directional filter twice
val bidirectional = directional.then(directional) {
firstParameters {
window = 50
perpendicular = false
}
secondParameters {
window = 3
perpendicular = true
}
}
val grain = FilmGrain()
grain.grainStrength = 1.0
// -- create a grain-blur composite filter
val grainBlur = grain.then(bidirectional)
post { input, output ->
grainBlur.apply(arrayOf(input, direction), output)
}
}
val image = loadImage("demo-data/images/image-001.png")
extend {
drawer.image(image)
}
}
}
}