Skip to content

Instantly share code, notes, and snippets.

@ylegall
Last active April 2, 2021 01:29
Show Gist options
  • Select an option

  • Save ylegall/a636601e75539e4ad0c9d7ac705601c0 to your computer and use it in GitHub Desktop.

Select an option

Save ylegall/a636601e75539e4ad0c9d7ac705601c0 to your computer and use it in GitHub Desktop.

Revisions

  1. ylegall revised this gist Feb 3, 2021. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion Dots.kt
    Original file line number Diff line number Diff line change
    @@ -49,6 +49,7 @@ fun main() = application {

    /**
    * create a circle packing by iteratively relaxing circles
    * NOTE this requires a custom KD Tree implementation.
    */
    fun relaxPoints(
    points: MutableList<Vector2>,
    @@ -110,6 +111,7 @@ fun main() = application {

    val newPoints = initialPoints.toMutableList()

    // change the max iterations here for time/accuracy trade-off
    repeat(6) {

    // move the points closer to the center
    @@ -126,6 +128,8 @@ fun main() = application {
    //val avg = integralImage.
    map(0.0, 256.0, 36.0, 6.0, result.toDouble() / (radius * radius))
    }

    // change the max iterations here for time/accuracy trade-off
    relaxPoints(newPoints, radii, 30)
    }
    println("done computing points for $filename")
    @@ -136,7 +140,6 @@ fun main() = application {
    val points2 = getImagePoints("data/images/image-2.jpg")
    val points3 = getImagePoints("data/images/image-3.jpg")


    extend(ScreenRecorder()) {
    outputFile = "stipple.mp4"
    frameClock = true
  2. ylegall revised this gist Feb 2, 2021. 1 changed file with 0 additions and 8 deletions.
    8 changes: 0 additions & 8 deletions Dots.kt
    Original file line number Diff line number Diff line change
    @@ -110,14 +110,6 @@ fun main() = application {

    val newPoints = initialPoints.toMutableList()

    extend(ScreenRecorder()) {
    frameRate = 30
    frameClock = true
    profile = MP4Profile().apply { mode(MP4Profile.WriterMode.Lossless) }
    outputFile = "stipple.mp4"
    maximumFrames = totalFrames.toLong()
    }

    repeat(6) {

    // move the points closer to the center
  3. ylegall created this gist Feb 2, 2021.
    174 changes: 174 additions & 0 deletions Dots.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,174 @@
    import org.openrndr.application
    import org.openrndr.color.ColorRGBa
    import org.openrndr.draw.ColorFormat
    import org.openrndr.draw.colorBuffer
    import org.openrndr.draw.loadImage
    import org.openrndr.draw.shadeStyle
    import org.openrndr.extra.noise.poissonDiskSampling
    import org.openrndr.extra.olive.oliveProgram
    import org.openrndr.extras.easing.easeQuadIn
    import org.openrndr.extras.easing.easeQuadInOut
    import org.openrndr.ffmpeg.MP4Profile
    import org.openrndr.ffmpeg.ScreenRecorder
    import org.openrndr.math.Vector2
    import org.openrndr.math.map
    import org.openrndr.math.mix
    import org.openrndr.math.smoothstep
    import org.openrndr.shape.IntRectangle
    import org.openrndr.shape.Rectangle
    import studio.rndnr.packture.IntegralImage
    import kotlin.math.abs

    /**
    * This is a template for a live program.
    *
    * It uses oliveProgram {} instead of program {}. All code inside the
    * oliveProgram {} can be changed while the program is running.
    */

    fun main() = application {

    configure {
    width = 920
    height = 920
    }

    //oliveProgram {
    program {

    val radius = 12
    val initialPoints = poissonDiskSampling(width.toDouble(), height.toDouble(), radius.toDouble()).also { println(it.size) }
    val totalFrames = 420

    fun cubicPulse(center: Double, width: Double, x: Double): Double {
    val x1 = abs(x - center)
    if (x1 > width) return 0.0
    val x2 = x1 / width
    return 1.0 - x2 * x2 * (3.0 - 2.0 * x2)
    }

    /**
    * create a circle packing by iteratively relaxing circles
    */
    fun relaxPoints(
    points: MutableList<Vector2>,
    radii: List<Double>,
    maxIterations: Int = -1,
    minOverlap: Double = 0.001
    ): Int {
    var iterations = 0
    val positionDeltas = MutableList(points.size) { Vector2.ZERO }
    val maxRadius = radii.maxOrNull() ?: 16.0

    while (iterations < maxIterations || maxIterations < 0) {
    val index = KDTree2.fromPoints(points.indices.toList()) { points[it] }
    var changed = false

    for (i in points.indices) {
    val p1 = points[i]
    val radius1 = radii[i]
    val neighbors = index.queryRange(Rectangle.fromCenter(p1, 4 * maxRadius, 4 * maxRadius)).filter { it != i }
    var overlappingNeighbors = 0

    for (j in neighbors) {
    val p2 = points[j]
    val radius2 = radii[j]
    val delta = p1 - p2
    val dist = delta.length
    val overlap = radius1 + radius2 - dist
    if (overlap > minOverlap) {
    overlappingNeighbors++
    positionDeltas[i] += delta.normalized * (overlap / 2)
    }
    }

    if (overlappingNeighbors > 0) {
    changed = true
    positionDeltas[i] = positionDeltas[i] / overlappingNeighbors.toDouble()
    }
    }

    if (!changed) {
    break
    }

    for (i in points.indices) {
    points[i] += positionDeltas[i]
    }

    positionDeltas.fill(Vector2.ZERO)
    iterations++
    }
    println("total iterations: $iterations")
    return iterations
    }

    fun getImagePoints(filename: String): List<Vector2> {
    val image = loadImage(filename)
    image.shadow.download()
    val integralImage = IntegralImage.fromColorBufferShadow(image.shadow)

    val newPoints = initialPoints.toMutableList()

    extend(ScreenRecorder()) {
    frameRate = 30
    frameClock = true
    profile = MP4Profile().apply { mode(MP4Profile.WriterMode.Lossless) }
    outputFile = "stipple.mp4"
    maximumFrames = totalFrames.toLong()
    }

    repeat(6) {

    // move the points closer to the center
    for (i in newPoints.indices) {
    val delta = newPoints[i] - image.bounds.center
    newPoints[i] += delta.normalized * 4.0
    //newPoints[i] = mix(newPoints[i], image.bounds.center, 0.05)
    }

    val radii: List<Double> = newPoints.map { point ->
    val x = point.x - radius/2
    val y = point.y - radius/2
    val result = integralImage.sum(IntRectangle(x.toInt(), y.toInt(), radius, radius))
    //val avg = integralImage.
    map(0.0, 256.0, 36.0, 6.0, result.toDouble() / (radius * radius))
    }
    relaxPoints(newPoints, radii, 30)
    }
    println("done computing points for $filename")
    return newPoints
    }

    val points1 = getImagePoints("data/images/image-1.jpg")
    val points2 = getImagePoints("data/images/image-2.jpg")
    val points3 = getImagePoints("data/images/image-3.jpg")


    extend(ScreenRecorder()) {
    outputFile = "stipple.mp4"
    frameClock = true
    frameRate = 30
    profile = MP4Profile().apply { mode(MP4Profile.WriterMode.Lossless) }
    maximumFrames = totalFrames.toLong()
    }

    extend {
    val t = ((frameCount - 1) % totalFrames) / totalFrames.toDouble()
    val imagePoints = when {
    t <= 0.33 -> points1
    t <= 0.66 -> points2
    else -> points3
    }
    val t1 = (t * 3.0) % 1.0
    val factor = smoothstep(0.0, 0.75, cubicPulse(0.5, 0.45, t1))
    val framePoints = initialPoints.indices.map { i ->
    mix(initialPoints[i], imagePoints[i], factor * factor)
    }

    drawer.stroke = null
    drawer.fill = ColorRGBa.WHITE
    drawer.circles(framePoints, 4.0)
    }
    }
    }