190 lines
5.7 KiB
Kotlin
190 lines
5.7 KiB
Kotlin
package org.openrndr.extra.jumpfill
|
|
|
|
import org.openrndr.color.ColorRGBa
|
|
import org.openrndr.draw.*
|
|
import org.openrndr.extra.fx.blend.Passthrough
|
|
import org.openrndr.extra.jumpflood.*
|
|
|
|
import org.openrndr.math.Vector2
|
|
import kotlin.math.*
|
|
|
|
class EncodePoints : Filter(filterShaderFromCode(jf_encode_points, "encode-points"))
|
|
|
|
class EncodeSubpixel : Filter(filterShaderFromCode(jf_encode_subpixel, "encode-subpixel")) {
|
|
var threshold by parameters
|
|
|
|
init {
|
|
threshold = 0.5
|
|
}
|
|
}
|
|
|
|
class JumpFlood : Filter(filterShaderFromCode(jf_jumpflood, "jumpflood")) {
|
|
var maxSteps: Int by parameters
|
|
var step: Int by parameters
|
|
}
|
|
|
|
enum class DecodeMode(val shaderDefine: String) {
|
|
DISTANCE("OUTPUT_DISTANCE"),
|
|
DIRECTION("OUTPUT_DIRECTION")
|
|
}
|
|
|
|
class PixelDirection(val decodeMode: DecodeMode = DecodeMode.DIRECTION) :
|
|
Filter(
|
|
filterShaderFromCode(
|
|
"#define ${decodeMode.shaderDefine}\n $jf_pixel_direction",
|
|
"pixel-direction")
|
|
) {
|
|
var distanceScale: Double by parameters
|
|
var originalSize: Vector2 by parameters
|
|
var normalizedDistance: Boolean by parameters
|
|
var unitDirection: Boolean by parameters
|
|
var signedMagnitude: Boolean by parameters
|
|
var flipV: Boolean by parameters
|
|
var outputIds: Boolean by parameters
|
|
|
|
|
|
init {
|
|
distanceScale = 1.0
|
|
originalSize = Vector2(512.0, 512.0)
|
|
normalizedDistance = false
|
|
unitDirection = false
|
|
flipV = true
|
|
outputIds = false
|
|
signedMagnitude = false
|
|
}
|
|
}
|
|
|
|
class PixelDistance : Filter(filterShaderFromCode(jf_pixel_distance, "pixel-distance")) {
|
|
var distanceScale: Double by parameters
|
|
var originalSize: Vector2 by parameters
|
|
var signedBit: Boolean by parameters
|
|
var signedDistance: Boolean by parameters
|
|
|
|
init {
|
|
distanceScale = 1.0
|
|
originalSize = Vector2(512.0, 512.0)
|
|
signedBit = true
|
|
signedDistance = false
|
|
}
|
|
}
|
|
|
|
class ContourPoints : Filter(filterShaderFromCode(jf_contour_points, "contour-points"))
|
|
|
|
class IdContourPoints : Filter(filterShaderFromCode(jf_id_contours, "id-contour-points"))
|
|
|
|
|
|
class Threshold : Filter(filterShaderFromCode(jf_threshold, "threshold")) {
|
|
var threshold: Double by parameters
|
|
|
|
init {
|
|
threshold = 0.5
|
|
}
|
|
}
|
|
|
|
class AlphaThreshold : Filter(filterShaderFromCode(jf_alpha_threshold, "alpha-threshold")) {
|
|
var threshold: Double by parameters
|
|
|
|
init {
|
|
threshold = 0.5
|
|
}
|
|
}
|
|
|
|
|
|
private val encodePoints by lazy { persistent { EncodePoints() } }
|
|
private val pixelDistance by lazy { persistent { PixelDistance() } }
|
|
private val pixelDirection by lazy { persistent { PixelDirection() } }
|
|
private val contourPoints by lazy { persistent { ContourPoints() } }
|
|
private val threshold by lazy { persistent { Threshold() } }
|
|
private val passthrough by lazy { persistent { Passthrough() } }
|
|
|
|
class JumpFlooder(
|
|
val width: Int, val height: Int, format: ColorFormat = ColorFormat.RGBa, type: ColorType = ColorType.FLOAT32,
|
|
val encodePoints: Filter = EncodePoints()
|
|
) {
|
|
|
|
private val dimension = max(width, height)
|
|
private val exp = ceil(log2(dimension.toDouble())).toInt()
|
|
val squareDim = 2.0.pow(exp.toDouble()).toInt()
|
|
val jumpFlood = JumpFlood()
|
|
|
|
private val coordinates =
|
|
listOf(
|
|
colorBuffer(squareDim, squareDim, format = format, type = type),
|
|
colorBuffer(squareDim, squareDim, format = format, type = type)
|
|
)
|
|
|
|
|
|
val final = colorBuffer(squareDim, squareDim, format = format, type = type)
|
|
|
|
private val square = colorBuffer(squareDim, squareDim, format = ColorFormat.RGBa, type = type).apply {
|
|
fill(ColorRGBa.BLACK.opacify(0.0))
|
|
}
|
|
|
|
|
|
fun jumpFlood(input: ColorBuffer): ColorBuffer {
|
|
if (input.width != width || input.height != height) {
|
|
throw IllegalArgumentException("dimensions mismatch")
|
|
}
|
|
|
|
input.copyTo(square)
|
|
encodePoints.apply(square, coordinates[0])
|
|
|
|
jumpFlood.maxSteps = exp
|
|
for (i in 0 until exp) {
|
|
jumpFlood.step = i
|
|
jumpFlood.apply(coordinates[i % 2], coordinates[(i + 1) % 2])
|
|
}
|
|
|
|
coordinates[exp % 2].copyTo(final)
|
|
|
|
return final
|
|
}
|
|
|
|
fun destroy() {
|
|
coordinates.forEach { it.destroy() }
|
|
square.destroy()
|
|
final.destroy()
|
|
}
|
|
}
|
|
|
|
private fun encodeDecodeBitmap(
|
|
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(_result)
|
|
|
|
decoder.parameters["originalSize"] = Vector2(_jumpFlooder.squareDim.toDouble(), _jumpFlooder.squareDim.toDouble())
|
|
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 red (> 0)
|
|
*/
|
|
fun centroidsFromBitmap(
|
|
bitmap: ColorBuffer,
|
|
jumpFlooder: JumpFlooder? = null,
|
|
result: ColorBuffer? = null
|
|
): ColorBuffer = encodeDecodeBitmap(passthrough, passthrough, bitmap, jumpFlooder, result)
|
|
|
|
fun distanceFieldFromBitmap(
|
|
bitmap: ColorBuffer,
|
|
jumpFlooder: JumpFlooder? = null,
|
|
result: ColorBuffer? = null
|
|
): ColorBuffer = encodeDecodeBitmap(contourPoints, pixelDistance, bitmap, jumpFlooder, result)
|
|
|
|
fun directionFieldFromBitmap(
|
|
bitmap: ColorBuffer,
|
|
jumpFlooder: JumpFlooder? = null,
|
|
result: ColorBuffer? = null
|
|
): ColorBuffer = encodeDecodeBitmap(contourPoints, pixelDirection, bitmap, jumpFlooder, result) |