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? = 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() 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() } }