computer-vision-project/src/main/kotlin/network/rs485/ben/computervision/WindowManager.kt

130 lines
4.3 KiB
Kotlin

package network.rs485.ben.computervision
import kotlinx.coroutines.*
import org.opencv.core.Mat
import org.opencv.highgui.HighGui
import org.opencv.highgui.ImageWindow
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import java.awt.image.BufferedImage
import java.util.concurrent.atomic.AtomicInteger
import javax.swing.ImageIcon
import javax.swing.JLabel
import kotlin.system.exitProcess
val DEFAULT_WINDOW_MANAGER_FACTORY = WindowManagerFactory(::WindowManager)
val NOOP_WINDOW_MANAGER_FACTORY = WindowManagerFactory { NoopWindowManager }
fun interface WindowManagerFactory {
fun createWindowManager(windowName: String): IWindowManager
}
interface IWindowManager {
suspend fun display(img: Mat)
suspend fun updateImage(img: Mat)
fun dispose()
fun setDimension(width: Int, height: Int)
}
private object NoopWindowManager : IWindowManager {
override suspend fun display(img: Mat) = Unit
override suspend fun updateImage(img: Mat) = Unit
override fun dispose() = Unit
override fun setDimension(width: Int, height: Int) = Unit
}
private class WindowManager(windowName: String) : IWindowManager {
private var updateState: AtomicInteger = AtomicInteger(2)
private var disposeDeferred: CompletableDeferred<Unit>? = null
private val window: ImageWindow = ImageWindow(windowName, 0)
override suspend fun display(img: Mat) = withContext(Dispatchers.Main) {
if (disposeDeferred != null) {
throw IllegalStateException("A window is already shown")
}
window.setMat(img)
window.show()
updateState.set(0)
val deferred = CompletableDeferred<Unit>()
disposeDeferred = deferred
try {
window.frame.addWindowListener(object : WindowAdapter() {
override fun windowClosed(e: WindowEvent?) {
deferred.cancel("Window closed")
}
})
window.frame.addKeyListener(object : KeyListener {
override fun keyTyped(e: KeyEvent) {}
override fun keyPressed(e: KeyEvent) {}
override fun keyReleased(e: KeyEvent) {
if (e.keyCode == KeyEvent.VK_ESCAPE) {
deferred.cancel("Escape pressed")
}
}
})
deferred.await()
} finally {
disposeDeferred = null
window.dispose()
}
}
override suspend fun updateImage(img: Mat) {
window.setMat(img)
val state = updateState.getAndUpdate { it.plus(1).coerceAtMost(3) }
if (state == 3) return
withContext(Dispatchers.Main) {
updateState.updateAndGet { it.minus(1).coerceAtLeast(0) }
window.show()
}
}
override fun dispose() {
disposeDeferred?.complete(Unit)
}
override fun setDimension(width: Int, height: Int) = window.setNewDimension(width, height)
/**
* @see org.opencv.highgui.HighGui.waitKey(Int)
*/
private fun ImageWindow.show() {
if (img != null) {
val bufferedImage = try {
HighGui.toBufferedImage(img)
} catch (error: UnsupportedOperationException) {
System.err.println("Cannot show image ($img) in window ${window.name}: ${error.message}")
BufferedImage(img.width(), img.height(), BufferedImage.TYPE_INT_RGB).also {
for (y in 0 until it.height) {
if (y % 2 == 1) {
for (x in 0 until it.width) {
it.setRGB(x, y, 0xff00ff)
}
}
}
}
}
val imageIcon = ImageIcon(bufferedImage)
if (lbl == null) {
val frame = HighGui.createJFrame(name, flag)
val lbl = JLabel(imageIcon)
setFrameLabelVisible(frame, lbl)
} else {
lbl.icon = imageIcon
}
} else {
System.err.println("Error: no imshow associated with namedWindow: \"$name\"")
exitProcess(-1)
}
}
private fun ImageWindow.dispose() {
frame.dispose()
}
}