From 5d3eadd1563bf744b2d6583cc6da3ab1b5cb7555 Mon Sep 17 00:00:00 2001 From: Rein van der Woerd Date: Tue, 7 Apr 2020 17:04:35 +0200 Subject: [PATCH] Add optional QR-code overlay for orx-rabbit-control * Optional QR-code overlay for orx-rabbit-control * Animate the QR-code overlay * Changed orx-rabbit-control to the new demo structure * Changed orx-rabbit-control README.MD --- orx-rabbit-control/README.MD | 4 + orx-rabbit-control/build.gradle | 26 ++++- .../kotlin/DemoRabbitControl.kt} | 6 +- .../kotlin/DemoRabbitControlManualOverlay.kt | 45 ++++++++ .../resources/fonts/Roboto-Regular.ttf | Bin .../src/main/kotlin/RabbitControlServer.kt | 107 +++++++++++++++++- 6 files changed, 177 insertions(+), 11 deletions(-) rename orx-rabbit-control/src/{test/kotlin/RabbitControlServerTest.kt => demo/kotlin/DemoRabbitControl.kt} (93%) create mode 100644 orx-rabbit-control/src/demo/kotlin/DemoRabbitControlManualOverlay.kt rename orx-rabbit-control/src/{test => demo}/resources/fonts/Roboto-Regular.ttf (100%) diff --git a/orx-rabbit-control/README.MD b/orx-rabbit-control/README.MD index 6002e42f..a0bdb972 100644 --- a/orx-rabbit-control/README.MD +++ b/orx-rabbit-control/README.MD @@ -6,6 +6,10 @@ +For examples, check out the [demo](./src/demo/kotlin) folder. + + + ### Using the web client Go to [rabbitcontrol.github.io/client](https://rabbitcontrol.github.io/client/) and enter your IP-address and port. diff --git a/orx-rabbit-control/build.gradle b/orx-rabbit-control/build.gradle index 2080ff96..af139d09 100644 --- a/orx-rabbit-control/build.gradle +++ b/orx-rabbit-control/build.gradle @@ -1,9 +1,27 @@ +repositories { + maven { url 'https://jitpack.io' } +} + +sourceSets { + demo { + java { + srcDirs = ["src/demo/kotlin"] + compileClasspath += main.getCompileClasspath() + runtimeClasspath += main.getRuntimeClasspath() + } + } +} + dependencies { api project(":orx-parameters") + api project(":orx-compositor") + api project(":orx-image-fit") - implementation "org.openrndr:openrndr-core:$openrndrVersion" - implementation "org.openrndr:openrndr-gl3:$openrndrVersion" - implementation "org.lwjgl:lwjgl-opengl:3.2.3" - implementation "org.openrndr:openrndr-gl3-natives-macos:$openrndrVersion" implementation "io.github.rabbitcontrol:rcp:0.3.0" + implementation "com.google.zxing:core:3.3.0" + implementation "com.google.zxing:javase:3.3.0" + + demoRuntimeOnly("org.openrndr:openrndr-gl3:$openrndrVersion") + demoRuntimeOnly("org.openrndr:openrndr-gl3-natives-$openrndrOS:$openrndrVersion") + demoImplementation(sourceSets.getByName("main").output) } diff --git a/orx-rabbit-control/src/test/kotlin/RabbitControlServerTest.kt b/orx-rabbit-control/src/demo/kotlin/DemoRabbitControl.kt similarity index 93% rename from orx-rabbit-control/src/test/kotlin/RabbitControlServerTest.kt rename to orx-rabbit-control/src/demo/kotlin/DemoRabbitControl.kt index 797a1236..db808803 100644 --- a/orx-rabbit-control/src/test/kotlin/RabbitControlServerTest.kt +++ b/orx-rabbit-control/src/demo/kotlin/DemoRabbitControl.kt @@ -9,13 +9,13 @@ import org.openrndr.math.Vector4 fun main() = application { configure { - width = 400 - height = 400 + width = 800 + height = 800 } program { val rabbit = RabbitControlServer() - val font= loadFont("orx-rabbit-control/src/test/resources/fonts/Roboto-Regular.ttf", 20.0) + val font= loadFont("orx-rabbit-control/src/demo/resources/fonts/Roboto-Regular.ttf", 20.0) val settings = object { @TextParameter("A string") var s: String = "Hello" diff --git a/orx-rabbit-control/src/demo/kotlin/DemoRabbitControlManualOverlay.kt b/orx-rabbit-control/src/demo/kotlin/DemoRabbitControlManualOverlay.kt new file mode 100644 index 00000000..784d6940 --- /dev/null +++ b/orx-rabbit-control/src/demo/kotlin/DemoRabbitControlManualOverlay.kt @@ -0,0 +1,45 @@ +import org.openrndr.KEY_HOME +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.extra.parameters.* + + +fun main() = application { + configure { + width = 800 + height = 800 + } + + program { + val rabbit = RabbitControlServer(showQRUntilClientConnects = false) + + val settings = object { + @BooleanParameter("White on black") + var whiteOnBlack: Boolean = true + } + + rabbit.add(settings) + extend(rabbit) + + /** + * Example: only show the QR code when the [KEY_HOME] button is pressed + */ + keyboard.keyDown.listen { + when (it.key) { + KEY_HOME -> rabbit.showQRCode = true + } + } + + keyboard.keyUp.listen { + when (it.key) { + KEY_HOME -> rabbit.showQRCode = false + } + } + + extend { + drawer.background(if (settings.whiteOnBlack) ColorRGBa.BLACK else ColorRGBa.WHITE) + drawer.fill = if (settings.whiteOnBlack) ColorRGBa.WHITE else ColorRGBa.BLACK + drawer.circle(drawer.bounds.center, 250.0) + } + } +} diff --git a/orx-rabbit-control/src/test/resources/fonts/Roboto-Regular.ttf b/orx-rabbit-control/src/demo/resources/fonts/Roboto-Regular.ttf similarity index 100% rename from orx-rabbit-control/src/test/resources/fonts/Roboto-Regular.ttf rename to orx-rabbit-control/src/demo/resources/fonts/Roboto-Regular.ttf diff --git a/orx-rabbit-control/src/main/kotlin/RabbitControlServer.kt b/orx-rabbit-control/src/main/kotlin/RabbitControlServer.kt index f41130be..293cb829 100644 --- a/orx-rabbit-control/src/main/kotlin/RabbitControlServer.kt +++ b/orx-rabbit-control/src/main/kotlin/RabbitControlServer.kt @@ -1,30 +1,78 @@ +import com.google.zxing.BarcodeFormat +import com.google.zxing.client.j2se.MatrixToImageWriter +import com.google.zxing.qrcode.QRCodeWriter import org.openrndr.Extension import org.openrndr.Program import org.openrndr.color.ColorRGBa +import org.openrndr.draw.ColorBufferProxy +import org.openrndr.draw.Drawer +import org.openrndr.draw.isolated +import org.openrndr.extra.compositor.* +import org.openrndr.extra.fx.blend.Darken import org.openrndr.extra.parameters.Parameter import org.openrndr.extra.parameters.ParameterType import org.openrndr.extra.parameters.listParameters -import org.openrndr.extra.parameters.title +import org.openrndr.extras.imageFit.FitMethod +import org.openrndr.extras.imageFit.imageFit +import org.openrndr.internal.colorBufferLoader import org.openrndr.math.Vector2 import org.openrndr.math.Vector3 import org.openrndr.math.Vector4 +import org.openrndr.math.mix import org.rabbitcontrol.rcp.RCPServer import org.rabbitcontrol.rcp.model.interfaces.IParameter import org.rabbitcontrol.rcp.model.parameter.* import org.rabbitcontrol.rcp.transport.websocket.server.WebsocketServerTransporterNetty import java.awt.Color +import java.io.File +import java.net.InetSocketAddress +import java.net.Socket +import java.nio.file.FileSystems +import java.nio.file.Path import kotlin.reflect.KMutableProperty1 -class RabbitControlServer : Extension { +class RabbitControlServer(private val showQRUntilClientConnects: Boolean = true, port: Int = 10000) : Extension { private val rabbitServer = RCPServer() private val transporter = WebsocketServerTransporterNetty() private var parameterMap = mutableMapOf>() + private var qrCodeImageProxy: ColorBufferProxy? = null + private var qrImagePath: Path? = null + private var qrOverlayComposition: Composite? = null + + + /** + * Animate the opacity to make it look smooooth + */ + private var currentOpacity = 0.0 + + private var targetOpacity: Double = 0.0 + get() = if (shouldShowQR) 0.8 else 0.0 + + private var shouldShowQR = false + get() = (rabbitServer.connectionCount == 0 && showQRUntilClientConnects) || showQRCode + + + /** + * Used to manually show and hide the QR code and override the default + * behaviour of (only) showing the code when no clients are connected + */ + var showQRCode = false + init { rabbitServer.addTransporter(transporter) - transporter.bind(10000) + transporter.bind(port) + + // FIXME please help me find a better way to get the local address + val socket = Socket() + socket.connect(InetSocketAddress("google.com", 80)) + val ip = socket.localAddress.toString().replace("/", "") + + val clientUrlWithHash = "https://rabbitcontrol.github.io/client/#$ip:$port" + qrCodeImageProxy = getQRCodeImageProxy(barcodeText = clientUrlWithHash) + println("RabbitControl Web Client: $clientUrlWithHash") /** * Update the object when it has been updated in RabbitControl @@ -70,7 +118,38 @@ class RabbitControlServer : Extension { } - fun add(objectWithParameters: Any, label: String? = objectWithParameters.title()) { + override fun setup(program: Program) { + /** + * Creating the Composite for the overlay needs to happen in setup(), + * as we need access to [Program.drawer] + */ + qrOverlayComposition = compose { + layer { + draw { + program.drawer.isolated { + fill = ColorRGBa.WHITE.opacify(currentOpacity) + stroke = null + rectangle(0.0,0.0, width.toDouble(), height.toDouble()) + } + } + + layer { + blend(Darken()) { + clip = true + } + + draw { + qrCodeImageProxy!!.colorBuffer?.let { + program.drawer.imageFit(it, program.width / 4.0,program.height / 4.0, program.width * .5, program.height * .5, 0.0,0.0, FitMethod.Contain) + } + } + } + } + } + } + + + fun add(objectWithParameters: Any) { val parameters = objectWithParameters.listParameters() parameters.forEach { @@ -142,6 +221,26 @@ class RabbitControlServer : Extension { override fun shutdown(program: Program) { transporter.dispose() + // Delete the temporary file + File(qrImagePath!!.toUri()).delete() + } + + // FIXME is it possible to avoid the file entirely? + private fun getQRCodeImageProxy(barcodeText: String): ColorBufferProxy { + val qrCodeWriter = QRCodeWriter() + val bitMatrix = qrCodeWriter.encode(barcodeText, BarcodeFormat.QR_CODE, 500, 500) + qrImagePath = FileSystems.getDefault().getPath("./qr.JPG") + MatrixToImageWriter.writeToPath(bitMatrix, "JPG", qrImagePath) + return colorBufferLoader.loadFromUrl(qrImagePath!!.toUri().toURL().toString()) + } + + override fun afterDraw(drawer: Drawer, program: Program) { + currentOpacity = mix(targetOpacity, currentOpacity, 0.8) + + // Don't draw if it isn't necessary + if (currentOpacity > 0.0) { + qrOverlayComposition?.draw(drawer) + } } }