[orx-fx] Add CompositeFilter
This commit is contained in:
@@ -59,6 +59,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":orx-color"))
|
implementation(project(":orx-color"))
|
||||||
implementation(project(":orx-fx"))
|
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