130 lines
4.3 KiB
Kotlin
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()
|
|
}
|
|
|
|
}
|