diff --git a/README.md b/README.md index 2d7e2676..be82fa66 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,29 @@ Should you experience any issues, [please try the latest development version](ht ![Overview of sciview's user interface](https://gblobscdn.gitbook.com/assets%2F-LqBCy3SBefXis0YnrcI%2F-MK5WLQvMLIvw2GF6Rn2%2F-MK5WMGzmSavDTwlGro2%2Fmain-cheatsheet.jpg?alt=media&token=70c82549-e939-4752-af12-1756492a5f01) +## API Features + +### Custom Window Dimensions + +SciView now supports setting custom window dimensions via API, which is essential for VR headsets that require specific resolutions: + +```kotlin +// Create SciView with custom dimensions +val sciview = SciView.create(1920, 1080) + +// Or resize an existing instance +sciview.setWindowSize(2880, 1700) // Example: Oculus Quest 2 resolution + +// Query current dimensions +val (width, height) = sciview.getWindowSize() +``` + +This feature is particularly useful for: +- VR headset integration requiring exact resolutions +- Multi-monitor setups +- Creating screenshots or recordings at specific resolutions +- Kiosk or presentation modes + ## Developers [Kyle Harrington](https://kyleharrington.com), University of Idaho & [Ulrik Guenther](https://ulrik.is/writing), MPI-CBG diff --git a/src/main/kotlin/sc/iview/SciView.kt b/src/main/kotlin/sc/iview/SciView.kt index 5e7d9257..3a1987bd 100644 --- a/src/main/kotlin/sc/iview/SciView.kt +++ b/src/main/kotlin/sc/iview/SciView.kt @@ -1866,6 +1866,57 @@ class SciView : SceneryBase, CalibratedRealInterval { println(scijavaContext!!.serviceIndex) } + /** + * Set the window dimensions of the sciview rendering window. + * This is essential for VR headsets that require specific resolutions. + * + * @param width The desired width of the window in pixels + * @param height The desired height of the window in pixels + * @return true if the window was successfully resized, false otherwise + */ + fun setWindowSize(width: Int, height: Int): Boolean { + if (width <= 0 || height <= 0) { + log.error("Window dimensions must be positive: width=$width, height=$height") + return false + } + + try { + // Update internal dimensions + windowWidth = width + windowHeight = height + + // Update the main window frame if it exists + if (mainWindow is SwingMainWindow) { + val swingWindow = mainWindow as SwingMainWindow + swingWindow.frame.setSize(width, height) + + // Update the renderer dimensions + renderer?.let { r -> + r.reshape(width, height) + } + + // Update camera aspect ratio + camera?.perspectiveCamera(50.0f, width, height, 0.1f, 1000.0f) + } + + log.info("Window resized to ${width}x${height}") + return true + } catch (e: Exception) { + log.error("Failed to resize window: ${e.message}") + e.printStackTrace() + return false + } + } + + /** + * Get the current window dimensions. + * + * @return a Pair containing the width and height of the window + */ + fun getWindowSize(): Pair { + return Pair(windowWidth, windowHeight) + } + /** * Return the color table corresponding to the [lutName] * @param lutName a String represening an ImageJ style LUT name, like Fire.lut @@ -1952,6 +2003,26 @@ class SciView : SceneryBase, CalibratedRealInterval { val sciViewService = context.service(SciViewService::class.java) return sciViewService.orCreateActiveSciView } + + /** + * Static launching method with custom window dimensions + * + * @param width The desired width of the window in pixels + * @param height The desired height of the window in pixels + * @return a newly created SciView with specified dimensions + */ + @JvmStatic + @Throws(Exception::class) + fun create(width: Int, height: Int): SciView { + xinitThreads() + val context = Context(ImageJService::class.java, SciJavaService::class.java, SCIFIOService::class.java) + val objectService = context.service(ObjectService::class.java) + objectService.addObject(Utils.SciviewStandalone()) + val sciViewService = context.service(SciViewService::class.java) + val sciView = sciViewService.orCreateActiveSciView + sciView.setWindowSize(width, height) + return sciView + } /** * Static launching method diff --git a/src/main/kotlin/sc/iview/commands/MenuWeights.kt b/src/main/kotlin/sc/iview/commands/MenuWeights.kt index 610deead..29756444 100644 --- a/src/main/kotlin/sc/iview/commands/MenuWeights.kt +++ b/src/main/kotlin/sc/iview/commands/MenuWeights.kt @@ -109,6 +109,7 @@ object MenuWeights { const val DEMO_BASIC_IMAGEPLANE = 4.0 const val DEMO_BASIC_VOLUME = 6.0 const val DEMO_BASIC_POINTCLOUD = 7.0 + const val DEMO_BASIC_CUSTOM_WINDOW = 8.0 // Demo/Animation const val DEMO_ANIMATION_PARTICLE = 0.0 const val DEMO_ANIMATION_VOLUMETIMESERIES = 1.0 diff --git a/src/main/kotlin/sc/iview/commands/demo/basic/CustomWindowSizeDemo.kt b/src/main/kotlin/sc/iview/commands/demo/basic/CustomWindowSizeDemo.kt new file mode 100644 index 00000000..15bcdb19 --- /dev/null +++ b/src/main/kotlin/sc/iview/commands/demo/basic/CustomWindowSizeDemo.kt @@ -0,0 +1,129 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2024 sciview developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package sc.iview.commands.demo.basic + +import org.joml.Vector3f +import org.scijava.command.Command +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import org.scijava.util.ColorRGB +import sc.iview.SciView +import sc.iview.commands.MenuWeights + +/** + * Demo to test custom window sizing API. + * Shows how to set custom window dimensions for VR or other specific display requirements. + * + * @author Kyle Harrington + */ +@Plugin(type = Command::class, + label = "Custom Window Size Demo", + menuRoot = "SciView", + menu = [Menu(label = "Demo", weight = MenuWeights.DEMO), + Menu(label = "Basic", weight = MenuWeights.DEMO_BASIC), + Menu(label = "Custom Window Size", weight = MenuWeights.DEMO_BASIC_CUSTOM_WINDOW)]) +class CustomWindowSizeDemo : Command { + @Parameter + private lateinit var sciview: SciView + + @Parameter(label = "Window Width", min = "100", max = "3840") + private var width: Int = 1920 + + @Parameter(label = "Window Height", min = "100", max = "2160") + private var height: Int = 1080 + + override fun run() { + // Get current window size + val (currentWidth, currentHeight) = sciview.getWindowSize() + println("Current window size: ${currentWidth}x${currentHeight}") + + // Set new window size + println("Setting window size to ${width}x${height}...") + val success = sciview.setWindowSize(width, height) + + if (success) { + println("Window successfully resized to ${width}x${height}") + + // Add some demo content to visualize the new dimensions + sciview.addSphere( + position = Vector3f(0f, 0f, 0f), + radius = 1f, + color = ColorRGB(128, 255, 128) + ) { + name = "Center Sphere" + } + + // Add corner markers to show the viewport + val aspectRatio = width.toFloat() / height.toFloat() + val markerSize = 0.2f + + // Top-left + sciview.addBox( + position = Vector3f(-aspectRatio * 2, 2f, -5f), + size = Vector3f(markerSize, markerSize, markerSize), + color = ColorRGB(255, 0, 0) + ) { + name = "Top-Left Marker" + } + + // Top-right + sciview.addBox( + position = Vector3f(aspectRatio * 2, 2f, -5f), + size = Vector3f(markerSize, markerSize, markerSize), + color = ColorRGB(0, 255, 0) + ) { + name = "Top-Right Marker" + } + + // Bottom-left + sciview.addBox( + position = Vector3f(-aspectRatio * 2, -2f, -5f), + size = Vector3f(markerSize, markerSize, markerSize), + color = ColorRGB(0, 0, 255) + ) { + name = "Bottom-Left Marker" + } + + // Bottom-right + sciview.addBox( + position = Vector3f(aspectRatio * 2, -2f, -5f), + size = Vector3f(markerSize, markerSize, markerSize), + color = ColorRGB(255, 255, 0) + ) { + name = "Bottom-Right Marker" + } + + // Center the camera + sciview.centerOnScene() + } else { + println("Failed to resize window") + } + } +}