Read canvas image by HTTP: Difference between revisions

Jump to navigation Jump to search
Line 184: Line 184:


== Patches ==
== Patches ==
=== FlightGear ===
<syntaxhighlight lang="diff">
diff --git a/src/Canvas/canvas_mgr.cxx b/src/Canvas/canvas_mgr.cxx
index 6646b77..5316543 100644
--- a/src/Canvas/canvas_mgr.cxx
+++ b/src/Canvas/canvas_mgr.cxx
@@ -64,7 +64,7 @@ CanvasMgr::CanvasMgr():
    fgGetNode("/sim/signals/model-reinit", true)
  )
{
-
+ SG_LOG(SG_GENERAL, SG_ALERT, "CanvasMgr() constructor invoked");
}
//----------------------------------------------------------------------------
diff --git a/src/Network/http/CMakeLists.txt b/src/Network/http/CMakeLists.txt
index 9e913d0..1a099cb 100644
--- a/src/Network/http/CMakeLists.txt
+++ b/src/Network/http/CMakeLists.txt
@@ -3,6 +3,7 @@ include(FlightGearComponent)
set(SOURCES
httpd.cxx
ScreenshotUriHandler.cxx
+ CanvasImageUriHandler.cxx
PropertyUriHandler.cxx
JsonUriHandler.cxx
    FlightHistoryUriHandler.cxx
@@ -19,6 +20,7 @@ set(HEADERS
urihandler.hxx
httpd.hxx
ScreenshotUriHandler.hxx
+ CanvasImageUriHandler.hxx
PropertyUriHandler.hxx
JsonUriHandler.hxx
    FlightHistoryUriHandler.hxx
diff --git a/src/Network/http/CanvasImageUriHandler.cxx b/src/Network/http/CanvasImageUriHandler.cxx
new file mode 100644
index 0000000..7cd5a0e
--- /dev/null
+++ b/src/Network/http/CanvasImageUriHandler.cxx
@@ -0,0 +1,335 @@
+// CanvasImageUriHandler.cxx -- Provide canvasimages via http
+//
+// Started by Curtis Olson, started June 2001.
+// osg support written by James Turner
+// Ported to new httpd infrastructure by Torsten Dreyer
+// Derived from ScreenshotUrihandler originally by Torsten Dreyer)
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+#include "CanvasImageUriHandler.hxx"
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+#include <osgDB/Registry>
+#include <osgDB/ReaderWriter>
+#include <osgUtil/SceneView>
+#include <osgViewer/Viewer>
+
+#include <Canvas/canvas_mgr.hxx>
+#include <simgear/canvas/Canvas.hxx>
+
+#include <simgear/threads/SGQueue.hxx>
+#include <simgear/structure/Singleton.hxx>
+#include <Main/globals.hxx>
+#include <Viewer/renderer.hxx>
+
+#include <queue>
+#include <boost/lexical_cast.hpp>
+
+using std::string;
+using std::vector;
+using std::list;
+
+namespace sc = simgear::canvas;
+
+namespace flightgear {
+    namespace http {
+
+        ///////////////////////////////////////////////////////////////////////////
+
+        class StringReadyListener {
+        public:
+            virtual void stringReady(const std::string &) = 0;
+
+            virtual ~StringReadyListener() {
+            }
+        };
+
+        struct ImageCompressionTask {
+            StringReadyListener * stringReadyListener;
+            string format;
+            osg::ref_ptr<osg::Image> image;
+
+            ImageCompressionTask() {
+                stringReadyListener = NULL;
+            }
+
+            ImageCompressionTask(const ImageCompressionTask & other) {
+                stringReadyListener = other.stringReadyListener;
+                format = other.format;
+                image = other.image;
+            }
+
+            ImageCompressionTask & operator=(const ImageCompressionTask & other) {
+                stringReadyListener = other.stringReadyListener;
+                format = other.format;
+                image = other.image;
+                return *this;
+            }
+
+        };
+        //TODO reuse from screenshoturihandler
+
+        class ImageCompressorCI : public OpenThreads::Thread {
+        public:
+
+            ImageCompressorCI() {
+            }
+            virtual void run();
+            void addTask(ImageCompressionTask & task);
+        private:
+            typedef SGBlockingQueue<ImageCompressionTask> TaskList;
+            TaskList _tasks;
+        };
+
+        typedef simgear::Singleton<ImageCompressorCI> ImageCompressorCISingleton;
+
+        void ImageCompressorCI::run() {
+            osg::ref_ptr<osgDB::ReaderWriter::Options> options = new osgDB::ReaderWriter::Options("JPEG_QUALITY 80 PNG_COMPRESSION 9");
+
+            SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI is running");
+            for (;;) {
+                ImageCompressionTask task = _tasks.pop();
+                SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI has an image");
+
+                if (NULL != task.stringReadyListener) {
+                    SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI checking for writer for " << task.format);
+                    osgDB::ReaderWriter* writer = osgDB::Registry::instance()->getReaderWriterForExtension(task.format);
+                    if (!writer)
+                        continue;
+
+                    SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI compressing to " << task.format);
+                    std::stringstream outputStream;
+                    osgDB::ReaderWriter::WriteResult wr;
+                    wr = writer->writeImage(*task.image, outputStream, options);
+
+                    if (wr.success()) {
+                        SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI compressed to  " << task.format);
+                        task.stringReadyListener->stringReady(outputStream.str());
+                    }
+                    SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI done for this image" << task.format);
+
+                }
+            }
+            SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI exiting");
+        }
+
+        void ImageCompressorCI::addTask(ImageCompressionTask & task) {
+            _tasks.push(task);
+        }
+
+
+        ///////////////////////////////////////////////////////////////////////////
+
+        class CanvasImageRequest : public ConnectionData, public simgear::canvas::CanvasImageReadyListener, StringReadyListener {
+        public:
+            ImageCompressionTask *currenttask=NULL;
+            sc::CanvasPtr canvas;
+
+            CanvasImageRequest(const string & window, const string & type, int canvasindex, bool stream)
+            : _type(type), _stream(stream) {
+                SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImageRequest: \n");
+               
+                if (NULL == osgDB::Registry::instance()->getReaderWriterForExtension(_type))
+                    throw sg_format_exception("Unsupported image type: " + type, type);
+
+                CanvasMgr* canvas_mgr = static_cast<CanvasMgr*> (globals->get_subsystem("Canvas"));
+                if (!canvas_mgr) {
+                    SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImage:CanvasMgr not found\n");
+                } else {
+                    canvas = canvas_mgr->getCanvas(canvasindex);
+                    if (!canvas) {
+                        SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImage:Canvas not found\n");
+                    } else {
+                        SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImage:Canvas found\n");
+                        //SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImageRequest: found camera %d. width=%d, height=%d\n", camera, canvas->getSizeX(), canvas->getSizeY());
+
+                        SGConstPropertyNode_ptr canvasnode = canvas->getProps();
+                        if (canvasnode) {
+                            const char *canvasname = canvasnode->getStringValue("name");
+                            if (canvasname) {
+                                SG_LOG(SG_NETWORK, SG_INFO, "CanvasImageRequest: node=%s(%s)\n");//, canvasnode->getDisplayName().c_str(), canvasname);
+                            }
+                        }
+
+                        canvas->subscribe(this);
+                    }
+                }
+            }
+
+            // Assumption: when unsubscribe returns,there might just be a compressor thread running,
+            // causing a crash when the deconstructor finishes. Rare, but might happen. Just wait to be sure.
+            virtual ~CanvasImageRequest() {
+                if (currenttask){
+                    SG_LOG(SG_NETWORK, SG_INFO, "canvasimage task running");
+#ifdef _WIN32
+                    Sleep(15000);
+#else
+                    sleep(15);
+#endif
+                }
+
+                if (canvas){
+                    canvas->unsubscribe(this);
+                }
+                //_canvasimageCallback->unsubscribe(this);
+            }
+
+            virtual void imageReady(osg::ref_ptr<osg::Image> rawImage) {
+                SG_LOG(SG_NETWORK, SG_INFO, "CanvasImage:imageReady");
+                // called from a rendering thread, not from the main loop
+                ImageCompressionTask task;
+                currenttask = &task;
+                task.image = rawImage;
+                task.format = _type;
+                task.stringReadyListener = this;
+                ImageCompressorCISingleton::instance()->addTask(task);
+            }
+
+            void requestCanvasImage() {
+            //    _canvasimageCallback->subscribe(this);
+            }
+
+            mutable OpenThreads::Mutex _lock;
+
+            virtual void stringReady(const string & s) {
+                SG_LOG(SG_NETWORK, SG_INFO, "CanvasImage:stringReady");
+               
+                // called from the compressor thread
+                OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
+                _compressedData = s;
+                // allow destructor
+                currenttask = NULL;
+            }
+
+            string getCanvasImage() {
+                string reply;
+                {
+                    // called from the main loop
+                    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
+                    reply = _compressedData;
+                    _compressedData.clear();
+                }
+                return reply;
+            }
+
+            bool isStream() const {
+                return _stream;
+            }
+
+            const string & getType() const {
+                return _type;
+            }
+
+        private:
+            string _type;
+            bool _stream;
+            string _compressedData;
+            //CanvasImageCallback * _canvasimageCallback;
+        };
+
+        CanvasImageUriHandler::CanvasImageUriHandler(const char * uri)
+        : URIHandler(uri) {
+        }
+
+        CanvasImageUriHandler::~CanvasImageUriHandler() {
+            ImageCompressorCISingleton::instance()->cancel();
+            //ImageCompressorSingleton::instance()->join();
+        }
+
+        const static string KEY("CanvasImageUriHandler::CanvasImageRequest");
+#define BOUNDARY "--fgfs-canvasimage-boundary"
+
+        bool CanvasImageUriHandler::handleGetRequest(const HTTPRequest & request, HTTPResponse & response, Connection * connection) {
+            if (!ImageCompressorCISingleton::instance()->isRunning())
+                ImageCompressorCISingleton::instance()->start();
+
+            string type = request.RequestVariables.get("type");
+            if (type.empty()) type = "jpg";
+
+            string canvasindex = request.RequestVariables.get("canvasindex");
+            if (canvasindex.empty()) canvasindex = "0";
+           
+            //  string camera = request.RequestVariables.get("camera");
+            string window = request.RequestVariables.get("window");
+
+            bool stream = (false == request.RequestVariables.get("stream").empty());
+
+            SGSharedPtr<CanvasImageRequest> canvasimageRequest;
+            try {
+                SG_LOG(SG_NETWORK, SG_INFO, "new CanvasImageRequest(" << window << "," << type << "," << stream << ")");
+                canvasimageRequest = new CanvasImageRequest(window, type, atoi(canvasindex.c_str()),stream);
+            } catch (sg_format_exception & ex) {
+                SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
+                response.Header["Content-Type"] = "text/plain";
+                response.StatusCode = 410;
+                response.Content = ex.getFormattedMessage();
+                return true;
+            } catch (sg_error & ex) {
+                SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
+                response.Header["Content-Type"] = "text/plain";
+                response.StatusCode = 500;
+                response.Content = ex.getFormattedMessage();
+                return true;
+            }
+
+            if (false == stream) {
+                response.Header["Content-Type"] = string("image/").append(type);
+                response.Header["Content-Disposition"] = string("inline; filename=\"fgfs-canvasimage.").append(type).append("\"");
+            } else {
+                response.Header["Content-Type"] = string("multipart/x-mixed-replace; boundary=" BOUNDARY);
+
+            }
+
+            connection->put(KEY, canvasimageRequest);
+            return false; // call me again thru poll
+        }
+
+        bool CanvasImageUriHandler::poll(Connection * connection) {
+
+            SGSharedPtr<ConnectionData> data = connection->get(KEY);
+            CanvasImageRequest * canvasimageRequest = dynamic_cast<CanvasImageRequest*> (data.get());
+            if (NULL == canvasimageRequest) return true; // Should not happen, kill the connection
+
+            const string & canvasimage = canvasimageRequest->getCanvasImage();
+            if (canvasimage.empty()) {
+                SG_LOG(SG_NETWORK, SG_INFO, "No canvasimage available.");
+                return false; // not ready yet, call again.
+            }
+
+            SG_LOG(SG_NETWORK, SG_INFO, "CanvasImage is ready, size=" << canvasimage.size());
+
+            if (canvasimageRequest->isStream()) {
+                string s(BOUNDARY "\r\nContent-Type: image/");
+                s.append(canvasimageRequest->getType()).append("\r\nContent-Length:");
+                s += boost::lexical_cast<string>(canvasimage.size());
+                s += "\r\n\r\n";
+                connection->write(s.c_str(), s.length());
+            }
+
+            connection->write(canvasimage.data(), canvasimage.size());
+
+            /* unknown purpose
+            if (canvasimageRequest->isStream()) {
+                canvasimageRequest->requestCanvasImage();
+                // continue until user closes connection
+                return false;
+            }
+            */
+
+            // single canvasimage, send terminating chunk
+            connection->remove(KEY);
+            connection->write("", 0);
+            return true; // done.
+        }
+
+    } // namespace http
+} // namespace flightgear
+
diff --git a/src/Network/http/CanvasImageUriHandler.hxx b/src/Network/http/CanvasImageUriHandler.hxx
new file mode 100644
index 0000000..f50be37
--- /dev/null
+++ b/src/Network/http/CanvasImageUriHandler.hxx
@@ -0,0 +1,40 @@
+// CanvasImageUriHandler.hxx -- Provide canvasimages via http
+//
+// Written by Torsten Dreyer, started April 2014.
+//
+// Copyright (C) 2014  Torsten Dreyer
+// derived from ScreenshotUrihandler by ThomasS
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+#ifndef __FG_CANVASIMAGE_URI_HANDLER_HXX
+#define __FG_CANVASIMAGE_URI_HANDLER_HXX
+
+#include "urihandler.hxx"
+
+namespace flightgear {
+namespace http {
+
+class CanvasImageUriHandler : public URIHandler {
+public:
+  CanvasImageUriHandler( const char * uri = "/canvasimage/" );
+  ~CanvasImageUriHandler();
+  virtual bool handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
+  virtual bool poll( Connection * connection );
+};
+
+} // namespace http
+} // namespace flightgear
+
+#endif //#define __FG_CANVASIMAGE_URI_HANDLER_HXX
diff --git a/src/Network/http/httpd.cxx b/src/Network/http/httpd.cxx
index 358b247..3bfff3a 100644
--- a/src/Network/http/httpd.cxx
+++ b/src/Network/http/httpd.cxx
@@ -22,6 +22,7 @@
#include "HTTPRequest.hxx"
#include "PropertyChangeWebsocket.hxx"
#include "ScreenshotUriHandler.hxx"
+#include "CanvasImageUriHandler.hxx"
#include "PropertyUriHandler.hxx"
#include "JsonUriHandler.hxx"
#include "FlightHistoryUriHandler.hxx"
@@ -443,6 +444,11 @@ void MongooseHttpd::init()
      _uriHandler.push_back(new flightgear::http::ScreenshotUriHandler(uri));
    }
+if ((uri = n->getStringValue("canvasimage"))[0] != 0) {
+      SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding canvasimage uri handler at " << uri);
+      _uriHandler.push_back(new flightgear::http::CanvasImageUriHandler(uri));
+    }
+
    if ((uri = n->getStringValue("property"))[0] != 0) {
      SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding property uri handler at " << uri);
      _uriHandler.push_back(new flightgear::http::PropertyUriHandler(uri));
</syntaxhighlight>
=== Base Package ===
=== Base Package ===
CanvasView.html
CanvasView.html
183

edits

Navigation menu