Skip to content

Commit

Permalink
Liss
Browse files Browse the repository at this point in the history
  • Loading branch information
igr committed Dec 2, 2024
1 parent b4a3caf commit 24536ed
Show file tree
Hide file tree
Showing 13 changed files with 276 additions and 11 deletions.
Binary file added arts/lissajous/Liss.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.oblac.gart
package dev.oblac.gart.liss1

import dev.oblac.gart.Gart
import dev.oblac.gart.color.Palettes
import dev.oblac.gart.color.alpha
import dev.oblac.gart.gfx.fillOf
Expand Down
148 changes: 148 additions & 0 deletions arts/lissajous/src/main/kotlin/dev/oblac/gart/liss2/Liss.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package dev.oblac.gart.liss2

import dev.oblac.gart.Dimension
import dev.oblac.gart.Frames
import dev.oblac.gart.Gart
import dev.oblac.gart.color.Palettes
import dev.oblac.gart.color.toIntColor
import dev.oblac.gart.gfx.drawCircle
import dev.oblac.gart.gfx.fillOf
import dev.oblac.gart.gfx.pointOf
import dev.oblac.gart.math.TAUf
import dev.oblac.gart.math.map
import org.jetbrains.skia.Canvas
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random

val gart = Gart.of(
"Liss", 1024, 1024,
30
)
val d = gart.d
const val xrad = 100f
const val yrad = 100f

var c1 = 0xFF1a1a1a.toIntColor()
var c2 = 0xFFe5e5e5.toIntColor()

data class Config(val a: Int, val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int)

val configs = listOf(
Config(7, 6, 14, 5, 50, 2, 5),
Config(9, 6, 12, 6, 50, 2, 11),
Config(9, 4, 11, 6, 50, 5, 9),
Config(6, 6, 12, 6, 50, 10, 10),
Config(5, 6, 12, 6, 50, 7, 2),
Config(4, 3, 14, 8, 50, 4, 7),
Config(9, 9, 12, 7, 50, 5, 11),
Config(6, 7, 11, 7, 50, 7, 5),
Config(6, 6, 13, 5, 50, 2, 2),
Config(7, 7, 12, 7, 50, 3, 4),
Config(7, 6, 14, 8, 50, 3, 6),
Config(7, 8, 13, 8, 50, 10, 7),
Config(8, 8, 13, 7, 50, 11, 10),
Config(9, 8, 13, 7, 50, 5, 9),
Config(7, 6, 12, 6, 50, 5, 9),
Config(8, 7, 14, 8, 50, 5, 5),
Config(7, 7, 13, 7, 50, 10, 11),
Config(6, 7, 11, 6, 50, 8, 5),
Config(2, 3, 14, 8, 50, 5, 2),
Config(6, 7, 10, 6, 50, 8, 4),
Config(7, 7, 11, 6, 50, 3, 7),
Config(7, 9, 13, 7, 50, 9, 6),
Config(8, 9, 11, 8, 50, 10, 5),
Config(9, 9, 14, 5, 50, 10, 7)
)

data class Params(
var lx: Int,
var ly: Int,
var coef: Int,
var divAngle: Int,
var numP: Int,
var a1: Int,
var a2: Int
)

val params = Params(
lx = 9,
ly = 9,
coef = 14,
divAngle = 5,
numP = 50,
a1 = 10,
a2 = 7
)

fun select(configs: List<Config>, params: Params) {
// 6, 12, 17
val cfg = configs[17]
params.lx = cfg.a
params.ly = cfg.b
params.coef = cfg.c
params.divAngle = cfg.d
params.numP = cfg.e
params.a1 = cfg.f
params.a2 = cfg.g
}

fun main() {
select(configs, params)
// changeConfigX()
// changeConfigY()

val m = gart.movieGif()
val w = gart.window()
val movieEnds = 360L
m.record(w).show { c, _, f ->
println(f.frame)
draw(c, f)
f.onFrame(movieEnds) {
m.stopRecording()
gart.saveMovie(m)
}
}
}

fun changeConfigX() {
params.lx = Random.nextInt(3, 9)
params.ly = Random.nextInt(3, 9)
}

fun changeConfigY() {
params.a1 = Random.nextInt(2, 12)
params.a2 = Random.nextInt(2, 12)
params.coef = Random.nextInt(10, 15)
params.divAngle = Random.nextInt(4, 9)
}

fun draw(c: Canvas, f: Frames) {
val t = f.frame

c.clear(c1)
val leeeen = 30
for (i in 0 until leeeen) {
val p = (i + t).toFloat() / leeeen
computePos(c, d, p)
}
}

fun computePos(c: Canvas, d: Dimension, p: Float) {
val angle = map(p, 0f, 1f, 0f, TAUf / params.divAngle)

for (i in 0 until params.numP) {
val a = map(i.toFloat(), 0f, params.numP, 0f, TAUf)
val rad = 0.35
val x = sin(a + angle * params.lx) * d.hf / 1.5 * rad
val y = cos(a + angle * params.ly) * d.hf / 1.5 * rad
val r = 1 + sin((angle * params.coef) / 2) * 2
c.save()
c.translate(x.toFloat() * 1.4f, y.toFloat() * 1.4f)
c.translate(sin(a + angle * params.a1) * xrad, cos(a + angle * params.a2) * yrad)
c.drawCircle(pointOf(d.cx, d.cy), r * 2, fillOf(pal.safe(r * 3)))
c.restore()
}
}

val pal = Palettes.cool32
6 changes: 6 additions & 0 deletions gart/src/main/kotlin/dev/oblac/gart/Dimension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dev.oblac.gart

import org.jetbrains.skia.Point
import org.jetbrains.skia.Rect
import kotlin.math.sqrt

/**
* Represents a virtual dimensions.
Expand Down Expand Up @@ -65,6 +66,11 @@ data class Dimension(val w: Int, val h: Int) {
*/
val area = w * h

/**
* Diagonal.
*/
val diag = (sqrt(wf * wf + hf * hf)) / 2f

fun isInside(x: Float, y: Float) = x.toInt() in 0 until w && y.toInt() in 0 until h

/**
Expand Down
4 changes: 3 additions & 1 deletion gart/src/main/kotlin/dev/oblac/gart/Gart.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ data class Gart(

fun movie(d: Dimension = this.d, name: String = "${this.name}.mp4") = Movie(d, name)

fun movieGif(d: Dimension = this.d, name: String = "${this.name}.gif") = Movie(d, name, format = MovieFormat.GIF)

fun saveImage(gartvas: Gartvas, name: String = "${this.name}.png") = saveImageToFile(gartvas, name)

fun saveImage(canvas: Canvas, d: Dimension = this.d, name: String = "${this.name}.png") = saveImageToFile(canvas, d, name)

fun saveImage(image: Image, name: String = "${this.name}.png") = saveImageToFile(image, name)

fun saveMovie(movie: Movie, fps: Int = this.fps, name: String = "${this.name}.mp4") = saveMovieToFile(movie, fps, name)
fun saveMovie(movie: Movie, fps: Int = this.fps, name: String = this.name) = saveMovieToFile(movie, fps, name)

override fun toString(): String {
return "gȧrt! • $name"
Expand Down
9 changes: 8 additions & 1 deletion gart/src/main/kotlin/dev/oblac/gart/Movie.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import org.jetbrains.skia.Image
/**
* Movie is just a simple buffer of snapshot images.
*/
class Movie(val d: Dimension, private val name: String, startRecording: Boolean = true) {
class Movie(
val d: Dimension,
private val name: String,
startRecording: Boolean = true,
val format: MovieFormat = MovieFormat.MP4
) {
private val allFrames = mutableListOf<Image>()
private val gartvas = Gartvas(d)
private val bitmap = gartvas.createBitmap()
Expand Down Expand Up @@ -49,6 +54,8 @@ class Movie(val d: Dimension, private val name: String, startRecording: Boolean

fun totalFrames() = allFrames.size

operator fun get(index: Int) = allFrames[index]

/**
* Decorates window with movie recording.
*/
Expand Down
4 changes: 2 additions & 2 deletions gart/src/main/kotlin/dev/oblac/gart/color/Palette.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ class Palette(internal val colors: IntArray) {
return colors[position]
}

fun safe(position: Int): Int {
return colors[abs(position) % size]
fun safe(position: Number): Int {
return colors[abs(position.toInt()) % size]
}

fun relative(offset: Float): Int {
Expand Down
6 changes: 3 additions & 3 deletions gart/src/main/kotlin/dev/oblac/gart/gfx/RectIsometric.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ sealed class RectIsometric(x: Float, y: Float, a: Float, b: Float, alpha: Degree
val bh = b * sin(beta)
val bw = b * cos(beta)

bottom = Point(x + aw, y + ah)
right = Point(bottom.x + bw, bottom.y - bh)
top = Point(x + bw, y - bh)
bottom = pointOf(x + aw, y + ah)
right = pointOf(bottom.x + bw, bottom.y - bh)
top = pointOf(x + bw, y - bh)
}

fun path(): Path {
Expand Down
2 changes: 1 addition & 1 deletion gart/src/main/kotlin/dev/oblac/gart/gfx/circle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fun createCircle(center: Point, radius: Float, steps: Int): List<Point> {
val angle = deltaAngle * i
val x = center.x + radius * cos(angle)
val y = center.y - radius * sin(angle)
points.add(Point(x, y))
points.add(pointOf(x, y))
}
return points
}
2 changes: 1 addition & 1 deletion gart/src/main/kotlin/dev/oblac/gart/gfx/point.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fun randomPoint(cx: Float, cy: Float, rmax: Float, rmin: Float = rmax): Point {
fun Point.Companion.random(d: Dimension): Point = randomPoint(d)

fun Pair<Number, Number>.toPoint(): Point = Point(first.toFloat(), second.toFloat())
fun Point(x: Number, y: Number): Point = Point(x.toFloat(), y.toFloat())
fun pointOf(x: Number, y: Number): Point = Point(x.toFloat(), y.toFloat())

fun Point.isCloseTo(other: Point, tolerance: Float): Boolean {
return distanceTo(other) < tolerance
Expand Down
72 changes: 72 additions & 0 deletions gart/src/main/kotlin/dev/oblac/gart/gif/GifSequenceWriter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package dev.oblac.gart.gif

import java.awt.image.RenderedImage
import javax.imageio.*
import javax.imageio.metadata.IIOMetadata
import javax.imageio.metadata.IIOMetadataNode
import javax.imageio.stream.ImageOutputStream

class GifSequenceWriter(
out: ImageOutputStream,
imageType: Int,
delay: Int,
loop: Boolean
) {

private val writer: ImageWriter = ImageIO.getImageWritersBySuffix("gif").next()
private val params: ImageWriteParam = writer.defaultWriteParam
private val metadata: IIOMetadata

init {
val imageTypeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(imageType)
metadata = writer.getDefaultImageMetadata(imageTypeSpecifier, params)
configureRootMetadata(delay, loop)
writer.output = out
writer.prepareWriteSequence(null)
}

private fun configureRootMetadata(delay: Int, loop: Boolean) {
val metaFormatName = metadata.nativeMetadataFormatName
val root = metadata.getAsTree(metaFormatName) as IIOMetadataNode

val graphicsControlExtensionNode = getNode(root, "GraphicControlExtension")
graphicsControlExtensionNode.setAttribute("disposalMethod", "none")
graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE")
graphicsControlExtensionNode.setAttribute("transparentColorFlag", "FALSE")
graphicsControlExtensionNode.setAttribute("delayTime", (delay / 10).toString())
graphicsControlExtensionNode.setAttribute("transparentColorIndex", "0")

val commentsNode = getNode(root, "CommentExtensions")
commentsNode.setAttribute("CommentExtension", "Created by: https://memorynotfound.com")

val appExtensionsNode = getNode(root, "ApplicationExtensions")
val child = IIOMetadataNode("ApplicationExtension")
child.setAttribute("applicationID", "NETSCAPE")
child.setAttribute("authenticationCode", "2.0")

val loopContinuously = if (loop) 0 else 1
child.userObject = byteArrayOf(0x1, (loopContinuously and 0xFF).toByte(), (loopContinuously shr 8 and 0xFF).toByte())
appExtensionsNode.appendChild(child)
metadata.setFromTree(metaFormatName, root)
}

private fun getNode(rootNode: IIOMetadataNode, nodeName: String): IIOMetadataNode {
val nNodes = rootNode.length
for (i in 0 until nNodes) {
if (rootNode.item(i).nodeName.equals(nodeName, ignoreCase = true)) {
return rootNode.item(i) as IIOMetadataNode
}
}
val node = IIOMetadataNode(nodeName)
rootNode.appendChild(node)
return node
}

fun writeToSequence(img: RenderedImage) {
writer.writeToSequence(IIOImage(img, null, metadata), params)
}

fun close() {
writer.endWriteSequence()
}
}
1 change: 1 addition & 0 deletions gart/src/main/kotlin/dev/oblac/gart/math/constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const val ULTRA_WIDE_SCREEN_RATIO = 21.0 / 9
const val CINEMA_RATIO = 2.4
const val GOLDEN_RATIO = 1.61803398875
const val PIf = Math.PI.toFloat()
const val TAUf = 2 * PIf
const val DOUBLE_PIf = 2 * Math.PI.toFloat()
const val HALF_PIf = PIf / 2
const val QUARTER_PIf = PIf / 4
30 changes: 29 additions & 1 deletion gart/src/main/kotlin/dev/oblac/gart/media.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package dev.oblac.gart

import dev.oblac.gart.gif.GifSequenceWriter
import dev.oblac.gart.util.toBufferedImage
import dev.oblac.gart.video.VideoRecorder
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.EncodedImageFormat
import org.jetbrains.skia.Image
import java.io.File
import javax.imageio.stream.FileImageOutputStream

internal fun saveImageToFile(gartvas: Gartvas, name: String) {
saveImageToFile(gartvas.snapshot(), name)
Expand All @@ -28,12 +31,36 @@ fun saveImageToFile(image: Image, name: String) {
}
}

enum class MovieFormat {
MP4, GIF
}

internal fun saveMovieToFile(movie: Movie, fps: Int, name: String) {
val vcr = VideoRecorder(movie.d.w, movie.d.h, fps)
when (movie.format) {
MovieFormat.MP4 -> saveMp4ToFile(movie, fps, "$name.mp4")
MovieFormat.GIF -> saveGifToFile(movie, fps, "$name.gif")
}
}

private fun saveGifToFile(movie: Movie, fps: Int, name: String) {
val size = movie.totalFrames()

val out = FileImageOutputStream(File(name))

val gif = GifSequenceWriter(out, java.awt.image.BufferedImage.TYPE_INT_ARGB, 1000 / fps, true)
movie.forEachFrame { index, it ->
gif.writeToSequence(it.toBufferedImage())
val donePercentage = index / size.toDouble() * 100
print("${donePercentage.toInt()}% \r")
}
gif.close()
println("Gif saved: $name")
}

private fun saveMp4ToFile(movie: Movie, fps: Int, name: String) {
val size = movie.totalFrames()

val vcr = VideoRecorder(movie.d.w, movie.d.h, fps)
movie.forEachFrame { index, it ->
vcr.writeFrame(it.peekPixels()!!.buffer.bytes)
val donePercentage = index / size.toDouble() * 100
Expand All @@ -46,3 +73,4 @@ internal fun saveMovieToFile(movie: Movie, fps: Int, name: String) {
File(name).writeBytes(videoBytes)
println("Video saved: $name")
}

0 comments on commit 24536ed

Please sign in to comment.