From 2a85a22dba62c875cb579a414be90729e092626a Mon Sep 17 00:00:00 2001
From: scrawl <scrawl@baseoftrash.de>
Date: Wed, 3 Jun 2015 16:40:16 +0200
Subject: [PATCH] Write savegame screenshot

---
 apps/openmw/mwbase/world.hpp              |  3 +-
 apps/openmw/mwrender/renderingmanager.cpp | 59 +++++++++++++++++++++++
 apps/openmw/mwrender/renderingmanager.hpp |  3 ++
 apps/openmw/mwstate/statemanagerimp.cpp   | 41 ++++++++++++----
 apps/openmw/mwstate/statemanagerimp.hpp   |  2 +
 apps/openmw/mwworld/worldimp.cpp          |  4 +-
 apps/openmw/mwworld/worldimp.hpp          |  2 +-
 7 files changed, 101 insertions(+), 13 deletions(-)

diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp
index f258eb300a..bb48afc9d6 100644
--- a/apps/openmw/mwbase/world.hpp
+++ b/apps/openmw/mwbase/world.hpp
@@ -23,6 +23,7 @@ namespace osg
 {
     class Vec3f;
     class Quat;
+    class Image;
 }
 
 namespace Loading
@@ -438,7 +439,7 @@ namespace MWBase
             virtual void reattachPlayerCamera() = 0;
 
             /// \todo this does not belong here
-            virtual void screenshot (Ogre::Image& image, int w, int h) = 0;
+            virtual void screenshot (osg::Image* image, int w, int h) = 0;
 
             /// Find default position inside exterior cell specified by name
             /// \return false if exterior with given name not exists, true otherwise
diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp
index 6205c3ad87..7af21a5a6e 100644
--- a/apps/openmw/mwrender/renderingmanager.cpp
+++ b/apps/openmw/mwrender/renderingmanager.cpp
@@ -379,6 +379,65 @@ namespace MWRender
         mWater->setHeight(height);
     }
 
+    class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback
+    {
+    public:
+        virtual void operator () (osg::RenderInfo& renderInfo) const
+        {
+            mCondition.signal();
+        }
+
+        mutable OpenThreads::Condition mCondition;
+    };
+
+    void RenderingManager::screenshot(osg::Image *image, int w, int h)
+    {
+        int oldCullMask = mViewer->getCamera()->getCullMask();
+        mViewer->getCamera()->setCullMask(oldCullMask & (~Mask_GUI));
+
+        osg::ref_ptr<osg::Camera> rttCamera (new osg::Camera);
+        rttCamera->setNodeMask(Mask_RenderToTexture);
+        rttCamera->attach(osg::Camera::COLOR_BUFFER, image);
+        rttCamera->setRenderOrder(osg::Camera::PRE_RENDER);
+        rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
+        rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT);
+        rttCamera->setClearColor(mViewer->getCamera()->getClearColor());
+        rttCamera->setClearMask(mViewer->getCamera()->getClearMask());
+        rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance);
+        rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix());
+        rttCamera->setViewport(0, 0, w, h);
+        rttCamera->setGraphicsContext(mViewer->getCamera()->getGraphicsContext());
+
+        osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
+        texture->setInternalFormat(GL_RGB);
+        texture->setTextureSize(w, h);
+        texture->setResizeNonPowerOfTwoHint(false);
+        rttCamera->attach(osg::Camera::COLOR_BUFFER, texture);
+
+        image->setDataType(GL_UNSIGNED_BYTE);
+        image->setPixelFormat(texture->getInternalFormat());
+
+        rttCamera->addChild(mLightRoot);
+
+        mRootNode->addChild(rttCamera);
+
+        mViewer->frame();
+
+        // The draw needs to complete before we can copy back our image.
+        osg::ref_ptr<NotifyDrawCompletedCallback> callback (new NotifyDrawCompletedCallback);
+        rttCamera->setFinalDrawCallback(callback);
+        OpenThreads::Mutex m;
+        m.lock();
+        callback->mCondition.wait(&m);
+        m.unlock();
+
+        rttCamera->removeChildren(0, rttCamera->getNumChildren());
+        rttCamera->setGraphicsContext(NULL);
+        mRootNode->removeChild(rttCamera);
+
+        mViewer->getCamera()->setCullMask(oldCullMask);
+    }
+
     void RenderingManager::getCameraToViewportRay(float nX, float nY, osg::Vec3f &origin, osg::Vec3f &dest)
     {
         osg::Matrix viewProj = mViewer->getCamera()->getViewMatrix() * mViewer->getCamera()->getProjectionMatrix();
diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp
index c22d3c7bfd..4452ae9f85 100644
--- a/apps/openmw/mwrender/renderingmanager.hpp
+++ b/apps/openmw/mwrender/renderingmanager.hpp
@@ -82,6 +82,9 @@ namespace MWRender
         void setWaterEnabled(bool enabled);
         void setWaterHeight(float level);
 
+        /// Take a screenshot of w*h onto the given image, not including the GUI.
+        void screenshot(osg::Image* image, int w, int h);
+
         /// Return the object under the mouse cursor / crosshair position, given by nX and nY normalized screen coordinates,
         /// where (0,0) is the top left corner.
         MWWorld::Ptr getFacedObject(const float nX, const float nY, float maxDistance, bool ignorePlayer);
diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp
index d403590dd1..ce4d8f9581 100644
--- a/apps/openmw/mwstate/statemanagerimp.cpp
+++ b/apps/openmw/mwstate/statemanagerimp.cpp
@@ -10,7 +10,9 @@
 
 #include <components/settings/settings.hpp>
 
-#include <OgreImage.h>
+#include <osg/Image>
+
+#include <osgDB/Registry>
 
 #include <boost/filesystem/fstream.hpp>
 #include <boost/filesystem/operations.hpp>
@@ -179,14 +181,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
         profile.mTimePlayed = mTimePlayed;
         profile.mDescription = description;
 
-        /*
-        int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing
-        Ogre::Image screenshot;
-        world.screenshot(screenshot, screenshotW, screenshotH);
-        Ogre::DataStreamPtr encoded = screenshot.encode("jpg");
-        profile.mScreenshot.resize(encoded->size());
-        encoded->read(&profile.mScreenshot[0], encoded->size());
-        */
+        writeScreenshot(profile.mScreenshot);
 
         if (!slot)
             slot = getCurrentCharacter()->createSlot (profile);
@@ -572,3 +567,31 @@ bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const
     }
     return true;
 }
+
+void MWState::StateManager::writeScreenshot(std::vector<char> &imageData) const
+{
+    int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing
+
+    osg::ref_ptr<osg::Image> screenshot (new osg::Image);
+
+    MWBase::Environment::get().getWorld()->screenshot(screenshot.get(), screenshotW, screenshotH);
+
+    osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg");
+    if (!readerwriter)
+    {
+        std::cerr << "Unable to write screenshot, can't find a jpg ReaderWriter" << std::endl;
+        return;
+    }
+
+    std::ostringstream ostream;
+    osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*screenshot, ostream);
+    if (!result.success())
+    {
+        std::cerr << "Unable to write screenshot: " << result.message() << std::endl;
+        return;
+    }
+
+    std::string data = ostream.str();
+    imageData = std::vector<char>(data.begin(), data.end());
+
+}
diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp
index 37f38f8df7..59dc919d14 100644
--- a/apps/openmw/mwstate/statemanagerimp.hpp
+++ b/apps/openmw/mwstate/statemanagerimp.hpp
@@ -25,6 +25,8 @@ namespace MWState
 
             bool verifyProfile (const ESM::SavedGame& profile) const;
 
+            void writeScreenshot (std::vector<char>& imageData) const;
+
             std::map<int, int> buildContentFileIndexMap (const ESM::ESMReader& reader) const;
 
         public:
diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp
index 7528e9286e..7bc46c0780 100644
--- a/apps/openmw/mwworld/worldimp.cpp
+++ b/apps/openmw/mwworld/worldimp.cpp
@@ -2128,9 +2128,9 @@ namespace MWWorld
         return mRendering->getAnimation(ptr);
     }
 
-    void World::screenshot(Ogre::Image &image, int w, int h)
+    void World::screenshot(osg::Image* image, int w, int h)
     {
-        //mRendering->screenshot(image, w, h);
+        mRendering->screenshot(image, w, h);
     }
 
     void World::activateDoor(const MWWorld::Ptr& door)
diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp
index 4c4df2c995..20d6bf9a1c 100644
--- a/apps/openmw/mwworld/worldimp.hpp
+++ b/apps/openmw/mwworld/worldimp.hpp
@@ -524,7 +524,7 @@ namespace MWWorld
             virtual void reattachPlayerCamera();
 
             /// \todo this does not belong here
-            virtual void screenshot (Ogre::Image& image, int w, int h);
+            virtual void screenshot (osg::Image* image, int w, int h);
 
             /// Find center of exterior cell above land surface
             /// \return false if exterior with given name not exists, true otherwise