Huge progress today. Buggy behavior with multiple snapshots remains

This commit is contained in:
Zach Fox 2016-11-14 17:41:46 -08:00
parent 8f9ffd2bc5
commit a81289a0d7
10 changed files with 735 additions and 678 deletions

View file

@ -152,6 +152,7 @@
#include "ui/LoginDialog.h"
#include "ui/overlays/Cube3DOverlay.h"
#include "ui/Snapshot.h"
#include "ui/SnapshotAnimated.h"
#include "ui/StandAloneJSConsole.h"
#include "ui/Stats.h"
#include "ui/UpdateDialog.h"
@ -5428,19 +5429,9 @@ void Application::toggleLogDialog() {
}
}
// If the snapshot width or the framerate are too high for the
// application to handle, the framerate of the output GIF will drop.
#define SNAPSNOT_ANIMATED_WIDTH (720)
// This value should divide evenly into 100. Snapshot framerate is NOT guaranteed.
#define SNAPSNOT_ANIMATED_FRAMERATE_FPS (25)
#define SNAPSNOT_ANIMATED_DURATION_SECS (3)
#define SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC (1000/SNAPSNOT_ANIMATED_FRAMERATE_FPS)
#define SNAPSNOT_ANIMATED_NUM_FRAMES (SNAPSNOT_ANIMATED_DURATION_SECS * SNAPSNOT_ANIMATED_FRAMERATE_FPS)
void Application::takeSnapshot(bool notify, float aspectRatio) {
postLambdaEvent([notify, aspectRatio, this] {
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) {
postLambdaEvent([notify, includeAnimated, aspectRatio, this] {
QMediaPlayer* player = new QMediaPlayer();
QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav");
player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
@ -5449,87 +5440,7 @@ void Application::takeSnapshot(bool notify, float aspectRatio) {
// Get a screenshot and save it
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio));
// If we're in the middle of capturing a GIF...
if (_animatedSnapshotFirstFrameTimestamp != 0)
{
// Notify the window scripting interface that we've taken a Snapshot
emit DependencyManager::get<WindowScriptingInterface>()->snapshotTaken(path, notify);
// Protect against clobbering it and return immediately.
// (Perhaps with a "snapshot failed" message?
}
else
{
// Reset the current animated snapshot frame
qApp->_animatedSnapshotFirstFrameTimestamp = 0;
// Reset the current animated snapshot frame timestamp
qApp->_animatedSnapshotTimestamp = 0;
// Change the extension of the future GIF saved snapshot file to "gif"
qApp->_animatedSnapshotPath = path.replace("jpg", "gif");
// Ensure the snapshot timer is Precise (attempted millisecond precision)
qApp->animatedSnapshotTimer.setTimerType(Qt::PreciseTimer);
// Connect the animatedSnapshotTimer QTimer to the lambda slot function
connect(&(qApp->animatedSnapshotTimer), &QTimer::timeout, [=] {
// Get a screenshot from the display, then scale the screenshot down,
// then convert it to the image format the GIF library needs,
// then save all that to the QImage named "frame"
QImage frame(qApp->getActiveDisplayPlugin()->getScreenshot(aspectRatio));
frame = frame.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH);
frame = frame.convertToFormat(QImage::Format_RGBA8888);
// If this is the first frame...
if (qApp->_animatedSnapshotTimestamp == 0)
{
// Write out the header and beginning of the GIF file
GifBegin(&(qApp->_animatedSnapshotGifWriter), qPrintable(qApp->_animatedSnapshotPath), frame.width(), frame.height(), SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10);
// Write the first to the gif
GifWriteFrame(&(qApp->_animatedSnapshotGifWriter),
(uint8_t*)frame.bits(),
frame.width(),
frame.height(),
SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10);
// Record the current frame timestamp
qApp->_animatedSnapshotTimestamp = QDateTime::currentMSecsSinceEpoch();
qApp->_animatedSnapshotFirstFrameTimestamp = qApp->_animatedSnapshotTimestamp;
}
else
{
// If that was the last frame...
if ((qApp->_animatedSnapshotTimestamp - qApp->_animatedSnapshotFirstFrameTimestamp) >= (SNAPSNOT_ANIMATED_DURATION_SECS * 1000))
{
// Reset the current frame timestamp
qApp->_animatedSnapshotTimestamp = 0;
qApp->_animatedSnapshotFirstFrameTimestamp = 0;
// Write out the end of the GIF
GifEnd(&(qApp->_animatedSnapshotGifWriter));
// Notify the Window Scripting Interface that the snapshot was taken
emit DependencyManager::get<WindowScriptingInterface>()->snapshotTaken(qApp->_animatedSnapshotPath, false);
// Stop the snapshot QTimer
qApp->animatedSnapshotTimer.stop();
}
else
{
// Variable used to determine how long the current frame took to pack
qint64 temp = QDateTime::currentMSecsSinceEpoch();
// Write the frame to the gif
GifWriteFrame(&(qApp->_animatedSnapshotGifWriter),
(uint8_t*)frame.bits(),
frame.width(),
frame.height(),
round(((float)(QDateTime::currentMSecsSinceEpoch() - qApp->_animatedSnapshotTimestamp + qApp->_animatedSnapshotLastWriteFrameDuration)) / 10));
// Record how long it took for the current frame to pack
qApp->_animatedSnapshotLastWriteFrameDuration = QDateTime::currentMSecsSinceEpoch() - temp;
// Record the current frame timestamp
qApp->_animatedSnapshotTimestamp = QDateTime::currentMSecsSinceEpoch();
}
}
});
// Start the animatedSnapshotTimer QTimer - argument for this is in milliseconds
qApp->animatedSnapshotTimer.start(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC);
}
SnapshotAnimated::saveSnapshotAnimated(includeAnimated, path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>());
});
}
void Application::shareSnapshot(const QString& path) {

View file

@ -267,7 +267,7 @@ public:
float getAvatarSimrate() const { return _avatarSimCounter.rate(); }
float getAverageSimsPerSecond() const { return _simCounter.rate(); }
void takeSnapshot(bool notify, float aspectRatio = 0.0f);
void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f);
void shareSnapshot(const QString& filename);
model::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; }
@ -610,13 +610,6 @@ private:
model::SkyboxPointer _defaultSkybox { new ProceduralSkybox() } ;
gpu::TexturePointer _defaultSkyboxTexture;
gpu::TexturePointer _defaultSkyboxAmbientTexture;
QTimer animatedSnapshotTimer;
GifWriter _animatedSnapshotGifWriter;
qint64 _animatedSnapshotTimestamp { 0 };
qint64 _animatedSnapshotFirstFrameTimestamp { 0 };
qint64 _animatedSnapshotLastWriteFrameDuration { 20 };
QString _animatedSnapshotPath;
};

View file

@ -199,8 +199,8 @@ void WindowScriptingInterface::copyToClipboard(const QString& text) {
QApplication::clipboard()->setText(text);
}
void WindowScriptingInterface::takeSnapshot(bool notify, float aspectRatio) {
qApp->takeSnapshot(notify, aspectRatio);
void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) {
qApp->takeSnapshot(notify, includeAnimated, aspectRatio);
}
void WindowScriptingInterface::shareSnapshot(const QString& path) {

View file

@ -52,7 +52,7 @@ public slots:
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
void showAssetServer(const QString& upload = "");
void copyToClipboard(const QString& text);
void takeSnapshot(bool notify = true, float aspectRatio = 0.0f);
void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f);
void shareSnapshot(const QString& path);
bool isPhysicsEnabled();
@ -60,7 +60,7 @@ signals:
void domainChanged(const QString& domainHostname);
void svoImportRequested(const QString& url);
void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo);
void snapshotTaken(const QString& path, bool notify);
void snapshotTaken(const QString& pathStillSnapshot, const QString& pathAnimatedSnapshot, bool notify);
void snapshotShared(const QString& error);
private:

View file

@ -0,0 +1,105 @@
//
// SnapshotAnimated.cpp
// interface/src/ui
//
// Created by Zach Fox on 11/14/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QDateTime>
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtGui/QImage>
#include "SnapshotAnimated.h"
QTimer SnapshotAnimated::snapshotAnimatedTimer;
GifWriter SnapshotAnimated::snapshotAnimatedGifWriter;
qint64 SnapshotAnimated::snapshotAnimatedTimestamp = 0;
qint64 SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0;
qint64 SnapshotAnimated::snapshotAnimatedLastWriteFrameDuration = 0;
void SnapshotAnimated::saveSnapshotAnimated(bool includeAnimated, QString pathStillSnapshot, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm) {
// If we're not in the middle of capturing an animated snapshot...
if ((snapshotAnimatedFirstFrameTimestamp == 0) && (includeAnimated))
{
// Define the output location of the animated snapshot
QString pathAnimatedSnapshot(pathStillSnapshot);
pathAnimatedSnapshot.replace("jpg", "gif");
// Reset the current animated snapshot last frame duration
snapshotAnimatedLastWriteFrameDuration = SNAPSNOT_ANIMATED_INITIAL_WRITE_DURATION_MSEC;
// Ensure the snapshot timer is Precise (attempted millisecond precision)
snapshotAnimatedTimer.setTimerType(Qt::PreciseTimer);
// Connect the snapshotAnimatedTimer QTimer to the lambda slot function
QObject::connect(&(snapshotAnimatedTimer), &QTimer::timeout, [=] {
// Get a screenshot from the display, then scale the screenshot down,
// then convert it to the image format the GIF library needs,
// then save all that to the QImage named "frame"
QImage frame(app->getActiveDisplayPlugin()->getScreenshot(aspectRatio));
frame = frame.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH);
frame = frame.convertToFormat(QImage::Format_RGBA8888);
// If this is the first frame...
if (snapshotAnimatedTimestamp == 0)
{
// Write out the header and beginning of the GIF file
GifBegin(&(snapshotAnimatedGifWriter), qPrintable(pathAnimatedSnapshot), frame.width(), frame.height(), SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10);
// Write the first to the gif
GifWriteFrame(&(snapshotAnimatedGifWriter),
(uint8_t*)frame.bits(),
frame.width(),
frame.height(),
SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10);
// Record the current frame timestamp
snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
snapshotAnimatedFirstFrameTimestamp = snapshotAnimatedTimestamp;
}
else
{
// If that was the last frame...
if ((snapshotAnimatedTimestamp - snapshotAnimatedFirstFrameTimestamp) >= (SNAPSNOT_ANIMATED_DURATION_SECS * 1000))
{
// Reset the current frame timestamp
snapshotAnimatedTimestamp = 0;
snapshotAnimatedFirstFrameTimestamp = 0;
// Write out the end of the GIF
GifEnd(&(snapshotAnimatedGifWriter));
// Stop the snapshot QTimer
snapshotAnimatedTimer.stop();
emit dm->snapshotTaken(pathStillSnapshot, pathAnimatedSnapshot, false);
qDebug() << "still: " << pathStillSnapshot << "anim: " << pathAnimatedSnapshot;
//emit dm->snapshotTaken("C:\\Users\\Zach Fox\\Desktop\\hifi-snap-by-zfox-on-2016-11-14_17-07-33.jpg", "C:\\Users\\Zach Fox\\Desktop\\hifi-snap-by-zfox-on-2016-11-14_17-10-02.gif", false);
}
else
{
// Variable used to determine how long the current frame took to pack
qint64 framePackStartTime = QDateTime::currentMSecsSinceEpoch();
// Write the frame to the gif
GifWriteFrame(&(snapshotAnimatedGifWriter),
(uint8_t*)frame.bits(),
frame.width(),
frame.height(),
round(((float)(QDateTime::currentMSecsSinceEpoch() - snapshotAnimatedTimestamp + snapshotAnimatedLastWriteFrameDuration)) / 10));
// Record how long it took for the current frame to pack
snapshotAnimatedLastWriteFrameDuration = QDateTime::currentMSecsSinceEpoch() - framePackStartTime;
// Record the current frame timestamp
snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
}
}
});
// Start the snapshotAnimatedTimer QTimer - argument for this is in milliseconds
snapshotAnimatedTimer.start(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC);
}
// If we're already in the middle of capturing an animated snapshot...
else
{
// Just tell the dependency manager that the capture of the still snapshot has taken place.
emit dm->snapshotTaken(pathStillSnapshot, "", false);
}
}

View file

@ -0,0 +1,44 @@
//
// SnapshotAnimated.h
// interface/src/ui
//
// Created by Zach Fox on 11/14/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_SnapshotAnimated_h
#define hifi_SnapshotAnimated_h
#include <Application.h>
#include <DependencyManager.h>
#include <GifCreator.h>
#include <qtimer.h>
#include "scripting/WindowScriptingInterface.h"
// If the snapshot width or the framerate are too high for the
// application to handle, the framerate of the output GIF will drop.
#define SNAPSNOT_ANIMATED_WIDTH (720)
// This value should divide evenly into 100. Snapshot framerate is NOT guaranteed.
#define SNAPSNOT_ANIMATED_TARGET_FRAMERATE (25)
#define SNAPSNOT_ANIMATED_DURATION_SECS (3)
#define SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC (1000/SNAPSNOT_ANIMATED_TARGET_FRAMERATE)
// This is the fudge factor that we add to the *first* GIF frame's "delay" value
#define SNAPSNOT_ANIMATED_INITIAL_WRITE_DURATION_MSEC (20)
#define SNAPSNOT_ANIMATED_NUM_FRAMES (SNAPSNOT_ANIMATED_DURATION_SECS * SNAPSNOT_ANIMATED_TARGET_FRAMERATE)
class SnapshotAnimated {
private:
static QTimer snapshotAnimatedTimer;
static GifWriter snapshotAnimatedGifWriter;
static qint64 snapshotAnimatedTimestamp;
static qint64 snapshotAnimatedFirstFrameTimestamp;
static qint64 snapshotAnimatedLastWriteFrameDuration;
public:
static void saveSnapshotAnimated(bool includeAnimated, QString stillSnapshotPath, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm);
};
#endif // hifi_SnapshotAnimated_h

View file

@ -1,48 +1,48 @@
<html>
<head>
<head>
<title>Share</title>
<link rel="stylesheet" type="text/css" href="css/edit-style.css">
<link rel="stylesheet" type="text/css" href="css/SnapshotReview.css">
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
<script type="text/javascript" src="js/SnapshotReview.js"></script>
</head>
</head>
<body>
<body>
<div class="snapshot-container">
<div class="snapshot-column-left">
<div class="snapsection">
<label class="title">Snapshot successfully saved!</label>
</div>
<hr />
<div class="snapsection">
<div id="sharing">
<div class="prompt">Would you like to share your pic in the Snapshots feed?</div>
<div class="button">
<span class="compound-button">
<input type="button" class="blue" id="share" value="Share in Feed" onclick="shareSelected()"/>
<span class="glyph"></span>
</span>
<div class="snapshot-column-left">
<div class="snapsection">
<label class="title">Snapshot successfully saved!</label>
</div>
<hr />
<div class="snapsection">
<div id="sharing">
<div class="prompt">Would you like to share your pics in the Snapshots feed?</div>
<div class="button">
<span class="compound-button">
<input type="button" class="blue" id="share" value="Share in Feed" onclick="shareSelected()" />
<span class="glyph"></span>
</span>
</div>
</div>
<div class="button">
<input type="button" class="black" id="close" value="Don't Share" onclick="doNotShare()" />
</div>
</div>
<hr />
<div class="snapsection">
<span class="setting">
<input type="button" class="glyph naked" id="snapshotSettings" value="@" onclick="snapshotSettings()" />
<label for="snapshotSettings">Snapshot settings</label>
</span>
<span class="setting checkbox">
<input id="openFeed" type="checkbox" checked />
<label for="openFeed">Open feed after</label>
</span>
</div>
</div>
<div class="button">
<input type="button" class="black" id="close" value="Don't Share" onclick="doNotShare()"/>
</div>
</div>
<hr />
<div class="snapsection">
<span class="setting">
<input type="button" class="glyph naked" id="snapshotSettings" value="@" onclick="snapshotSettings()" />
<label for="snapshotSettings">Snapshot settings</label>
</span>
<span class="setting checkbox">
<input id="openFeed" type="checkbox" checked/>
<label for="openFeed">Open feed after</label>
</span>
<div id="snapshot-images" class="snapshot-column-right">
</div>
</div>
<div id="snapshot-images" class="snapshot-column-right"/>
</div>
</div>
</body>
</body>
</html>

View file

@ -12,29 +12,30 @@
var paths = [], idCounter = 0, useCheckboxes;
function addImage(data) {
var div = document.createElement("DIV"),
input = document.createElement("INPUT"),
label = document.createElement("LABEL"),
img = document.createElement("IMG"),
id = "p" + idCounter++;
function toggle() { data.share = input.checked; }
img.src = data.localPath;
div.appendChild(img);
data.share = true;
if (useCheckboxes) { // I'd rather use css, but the included stylesheet is quite particular.
// Our stylesheet(?) requires input.id to match label.for. Otherwise input doesn't display the check state.
label.setAttribute('for', id); // cannot do label.for =
input.id = id;
input.type = "checkbox";
input.checked = true;
input.addEventListener('change', toggle);
div.class = "property checkbox";
div.appendChild(input);
div.appendChild(label);
if (data.localPath) {
var div = document.createElement("DIV"),
input = document.createElement("INPUT"),
label = document.createElement("LABEL"),
img = document.createElement("IMG"),
id = "p" + idCounter++;
function toggle() { data.share = input.checked; }
img.src = data.localPath;
div.appendChild(img);
data.share = true;
if (useCheckboxes) { // I'd rather use css, but the included stylesheet is quite particular.
// Our stylesheet(?) requires input.id to match label.for. Otherwise input doesn't display the check state.
label.setAttribute('for', id); // cannot do label.for =
input.id = id;
input.type = "checkbox";
input.checked = (id === "p0");
input.addEventListener('change', toggle);
div.class = "property checkbox";
div.appendChild(input);
div.appendChild(label);
}
document.getElementById("snapshot-images").appendChild(div);
paths.push(data);
}
document.getElementById("snapshot-images").appendChild(div);
paths.push(data);
}
function handleShareButtons(shareMsg) {
var openFeed = document.getElementById('openFeed');
@ -49,7 +50,7 @@ function handleShareButtons(shareMsg) {
window.onload = function () {
// Something like the following will allow testing in a browser.
//addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'});
//addImage({localPath: 'http://lorempixel.com/1512/1680'});
//addImage({ localPath: 'http://lorempixel.com/1512/1680' });
openEventBridge(function () {
// Set up a handler for receiving the data, and tell the .js we are ready to receive it.
EventBridge.scriptEventReceived.connect(function (message) {

File diff suppressed because it is too large Load diff

View file

@ -120,11 +120,11 @@ function onClicked() {
// take snapshot (with no notification)
Script.setTimeout(function () {
Window.takeSnapshot(false, 1.91);
Window.takeSnapshot(false, true, 1.91);
}, SNAPSHOT_DELAY);
}
function resetButtons(path, notify) {
function resetButtons(pathStillSnapshot, pathAnimatedSnapshot, notify) {
// show overlays if they were on
if (resetOverlays) {
Menu.setIsOptionChecked("Overlays", true);
@ -141,7 +141,8 @@ function resetButtons(path, notify) {
// last element in data array tells dialog whether we can share or not
confirmShare([
{ localPath: path },
{ localPath: pathAnimatedSnapshot },
{ localPath: pathStillSnapshot },
{
canShare: !!location.placename,
openFeedAfterShare: shouldOpenFeedAfterShare()