25#include <osgDB/Registry>
26#include <osgDB/ReaderWriter>
27#include <osgUtil/SceneView>
28#include <osgViewer/Viewer>
31#include <simgear/canvas/Canvas.hxx>
33#include <simgear/threads/SGQueue.hxx>
34#include <simgear/structure/Singleton.hxx>
44namespace sc = simgear::canvas;
102 typedef SGBlockingQueue<ImageCompressionTask> TaskList;
110 osg::ref_ptr<osgDB::ReaderWriter::Options>
options =
new osgDB::ReaderWriter::Options(
"JPEG_QUALITY 80 PNG_COMPRESSION 9");
112 SG_LOG(SG_NETWORK, SG_DEBUG,
"ImageCompressor is running");
115 SG_LOG(SG_NETWORK, SG_DEBUG,
"ImageCompressor has an image");
117 SG_LOG(SG_NETWORK, SG_DEBUG,
"ImageCompressor checking for writer for " << task.
format);
118 osgDB::ReaderWriter* writer = osgDB::Registry::instance()->getReaderWriterForExtension(task.
format);
122 SG_LOG(SG_NETWORK, SG_DEBUG,
"ImageCompressor compressing to " << task.
format);
123 std::stringstream outputStream;
124 osgDB::ReaderWriter::WriteResult wr;
125 wr = writer->writeImage(*task.
image, outputStream,
options);
128 SG_LOG(SG_NETWORK, SG_DEBUG,
"ImageCompressor compressed to " << task.
format);
131 SG_LOG(SG_NETWORK, SG_DEBUG,
"ImageCompressor done for this image" << task.
format);
134 SG_LOG(SG_NETWORK, SG_DEBUG,
"ImageCompressor exiting");
149 : _min_delta_tick(1.0/8.0)
151 _previousFrameTick = osg::Timer::instance()->tick();
156 osg::Timer_t n = osg::Timer::instance()->tick();
157 double dt = osg::Timer::instance()->delta_s(_previousFrameTick,n);
158 if (dt < _min_delta_tick)
160 _previousFrameTick = n;
162 bool hasSubscribers =
false;
164 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
165 hasSubscribers = !_subscribers.empty();
168 if (hasSubscribers) {
169 osg::ref_ptr<osg::Image> image =
new osg::Image;
170 const osg::Viewport* vp = renderInfo.getState()->getCurrentViewport();
171 image->readPixels(vp->x(), vp->y(), vp->width(), vp->height(), GL_RGB, GL_UNSIGNED_BYTE);
173 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
174 while (!_subscribers.empty()) {
176 _subscribers.back()->imageReady(image);
180 _subscribers.pop_back();
189 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
190 _subscribers.push_back(subscriber);
195 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
196 _subscribers.remove( subscriber );
200 mutable list<ImageReadyListener*> _subscribers;
201 mutable OpenThreads::Mutex _lock;
202 mutable double _previousFrameTick;
203 double _min_delta_tick;
211 : _type(type), _stream(stream)
213 if ( NULL == osgDB::Registry::instance()->getReaderWriterForExtension(_type))
214 throw sg_format_exception(
"Unsupported image type: " + type, type);
218 throw sg_error(
"Can't find a camera for window '" + window +
"'");
221 if ( NULL == camera->getFinalDrawCallback()) {
226 _screenshotCallback =
dynamic_cast<ScreenshotCallback*
>(camera->getFinalDrawCallback());
227 if ( NULL == _screenshotCallback)
228 throw sg_error(
"Can't find ScreenshotCallback");
235 _screenshotCallback->unsubscribe(
this);
242 task.
image = rawImage;
245 ImageCompressorSingleton::instance()->addTask(task);
250 _screenshotCallback->subscribe(
this);
258 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(
_lock);
267 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(
_lock);
268 reply = _compressedData;
269 _compressedData.clear();
274 osg::Camera*
findLastCamera(osgViewer::ViewerBase * viewer,
const string & windowName)
276 osgViewer::ViewerBase::Windows windows;
277 viewer->getWindows(windows);
279 osgViewer::GraphicsWindow* window = NULL;
281 if (!windowName.empty()) {
282 for (osgViewer::ViewerBase::Windows::iterator itr = windows.begin(); itr != windows.end(); ++itr) {
283 if ((*itr)->getTraits()->windowName == windowName) {
290 if ( NULL == window) {
291 if (!windowName.empty()) {
292 SG_LOG(SG_NETWORK, SG_INFO,
"requested window " << windowName <<
" not found, using first window");
294 window = *windows.begin();
297 SG_LOG(SG_NETWORK, SG_DEBUG,
"Looking for last Camera of window '" << window->getTraits()->windowName <<
"'");
299 osg::GraphicsContext::Cameras& cameras = window->getCameras();
300 osg::Camera* lastCamera = 0;
301 for (osg::GraphicsContext::Cameras::iterator cam_itr = cameras.begin(); cam_itr != cameras.end(); ++cam_itr) {
303 if ((*cam_itr)->getRenderOrder() > lastCamera->getRenderOrder()) {
304 lastCamera = (*cam_itr);
306 if ((*cam_itr)->getRenderOrder() == lastCamera->getRenderOrder()
307 && (*cam_itr)->getRenderOrderNum() >= lastCamera->getRenderOrderNum()) {
308 lastCamera = (*cam_itr);
311 lastCamera = *cam_itr;
331 string _compressedData;
344 : _type(type), _stream(stream) {
345 SG_LOG(SG_NETWORK, SG_DEBUG,
"CanvasImageRequest:");
347 if (NULL == osgDB::Registry::instance()->getReaderWriterForExtension(_type))
348 throw sg_format_exception(
"Unsupported image type: " + type, type, {},
false );
352 SG_LOG(SG_NETWORK, SG_WARN,
"CanvasImage:CanvasMgr not found");
354 canvas = canvas_mgr->getCanvas(canvasindex);
356 throw sg_error(
"CanvasImage:Canvas not found for index " + std::to_string(canvasindex), {},
false );
358 SG_LOG(SG_NETWORK, SG_DEBUG,
"CanvasImage:Canvas found for index " << canvasindex);
361 SGConstPropertyNode_ptr canvasnode =
canvas->getProps();
363 string canvasname = canvasnode->getStringValue(
"name");
364 if (!canvasname.empty()) {
365 SG_LOG(SG_NETWORK, SG_INFO,
"CanvasImageRequest: node=" << canvasnode->getDisplayName().c_str() <<
", canvasname =" << canvasname);
378 SG_LOG(SG_NETWORK, SG_ALERT,
"CanvasImage: task running, pausing for 15 seconds");
379 SGTimeStamp::sleepFor(SGTimeStamp::fromSec(15));
383 canvas->unsubscribe(
this);
388 SG_LOG(SG_NETWORK, SG_DEBUG,
"CanvasImage:imageReady");
392 task.
image = rawImage;
395 ImageCompressorSingleton::instance()->addTask(task);
405 SG_LOG(SG_NETWORK, SG_DEBUG,
"CanvasImage:stringReady");
408 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(
_lock);
418 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(
_lock);
419 reply = _compressedData;
420 _compressedData.clear();
436 string _compressedData;
446 ImageCompressorSingleton::instance()->cancel();
452#define BOUNDARY "--fgfs-screenshot-boundary"
456 if (!ImageCompressorSingleton::instance()->isRunning())
457 ImageCompressorSingleton::instance()->start();
460 if (type.empty()) type =
"jpg";
467 int canvasindex = -1;
469 if (!s_canvasindex.empty()) canvasindex =
atoi(s_canvasindex.c_str());
471 SGSharedPtr<ScreenshotRequest> screenshotRequest;
472 SGSharedPtr<CanvasImageRequest> canvasimageRequest;
474 SG_LOG(SG_NETWORK, SG_DEBUG,
"new ScreenshotRequest("<<window<<
","<<type<<
"," << stream <<
"," << canvasindex <<
")");
475 if (canvasindex == -1)
480 catch (sg_format_exception & ex)
482 SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
483 response.
Header[
"Content-Type"] =
"text/plain";
485 response.
Content = ex.getFormattedMessage();
488 catch (sg_error & ex)
490 SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
491 response.
Header[
"Content-Type"] =
"text/plain";
493 response.
Content = ex.getFormattedMessage();
498 response.
Header[
"Content-Type"] = string(
"image/").append(type);
499 response.
Header[
"Content-Disposition"] = string(
"inline; filename=\"fgfs-screen.").append(type).append(
"\"");
501 response.
Header[
"Content-Type"] = string(
"multipart/x-mixed-replace; boundary=" BOUNDARY);
505 if (canvasindex == -1)
517 if ( NULL == screenshotRequest)
return true;
519 const string & screenshot = screenshotRequest->
getScreenshot();
520 if (screenshot.empty()) {
521 SG_LOG(SG_NETWORK, SG_DEBUG,
"No screenshot available.");
525 SG_LOG(SG_NETWORK, SG_DEBUG,
"Screenshot is ready, size=" << screenshot.size());
527 if (screenshotRequest->
isStream()) {
528 std::ostringstream ss;
529 ss <<
BOUNDARY <<
"\r\nContent-Type: image/";
530 ss << screenshotRequest->
getType() <<
"\r\nContent-Length:";
531 ss << screenshot.size() <<
"\r\n\r\n";
532 connection->
write(ss.str().c_str(), ss.str().length());
535 connection->
write(screenshot.data(), screenshot.size());
537 if (screenshotRequest->
isStream()) {
545 connection->
write(
"", 0);
552 if (NULL == canvasimageRequest)
return true;
555 SG_LOG(SG_NETWORK, SG_INFO,
"CanvasImageRequest: not connected. Resubscribing");
559 const string & canvasimage = canvasimageRequest->
getCanvasImage();
560 if (canvasimage.empty()) {
561 SG_LOG(SG_NETWORK, SG_INFO,
"No canvasimage available.");
565 SG_LOG(SG_NETWORK, SG_DEBUG,
"CanvasImage is ready, size=" << canvasimage.size());
567 if (canvasimageRequest->
isStream()) {
568 std::ostringstream ss;
569 ss <<
BOUNDARY <<
"\r\nContent-Type: image/";
570 ss << canvasimageRequest->
getType() <<
"\r\nContent-Length:";
571 ss << canvasimage.size() <<
"\r\n\r\n";
572 connection->
write(ss.str().c_str(), ss.str().length());
574 connection->
write(canvasimage.data(), canvasimage.size());
575 if (canvasimageRequest->
isStream()) {
583 connection->
write(
"", 0);
bool options(int, char **)
ImageCompressionTask * currenttask
void requestCanvasImage()
const string & getType() const
virtual void stringReady(const string &s)
virtual ~CanvasImageRequest()
virtual void imageReady(osg::ref_ptr< osg::Image > rawImage)
CanvasImageRequest(const string &window, const string &type, int canvasindex, bool stream)
SGSharedPtr< ConnectionData > get(const std::string &key)
virtual void write(const char *data, size_t len)=0
void put(const std::string &key, SGSharedPtr< ConnectionData > value)
void remove(const std::string &key)
std::string get(const std::string &key) const
StringMap RequestVariables
void addTask(ImageCompressionTask &task)
virtual ~ImageReadyListener()
virtual void imageReady(osg::ref_ptr< osg::Image >)=0
Based on osgworks ScreenCapture.cpp.
virtual void operator()(osg::RenderInfo &renderInfo) const
void subscribe(ImageReadyListener *subscriber)
void unsubscribe(ImageReadyListener *subscriber)
ScreenshotRequest(const string &window, const string &type, bool stream)
virtual ~ScreenshotRequest()
const string & getType() const
virtual void stringReady(const string &s)
osg::Camera * findLastCamera(osgViewer::ViewerBase *viewer, const string &windowName)
virtual void imageReady(osg::ref_ptr< osg::Image > rawImage)
ScreenshotUriHandler(const std::string &uri="/screenshot/")
virtual bool poll(Connection *connection)
This method gets called from the httpd if the preceding handleRequest() or poll() method returned fal...
virtual bool handleGetRequest(const HTTPRequest &request, HTTPResponse &response, Connection *connection)
Convenience method for GET Requests, gets called by handleRequest if not overridden.
virtual ~StringReadyListener()
virtual void stringReady(const std::string &)=0
URIHandler(const std::string &uri)
static const string KEY_SCREENSHOT("ScreenshotUriHandler::ScreenshotRequest")
static const string KEY_CANVASIMAGE("ScreenshotUriHandler::CanvasImageRequest")
simgear::Singleton< ImageCompressor > ImageCompressorSingleton
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
static int atoi(const string &str)
ImageCompressionTask & operator=(const ImageCompressionTask &other)
ImageCompressionTask(const ImageCompressionTask &other)
StringReadyListener * stringReadyListener
osg::ref_ptr< osg::Image > image