package org.openrndr.poissonfill import org.openrndr.draw.* import org.openrndr.extra.fx.blend.Passthrough import org.openrndr.extra.fx.blend.Subtract import org.openrndr.filter.color.delinearize import org.openrndr.filter.color.linearize import org.openrndr.resourceUrl internal class BlendBoundary : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/blend-boundary.frag"))) class AlphaToBitmap : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/alpha-to-bitmap.frag"))) internal class BlendCombine : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/blend-combine.frag"))) { var softMaskGain: Double by parameters init { softMaskGain = 1.0 } } internal class Clamp : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/clamp.frag"))) { var minValue: Double by parameters var maxValue: Double by parameters } private val passthrough by lazy { Passthrough() } private val subtract by lazy { Subtract() } class PoissonBlender(val width: Int, val height: Int, type: ColorType = ColorType.FLOAT32) { private val pyramid = ConvolutionPyramid(width, height, 0, type = type) private val preprocess = colorBuffer(width, height, type = type) private val combined = colorBuffer(width, height, type = type) private val fillBoundary = BlendBoundary() private val fillCombine = BlendCombine() private val difference = colorBuffer(width, height, type = type) private val h1 = floatArrayOf(0.1507146f, 0.6835785f, 1.0334191f, 0.6836f, 0.1507f) private val h2 = 0.0269546f private val g = floatArrayOf(0.0311849f, 0.7752854f, 0.0311849f) init { pyramid.h1 = h1 pyramid.g = g pyramid.h2 = h2 } fun process(target: ColorBuffer, source: ColorBuffer, mask: ColorBuffer, softMask: ColorBuffer = mask, softMaskGain: Double = 1.0): ColorBuffer { subtract.apply(arrayOf(target, source), difference) fillBoundary.apply(arrayOf(difference, mask), preprocess) val result = pyramid.process(preprocess) fillCombine.softMaskGain = softMaskGain fillCombine.apply(arrayOf(result, target, source, mask, softMask), arrayOf(combined)) return combined } fun destroy() { pyramid.destroy() preprocess.destroy() combined.destroy() difference.destroy() } } class PoissonBlend: Filter2to1() { private var blender: PoissonBlender? = null val alphaToBitmap = AlphaToBitmap() var mask: ColorBuffer? = null override fun apply(source: Array, target: Array) { if (target.isNotEmpty()) { mask?.let { if (it.width != target[0].width || it.height != target[0].height) { it.destroy() mask = null } } if (mask == null) { mask = colorBuffer(target[0].width, target[0].height) } blender?.let { if (it.width != target[0].width || it.height != target[0].height) { it.destroy() blender = null } } if (blender == null) { blender = PoissonBlender(target[0].width, target[0].height) } mask?.let { alphaToBitmap.apply(source[1], it) } blender?.let { linearize.apply(source[0], source[0]) linearize.apply(source[1], source[1]) val result = it.process(source[0], source[1], mask ?: error("no mask")) result.copyTo(target[0]) delinearize.apply(target[0], target[0]) } } } }