diff --git a/orx-jumpflood/src/main/kotlin/JumpFlood.kt b/orx-jumpflood/src/main/kotlin/JumpFlood.kt index 75c2153c..3bd726cd 100644 --- a/orx-jumpflood/src/main/kotlin/JumpFlood.kt +++ b/orx-jumpflood/src/main/kotlin/JumpFlood.kt @@ -1,128 +1,143 @@ -package org.openrndr.extra.jumpfill - -import org.openrndr.color.ColorRGBa -import org.openrndr.draw.* -import org.openrndr.math.Matrix44 -import org.openrndr.math.Vector2 -import org.openrndr.resourceUrl - -class EncodePoints : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/encode-points.frag"))) -class JumpFlood : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/jumpflood.frag"))) { - var maxSteps: Int by parameters - var step: Int by parameters -} - -class PixelDistance : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/pixel-distance.frag"))) -class ContourPoints : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/contour-points.frag"))) -class Threshold : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/threshold.frag"))) { - var threshold by parameters - - init { - threshold = 0.5 - } -} - -val encodePoints by lazy { EncodePoints() } -val jumpFlood by lazy { JumpFlood() } -val pixelDistance by lazy { PixelDistance() } -val contourPoints by lazy { ContourPoints() } -val threshold by lazy { Threshold() } - - -class JumpFlooder(val width: Int, val height: Int) { - private val dimension = Math.max(width, height) - private val exp = Math.ceil(Math.log(dimension.toDouble()) / Math.log(2.0)).toInt() - private val squareDim = Math.pow(2.0, exp.toDouble()).toInt() - - private val coordinates = - listOf(colorBuffer(squareDim, squareDim, format = ColorFormat.RGB, type = ColorType.FLOAT32), - colorBuffer(squareDim, squareDim, format = ColorFormat.RGB, type = ColorType.FLOAT32)) - - private val final = renderTarget(width, height) { - colorBuffer(type = ColorType.FLOAT32) - } - - val result: ColorBuffer get() = final.colorBuffer(0) - - private val square = renderTarget(squareDim, squareDim) { - colorBuffer() - } - - private var contourUsed = false - val thresholded by lazy { colorBuffer(width, height) } - val edges by lazy { colorBuffer(width, height) } - - fun distanceToContour(drawer: Drawer, input: ColorBuffer, thresholdValue: Double = 0.5): ColorBuffer { - threshold.threshold = thresholdValue - threshold.apply(input, thresholded) - contourPoints.apply(thresholded, edges) - contourUsed = true - return jumpFlood(drawer, edges) - } - - fun directions(xRange: IntProgression = 0 until width, yRange: IntProgression = 0 until height): Array> { - result.shadow.download() - return result.shadow.mapIndexed(xRange, yRange) { _, _, r, g, _, _ -> Vector2(r, g) } - } - - - fun jumpFlood(drawer: Drawer, input: ColorBuffer): ColorBuffer { - if (input.width != width || input.height != height) { - throw IllegalArgumentException("dimensions mismatch") - } - - drawer.isolatedWithTarget(square) { - drawer.background(ColorRGBa.BLACK) - drawer.ortho(square) - drawer.view = Matrix44.IDENTITY - drawer.model = Matrix44.IDENTITY - drawer.image(input) - } - encodePoints.apply(square.colorBuffer(0), coordinates[0]) - - for (i in 0 until exp) { - jumpFlood.step = i - jumpFlood.apply(coordinates[i % 2], coordinates[(i + 1) % 2]) - } - - pixelDistance.apply(arrayOf(coordinates[exp % 2], thresholded), coordinates[exp % 2]) - drawer.isolatedWithTarget(final) { - drawer.background(ColorRGBa.BLACK) - drawer.ortho(final) - drawer.view = Matrix44.IDENTITY - drawer.model = Matrix44.IDENTITY - drawer.image(coordinates[exp % 2]) - } - return result - } - - fun destroy(destroyFinal: Boolean = true) { - coordinates.forEach { it.destroy() } - - square.colorBuffer(0).destroy() - square.detachColorBuffers() - square.destroy() - - if (destroyFinal) { - final.colorBuffer(0).destroy() - } - final.detachColorBuffers() - - final.destroy() - - if (contourUsed) { - edges.destroy() - thresholded.destroy() - } - - } - -} - -fun jumpFlood(drawer: Drawer, points: ColorBuffer): ColorBuffer { - val jumpFlooder = JumpFlooder(points.width, points.height) - jumpFlooder.jumpFlood(drawer, points) - val result = jumpFlooder.result - jumpFlooder.destroy(false) - return result -} \ No newline at end of file +package org.openrndr.extra.jumpfill + +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.* +import org.openrndr.filter.blend.passthrough +import org.openrndr.math.Matrix44 +import org.openrndr.resourceUrl + +class EncodePoints : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/encode-points.frag"))) +class JumpFlood : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/jumpflood.frag"))) { + var maxSteps: Int by parameters + var step: Int by parameters +} + +class PixelDirection : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/pixel-direction.frag"))) +class PixelDistance : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/pixel-distance.frag"))) +class ContourPoints : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/contour-points.frag"))) +class Threshold : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/threshold.frag"))) { + var threshold by parameters + + init { + threshold = 0.5 + } +} + +private val encodePoints by lazy { EncodePoints() } +private val jumpFlood by lazy { JumpFlood() } +private val pixelDistance by lazy { PixelDistance() } +private val pixelDirection by lazy { PixelDistance() } +private val contourPoints by lazy { ContourPoints() } +private val threshold by lazy { Threshold() } + +class JumpFlooder(val width: Int, val height: Int) { + private val dimension = Math.max(width, height) + private val exp = Math.ceil(Math.log(dimension.toDouble()) / Math.log(2.0)).toInt() + private val squareDim = Math.pow(2.0, exp.toDouble()).toInt() + + private val coordinates = + listOf(colorBuffer(squareDim, squareDim, format = ColorFormat.RGB, type = ColorType.FLOAT32), + colorBuffer(squareDim, squareDim, format = ColorFormat.RGB, type = ColorType.FLOAT32)) + + private val final = renderTarget(width, height) { + colorBuffer(type = ColorType.FLOAT32) + } + + val encoded: ColorBuffer get() = final.colorBuffer(0) + + private val square = renderTarget(squareDim, squareDim) { + colorBuffer() + } + +// fun distanceToContour(drawer: Drawer, input: ColorBuffer, thresholdValue: Double = 0.5): ColorBuffer { +// threshold.threshold = thresholdValue +// threshold.apply(input, thresholded) +// contourPoints.apply(thresholded, edges) +// contourUsed = true +// return jumpFlood(drawer, edges) +// } + +// fun directions(xRange: IntProgression = 0 until width, yRange: IntProgression = 0 until height): Array> { +// result.shadow.download() +// return result.shadow.mapIndexed(xRange, yRange) { _, _, r, g, _, _ -> Vector2(r, g) } +// } + + fun jumpFlood(drawer: Drawer, input: ColorBuffer): ColorBuffer { + if (input.width != width || input.height != height) { + throw IllegalArgumentException("dimensions mismatch") + } + + drawer.isolatedWithTarget(square) { + drawer.background(ColorRGBa.BLACK) + drawer.ortho(square) + drawer.view = Matrix44.IDENTITY + drawer.model = Matrix44.IDENTITY + drawer.image(input) + } + encodePoints.apply(square.colorBuffer(0), coordinates[0]) + + for (i in 0 until exp) { + jumpFlood.step = i + jumpFlood.apply(coordinates[i % 2], coordinates[(i + 1) % 2]) + } + + drawer.isolatedWithTarget(final) { + drawer.background(ColorRGBa.BLACK) + drawer.ortho(final) + drawer.view = Matrix44.IDENTITY + drawer.model = Matrix44.IDENTITY + drawer.image(coordinates[exp % 2]) + } + return encoded + } + + fun destroy() { + coordinates.forEach { it.destroy() } + square.colorBuffer(0).destroy() + square.detachColorBuffers() + square.destroy() + + final.colorBuffer(0).destroy() + final.detachColorBuffers() + final.destroy() + } +} + +private fun encodeDecodeBitmap(drawer: Drawer, preprocess: Filter, decoder: Filter, bitmap: ColorBuffer, + jumpFlooder: JumpFlooder? = null, + result: ColorBuffer? = null +): ColorBuffer { + val _jumpFlooder = jumpFlooder ?: JumpFlooder(bitmap.width, bitmap.height) + val _result = result ?: colorBuffer(bitmap.width, bitmap.height, type = ColorType.FLOAT16) + + preprocess.apply(bitmap, _result) + + val encoded = _jumpFlooder.jumpFlood(drawer, _result) + + decoder.apply(arrayOf(encoded, bitmap), _result) + if (jumpFlooder == null) { + _jumpFlooder.destroy() + } + + return _result +} + +/** + * Creates a color buffer containing the coordinates of the nearest centroids + * @param bitmap a ColorBuffer with centroids in white + */ +fun centroidsFromBitmap(drawer: Drawer, bitmap: ColorBuffer, + jumpFlooder: JumpFlooder? = null, + result: ColorBuffer? = null +): ColorBuffer = encodeDecodeBitmap(drawer, passthrough, passthrough, bitmap, jumpFlooder, result) + +fun distanceFieldFromBitmap(drawer: Drawer, bitmap: ColorBuffer, + jumpFlooder: JumpFlooder? = null, + result: ColorBuffer? = null +): ColorBuffer = encodeDecodeBitmap(drawer, contourPoints, pixelDistance, bitmap, jumpFlooder, result) + +fun directionFieldFromBitmap(drawer: Drawer, bitmap: ColorBuffer, + jumpFlooder: JumpFlooder? = null, + result: ColorBuffer? = null +): ColorBuffer = encodeDecodeBitmap(drawer, contourPoints, pixelDirection, bitmap, jumpFlooder, result) + diff --git a/orx-jumpflood/src/main/resources/shaders/gl3/contour-points.frag b/orx-jumpflood/src/main/resources/shaders/gl3/contour-points.frag index 19a46f9f..ceb56f98 100644 --- a/orx-jumpflood/src/main/resources/shaders/gl3/contour-points.frag +++ b/orx-jumpflood/src/main/resources/shaders/gl3/contour-points.frag @@ -8,17 +8,15 @@ out vec4 o_color; void main() { vec2 stepSize = 1.0 / textureSize(tex0, 0); float ref = step(0.5 , texture(tex0, v_texCoord0).r); - vec4 outc = vec4(-1.0, -1.0, 0.0, 1.0); - float contour = 0.0; - for (int y = -1; y <= 1; ++y) { - for (int x = -1; x <= 1; ++x) { - float smp = step(0.5, texture(tex0, v_texCoord0 + vec2(x,y) * stepSize).r); - if (smp != ref && ref == 1.0) { - contour = 1.0; - } - } - } + float laplacian = -4 * ref; + + laplacian += step(0.5, texture(tex0, v_texCoord0 + vec2(stepSize.x, 0.0)).r); + laplacian += step(0.5, texture(tex0, v_texCoord0 - vec2(stepSize.x, 0.0)).r); + laplacian += step(0.5, texture(tex0, v_texCoord0 + vec2(0.0, stepSize.y)).r); + laplacian += step(0.5, texture(tex0, v_texCoord0 - vec2(0.0, stepSize.y)).r); + + float contour = 1.0 - step(0.0, laplacian); o_color = vec4(contour, contour, contour, 1.0); } \ No newline at end of file diff --git a/orx-jumpflood/src/main/resources/shaders/gl3/pixel-direction.frag b/orx-jumpflood/src/main/resources/shaders/gl3/pixel-direction.frag new file mode 100644 index 00000000..2d81a52b --- /dev/null +++ b/orx-jumpflood/src/main/resources/shaders/gl3/pixel-direction.frag @@ -0,0 +1,16 @@ +#version 330 core + +uniform sampler2D tex0; +uniform sampler2D tex1; +in vec2 v_texCoord0; + +out vec4 o_color; + +void main() { + vec2 size = textureSize(tex0, 0); + vec2 pixelPosition = v_texCoord0; + vec2 centroidPixelPosition = texture(tex0, v_texCoord0).xy; + vec2 pixelDistance = (centroidPixelPosition - pixelPosition) * size * vec2(1.0, -1.0); + float threshold = texture(tex1, v_texCoord0).r; + o_color = vec4(pixelDistance, threshold, 1.0); +} \ No newline at end of file diff --git a/orx-jumpflood/src/main/resources/shaders/gl3/pixel-distance.frag b/orx-jumpflood/src/main/resources/shaders/gl3/pixel-distance.frag index 7eebf70a..10dcf56c 100644 --- a/orx-jumpflood/src/main/resources/shaders/gl3/pixel-distance.frag +++ b/orx-jumpflood/src/main/resources/shaders/gl3/pixel-distance.frag @@ -1,16 +1,16 @@ -#version 330 core - -uniform sampler2D tex0; -uniform sampler2D tex1; -in vec2 v_texCoord0; - -out vec4 o_color; - -void main() { - vec2 size = textureSize(tex0, 0); - vec2 pixelPosition = v_texCoord0; - vec2 centroidPixelPosition = texture(tex0, v_texCoord0).xy; - vec2 pixelDistance = (centroidPixelPosition - pixelPosition) * size * vec2(1.0, -1.0); - float threshold = texture(tex1, v_texCoord0).r; - o_color = vec4(pixelDistance, threshold, 1.0); +#version 330 core + +uniform sampler2D tex0; +uniform sampler2D tex1; +in vec2 v_texCoord0; + +out vec4 o_color; + +void main() { + vec2 size = textureSize(tex0, 0); + vec2 pixelPosition = v_texCoord0; + vec2 centroidPixelPosition = texture(tex0, v_texCoord0).xy; + vec2 pixelDistance = (centroidPixelPosition - pixelPosition) * size * vec2(1.0, -1.0); + float threshold = texture(tex1, v_texCoord0).r; + o_color = vec4(length(pixelDistance), threshold, 0.0, 1.0); } \ No newline at end of file