diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 852869b98e..5302a4063a 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -38,6 +38,7 @@ #include "MainWindow.h" #include "Snapshot.h" #include "SnapshotUploader.h" +#include "ToneMappingEffect.h" // filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg // %1 <= username, %2 <= date and time, %3 <= current location @@ -92,6 +93,7 @@ QString Snapshot::saveSnapshot(QImage image, const QString& filename) { return snapshotPath; } +QTimer Snapshot::snapshotTimer; qint16 Snapshot::snapshotIndex = 0; QVariant Snapshot::oldAttachedEntityId = 0; @@ -118,6 +120,8 @@ void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const QString& f oldFarClipPlaneDistance = secondaryCameraRenderConfig->property("farClipPlaneDistance"); // Initialize some secondary camera render config options for 360 snapshot capture + static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(0); + secondaryCameraRenderConfig->resetSizeSpectatorCamera(2048, 2048); secondaryCameraRenderConfig->setProperty("attachedEntityId", ""); secondaryCameraRenderConfig->setPosition(cameraPosition); @@ -129,10 +133,9 @@ void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const QString& f snapshotIndex = 0; - QTimer* snapshotTimer = new QTimer(); - snapshotTimer->setSingleShot(false); - snapshotTimer->setInterval(250); - connect(snapshotTimer, &QTimer::timeout, [&] { + snapshotTimer.setSingleShot(false); + snapshotTimer.setInterval(250); + connect(&snapshotTimer, &QTimer::timeout, [] { SecondaryCameraJobConfig* config = static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera")); if (snapshotIndex == 0) { downImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot(); @@ -153,6 +156,7 @@ void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const QString& f upImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot(); } else { // Reset secondary camera render config + static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(1); config->resetSizeSpectatorCamera(qApp->getWindow()->geometry().width(), qApp->getWindow()->geometry().height()); config->setProperty("attachedEntityId", oldAttachedEntityId); config->setProperty("vFoV", oldvFoV); @@ -162,15 +166,17 @@ void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const QString& f // Process six QImages QtConcurrent::run(convertToEquirectangular); - snapshotTimer->stop(); - snapshotTimer->deleteLater(); + snapshotTimer.stop(); } snapshotIndex++; }); - snapshotTimer->start(); + snapshotTimer.start(); } void Snapshot::convertToEquirectangular() { + // I got help from StackOverflow while writing this code: + // https://stackoverflow.com/questions/34250742/converting-a-cubemap-into-equirectangular-panorama + float outputImageWidth = 8192.0f; float outputImageHeight = 4096.0f; QImage outputImage(outputImageWidth, outputImageHeight, QImage::Format_RGB32); diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index 0e8f3f83bd..19e38605aa 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -54,6 +54,7 @@ public slots: private: static QFile* savedFileForSnapshot(QImage & image, bool isTemporary, const QString& userSelectedFilename = QString()); + static QTimer snapshotTimer; static qint16 snapshotIndex; static QVariant oldAttachedEntityId; static QVariant oldOrientation; diff --git a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml index c85f990a6d..7c44d68ad3 100644 --- a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml +++ b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml @@ -28,7 +28,7 @@ Rectangle { // The letterbox used for popup messages Hifi.LetterboxMessage { id: letterboxMessage; - z: 999; // Force the popup on top of everything else + z: 998; // Force the popup on top of everything else } function letterbox(headerGlyph, headerText, message) { letterboxMessage.headerGlyph = headerGlyph; @@ -45,7 +45,7 @@ Rectangle { id: titleBarContainer; // Size width: root.width; - height: 50; + height: 40; // Anchors anchors.left: parent.left; anchors.top: parent.top; @@ -185,6 +185,46 @@ Rectangle { // SPECTATOR APP DESCRIPTION END // + Rectangle { + z: 999; + id: processingSnapshot; + anchors.fill: parent; + visible: !take360SnapshotButton.enabled; + color: Qt.rgba(0.0, 0.0, 0.0, 0.8); + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + } + + AnimatedImage { + id: processingImage; + source: "processing.gif" + width: 74; + height: width; + anchors.verticalCenter: parent.verticalCenter; + anchors.horizontalCenter: parent.horizontalCenter; + } + + HifiStylesUit.RalewaySemiBold { + text: "Processing..."; + // Anchors + anchors.top: processingImage.bottom; + anchors.topMargin: 4; + anchors.horizontalCenter: parent.horizontalCenter; + width: paintedWidth; + // Text size + size: 26; + // Style + color: hifi.colors.white; + verticalAlignment: Text.AlignVCenter; + } + } + // // SPECTATOR CONTROLS START // @@ -194,7 +234,7 @@ Rectangle { height: root.height - spectatorDescriptionContainer.height - titleBarContainer.height; // Anchors anchors.top: spectatorDescriptionContainer.bottom; - anchors.topMargin: 20; + anchors.topMargin: 8; anchors.left: parent.left; anchors.leftMargin: 25; anchors.right: parent.right; @@ -215,7 +255,8 @@ Rectangle { onClicked: { camIsOn = !camIsOn; sendToScript({method: (camIsOn ? 'spectatorCameraOn' : 'spectatorCameraOff')}); - spectatorCameraPreview.ready = camIsOn; + fieldOfViewSlider.value = 45.0; + sendToScript({method: 'updateCameravFoV', vFoV: fieldOfViewSlider.value}); } } @@ -224,7 +265,7 @@ Rectangle { id: spectatorCameraImageContainer; anchors.left: parent.left; anchors.top: cameraToggleButton.bottom; - anchors.topMargin: 20; + anchors.topMargin: 8; anchors.right: parent.right; height: 250; color: cameraToggleButton.camIsOn ? "transparent" : "black"; @@ -263,6 +304,67 @@ Rectangle { } } + Item { + id: fieldOfView; + visible: cameraToggleButton.camIsOn; + anchors.top: spectatorCameraImageContainer.bottom; + anchors.topMargin: 8; + anchors.left: parent.left; + anchors.leftMargin: 8; + anchors.right: parent.right; + height: 35; + + HifiStylesUit.FiraSansRegular { + id: fieldOfViewLabel; + text: "Field of View (" + fieldOfViewSlider.value + "): "; + size: 16; + color: hifi.colors.lightGrayText; + anchors.left: parent.left; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + width: 140; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + + HifiControlsUit.Slider { + id: fieldOfViewSlider; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.right: resetvFoV.left; + anchors.rightMargin: 8; + anchors.left: fieldOfViewLabel.right; + anchors.leftMargin: 8; + colorScheme: hifi.colorSchemes.dark; + from: 10.0; + to: 120.0; + value: 45.0; + stepSize: 1; + + onValueChanged: { + sendToScript({method: 'updateCameravFoV', vFoV: value}); + } + onPressedChanged: { + if (!pressed) { + sendToScript({method: 'updateCameravFoV', vFoV: value}); + } + } + } + + HifiControlsUit.GlyphButton { + id: resetvFoV; + anchors.verticalCenter: parent.verticalCenter; + anchors.right: parent.right; + anchors.rightMargin: 6; + height: parent.height - 8; + width: height; + glyph: hifi.glyphs.reload; + onClicked: { + fieldOfViewSlider.value = 45.0; + } + } + } + // "Monitor Shows" Switch Label Glyph HifiStylesUit.HiFiGlyphs { @@ -338,19 +440,34 @@ Rectangle { } HifiControlsUit.Button { - id: take360SnapshotButton; - text: "Take 360 Snapshot"; - enabled: cameraToggleButton.camIsOn; + id: takeSnapshotButton; + visible: cameraToggleButton.camIsOn; + text: "Take Still Snapshot"; colorScheme: hifi.colorSchemes.dark; color: hifi.buttons.blue; - anchors.top: takeSnapshotFromControllerCheckBox.visible ? takeSnapshotFromControllerCheckBox.bottom : spectatorCameraImageContainer.bottom; + anchors.top: takeSnapshotFromControllerCheckBox.visible ? takeSnapshotFromControllerCheckBox.bottom : fieldOfView.bottom; anchors.topMargin: 8; anchors.left: parent.left; + width: parent.width/2 - 10; + height: 40; + onClicked: { + sendToScript({method: 'takeSecondaryCameraSnapshot'}); + } + } + HifiControlsUit.Button { + id: take360SnapshotButton; + visible: cameraToggleButton.camIsOn; + text: "Take 360 Snapshot"; + colorScheme: hifi.colorSchemes.dark; + color: hifi.buttons.blue; + anchors.top: takeSnapshotFromControllerCheckBox.visible ? takeSnapshotFromControllerCheckBox.bottom : fieldOfView.bottom; + anchors.topMargin: 8; anchors.right: parent.right; + width: parent.width/2 - 10; height: 40; onClicked: { take360SnapshotButton.enabled = false; - take360SnapshotButton.text = "360 SNAPSHOT PROCESSING..."; + take360SnapshotButton.text = "PROCESSING..."; sendToScript({method: 'takeSecondaryCamera360Snapshot'}); } } @@ -409,7 +526,7 @@ Rectangle { break; case 'enable360SnapshotButton': take360SnapshotButton.text = "Take 360 Snapshot"; - take360SnapshotButton.enabled = cameraToggleButton.camIsOn; + take360SnapshotButton.enabled = true; break; default: console.log('Unrecognized message from spectatorCamera.js:', JSON.stringify(message)); diff --git a/unpublishedScripts/marketplace/spectator-camera/processing.gif b/unpublishedScripts/marketplace/spectator-camera/processing.gif new file mode 100644 index 0000000000..0536bd1884 Binary files /dev/null and b/unpublishedScripts/marketplace/spectator-camera/processing.gif differ diff --git a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js index 66219b65f7..736a9a2688 100644 --- a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js +++ b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js @@ -193,6 +193,7 @@ tablet.screenChanged.connect(onTabletScreenChanged); Window.domainChanged.connect(onDomainChanged); Window.geometryChanged.connect(resizeViewFinderOverlay); + Window.stillSnapshotTaken.connect(onStillSnapshotTaken); Window.equirectangularSnapshotTaken.connect(onEquirectangularSnapshotTaken); Controller.keyPressEvent.connect(keyPressEvent); HMD.displayModeChanged.connect(onHMDChanged); @@ -394,14 +395,21 @@ } var takeSnapshotControllerMapping; var takeSnapshotControllerMappingName = 'Hifi-SpectatorCamera-Mapping-TakeSnapshot'; + function onStillSnapshotTaken() { + Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 1; + } function maybeTakeSnapshot() { if (camera) { - Audio.playSound(SNAPSHOT_SOUND, { - position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, - localOnly: true, - volume: 1.0 - }); - Window.takeSecondaryCameraSnapshot(); + Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 0; + // Wait a moment before taking the snapshot for the tonemapping curve to update + Script.setTimeout(function () { + Audio.playSound(SNAPSHOT_SOUND, { + position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, + localOnly: true, + volume: 1.0 + }); + Window.takeSecondaryCameraSnapshot(); + }, 250); } } function onEquirectangularSnapshotTaken() { @@ -559,6 +567,12 @@ case 'changeTakeSnapshotFromControllerPreference': setTakeSnapshotFromController(message.params); break; + case 'updateCameravFoV': + spectatorCameraConfig.vFoV = message.vFoV; + break; + case 'takeSecondaryCameraSnapshot': + maybeTakeSnapshot(); + break; case 'takeSecondaryCamera360Snapshot': maybeTake360Snapshot(); break; @@ -589,6 +603,7 @@ spectatorCameraOff(); Window.domainChanged.disconnect(onDomainChanged); Window.geometryChanged.disconnect(resizeViewFinderOverlay); + Window.stillSnapshotTaken.disconnect(onStillSnapshotTaken); Window.equirectangularSnapshotTaken.disconnect(onEquirectangularSnapshotTaken); addOrRemoveButton(true); if (tablet) {