]> git.mxchange.org Git - flightgear.git/blob - src/Network/http/ScreenshotUriHandler.cxx
Interim windows build fix
[flightgear.git] / src / Network / http / ScreenshotUriHandler.cxx
1 // ScreenshotUriHandler.cxx -- Provide screenshots via http
2 //
3 // Started by Curtis Olson, started June 2001.
4 // osg support written by James Turner
5 // Ported to new httpd infrastructure by Torsten Dreyer
6 //
7 // Copyright (C) 2014  Torsten Dreyer
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License as
11 // published by the Free Software Foundation; either version 2 of the
12 // License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 // General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
22
23 #include "ScreenshotUriHandler.hxx"
24
25 #include <osgDB/Registry>
26 #include <osgDB/ReaderWriter>
27 #include <osgUtil/SceneView>
28 #include <osgViewer/Viewer>
29
30 #include <simgear/threads/SGQueue.hxx>
31 #include <simgear/structure/Singleton.hxx>
32 #include <Main/globals.hxx>
33 #include <Viewer/renderer.hxx>
34
35 #include <queue>
36 #include <boost/lexical_cast.hpp>
37
38 using std::string;
39 using std::vector;
40 using std::list;
41
42 namespace flightgear {
43 namespace http {
44
45 ///////////////////////////////////////////////////////////////////////////
46
47 class ImageReadyListener {
48 public:
49   virtual void imageReady(osg::ref_ptr<osg::Image>) = 0;
50   virtual ~ImageReadyListener()
51   {
52   }
53 };
54
55 class StringReadyListener {
56 public:
57   virtual void stringReady(const std::string &) = 0;
58   virtual ~StringReadyListener()
59   {
60   }
61 };
62
63 struct ImageCompressionTask {
64   StringReadyListener * stringReadyListener;
65   string format;
66   osg::ref_ptr<osg::Image> image;
67
68   ImageCompressionTask()
69   {
70     stringReadyListener = NULL;
71   }
72
73   ImageCompressionTask(const ImageCompressionTask & other)
74   {
75     stringReadyListener = other.stringReadyListener;
76     format = other.format;
77     image = other.image;
78   }
79
80   ImageCompressionTask & operator =(const ImageCompressionTask & other)
81   {
82     stringReadyListener = other.stringReadyListener;
83     format = other.format;
84     image = other.image;
85     return *this;
86   }
87
88 };
89
90 class ImageCompressor: public OpenThreads::Thread {
91 public:
92   ImageCompressor()
93   {
94   }
95   virtual void run();
96   void addTask(ImageCompressionTask & task);
97   private:
98   typedef SGBlockingQueue<ImageCompressionTask> TaskList;
99   TaskList _tasks;
100 };
101
102 typedef simgear::Singleton<ImageCompressor> ImageCompressorSingleton;
103
104 void ImageCompressor::run()
105 {
106   osg::ref_ptr<osgDB::ReaderWriter::Options> options = new osgDB::ReaderWriter::Options("JPEG_QUALITY 80 PNG_COMPRESSION 9");
107
108   SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor is running");
109   for (;;) {
110     ImageCompressionTask task = _tasks.pop();
111     SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor has an image");
112     if ( NULL != task.stringReadyListener) {
113       SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor checking for writer for " << task.format);
114       osgDB::ReaderWriter* writer = osgDB::Registry::instance()->getReaderWriterForExtension(task.format);
115       if (!writer)
116       continue;
117
118       SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor compressing to " << task.format);
119       std::stringstream outputStream;
120       osgDB::ReaderWriter::WriteResult wr;
121       wr = writer->writeImage(*task.image, outputStream, options);
122
123       if (wr.success()) {
124         SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor compressed to  " << task.format);
125         task.stringReadyListener->stringReady(outputStream.str());
126       }
127       SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor done for this image" << task.format);
128     }
129   }
130   SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor exiting");
131 }
132
133 void ImageCompressor::addTask(ImageCompressionTask & task)
134 {
135   _tasks.push(task);
136 }
137
138 /**
139  * Based on <a href="http://code.google.com/p/osgworks">osgworks</a> ScreenCapture.cpp
140  *
141  */
142 class ScreenshotCallback: public osg::Camera::DrawCallback {
143 public:
144   ScreenshotCallback()
145       : _min_delta_tick(1.0/8.0)
146   {
147     _previousFrameTick = osg::Timer::instance()->tick();
148   }
149
150   virtual void operator ()(osg::RenderInfo& renderInfo) const
151   {
152     osg::Timer_t n = osg::Timer::instance()->tick();
153     double dt = osg::Timer::instance()->delta_s(_previousFrameTick,n);
154     if (dt < _min_delta_tick)
155         return;
156     _previousFrameTick = n;
157
158     bool hasSubscribers = false;
159     {
160       OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
161       hasSubscribers = !_subscribers.empty();
162
163     }
164     if (hasSubscribers) {
165       osg::ref_ptr<osg::Image> image = new osg::Image;
166       const osg::Viewport* vp = renderInfo.getState()->getCurrentViewport();
167       image->readPixels(vp->x(), vp->y(), vp->width(), vp->height(), GL_RGB, GL_UNSIGNED_BYTE);
168       {
169         OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
170         while (!_subscribers.empty()) {
171           try {
172             _subscribers.back()->imageReady(image);
173           }
174           catch (...) {
175           }
176           _subscribers.pop_back();
177
178         }
179       }
180     }
181   }
182
183   void subscribe(ImageReadyListener * subscriber)
184   {
185     OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
186     _subscribers.push_back(subscriber);
187   }
188
189   void unsubscribe(ImageReadyListener * subscriber)
190   {
191     OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
192     _subscribers.remove( subscriber );
193   }
194
195 private:
196   mutable list<ImageReadyListener*> _subscribers;
197   mutable OpenThreads::Mutex _lock;
198   mutable double _previousFrameTick;
199   double _min_delta_tick;
200 };
201
202 ///////////////////////////////////////////////////////////////////////////
203
204 class ScreenshotRequest: public ConnectionData, public ImageReadyListener, StringReadyListener {
205 public:
206   ScreenshotRequest(const string & window, const string & type, bool stream)
207       : _type(type), _stream(stream)
208   {
209     if ( NULL == osgDB::Registry::instance()->getReaderWriterForExtension(_type))
210     throw sg_format_exception("Unsupported image type: " + type, type);
211
212     osg::Camera * camera = findLastCamera(globals->get_renderer()->getViewer(), window);
213     if ( NULL == camera)
214     throw sg_error("Can't find a camera for window '" + window + "'");
215
216     // add our ScreenshotCallback to the camera
217     if ( NULL == camera->getFinalDrawCallback()) {
218       //TODO: are we leaking the Callback on reinit?
219       camera->setFinalDrawCallback(new ScreenshotCallback());
220     }
221
222     _screenshotCallback = dynamic_cast<ScreenshotCallback*>(camera->getFinalDrawCallback());
223     if ( NULL == _screenshotCallback)
224     throw sg_error("Can't find ScreenshotCallback");
225
226     requestScreenshot();
227   }
228
229   virtual ~ScreenshotRequest()
230   {
231     _screenshotCallback->unsubscribe(this);
232   }
233
234   virtual void imageReady(osg::ref_ptr<osg::Image> rawImage)
235   {
236     // called from a rendering thread, not from the main loop
237     ImageCompressionTask task;
238     task.image = rawImage;
239     task.format = _type;
240     task.stringReadyListener = this;
241     ImageCompressorSingleton::instance()->addTask(task);
242   }
243
244   void requestScreenshot()
245   {
246     _screenshotCallback->subscribe(this);
247   }
248
249   mutable OpenThreads::Mutex _lock;
250
251   virtual void stringReady(const string & s)
252   {
253     // called from the compressor thread
254     OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
255     _compressedData = s;
256   }
257
258   string getScreenshot()
259   {
260     string reply;
261     {
262       // called from the main loop
263       OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
264       reply = _compressedData;
265       _compressedData.clear();
266     }
267     return reply;
268   }
269
270   osg::Camera* findLastCamera(osgViewer::ViewerBase * viewer, const string & windowName)
271   {
272     osgViewer::ViewerBase::Windows windows;
273     viewer->getWindows(windows);
274
275     osgViewer::GraphicsWindow* window = NULL;
276
277     if (false == windowName.empty()) {
278       for (osgViewer::ViewerBase::Windows::iterator itr = windows.begin(); itr != windows.end(); ++itr) {
279         if ((*itr)->getTraits()->windowName == windowName) {
280           window = *itr;
281           break;
282         }
283       }
284     }
285
286     if ( NULL == window) {
287       if (false == windowName.empty()) {
288         SG_LOG(SG_NETWORK, SG_INFO, "requested window " << windowName << " not found, using first window");
289       }
290       window = *windows.begin();
291     }
292
293     SG_LOG(SG_NETWORK, SG_DEBUG, "Looking for last Camera of window '" << window->getTraits()->windowName << "'");
294
295     osg::GraphicsContext::Cameras& cameras = window->getCameras();
296     osg::Camera* lastCamera = 0;
297     for (osg::GraphicsContext::Cameras::iterator cam_itr = cameras.begin(); cam_itr != cameras.end(); ++cam_itr) {
298       if (lastCamera) {
299         if ((*cam_itr)->getRenderOrder() > lastCamera->getRenderOrder()) {
300           lastCamera = (*cam_itr);
301         }
302         if ((*cam_itr)->getRenderOrder() == lastCamera->getRenderOrder()
303             && (*cam_itr)->getRenderOrderNum() >= lastCamera->getRenderOrderNum()) {
304           lastCamera = (*cam_itr);
305         }
306       } else {
307         lastCamera = *cam_itr;
308       }
309     }
310
311     return lastCamera;
312   }
313
314   bool isStream() const
315   {
316     return _stream;
317   }
318
319   const string & getType() const
320   {
321     return _type;
322   }
323
324 private:
325   string _type;
326   bool _stream;
327   string _compressedData;
328   ScreenshotCallback * _screenshotCallback;
329 };
330
331 ScreenshotUriHandler::ScreenshotUriHandler(const char * uri)
332     : URIHandler(uri)
333 {
334 }
335
336 ScreenshotUriHandler::~ScreenshotUriHandler()
337 {
338   ImageCompressorSingleton::instance()->cancel();
339   //ImageCompressorSingleton::instance()->join();
340 }
341
342 const static string KEY("ScreenshotUriHandler::ScreenshotRequest");
343 #define BOUNDARY "--fgfs-screenshot-boundary"
344
345 bool ScreenshotUriHandler::handleGetRequest(const HTTPRequest & request, HTTPResponse & response, Connection * connection)
346 {
347   if (!ImageCompressorSingleton::instance()->isRunning())
348   ImageCompressorSingleton::instance()->start();
349
350   string type = request.RequestVariables.get("type");
351   if (type.empty()) type = "jpg";
352
353   //  string camera = request.RequestVariables.get("camera");
354   string window = request.RequestVariables.get("window");
355
356   bool stream = (false == request.RequestVariables.get("stream").empty());
357
358   SGSharedPtr<ScreenshotRequest> screenshotRequest;
359   try {
360     SG_LOG(SG_NETWORK, SG_DEBUG, "new ScreenshotRequest("<<window<<","<<type<<"," << stream << ")");
361     screenshotRequest = new ScreenshotRequest(window, type, stream);
362   }
363   catch (sg_format_exception & ex)
364   {
365     SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
366     response.Header["Content-Type"] = "text/plain";
367     response.StatusCode = 410;
368     response.Content = ex.getFormattedMessage();
369     return true;
370   }
371   catch (sg_error & ex)
372   {
373     SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
374     response.Header["Content-Type"] = "text/plain";
375     response.StatusCode = 500;
376     response.Content = ex.getFormattedMessage();
377     return true;
378   }
379
380   if (false == stream) {
381     response.Header["Content-Type"] = string("image/").append(type);
382     response.Header["Content-Disposition"] = string("inline; filename=\"fgfs-screen.").append(type).append("\"");
383   } else {
384     response.Header["Content-Type"] = string("multipart/x-mixed-replace; boundary=" BOUNDARY);
385
386   }
387
388   connection->put(KEY, screenshotRequest);
389   return false; // call me again thru poll
390 }
391
392 bool ScreenshotUriHandler::poll(Connection * connection)
393 {
394
395   SGSharedPtr<ConnectionData> data = connection->get(KEY);
396   ScreenshotRequest * screenshotRequest = dynamic_cast<ScreenshotRequest*>(data.get());
397   if ( NULL == screenshotRequest) return true; // Should not happen, kill the connection
398
399   const string & screenshot = screenshotRequest->getScreenshot();
400   if (screenshot.empty()) {
401     SG_LOG(SG_NETWORK, SG_DEBUG, "No screenshot available.");
402     return false; // not ready yet, call again.
403   }
404
405   SG_LOG(SG_NETWORK, SG_DEBUG, "Screenshot is ready, size=" << screenshot.size());
406
407   if (screenshotRequest->isStream()) {
408     string s( BOUNDARY "\r\nContent-Type: image/");
409     s.append(screenshotRequest->getType()).append("\r\nContent-Length:");
410     s += boost::lexical_cast<string>(screenshot.size());
411     s += "\r\n\r\n";
412     connection->write(s.c_str(), s.length());
413   }
414
415   connection->write(screenshot.data(), screenshot.size());
416
417   if (screenshotRequest->isStream()) {
418     screenshotRequest->requestScreenshot();
419     // continue until user closes connection
420     return false;
421   }
422
423   // single screenshot, send terminating chunk
424   connection->remove(KEY);
425   connection->write("", 0);
426   return true; // done.
427 }
428
429 } // namespace http
430 } // namespace flightgear
431