[orx-fx] Add CompositeFilter
This commit is contained in:
@@ -59,6 +59,7 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(project(":orx-color"))
|
||||
implementation(project(":orx-fx"))
|
||||
implementation(project(":orx-noise"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
122
orx-fx/src/commonMain/kotlin/composite/CompositeFilter.kt
Normal file
122
orx-fx/src/commonMain/kotlin/composite/CompositeFilter.kt
Normal 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()
|
||||
}
|
||||
63
orx-fx/src/demo/kotlin/DemoCompositeFilter01.kt
Normal file
63
orx-fx/src/demo/kotlin/DemoCompositeFilter01.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user