<commit_before>/*=========================================================================

  Library:   CTK

  Copyright (c) Kitware Inc.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0.txt

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

=========================================================================*/

// Qt includes
#include <QTimer>
#include <QVBoxLayout>
#include <QDebug>

// CTK includes
#include "ctkVTKAbstractView.h"
#include "ctkVTKAbstractView_p.h"
#include "ctkLogger.h"

// VTK includes
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkOpenGLRenderWindow.h>
#include <vtkRendererCollection.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkTextProperty.h>

//--------------------------------------------------------------------------
static ctkLogger logger("org.commontk.visualization.vtk.widgets.ctkVTKAbstractView");
//--------------------------------------------------------------------------
int ctkVTKAbstractViewPrivate::MultiSamples = 0;  // Default for static var
//--------------------------------------------------------------------------

// --------------------------------------------------------------------------
// ctkVTKAbstractViewPrivate methods

// --------------------------------------------------------------------------
ctkVTKAbstractViewPrivate::ctkVTKAbstractViewPrivate(ctkVTKAbstractView& object)
  : q_ptr(&object)
{
#if CTK_USE_QVTKOPENGLWIDGET
  this->RenderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
#else
  this->RenderWindow = vtkSmartPointer<vtkRenderWindow>::New();
#endif
  this->CornerAnnotation = vtkSmartPointer<vtkCornerAnnotation>::New();
  this->RequestTimer = 0;
  this->RenderEnabled = true;
  this->FPSVisible = false;
  this->FPSTimer = 0;
  this->FPS = 0;
}

// --------------------------------------------------------------------------
void ctkVTKAbstractViewPrivate::init()
{
  Q_Q(ctkVTKAbstractView);

  this->setParent(q);

#if CTK_USE_QVTKOPENGLWIDGET
  this->VTKWidget = new QVTKOpenGLWidget;
  this->VTKWidget->setEnableHiDPI(true);
  QObject::connect(this->VTKWidget, SIGNAL(resized()),
                   q, SLOT(forceRender()));
#else
  this->VTKWidget = new QVTKWidget;
#endif
  q->setLayout(new QVBoxLayout);
  q->layout()->setMargin(0);
  q->layout()->setSpacing(0);
  q->layout()->addWidget(this->VTKWidget);

  this->RequestTimer = new QTimer(q);
  this->RequestTimer->setSingleShot(true);
  QObject::connect(this->RequestTimer, SIGNAL(timeout()),
                   q, SLOT(forceRender()));

  this->FPSTimer = new QTimer(q);
  this->FPSTimer->setInterval(1000);
  QObject::connect(this->FPSTimer, SIGNAL(timeout()),
                   q, SLOT(updateFPS()));

  this->setupCornerAnnotation();
  this->setupRendering();

  // block renders and observe interactor to enforce framerate
  q->setInteractor(this->RenderWindow->GetInteractor());
}

// --------------------------------------------------------------------------
void ctkVTKAbstractViewPrivate::setupCornerAnnotation()
{
  this->CornerAnnotation->SetMaximumLineHeight(0.07);
  vtkTextProperty *tprop = this->CornerAnnotation->GetTextProperty();
  tprop->ShadowOn();
  this->CornerAnnotation->ClearAllTexts();
}

//---------------------------------------------------------------------------
void ctkVTKAbstractViewPrivate::setupRendering()
{
  Q_ASSERT(this->RenderWindow);
  this->RenderWindow->SetAlphaBitPlanes(1);
  int nSamples = ctkVTKAbstractView::multiSamples();
  if (nSamples < 0)
    {
    nSamples = vtkOpenGLRenderWindow::GetGlobalMaximumNumberOfMultiSamples();
    }
  this->RenderWindow->SetMultiSamples(nSamples);
  this->RenderWindow->StereoCapableWindowOn();
  this->VTKWidget->SetRenderWindow(this->RenderWindow);
}

//---------------------------------------------------------------------------
QList<vtkRenderer*> ctkVTKAbstractViewPrivate::renderers()const
{
  QList<vtkRenderer*> rendererList;

  vtkRendererCollection* rendererCollection = this->RenderWindow->GetRenderers();
  vtkCollectionSimpleIterator rendererIterator;
  rendererCollection->InitTraversal(rendererIterator);
  vtkRenderer *renderer;
  while ( (renderer= rendererCollection->GetNextRenderer(rendererIterator)) )
    {
    rendererList << renderer;
    }
  return rendererList;
}

//---------------------------------------------------------------------------
vtkRenderer* ctkVTKAbstractViewPrivate::firstRenderer()const
{
  return static_cast<vtkRenderer*>(this->RenderWindow->GetRenderers()
    ->GetItemAsObject(0));
}

//---------------------------------------------------------------------------
// ctkVTKAbstractView methods

// --------------------------------------------------------------------------
ctkVTKAbstractView::ctkVTKAbstractView(QWidget* parentWidget)
  : Superclass(parentWidget)
  , d_ptr(new ctkVTKAbstractViewPrivate(*this))
{
  Q_D(ctkVTKAbstractView);
  d->init();
}

// --------------------------------------------------------------------------
ctkVTKAbstractView::ctkVTKAbstractView(ctkVTKAbstractViewPrivate* pimpl, QWidget* parentWidget)
  : Superclass(parentWidget)
  , d_ptr(pimpl)
{
  // derived classes must call init manually. Calling init() here may results in
  // actions on a derived public class not yet finished to be created
}

//----------------------------------------------------------------------------
ctkVTKAbstractView::~ctkVTKAbstractView()
{
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::scheduleRender()
{
  Q_D(ctkVTKAbstractView);

  //logger.trace(QString("scheduleRender - RenderEnabled: %1 - Request render elapsed: %2ms").
  //             arg(d->RenderEnabled ? "true" : "false")
  //             .arg(d->RequestTime.elapsed()));

  if (!d->RenderEnabled)
    {
    return;
    }

  double msecsBeforeRender = 100. / d->RenderWindow->GetDesiredUpdateRate();
  if(d->VTKWidget->testAttribute(Qt::WA_WState_InPaintEvent))
    {
    // If the request comes from the system (widget exposed, resized...), the
    // render must be done immediately.
    this->forceRender();
    }
  else if (!d->RequestTime.isValid())
    {
    // If the DesiredUpdateRate is in "still mode", the requested framerate
    // is fake, it is just a way to allocate as much time as possible for the
    // rendering, it doesn't really mean that rendering must occur only once
    // every couple seconds. It just means it should be done when there is
    // time to do it. A timer of 0, kind of mean a rendering is done next time
    // it is idle.
    if (msecsBeforeRender > 10000)
      {
      msecsBeforeRender = 0;
      }
    d->RequestTime.start();
    d->RequestTimer->start(static_cast<int>(msecsBeforeRender));
    }
  else if (d->RequestTime.elapsed() > msecsBeforeRender)
    {
    // The rendering hasn't still be done, but msecsBeforeRender milliseconds
    // have already been elapsed, it is likely that RequestTimer has already
    // timed out, but the event queue hasn't been processed yet, rendering is
    // done now to ensure the desired framerate is respected.
    this->forceRender();
    }
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::forceRender()
{
  Q_D(ctkVTKAbstractView);

  if (this->sender() == d->RequestTimer  &&
      !d->RequestTime.isValid())
    {
    // The slot associated to the timeout signal is now called, however the
    // render has already been executed meanwhile. There is no need to do it
    // again.
    return;
    }

  // The timer can be stopped if it hasn't timed out yet.
  d->RequestTimer->stop();
  d->RequestTime = QTime();

  //logger.trace(QString("forceRender - RenderEnabled: %1")
  //             .arg(d->RenderEnabled ? "true" : "false"));

  if (!d->RenderEnabled || !this->isVisible())
    {
    return;
    }
  d->RenderWindow->Render();
}

//----------------------------------------------------------------------------
CTK_GET_CPP(ctkVTKAbstractView, vtkRenderWindow*, renderWindow, RenderWindow);

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setInteractor(vtkRenderWindowInteractor* newInteractor)
{
  Q_D(ctkVTKAbstractView);

  d->RenderWindow->SetInteractor(newInteractor);
  // Prevent the interactor to call Render() on the render window; only
  // scheduleRender() and forceRender() can Render() the window.
  // This is done to ensure the desired framerate is respected.
  newInteractor->SetEnableRender(false);
  qvtkReconnect(d->RenderWindow->GetInteractor(), newInteractor,
                vtkCommand::RenderEvent, this, SLOT(scheduleRender()));
}

//----------------------------------------------------------------------------
vtkRenderWindowInteractor* ctkVTKAbstractView::interactor()const
{
  Q_D(const ctkVTKAbstractView);
  return d->RenderWindow->GetInteractor();
}

//----------------------------------------------------------------------------
vtkInteractorObserver* ctkVTKAbstractView::interactorStyle()const
{
  return this->interactor() ?
    this->interactor()->GetInteractorStyle() : 0;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setCornerAnnotationText(const QString& text)
{
  Q_D(ctkVTKAbstractView);
  d->CornerAnnotation->ClearAllTexts();
  d->CornerAnnotation->SetText(2, text.toLatin1());
}

//----------------------------------------------------------------------------
QString ctkVTKAbstractView::cornerAnnotationText() const
{
  Q_D(const ctkVTKAbstractView);
  return QLatin1String(d->CornerAnnotation->GetText(2));
}

//----------------------------------------------------------------------------
vtkCornerAnnotation* ctkVTKAbstractView::cornerAnnotation() const
{
  Q_D(const ctkVTKAbstractView);
  return d->CornerAnnotation;
}

//----------------------------------------------------------------------------
#if CTK_USE_QVTKOPENGLWIDGET
QVTKOpenGLWidget * ctkVTKAbstractView::VTKWidget() const
#else
QVTKWidget * ctkVTKAbstractView::VTKWidget() const
#endif
{
  Q_D(const ctkVTKAbstractView);
  return d->VTKWidget;
}

//----------------------------------------------------------------------------
CTK_SET_CPP(ctkVTKAbstractView, bool, setRenderEnabled, RenderEnabled);
CTK_GET_CPP(ctkVTKAbstractView, bool, renderEnabled, RenderEnabled);

//----------------------------------------------------------------------------
QSize ctkVTKAbstractView::minimumSizeHint()const
{
  // Arbitrary size. 50x50 because smaller seems too small.
  return QSize(50, 50);
}

//----------------------------------------------------------------------------
QSize ctkVTKAbstractView::sizeHint()const
{
  // Arbitrary size. 300x300 is the default vtkRenderWindow size.
  return QSize(300, 300);
}

//----------------------------------------------------------------------------
bool ctkVTKAbstractView::hasHeightForWidth()const
{
  return true;
}

//----------------------------------------------------------------------------
int ctkVTKAbstractView::heightForWidth(int width)const
{
  // typically VTK render window tend to be square...
  return width;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setBackgroundColor(const QColor& newBackgroundColor)
{
  Q_D(ctkVTKAbstractView);
  double color[3];
  color[0] = newBackgroundColor.redF();
  color[1] = newBackgroundColor.greenF();
  color[2] = newBackgroundColor.blueF();
  foreach(vtkRenderer* renderer, d->renderers())
    {
    renderer->SetBackground(color);
    }
}

//----------------------------------------------------------------------------
QColor ctkVTKAbstractView::backgroundColor()const
{
  Q_D(const ctkVTKAbstractView);
  vtkRenderer* firstRenderer = d->firstRenderer();
  return firstRenderer ? QColor::fromRgbF(firstRenderer->GetBackground()[0],
                                          firstRenderer->GetBackground()[1],
                                          firstRenderer->GetBackground()[2])
                       : QColor();
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setBackgroundColor2(const QColor& newBackgroundColor)
{
  Q_D(ctkVTKAbstractView);
  double color[3];
  color[0] = newBackgroundColor.redF();
  color[1] = newBackgroundColor.greenF();
  color[2] = newBackgroundColor.blueF();
  foreach(vtkRenderer* renderer, d->renderers())
    {
    renderer->SetBackground2(color);
    }
}

//----------------------------------------------------------------------------
QColor ctkVTKAbstractView::backgroundColor2()const
{
  Q_D(const ctkVTKAbstractView);
  vtkRenderer* firstRenderer = d->firstRenderer();
  return firstRenderer ? QColor::fromRgbF(firstRenderer->GetBackground2()[0],
                                          firstRenderer->GetBackground2()[1],
                                          firstRenderer->GetBackground2()[2])
                       : QColor();
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setGradientBackground(bool enable)
{
  Q_D(ctkVTKAbstractView);
  foreach(vtkRenderer* renderer, d->renderers())
    {
    renderer->SetGradientBackground(enable);
    }
}

//----------------------------------------------------------------------------
bool ctkVTKAbstractView::gradientBackground()const
{
  Q_D(const ctkVTKAbstractView);
  vtkRenderer* firstRenderer = d->firstRenderer();
  return firstRenderer ? firstRenderer->GetGradientBackground() : false;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setFPSVisible(bool show)
{
  Q_D(ctkVTKAbstractView);
  if (d->FPSVisible == show)
    {
    return;
    }
  d->FPSVisible = show;
  vtkRenderer* renderer = d->firstRenderer();
  if (d->FPSVisible)
    {
    d->FPSTimer->start();
    qvtkConnect(renderer,
                vtkCommand::EndEvent, this, SLOT(onRender()));
    }
  else
    {
    d->FPSTimer->stop();
    qvtkDisconnect(renderer,
                   vtkCommand::EndEvent, this, SLOT(onRender()));
    d->CornerAnnotation->SetText(1, "");
    }
}

//----------------------------------------------------------------------------
bool ctkVTKAbstractView::isFPSVisible()const
{
  Q_D(const ctkVTKAbstractView);
  return d->FPSVisible;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::onRender()
{
  Q_D(ctkVTKAbstractView);
  ++d->FPS;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::updateFPS()
{
  Q_D(ctkVTKAbstractView);
  vtkRenderer* renderer = d->firstRenderer();
  double lastRenderTime = renderer ? renderer->GetLastRenderTimeInSeconds() : 0.;
  QString fpsString = tr("FPS: %1(%2s)").arg(d->FPS).arg(lastRenderTime);
  d->FPS = 0;
  d->CornerAnnotation->SetText(1, fpsString.toLatin1());
}

//----------------------------------------------------------------------------
bool ctkVTKAbstractView::useDepthPeeling()const
{
  Q_D(const ctkVTKAbstractView);
  vtkRenderer* renderer = d->firstRenderer();
  return renderer ? static_cast<bool>(renderer->GetUseDepthPeeling()):0;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setUseDepthPeeling(bool useDepthPeeling)
{
  Q_D(ctkVTKAbstractView);
  vtkRenderer* renderer = d->firstRenderer();
  if (!renderer)
    {
    return;
    }
  this->renderWindow()->SetAlphaBitPlanes( useDepthPeeling ? 1 : 0);
  int nSamples = ctkVTKAbstractView::multiSamples();
  if (nSamples < 0)
    {
    nSamples = vtkOpenGLRenderWindow::GetGlobalMaximumNumberOfMultiSamples();
    }
  this->renderWindow()->SetMultiSamples(useDepthPeeling ? 0 : nSamples);
  renderer->SetUseDepthPeeling(useDepthPeeling ? 1 : 0);
#if CTK_USE_QVTKOPENGLWIDGET
  renderer->SetUseDepthPeelingForVolumes(useDepthPeeling);
#endif
}

//----------------------------------------------------------------------------
int ctkVTKAbstractView::multiSamples()
{
  return ctkVTKAbstractViewPrivate::MultiSamples;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setMultiSamples(int number)
{
  ctkVTKAbstractViewPrivate::MultiSamples = number;
}
<commit_msg>BUG: Fixed refresh rate computation in ctkVTKAbstractView<commit_after>/*=========================================================================

  Library:   CTK

  Copyright (c) Kitware Inc.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0.txt

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

=========================================================================*/

// Qt includes
#include <QTimer>
#include <QVBoxLayout>
#include <QDebug>

// CTK includes
#include "ctkVTKAbstractView.h"
#include "ctkVTKAbstractView_p.h"
#include "ctkLogger.h"

// VTK includes
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkOpenGLRenderWindow.h>
#include <vtkRendererCollection.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkTextProperty.h>

//--------------------------------------------------------------------------
static ctkLogger logger("org.commontk.visualization.vtk.widgets.ctkVTKAbstractView");
//--------------------------------------------------------------------------
int ctkVTKAbstractViewPrivate::MultiSamples = 0;  // Default for static var
//--------------------------------------------------------------------------

// --------------------------------------------------------------------------
// ctkVTKAbstractViewPrivate methods

// --------------------------------------------------------------------------
ctkVTKAbstractViewPrivate::ctkVTKAbstractViewPrivate(ctkVTKAbstractView& object)
  : q_ptr(&object)
{
#if CTK_USE_QVTKOPENGLWIDGET
  this->RenderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
#else
  this->RenderWindow = vtkSmartPointer<vtkRenderWindow>::New();
#endif
  this->CornerAnnotation = vtkSmartPointer<vtkCornerAnnotation>::New();
  this->RequestTimer = 0;
  this->RenderEnabled = true;
  this->FPSVisible = false;
  this->FPSTimer = 0;
  this->FPS = 0;
}

// --------------------------------------------------------------------------
void ctkVTKAbstractViewPrivate::init()
{
  Q_Q(ctkVTKAbstractView);

  this->setParent(q);

#if CTK_USE_QVTKOPENGLWIDGET
  this->VTKWidget = new QVTKOpenGLWidget;
  this->VTKWidget->setEnableHiDPI(true);
  QObject::connect(this->VTKWidget, SIGNAL(resized()),
                   q, SLOT(forceRender()));
#else
  this->VTKWidget = new QVTKWidget;
#endif
  q->setLayout(new QVBoxLayout);
  q->layout()->setMargin(0);
  q->layout()->setSpacing(0);
  q->layout()->addWidget(this->VTKWidget);

  this->RequestTimer = new QTimer(q);
  this->RequestTimer->setSingleShot(true);
  QObject::connect(this->RequestTimer, SIGNAL(timeout()),
                   q, SLOT(forceRender()));

  this->FPSTimer = new QTimer(q);
  this->FPSTimer->setInterval(1000);
  QObject::connect(this->FPSTimer, SIGNAL(timeout()),
                   q, SLOT(updateFPS()));

  this->setupCornerAnnotation();
  this->setupRendering();

  // block renders and observe interactor to enforce framerate
  q->setInteractor(this->RenderWindow->GetInteractor());
}

// --------------------------------------------------------------------------
void ctkVTKAbstractViewPrivate::setupCornerAnnotation()
{
  this->CornerAnnotation->SetMaximumLineHeight(0.07);
  vtkTextProperty *tprop = this->CornerAnnotation->GetTextProperty();
  tprop->ShadowOn();
  this->CornerAnnotation->ClearAllTexts();
}

//---------------------------------------------------------------------------
void ctkVTKAbstractViewPrivate::setupRendering()
{
  Q_ASSERT(this->RenderWindow);
  this->RenderWindow->SetAlphaBitPlanes(1);
  int nSamples = ctkVTKAbstractView::multiSamples();
  if (nSamples < 0)
    {
    nSamples = vtkOpenGLRenderWindow::GetGlobalMaximumNumberOfMultiSamples();
    }
  this->RenderWindow->SetMultiSamples(nSamples);
  this->RenderWindow->StereoCapableWindowOn();
  this->VTKWidget->SetRenderWindow(this->RenderWindow);
}

//---------------------------------------------------------------------------
QList<vtkRenderer*> ctkVTKAbstractViewPrivate::renderers()const
{
  QList<vtkRenderer*> rendererList;

  vtkRendererCollection* rendererCollection = this->RenderWindow->GetRenderers();
  vtkCollectionSimpleIterator rendererIterator;
  rendererCollection->InitTraversal(rendererIterator);
  vtkRenderer *renderer;
  while ( (renderer= rendererCollection->GetNextRenderer(rendererIterator)) )
    {
    rendererList << renderer;
    }
  return rendererList;
}

//---------------------------------------------------------------------------
vtkRenderer* ctkVTKAbstractViewPrivate::firstRenderer()const
{
  return static_cast<vtkRenderer*>(this->RenderWindow->GetRenderers()
    ->GetItemAsObject(0));
}

//---------------------------------------------------------------------------
// ctkVTKAbstractView methods

// --------------------------------------------------------------------------
ctkVTKAbstractView::ctkVTKAbstractView(QWidget* parentWidget)
  : Superclass(parentWidget)
  , d_ptr(new ctkVTKAbstractViewPrivate(*this))
{
  Q_D(ctkVTKAbstractView);
  d->init();
}

// --------------------------------------------------------------------------
ctkVTKAbstractView::ctkVTKAbstractView(ctkVTKAbstractViewPrivate* pimpl, QWidget* parentWidget)
  : Superclass(parentWidget)
  , d_ptr(pimpl)
{
  // derived classes must call init manually. Calling init() here may results in
  // actions on a derived public class not yet finished to be created
}

//----------------------------------------------------------------------------
ctkVTKAbstractView::~ctkVTKAbstractView()
{
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::scheduleRender()
{
  Q_D(ctkVTKAbstractView);

  //logger.trace(QString("scheduleRender - RenderEnabled: %1 - Request render elapsed: %2ms").
  //             arg(d->RenderEnabled ? "true" : "false")
  //             .arg(d->RequestTime.elapsed()));

  if (!d->RenderEnabled)
    {
    return;
    }

  double msecsBeforeRender = 1000. / d->RenderWindow->GetDesiredUpdateRate();
  if(d->VTKWidget->testAttribute(Qt::WA_WState_InPaintEvent))
    {
    // If the request comes from the system (widget exposed, resized...), the
    // render must be done immediately.
    this->forceRender();
    }
  else if (!d->RequestTime.isValid())
    {
    // If the DesiredUpdateRate is in "still mode", the requested framerate
    // is fake, it is just a way to allocate as much time as possible for the
    // rendering, it doesn't really mean that rendering must occur only once
    // every couple seconds. It just means it should be done when there is
    // time to do it. A timer of 0, kind of mean a rendering is done next time
    // it is idle.
    if (msecsBeforeRender > 10000)
      {
      msecsBeforeRender = 0;
      }
    d->RequestTime.start();
    d->RequestTimer->start(static_cast<int>(msecsBeforeRender));
    }
  else if (d->RequestTime.elapsed() > msecsBeforeRender)
    {
    // The rendering hasn't still be done, but msecsBeforeRender milliseconds
    // have already been elapsed, it is likely that RequestTimer has already
    // timed out, but the event queue hasn't been processed yet, rendering is
    // done now to ensure the desired framerate is respected.
    this->forceRender();
    }
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::forceRender()
{
  Q_D(ctkVTKAbstractView);

  if (this->sender() == d->RequestTimer  &&
      !d->RequestTime.isValid())
    {
    // The slot associated to the timeout signal is now called, however the
    // render has already been executed meanwhile. There is no need to do it
    // again.
    return;
    }

  // The timer can be stopped if it hasn't timed out yet.
  d->RequestTimer->stop();
  d->RequestTime = QTime();

  //logger.trace(QString("forceRender - RenderEnabled: %1")
  //             .arg(d->RenderEnabled ? "true" : "false"));

  if (!d->RenderEnabled || !this->isVisible())
    {
    return;
    }
  d->RenderWindow->Render();
}

//----------------------------------------------------------------------------
CTK_GET_CPP(ctkVTKAbstractView, vtkRenderWindow*, renderWindow, RenderWindow);

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setInteractor(vtkRenderWindowInteractor* newInteractor)
{
  Q_D(ctkVTKAbstractView);

  d->RenderWindow->SetInteractor(newInteractor);
  // Prevent the interactor to call Render() on the render window; only
  // scheduleRender() and forceRender() can Render() the window.
  // This is done to ensure the desired framerate is respected.
  newInteractor->SetEnableRender(false);
  qvtkReconnect(d->RenderWindow->GetInteractor(), newInteractor,
                vtkCommand::RenderEvent, this, SLOT(scheduleRender()));
}

//----------------------------------------------------------------------------
vtkRenderWindowInteractor* ctkVTKAbstractView::interactor()const
{
  Q_D(const ctkVTKAbstractView);
  return d->RenderWindow->GetInteractor();
}

//----------------------------------------------------------------------------
vtkInteractorObserver* ctkVTKAbstractView::interactorStyle()const
{
  return this->interactor() ?
    this->interactor()->GetInteractorStyle() : 0;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setCornerAnnotationText(const QString& text)
{
  Q_D(ctkVTKAbstractView);
  d->CornerAnnotation->ClearAllTexts();
  d->CornerAnnotation->SetText(2, text.toLatin1());
}

//----------------------------------------------------------------------------
QString ctkVTKAbstractView::cornerAnnotationText() const
{
  Q_D(const ctkVTKAbstractView);
  return QLatin1String(d->CornerAnnotation->GetText(2));
}

//----------------------------------------------------------------------------
vtkCornerAnnotation* ctkVTKAbstractView::cornerAnnotation() const
{
  Q_D(const ctkVTKAbstractView);
  return d->CornerAnnotation;
}

//----------------------------------------------------------------------------
#if CTK_USE_QVTKOPENGLWIDGET
QVTKOpenGLWidget * ctkVTKAbstractView::VTKWidget() const
#else
QVTKWidget * ctkVTKAbstractView::VTKWidget() const
#endif
{
  Q_D(const ctkVTKAbstractView);
  return d->VTKWidget;
}

//----------------------------------------------------------------------------
CTK_SET_CPP(ctkVTKAbstractView, bool, setRenderEnabled, RenderEnabled);
CTK_GET_CPP(ctkVTKAbstractView, bool, renderEnabled, RenderEnabled);

//----------------------------------------------------------------------------
QSize ctkVTKAbstractView::minimumSizeHint()const
{
  // Arbitrary size. 50x50 because smaller seems too small.
  return QSize(50, 50);
}

//----------------------------------------------------------------------------
QSize ctkVTKAbstractView::sizeHint()const
{
  // Arbitrary size. 300x300 is the default vtkRenderWindow size.
  return QSize(300, 300);
}

//----------------------------------------------------------------------------
bool ctkVTKAbstractView::hasHeightForWidth()const
{
  return true;
}

//----------------------------------------------------------------------------
int ctkVTKAbstractView::heightForWidth(int width)const
{
  // typically VTK render window tend to be square...
  return width;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setBackgroundColor(const QColor& newBackgroundColor)
{
  Q_D(ctkVTKAbstractView);
  double color[3];
  color[0] = newBackgroundColor.redF();
  color[1] = newBackgroundColor.greenF();
  color[2] = newBackgroundColor.blueF();
  foreach(vtkRenderer* renderer, d->renderers())
    {
    renderer->SetBackground(color);
    }
}

//----------------------------------------------------------------------------
QColor ctkVTKAbstractView::backgroundColor()const
{
  Q_D(const ctkVTKAbstractView);
  vtkRenderer* firstRenderer = d->firstRenderer();
  return firstRenderer ? QColor::fromRgbF(firstRenderer->GetBackground()[0],
                                          firstRenderer->GetBackground()[1],
                                          firstRenderer->GetBackground()[2])
                       : QColor();
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setBackgroundColor2(const QColor& newBackgroundColor)
{
  Q_D(ctkVTKAbstractView);
  double color[3];
  color[0] = newBackgroundColor.redF();
  color[1] = newBackgroundColor.greenF();
  color[2] = newBackgroundColor.blueF();
  foreach(vtkRenderer* renderer, d->renderers())
    {
    renderer->SetBackground2(color);
    }
}

//----------------------------------------------------------------------------
QColor ctkVTKAbstractView::backgroundColor2()const
{
  Q_D(const ctkVTKAbstractView);
  vtkRenderer* firstRenderer = d->firstRenderer();
  return firstRenderer ? QColor::fromRgbF(firstRenderer->GetBackground2()[0],
                                          firstRenderer->GetBackground2()[1],
                                          firstRenderer->GetBackground2()[2])
                       : QColor();
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setGradientBackground(bool enable)
{
  Q_D(ctkVTKAbstractView);
  foreach(vtkRenderer* renderer, d->renderers())
    {
    renderer->SetGradientBackground(enable);
    }
}

//----------------------------------------------------------------------------
bool ctkVTKAbstractView::gradientBackground()const
{
  Q_D(const ctkVTKAbstractView);
  vtkRenderer* firstRenderer = d->firstRenderer();
  return firstRenderer ? firstRenderer->GetGradientBackground() : false;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setFPSVisible(bool show)
{
  Q_D(ctkVTKAbstractView);
  if (d->FPSVisible == show)
    {
    return;
    }
  d->FPSVisible = show;
  vtkRenderer* renderer = d->firstRenderer();
  if (d->FPSVisible)
    {
    d->FPSTimer->start();
    qvtkConnect(renderer,
                vtkCommand::EndEvent, this, SLOT(onRender()));
    }
  else
    {
    d->FPSTimer->stop();
    qvtkDisconnect(renderer,
                   vtkCommand::EndEvent, this, SLOT(onRender()));
    d->CornerAnnotation->SetText(1, "");
    }
}

//----------------------------------------------------------------------------
bool ctkVTKAbstractView::isFPSVisible()const
{
  Q_D(const ctkVTKAbstractView);
  return d->FPSVisible;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::onRender()
{
  Q_D(ctkVTKAbstractView);
  ++d->FPS;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::updateFPS()
{
  Q_D(ctkVTKAbstractView);
  vtkRenderer* renderer = d->firstRenderer();
  double lastRenderTime = renderer ? renderer->GetLastRenderTimeInSeconds() : 0.;
  QString fpsString = tr("FPS: %1(%2s)").arg(d->FPS).arg(lastRenderTime);
  d->FPS = 0;
  d->CornerAnnotation->SetText(1, fpsString.toLatin1());
}

//----------------------------------------------------------------------------
bool ctkVTKAbstractView::useDepthPeeling()const
{
  Q_D(const ctkVTKAbstractView);
  vtkRenderer* renderer = d->firstRenderer();
  return renderer ? static_cast<bool>(renderer->GetUseDepthPeeling()):0;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setUseDepthPeeling(bool useDepthPeeling)
{
  Q_D(ctkVTKAbstractView);
  vtkRenderer* renderer = d->firstRenderer();
  if (!renderer)
    {
    return;
    }
  this->renderWindow()->SetAlphaBitPlanes( useDepthPeeling ? 1 : 0);
  int nSamples = ctkVTKAbstractView::multiSamples();
  if (nSamples < 0)
    {
    nSamples = vtkOpenGLRenderWindow::GetGlobalMaximumNumberOfMultiSamples();
    }
  this->renderWindow()->SetMultiSamples(useDepthPeeling ? 0 : nSamples);
  renderer->SetUseDepthPeeling(useDepthPeeling ? 1 : 0);
#if CTK_USE_QVTKOPENGLWIDGET
  renderer->SetUseDepthPeelingForVolumes(useDepthPeeling);
#endif
}

//----------------------------------------------------------------------------
int ctkVTKAbstractView::multiSamples()
{
  return ctkVTKAbstractViewPrivate::MultiSamples;
}

//----------------------------------------------------------------------------
void ctkVTKAbstractView::setMultiSamples(int number)
{
  ctkVTKAbstractViewPrivate::MultiSamples = number;
}
<|endoftext|>


<commit_before>#include "common.hpp"
#include "catch.hpp"
#include "RTC/RTCP/XrDelaySinceLastRr.hpp"
#include "RTC/RTCP/XrReceiverReferenceTime.hpp"
#include <iostream>

using namespace RTC::RTCP;

SCENARIO("RTCP XrDelaySinceLastRt parsing", "[parser][rtcp][xr-dlrr]")
{
	SECTION("create RRT")
	{
		// Create local report and check content.
		auto* report1 = new ReceiverReferenceTime();

		report1->SetNtpSec(11111111);
		report1->SetNtpFrac(22222222);

		REQUIRE(report1->GetType() == ExtendedReportBlock::Type::RRT);
		REQUIRE(report1->GetNtpSec() == 11111111);
		REQUIRE(report1->GetNtpFrac() == 22222222);

		// Serialize the report into an external buffer.
		uint8_t bufferReport1[256]{ 0 };

		report1->Serialize(bufferReport1);

		// Create a new report out of the external buffer.
		auto report2 = ReceiverReferenceTime::Parse(bufferReport1, report1->GetSize());

		REQUIRE(report1->GetType() == report2->GetType());
		REQUIRE(report1->GetNtpSec() == report2->GetNtpSec());
		REQUIRE(report1->GetNtpFrac() == report2->GetNtpFrac());

		// Create a local packet.
		std::unique_ptr<ExtendedReportPacket> packet1(new ExtendedReportPacket());

		packet1->SetSsrc(2222);
		packet1->AddReport(report1);
		packet1->AddReport(report2);

		REQUIRE(packet1->GetType() == Type::XR);
		REQUIRE(packet1->GetCount() == 0);
		REQUIRE(packet1->GetSsrc() == 2222);

		// Total size:
		// -  RTCP common header
		// -  SSRC
		// -  block 1
		// -  block 2
		REQUIRE(packet1->GetSize() == 4 + 4 + 12 + 12);

		// Serialize the packet into an external buffer.
		uint8_t bufferPacket1[256]{ 0 };
		uint8_t bufferPacket2[256]{ 0 };

		packet1->Serialize(bufferPacket1);

		// Create a new packet out of the external buffer.
		auto packet2 = ExtendedReportPacket::Parse(bufferPacket1, packet1->GetSize());

		REQUIRE(packet2->GetType() == packet1->GetType());
		REQUIRE(packet2->GetCount() == packet1->GetCount());
		REQUIRE(packet2->GetSsrc() == packet1->GetSsrc());
		REQUIRE(packet2->GetSize() == packet1->GetSize());

		packet2->Serialize(bufferPacket2);

		REQUIRE(std::memcmp(bufferPacket1, bufferPacket2, packet1->GetSize()) == 0);
	}

	SECTION("create DLRR")
	{
		// Create local report and check content.
		auto* report1   = new DelaySinceLastRr();
		auto* ssrcInfo1 = new DelaySinceLastRr::SsrcInfo();

		ssrcInfo1->SetSsrc(1234);
		ssrcInfo1->SetLastReceiverReport(11111111);
		ssrcInfo1->SetDelaySinceLastReceiverReport(22222222);

		REQUIRE(ssrcInfo1->GetSsrc() == 1234);
		REQUIRE(ssrcInfo1->GetLastReceiverReport() == 11111111);
		REQUIRE(ssrcInfo1->GetDelaySinceLastReceiverReport() == 22222222);
		REQUIRE(ssrcInfo1->GetSize() == sizeof(DelaySinceLastRr::SsrcInfo::Body));

		report1->AddSsrcInfo(ssrcInfo1);

		// Serialize the report into an external buffer.
		uint8_t bufferReport1[256]{ 0 };

		report1->Serialize(bufferReport1);

		// Create a new report out of the external buffer.
		auto report2 = DelaySinceLastRr::Parse(bufferReport1, report1->GetSize());

		REQUIRE(report1->GetType() == report2->GetType());

		auto ssrcInfoIt = report2->Begin();
		auto* ssrcInfo2 = *ssrcInfoIt;

		REQUIRE(ssrcInfo1->GetSsrc() == ssrcInfo2->GetSsrc());
		REQUIRE(ssrcInfo1->GetLastReceiverReport() == ssrcInfo2->GetLastReceiverReport());
		REQUIRE(
		  ssrcInfo1->GetDelaySinceLastReceiverReport() == ssrcInfo2->GetDelaySinceLastReceiverReport());
		REQUIRE(ssrcInfo1->GetSize() == ssrcInfo2->GetSize());

		// Create a local packet.
		std::unique_ptr<ExtendedReportPacket> packet1(new ExtendedReportPacket());

		packet1->SetSsrc(2222);
		packet1->AddReport(report1);
		packet1->AddReport(report2);

		REQUIRE(packet1->GetType() == Type::XR);
		REQUIRE(packet1->GetCount() == 0);
		REQUIRE(packet1->GetSsrc() == 2222);

		// Total size:
		// -  RTCP common header
		// -  SSRC
		// -  block 1
		// -  block 2
		REQUIRE(packet1->GetSize() == 4 + 4 + 16 + 16);

		// Serialize the packet into an external buffer.
		uint8_t bufferPacket1[256]{ 0 };
		uint8_t bufferPacket2[256]{ 0 };

		packet1->Serialize(bufferPacket1);

		// Create a new packet out of the external buffer.
		auto packet2 = ExtendedReportPacket::Parse(bufferPacket1, packet1->GetSize());

		REQUIRE(packet2->GetType() == packet1->GetType());
		REQUIRE(packet2->GetCount() == packet1->GetCount());
		REQUIRE(packet2->GetSsrc() == packet1->GetSsrc());
		REQUIRE(packet2->GetSize() == packet1->GetSize());

		packet2->Serialize(bufferPacket2);

		REQUIRE(std::memcmp(bufferPacket1, bufferPacket2, packet1->GetSize()) == 0);
	}
}
<commit_msg>RTCP XR: include missing header file<commit_after>#include "common.hpp"
#include "catch.hpp"
#include "RTC/RTCP/XrDelaySinceLastRr.hpp"
#include "RTC/RTCP/XrReceiverReferenceTime.hpp"
#include <cstring> // std::memcmp

using namespace RTC::RTCP;

SCENARIO("RTCP XrDelaySinceLastRt parsing", "[parser][rtcp][xr-dlrr]")
{
	SECTION("create RRT")
	{
		// Create local report and check content.
		auto* report1 = new ReceiverReferenceTime();

		report1->SetNtpSec(11111111);
		report1->SetNtpFrac(22222222);

		REQUIRE(report1->GetType() == ExtendedReportBlock::Type::RRT);
		REQUIRE(report1->GetNtpSec() == 11111111);
		REQUIRE(report1->GetNtpFrac() == 22222222);

		// Serialize the report into an external buffer.
		uint8_t bufferReport1[256]{ 0 };

		report1->Serialize(bufferReport1);

		// Create a new report out of the external buffer.
		auto report2 = ReceiverReferenceTime::Parse(bufferReport1, report1->GetSize());

		REQUIRE(report1->GetType() == report2->GetType());
		REQUIRE(report1->GetNtpSec() == report2->GetNtpSec());
		REQUIRE(report1->GetNtpFrac() == report2->GetNtpFrac());

		// Create a local packet.
		std::unique_ptr<ExtendedReportPacket> packet1(new ExtendedReportPacket());

		packet1->SetSsrc(2222);
		packet1->AddReport(report1);
		packet1->AddReport(report2);

		REQUIRE(packet1->GetType() == Type::XR);
		REQUIRE(packet1->GetCount() == 0);
		REQUIRE(packet1->GetSsrc() == 2222);

		// Total size:
		// -  RTCP common header
		// -  SSRC
		// -  block 1
		// -  block 2
		REQUIRE(packet1->GetSize() == 4 + 4 + 12 + 12);

		// Serialize the packet into an external buffer.
		uint8_t bufferPacket1[256]{ 0 };
		uint8_t bufferPacket2[256]{ 0 };

		packet1->Serialize(bufferPacket1);

		// Create a new packet out of the external buffer.
		auto packet2 = ExtendedReportPacket::Parse(bufferPacket1, packet1->GetSize());

		REQUIRE(packet2->GetType() == packet1->GetType());
		REQUIRE(packet2->GetCount() == packet1->GetCount());
		REQUIRE(packet2->GetSsrc() == packet1->GetSsrc());
		REQUIRE(packet2->GetSize() == packet1->GetSize());

		packet2->Serialize(bufferPacket2);

		REQUIRE(std::memcmp(bufferPacket1, bufferPacket2, packet1->GetSize()) == 0);
	}

	SECTION("create DLRR")
	{
		// Create local report and check content.
		auto* report1   = new DelaySinceLastRr();
		auto* ssrcInfo1 = new DelaySinceLastRr::SsrcInfo();

		ssrcInfo1->SetSsrc(1234);
		ssrcInfo1->SetLastReceiverReport(11111111);
		ssrcInfo1->SetDelaySinceLastReceiverReport(22222222);

		REQUIRE(ssrcInfo1->GetSsrc() == 1234);
		REQUIRE(ssrcInfo1->GetLastReceiverReport() == 11111111);
		REQUIRE(ssrcInfo1->GetDelaySinceLastReceiverReport() == 22222222);
		REQUIRE(ssrcInfo1->GetSize() == sizeof(DelaySinceLastRr::SsrcInfo::Body));

		report1->AddSsrcInfo(ssrcInfo1);

		// Serialize the report into an external buffer.
		uint8_t bufferReport1[256]{ 0 };

		report1->Serialize(bufferReport1);

		// Create a new report out of the external buffer.
		auto report2 = DelaySinceLastRr::Parse(bufferReport1, report1->GetSize());

		REQUIRE(report1->GetType() == report2->GetType());

		auto ssrcInfoIt = report2->Begin();
		auto* ssrcInfo2 = *ssrcInfoIt;

		REQUIRE(ssrcInfo1->GetSsrc() == ssrcInfo2->GetSsrc());
		REQUIRE(ssrcInfo1->GetLastReceiverReport() == ssrcInfo2->GetLastReceiverReport());
		REQUIRE(
		  ssrcInfo1->GetDelaySinceLastReceiverReport() == ssrcInfo2->GetDelaySinceLastReceiverReport());
		REQUIRE(ssrcInfo1->GetSize() == ssrcInfo2->GetSize());

		// Create a local packet.
		std::unique_ptr<ExtendedReportPacket> packet1(new ExtendedReportPacket());

		packet1->SetSsrc(2222);
		packet1->AddReport(report1);
		packet1->AddReport(report2);

		REQUIRE(packet1->GetType() == Type::XR);
		REQUIRE(packet1->GetCount() == 0);
		REQUIRE(packet1->GetSsrc() == 2222);

		// Total size:
		// -  RTCP common header
		// -  SSRC
		// -  block 1
		// -  block 2
		REQUIRE(packet1->GetSize() == 4 + 4 + 16 + 16);

		// Serialize the packet into an external buffer.
		uint8_t bufferPacket1[256]{ 0 };
		uint8_t bufferPacket2[256]{ 0 };

		packet1->Serialize(bufferPacket1);

		// Create a new packet out of the external buffer.
		auto packet2 = ExtendedReportPacket::Parse(bufferPacket1, packet1->GetSize());

		REQUIRE(packet2->GetType() == packet1->GetType());
		REQUIRE(packet2->GetCount() == packet1->GetCount());
		REQUIRE(packet2->GetSsrc() == packet1->GetSsrc());
		REQUIRE(packet2->GetSize() == packet1->GetSize());

		packet2->Serialize(bufferPacket2);

		REQUIRE(std::memcmp(bufferPacket1, bufferPacket2, packet1->GetSize()) == 0);
	}
}
<|endoftext|>


<commit_before>/* <x0/plugins/proxy.cpp>
 *
 * This file is part of the x0 web server project and is released under LGPL-3.
 * http://www.xzero.io/
 *
 * (c) 2009-2010 Christian Parpart <trapni@gentoo.org>
 */

#include <x0/http/HttpPlugin.h>
#include <x0/http/HttpServer.h>
#include <x0/http/HttpRequest.h>
#include <x0/io/BufferSource.h>
#include <x0/io/BufferRefSource.h>
#include <x0/SocketSpec.h>
#include <x0/strutils.h>
#include <x0/Url.h>
#include <x0/Types.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>

/* {{{ -- configuration proposal:
 *
 * handler setup {
 * }
 *
 * handler main {
 *     proxy.reverse 'http://127.0.0.1:3000';
 * }
 *
 * --------------------------------------------------------------------------
 * possible tweaks:
 *  - bufsize (0 = unbuffered)
 *  - timeout.connect
 *  - timeout.write
 *  - timeout.read
 *  - ignore_clientabort
 * };
 *
 *
 */ // }}}

#if 0
#	define TRACE(msg...) DEBUG("proxy: " msg)
#else
#	define TRACE(msg...) /*!*/
#endif

// {{{ ProxyConnection API
class ProxyConnection :
	public x0::HttpMessageProcessor
{
private:
	int refCount_;

	x0::HttpRequest *request_;		//!< client's request
	x0::Socket* backend_;			//!< connection to backend app
	bool cloak_;					//!< to cloak or not to cloak the "Server" response header

	int connectTimeout_;
	int readTimeout_;
	int writeTimeout_;

	x0::Buffer writeBuffer_;
	size_t writeOffset_;
	size_t writeProgress_;

	x0::Buffer readBuffer_;
	bool processingDone_;

private:
	void ref();
	void unref();
	inline void close();
	inline void readSome();
	inline void writeSome();

	void onConnected(x0::Socket* s, int revents);
	void io(x0::Socket* s, int revents);
	void onRequestChunk(const x0::BufferRef& chunk);

	static void onAbort(void *p);
	void onWriteComplete();

	// response (HttpMessageProcessor)
	virtual bool onMessageBegin(int versionMajor, int versionMinor, int code, const x0::BufferRef& text);
	virtual bool onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value);
	virtual bool onMessageContent(const x0::BufferRef& chunk);
	virtual bool onMessageEnd();

public:
	inline ProxyConnection();
	~ProxyConnection();

	void start(x0::HttpRequest* in, x0::Socket* backend, bool cloak);
};
// }}}

// {{{ ProxyConnection impl
ProxyConnection::ProxyConnection() :
	x0::HttpMessageProcessor(x0::HttpMessageProcessor::RESPONSE),
	refCount_(1),
	request_(nullptr),
	backend_(nullptr),
	cloak_(false),

	connectTimeout_(0),
	readTimeout_(0),
	writeTimeout_(0),
	writeBuffer_(),
	writeOffset_(0),
	writeProgress_(0),
	readBuffer_(),
	processingDone_(false)
{
	TRACE("ProxyConnection()");
}

ProxyConnection::~ProxyConnection()
{
	TRACE("~ProxyConnection()");

	if (backend_) {
		backend_->close();

		delete backend_;
	}

	if (request_) {
		if (request_->status == x0::HttpError::Undefined)
			request_->status = x0::HttpError::ServiceUnavailable;

		request_->finish();
	}
}

void ProxyConnection::ref()
{
	++refCount_;
}

void ProxyConnection::unref()
{
	assert(refCount_ > 0);

	--refCount_;

	if (refCount_ == 0) {
		delete this;
	}
}

void ProxyConnection::close()
{
	if (backend_)
		// stop watching on any backend I/O events, if active
		backend_->close();

	unref(); // the one from the constructor
}

void ProxyConnection::onAbort(void *p)
{
	ProxyConnection *self = reinterpret_cast<ProxyConnection *>(p);
	self->close();
}

void ProxyConnection::start(x0::HttpRequest* in, x0::Socket* backend, bool cloak)
{
	TRACE("ProxyConnection.start(in, backend, cloak=%d)", cloak);

	request_ = in;
	request_->setAbortHandler(&ProxyConnection::onAbort, this);
	backend_ = backend;
	cloak_ = cloak;

	// request line
	writeBuffer_.push_back(request_->method);
	writeBuffer_.push_back(' ');
	writeBuffer_.push_back(request_->uri);
	writeBuffer_.push_back(" HTTP/1.1\r\n");

	// request headers
	for (auto& header: request_->requestHeaders) {
		if (iequals(header.name, "Content-Transfer")
				|| iequals(header.name, "Expect")
				|| iequals(header.name, "Connection")
				|| iequals(header.name, "X-Forwarded-For")
				|| iequals(header.name, "X-Forwarded-Proto")) {
			TRACE("skip requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
			continue;
		}

		TRACE("pass requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
		writeBuffer_.push_back(header.name);
		writeBuffer_.push_back(": ");
		writeBuffer_.push_back(header.value);
		writeBuffer_.push_back("\r\n");
	}

	// additional headers to add
	writeBuffer_.push_back("Connection: closed\r\n");

	writeBuffer_.push_back("X-Forwarded-For: ");
	writeBuffer_.push_back(request_->connection.remoteIP());
	writeBuffer_.push_back("\r\n");

#if defined(WITH_SSL)
	if (request_->connection.isSecure())
		writeBuffer_.push_back("X-Forwarded-Proto: https\r\n");
	else
		writeBuffer_.push_back("X-Forwarded-Proto: http\r\n");
#endif

	// request headers terminator
	writeBuffer_.push_back("\r\n");

	if (request_->contentAvailable()) {
		TRACE("start: request content available: reading.");
		request_->setBodyCallback<ProxyConnection, &ProxyConnection::onRequestChunk>(this);
	}

	if (backend_->state() == x0::Socket::Connecting) {
		TRACE("start: connect in progress");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::onConnected>(this);
	} else { // connected
		TRACE("start: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

void ProxyConnection::onConnected(x0::Socket* s, int revents)
{
	TRACE("onConnected: content? %d", request_->contentAvailable());
	//TRACE("onConnected.pending:\n%s\n", writeBuffer_.c_str());

	if (backend_->state() == x0::Socket::Operational) {
		TRACE("onConnected: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite); // flush already serialized request
	} else {
		close();
	}
}

/** transferres a request body chunk to the origin server.  */
void ProxyConnection::onRequestChunk(const x0::BufferRef& chunk)
{
	TRACE("onRequestChunk(nb:%ld)", chunk.size());
	writeBuffer_.push_back(chunk);

	if (backend_->state() == x0::Socket::Operational) {
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

inline bool validateResponseHeader(const x0::BufferRef& name)
{
	// XXX do not allow origin's connection-level response headers to be passed to the client.
	if (iequals(name, "Connection"))
		return false;

	if (iequals(name, "Transfer-Encoding"))
		return false;

	return true;
}

/** callback, invoked when the origin server has passed us the response status line.
 *
 * We will use the status code only.
 * However, we could pass the text field, too - once x0 core supports it.
 */
bool ProxyConnection::onMessageBegin(int major, int minor, int code, const x0::BufferRef& text)
{
	TRACE("ProxyConnection(%p).status(HTTP/%d.%d, %d, '%s')", (void*)this, major, minor, code, text.str().c_str());

	request_->status = static_cast<x0::HttpError>(code);
	TRACE("status: %d", (int)request_->status);
	return true;
}

/** callback, invoked on every successfully parsed response header.
 *
 * We will pass this header directly to the client's response,
 * if that is NOT a connection-level header.
 */
bool ProxyConnection::onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value)
{
	TRACE("ProxyConnection(%p).onHeader('%s', '%s')", (void*)this, name.str().c_str(), value.str().c_str());

	if (!validateResponseHeader(name)) {
		TRACE("skipping connection-level header");
		goto done;
	}

	if (cloak_ && iequals(name, "Server")) {
		TRACE("skipping \"Server\"-header");
		goto done;
	}

	request_->responseHeaders.push_back(name.str(), value.str());

done:
	return true;
}

/** callback, invoked on a new response content chunk. */
bool ProxyConnection::onMessageContent(const x0::BufferRef& chunk)
{
	TRACE("messageContent(nb:%lu) state:%s", chunk.size(), backend_->state_str());

	// stop watching for more input
	backend_->setMode(x0::Socket::None);

	// transfer response-body chunk to client
	request_->write<x0::BufferRefSource>(chunk);

	// start listening on backend I/O when chunk has been fully transmitted
	ref();
	request_->writeCallback<ProxyConnection, &ProxyConnection::onWriteComplete>(this);

	return true;
}

void ProxyConnection::onWriteComplete()
{
	TRACE("chunk write complete: %s", state_str());
	backend_->setMode(x0::Socket::Read);
	unref();
}

bool ProxyConnection::onMessageEnd()
{
	TRACE("messageEnd() backend-state:%s", backend_->state_str());
	processingDone_ = true;
	return false;
}

void ProxyConnection::io(x0::Socket* s, int revents)
{
	TRACE("io(0x%04x)", revents);

	if (revents & x0::Socket::Read)
		readSome();

	if (revents & x0::Socket::Write)
		writeSome();
}

void ProxyConnection::writeSome()
{
	TRACE("writeSome() - %s (%d)", state_str(), request_->contentAvailable());

	ssize_t rv = backend_->write(writeBuffer_.data() + writeOffset_, writeBuffer_.size() - writeOffset_);

	if (rv > 0) {
		TRACE("write request: %ld (of %ld) bytes", rv, writeBuffer_.size() - writeOffset_);

		writeOffset_ += rv;
		writeProgress_ += rv;

		if (writeOffset_ == writeBuffer_.size()) {
			TRACE("writeOffset == writeBuffser.size (%ld) p:%ld, ca: %d, clr:%ld", writeOffset_,
				writeProgress_, request_->contentAvailable(), request_->connection.contentLength());

			writeOffset_ = 0;
			writeBuffer_.clear();
			backend_->setMode(x0::Socket::Read);
		}
	} else {
		TRACE("write request failed(%ld): %s", rv, strerror(errno));
		close();
	}
}

void ProxyConnection::readSome()
{
	TRACE("readSome() - %s", state_str());

	std::size_t lower_bound = readBuffer_.size();

	if (lower_bound == readBuffer_.capacity())
		readBuffer_.setCapacity(lower_bound + 4096);

	ssize_t rv = backend_->read(readBuffer_);

	if (rv > 0) {
		TRACE("read response: %ld bytes", rv);
		std::size_t np = process(readBuffer_.ref(lower_bound, rv));
		(void) np;
		TRACE("readSome(): process(): %ld / %ld", np, rv);

		if (processingDone_ || state() == SYNTAX_ERROR) {
			close();
		} else {
			TRACE("resume with io:%d, state:%s", backend_->mode(), backend_->state_str());
			backend_->setMode(x0::Socket::Read);
		}
	} else if (rv == 0) {
		TRACE("http server connection closed");
		close();
	} else {
		TRACE("read response failed(%ld): %s", rv, strerror(errno));

		switch (errno) {
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
		case EWOULDBLOCK:
#endif
		case EAGAIN:
		case EINTR:
			backend_->setMode(x0::Socket::Read);
			break;
		default:
			close();
			break;
		}
	}
}

// }}}

// {{{ plugin class
/**
 * \ingroup plugins
 * \brief proxy content generator plugin
 */
class proxy_plugin :
	public x0::HttpPlugin
{
private:
	bool cloak_;

public:
	proxy_plugin(x0::HttpServer& srv, const std::string& name) :
		x0::HttpPlugin(srv, name),
		cloak_(true)
	{
		registerHandler<proxy_plugin, &proxy_plugin::proxy_reverse>("proxy.reverse");
		registerSetupProperty<proxy_plugin, &proxy_plugin::proxy_cloak>("proxy.cloak", x0::FlowValue::BOOLEAN);
	}

	~proxy_plugin()
	{
	}

private:
	void proxy_cloak(const x0::FlowParams& args, x0::FlowValue& result)
	{
		if (args.size() && (args[0].isBool() || args[0].isNumber())) {
			cloak_ = args[0].toBool();
			TRACE("proxy cloak: %s", cloak_ ? "true" : "false");
		}

		result.set(cloak_);
	}

	bool proxy_reverse(x0::HttpRequest *in, const x0::FlowParams& args)
	{
		// TODO: reuse already spawned proxy connections instead of recreating each time.
		x0::SocketSpec spec;
		spec << args;
		if (!spec.isValid() || spec.backlog >= 0) {
			in->log(x0::Severity::error, "Invalid socket spec passed.");
			return false;
		}

		x0::Socket* backend = new x0::Socket(in->connection.worker().loop());
		if (spec.isLocal()) {
			TRACE("unix socket: '%s'", spec.str().c_str());
			backend->openUnix(spec.local);
		} else {
			backend->openTcp(spec.address.str(), spec.port);
		}

		if (backend->isOpen()) {
			TRACE("in.content? %d", in->contentAvailable());
			if (ProxyConnection* pc = new ProxyConnection()) {
				pc->start(in, backend, cloak_);
				return true;
			}
		}

		in->status = x0::HttpError::ServiceUnavailable;
		in->finish();

		return true;
	}
};
// }}}

X0_EXPORT_PLUGIN(proxy)
<commit_msg>[plugins] proxy: pass O_NONBLOCK + O_CLOEXEC explicitely and use Socket::open() instead of dual call openTcp vs openUnix<commit_after>/* <x0/plugins/proxy.cpp>
 *
 * This file is part of the x0 web server project and is released under LGPL-3.
 * http://www.xzero.io/
 *
 * (c) 2009-2010 Christian Parpart <trapni@gentoo.org>
 */

#include <x0/http/HttpPlugin.h>
#include <x0/http/HttpServer.h>
#include <x0/http/HttpRequest.h>
#include <x0/io/BufferSource.h>
#include <x0/io/BufferRefSource.h>
#include <x0/SocketSpec.h>
#include <x0/strutils.h>
#include <x0/Url.h>
#include <x0/Types.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>

/* {{{ -- configuration proposal:
 *
 * handler setup {
 * }
 *
 * handler main {
 *     proxy.reverse 'http://127.0.0.1:3000';
 * }
 *
 * --------------------------------------------------------------------------
 * possible tweaks:
 *  - bufsize (0 = unbuffered)
 *  - timeout.connect
 *  - timeout.write
 *  - timeout.read
 *  - ignore_clientabort
 * };
 *
 *
 */ // }}}

#if 0
#	define TRACE(msg...) DEBUG("proxy: " msg)
#else
#	define TRACE(msg...) /*!*/
#endif

// {{{ ProxyConnection API
class ProxyConnection :
	public x0::HttpMessageProcessor
{
private:
	int refCount_;

	x0::HttpRequest *request_;		//!< client's request
	x0::Socket* backend_;			//!< connection to backend app
	bool cloak_;					//!< to cloak or not to cloak the "Server" response header

	int connectTimeout_;
	int readTimeout_;
	int writeTimeout_;

	x0::Buffer writeBuffer_;
	size_t writeOffset_;
	size_t writeProgress_;

	x0::Buffer readBuffer_;
	bool processingDone_;

private:
	void ref();
	void unref();
	inline void close();
	inline void readSome();
	inline void writeSome();

	void onConnected(x0::Socket* s, int revents);
	void io(x0::Socket* s, int revents);
	void onRequestChunk(const x0::BufferRef& chunk);

	static void onAbort(void *p);
	void onWriteComplete();

	// response (HttpMessageProcessor)
	virtual bool onMessageBegin(int versionMajor, int versionMinor, int code, const x0::BufferRef& text);
	virtual bool onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value);
	virtual bool onMessageContent(const x0::BufferRef& chunk);
	virtual bool onMessageEnd();

public:
	inline ProxyConnection();
	~ProxyConnection();

	void start(x0::HttpRequest* in, x0::Socket* backend, bool cloak);
};
// }}}

// {{{ ProxyConnection impl
ProxyConnection::ProxyConnection() :
	x0::HttpMessageProcessor(x0::HttpMessageProcessor::RESPONSE),
	refCount_(1),
	request_(nullptr),
	backend_(nullptr),
	cloak_(false),

	connectTimeout_(0),
	readTimeout_(0),
	writeTimeout_(0),
	writeBuffer_(),
	writeOffset_(0),
	writeProgress_(0),
	readBuffer_(),
	processingDone_(false)
{
	TRACE("ProxyConnection()");
}

ProxyConnection::~ProxyConnection()
{
	TRACE("~ProxyConnection()");

	if (backend_) {
		backend_->close();

		delete backend_;
	}

	if (request_) {
		if (request_->status == x0::HttpError::Undefined)
			request_->status = x0::HttpError::ServiceUnavailable;

		request_->finish();
	}
}

void ProxyConnection::ref()
{
	++refCount_;
}

void ProxyConnection::unref()
{
	assert(refCount_ > 0);

	--refCount_;

	if (refCount_ == 0) {
		delete this;
	}
}

void ProxyConnection::close()
{
	if (backend_)
		// stop watching on any backend I/O events, if active
		backend_->close();

	unref(); // the one from the constructor
}

void ProxyConnection::onAbort(void *p)
{
	ProxyConnection *self = reinterpret_cast<ProxyConnection *>(p);
	self->close();
}

void ProxyConnection::start(x0::HttpRequest* in, x0::Socket* backend, bool cloak)
{
	TRACE("ProxyConnection.start(in, backend, cloak=%d)", cloak);

	request_ = in;
	request_->setAbortHandler(&ProxyConnection::onAbort, this);
	backend_ = backend;
	cloak_ = cloak;

	// request line
	writeBuffer_.push_back(request_->method);
	writeBuffer_.push_back(' ');
	writeBuffer_.push_back(request_->uri);
	writeBuffer_.push_back(" HTTP/1.1\r\n");

	// request headers
	for (auto& header: request_->requestHeaders) {
		if (iequals(header.name, "Content-Transfer")
				|| iequals(header.name, "Expect")
				|| iequals(header.name, "Connection")
				|| iequals(header.name, "X-Forwarded-For")
				|| iequals(header.name, "X-Forwarded-Proto")) {
			TRACE("skip requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
			continue;
		}

		TRACE("pass requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
		writeBuffer_.push_back(header.name);
		writeBuffer_.push_back(": ");
		writeBuffer_.push_back(header.value);
		writeBuffer_.push_back("\r\n");
	}

	// additional headers to add
	writeBuffer_.push_back("Connection: closed\r\n");

	writeBuffer_.push_back("X-Forwarded-For: ");
	writeBuffer_.push_back(request_->connection.remoteIP());
	writeBuffer_.push_back("\r\n");

#if defined(WITH_SSL)
	if (request_->connection.isSecure())
		writeBuffer_.push_back("X-Forwarded-Proto: https\r\n");
	else
		writeBuffer_.push_back("X-Forwarded-Proto: http\r\n");
#endif

	// request headers terminator
	writeBuffer_.push_back("\r\n");

	if (request_->contentAvailable()) {
		TRACE("start: request content available: reading.");
		request_->setBodyCallback<ProxyConnection, &ProxyConnection::onRequestChunk>(this);
	}

	if (backend_->state() == x0::Socket::Connecting) {
		TRACE("start: connect in progress");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::onConnected>(this);
	} else { // connected
		TRACE("start: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

void ProxyConnection::onConnected(x0::Socket* s, int revents)
{
	TRACE("onConnected: content? %d", request_->contentAvailable());
	//TRACE("onConnected.pending:\n%s\n", writeBuffer_.c_str());

	if (backend_->state() == x0::Socket::Operational) {
		TRACE("onConnected: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite); // flush already serialized request
	} else {
		close();
	}
}

/** transferres a request body chunk to the origin server.  */
void ProxyConnection::onRequestChunk(const x0::BufferRef& chunk)
{
	TRACE("onRequestChunk(nb:%ld)", chunk.size());
	writeBuffer_.push_back(chunk);

	if (backend_->state() == x0::Socket::Operational) {
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

inline bool validateResponseHeader(const x0::BufferRef& name)
{
	// XXX do not allow origin's connection-level response headers to be passed to the client.
	if (iequals(name, "Connection"))
		return false;

	if (iequals(name, "Transfer-Encoding"))
		return false;

	return true;
}

/** callback, invoked when the origin server has passed us the response status line.
 *
 * We will use the status code only.
 * However, we could pass the text field, too - once x0 core supports it.
 */
bool ProxyConnection::onMessageBegin(int major, int minor, int code, const x0::BufferRef& text)
{
	TRACE("ProxyConnection(%p).status(HTTP/%d.%d, %d, '%s')", (void*)this, major, minor, code, text.str().c_str());

	request_->status = static_cast<x0::HttpError>(code);
	TRACE("status: %d", (int)request_->status);
	return true;
}

/** callback, invoked on every successfully parsed response header.
 *
 * We will pass this header directly to the client's response,
 * if that is NOT a connection-level header.
 */
bool ProxyConnection::onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value)
{
	TRACE("ProxyConnection(%p).onHeader('%s', '%s')", (void*)this, name.str().c_str(), value.str().c_str());

	if (!validateResponseHeader(name)) {
		TRACE("skipping connection-level header");
		goto done;
	}

	if (cloak_ && iequals(name, "Server")) {
		TRACE("skipping \"Server\"-header");
		goto done;
	}

	request_->responseHeaders.push_back(name.str(), value.str());

done:
	return true;
}

/** callback, invoked on a new response content chunk. */
bool ProxyConnection::onMessageContent(const x0::BufferRef& chunk)
{
	TRACE("messageContent(nb:%lu) state:%s", chunk.size(), backend_->state_str());

	// stop watching for more input
	backend_->setMode(x0::Socket::None);

	// transfer response-body chunk to client
	request_->write<x0::BufferRefSource>(chunk);

	// start listening on backend I/O when chunk has been fully transmitted
	ref();
	request_->writeCallback<ProxyConnection, &ProxyConnection::onWriteComplete>(this);

	return true;
}

void ProxyConnection::onWriteComplete()
{
	TRACE("chunk write complete: %s", state_str());
	backend_->setMode(x0::Socket::Read);
	unref();
}

bool ProxyConnection::onMessageEnd()
{
	TRACE("messageEnd() backend-state:%s", backend_->state_str());
	processingDone_ = true;
	return false;
}

void ProxyConnection::io(x0::Socket* s, int revents)
{
	TRACE("io(0x%04x)", revents);

	if (revents & x0::Socket::Read)
		readSome();

	if (revents & x0::Socket::Write)
		writeSome();
}

void ProxyConnection::writeSome()
{
	TRACE("writeSome() - %s (%d)", state_str(), request_->contentAvailable());

	ssize_t rv = backend_->write(writeBuffer_.data() + writeOffset_, writeBuffer_.size() - writeOffset_);

	if (rv > 0) {
		TRACE("write request: %ld (of %ld) bytes", rv, writeBuffer_.size() - writeOffset_);

		writeOffset_ += rv;
		writeProgress_ += rv;

		if (writeOffset_ == writeBuffer_.size()) {
			TRACE("writeOffset == writeBuffser.size (%ld) p:%ld, ca: %d, clr:%ld", writeOffset_,
				writeProgress_, request_->contentAvailable(), request_->connection.contentLength());

			writeOffset_ = 0;
			writeBuffer_.clear();
			backend_->setMode(x0::Socket::Read);
		}
	} else {
		TRACE("write request failed(%ld): %s", rv, strerror(errno));
		close();
	}
}

void ProxyConnection::readSome()
{
	TRACE("readSome() - %s", state_str());

	std::size_t lower_bound = readBuffer_.size();

	if (lower_bound == readBuffer_.capacity())
		readBuffer_.setCapacity(lower_bound + 4096);

	ssize_t rv = backend_->read(readBuffer_);

	if (rv > 0) {
		TRACE("read response: %ld bytes", rv);
		std::size_t np = process(readBuffer_.ref(lower_bound, rv));
		(void) np;
		TRACE("readSome(): process(): %ld / %ld", np, rv);

		if (processingDone_ || state() == SYNTAX_ERROR) {
			close();
		} else {
			TRACE("resume with io:%d, state:%s", backend_->mode(), backend_->state_str());
			backend_->setMode(x0::Socket::Read);
		}
	} else if (rv == 0) {
		TRACE("http server connection closed");
		close();
	} else {
		TRACE("read response failed(%ld): %s", rv, strerror(errno));

		switch (errno) {
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
		case EWOULDBLOCK:
#endif
		case EAGAIN:
		case EINTR:
			backend_->setMode(x0::Socket::Read);
			break;
		default:
			close();
			break;
		}
	}
}

// }}}

// {{{ plugin class
/**
 * \ingroup plugins
 * \brief proxy content generator plugin
 */
class proxy_plugin :
	public x0::HttpPlugin
{
private:
	bool cloak_;

public:
	proxy_plugin(x0::HttpServer& srv, const std::string& name) :
		x0::HttpPlugin(srv, name),
		cloak_(true)
	{
		registerHandler<proxy_plugin, &proxy_plugin::proxy_reverse>("proxy.reverse");
		registerSetupProperty<proxy_plugin, &proxy_plugin::proxy_cloak>("proxy.cloak", x0::FlowValue::BOOLEAN);
	}

	~proxy_plugin()
	{
	}

private:
	void proxy_cloak(const x0::FlowParams& args, x0::FlowValue& result)
	{
		if (args.size() && (args[0].isBool() || args[0].isNumber())) {
			cloak_ = args[0].toBool();
			TRACE("proxy cloak: %s", cloak_ ? "true" : "false");
		}

		result.set(cloak_);
	}

	bool proxy_reverse(x0::HttpRequest *in, const x0::FlowParams& args)
	{
		// TODO: reuse already spawned proxy connections instead of recreating each time.
		x0::SocketSpec spec;
		spec << args;
		if (!spec.isValid() || spec.backlog >= 0) {
			in->log(x0::Severity::error, "Invalid socket spec passed.");
			return false;
		}

		x0::Socket* backend = new x0::Socket(in->connection.worker().loop());
		backend->open(spec, O_NONBLOCK | O_CLOEXEC);

		if (backend->isOpen()) {
			TRACE("in.content? %d", in->contentAvailable());
			if (ProxyConnection* pc = new ProxyConnection()) {
				pc->start(in, backend, cloak_);
				return true;
			}
		}

		in->status = x0::HttpError::ServiceUnavailable;
		in->finish();

		return true;
	}
};
// }}}

X0_EXPORT_PLUGIN(proxy)
<|endoftext|>


<commit_before>/*ckwg +5
 * Copyright 2011 by Kitware, Inc. All Rights Reserved. Please refer to
 * KITWARE_LICENSE.TXT for licensing information, or contact General Counsel,
 * Kitware, Inc., 28 Corporate Drive, Clifton Park, NY 12065.
 */

#include "modules.h"

#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>

#include <string>
#include <vector>

#include <dlfcn.h>

namespace vistk
{

typedef void* library_t;
typedef void* function_t;
typedef void (*load_module_t)();
typedef char const* envvar_name_t;
typedef char const* envvar_value_t;
typedef std::string module_path_t;
typedef std::vector<module_path_t> module_paths_t;
typedef std::string lib_suffix_t;
typedef char const* const function_name_t;

static void load_from_module(module_path_t const path);
static bool is_separator(char ch);

static function_name_t const edge_function_name = function_name_t("register_edges");
static function_name_t const pipeline_function_name = function_name_t("register_pipelines");
static function_name_t const process_function_name = function_name_t("register_processes");
static envvar_name_t const vistk_module_envvar = envvar_name_t("VISTK_MODULE_PATH");
static lib_suffix_t const library_suffix = lib_suffix_t(".so");

void load_known_modules()
{
  module_paths_t module_dirs;

#ifdef VISTK_LIBRARY_OUTPUT_PATH
  module_dirs.push_back(VISTK_LIBRARY_OUTPUT_PATH);
#endif

#ifdef VISTK_MODULE_INSTALL_PATH
  module_dirs.push_back(VISTK_MODULE_INSTALL_PATH);
#endif

  envvar_value_t const extra_module_dirs = getenv(vistk_module_envvar);

  if (extra_module_dirs)
  {
    boost::split(module_dirs, extra_module_dirs, is_separator, boost::token_compress_on);
  }

  BOOST_FOREACH (module_path_t const& module_dir, module_dirs)
  {
    if (module_dir.empty())
    {
      continue;
    }

    if (!boost::filesystem::exists(module_dir))
    {
      /// \todo Log error that path doesn't exist.
      continue;
    }

    if (!boost::filesystem::is_directory(module_dir))
    {
      /// \todo Log error that path isn't a directory.
      continue;
    }

    boost::system::error_code ec;
    boost::filesystem::directory_iterator module_dir_iter(module_dir, ec);

    while (module_dir_iter != boost::filesystem::directory_iterator())
    {
      boost::filesystem::directory_entry const ent = *module_dir_iter;

      ++module_dir_iter;

      if (!boost::ends_with(ent.path().native(), library_suffix))
      {
        continue;
      }

      if (ent.status().type() != boost::filesystem::regular_file)
      {
        /// \todo Log warning that we found a non-file matching path.
        continue;
      }

      load_from_module(ent.path().native());
    }
  }
}

void load_from_module(module_path_t const path)
{
  /// \todo Support more than POSIX. Use kwsys?
  library_t library = dlopen(path, RTLD_LAZY);

  if (!library)
  {
    return;
  }

  function_t edge_function = dlsym(library, edge_function_name);
  function_t pipeline_function = dlsym(library, pipeline_function_name);
  function_t process_function = dlsym(library, process_function_name);

  load_module_t edge_registrar = reinterpret_cast<load_module_t>(edge_function);
  load_module_t pipeline_registrar = reinterpret_cast<load_module_t>(pipeline_function);
  load_module_t process_registrar = reinterpret_cast<load_module_t>(process_function);

  bool functions_found = false;

  if (edge_registrar)
  {
    (*edge_registrar)();
    functions_found = true;
  }
  if (pipeline_registrar)
  {
    (*pipeline_registrar)();
    functions_found = true;
  }
  if (process_registrar)
  {
    (*process_registrar)();
    functions_found = true;
  }

  if (!functions_found)
  {
    int const ret = dlclose(library);

    if (ret)
    {
      /// \todo Log the error.
    }
  }
}

bool is_separator(char ch)
{
  char const separator =
#if defined(_WIN32) || defined(_WIN64)
    ';';
#else
    ':';
#endif

  return (ch == separator);
}

} // end namespace vistk
<commit_msg>Use strings for function names<commit_after>/*ckwg +5
 * Copyright 2011 by Kitware, Inc. All Rights Reserved. Please refer to
 * KITWARE_LICENSE.TXT for licensing information, or contact General Counsel,
 * Kitware, Inc., 28 Corporate Drive, Clifton Park, NY 12065.
 */

#include "modules.h"

#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>

#include <string>
#include <vector>

#include <dlfcn.h>

namespace vistk
{

typedef void* library_t;
typedef void* function_t;
typedef void (*load_module_t)();
typedef char const* envvar_name_t;
typedef char const* envvar_value_t;
typedef std::string module_path_t;
typedef std::vector<module_path_t> module_paths_t;
typedef std::string lib_suffix_t;
typedef std::string function_name_t;

static void load_from_module(module_path_t const path);
static bool is_separator(char ch);

static function_name_t const edge_function_name = function_name_t("register_edges");
static function_name_t const pipeline_function_name = function_name_t("register_pipelines");
static function_name_t const process_function_name = function_name_t("register_processes");
static envvar_name_t const vistk_module_envvar = envvar_name_t("VISTK_MODULE_PATH");
static lib_suffix_t const library_suffix = lib_suffix_t(".so");

void load_known_modules()
{
  module_paths_t module_dirs;

#ifdef VISTK_LIBRARY_OUTPUT_PATH
  module_dirs.push_back(VISTK_LIBRARY_OUTPUT_PATH);
#endif

#ifdef VISTK_MODULE_INSTALL_PATH
  module_dirs.push_back(VISTK_MODULE_INSTALL_PATH);
#endif

  envvar_value_t const extra_module_dirs = getenv(vistk_module_envvar);

  if (extra_module_dirs)
  {
    boost::split(module_dirs, extra_module_dirs, is_separator, boost::token_compress_on);
  }

  BOOST_FOREACH (module_path_t const& module_dir, module_dirs)
  {
    if (module_dir.empty())
    {
      continue;
    }

    if (!boost::filesystem::exists(module_dir))
    {
      /// \todo Log error that path doesn't exist.
      continue;
    }

    if (!boost::filesystem::is_directory(module_dir))
    {
      /// \todo Log error that path isn't a directory.
      continue;
    }

    boost::system::error_code ec;
    boost::filesystem::directory_iterator module_dir_iter(module_dir, ec);

    while (module_dir_iter != boost::filesystem::directory_iterator())
    {
      boost::filesystem::directory_entry const ent = *module_dir_iter;

      ++module_dir_iter;

      if (!boost::ends_with(ent.path().native(), library_suffix))
      {
        continue;
      }

      if (ent.status().type() != boost::filesystem::regular_file)
      {
        /// \todo Log warning that we found a non-file matching path.
        continue;
      }

      load_from_module(ent.path().native());
    }
  }
}

void load_from_module(module_path_t const path)
{
  /// \todo Support more than POSIX. Use kwsys?
  library_t library = dlopen(path, RTLD_LAZY);

  if (!library)
  {
    return;
  }

  function_t edge_function = dlsym(library, edge_function_name.c_str());
  function_t pipeline_function = dlsym(library, pipeline_function_name.c_str());
  function_t process_function = dlsym(library, process_function_name.c_str());

  load_module_t edge_registrar = reinterpret_cast<load_module_t>(edge_function);
  load_module_t pipeline_registrar = reinterpret_cast<load_module_t>(pipeline_function);
  load_module_t process_registrar = reinterpret_cast<load_module_t>(process_function);

  bool functions_found = false;

  if (edge_registrar)
  {
    (*edge_registrar)();
    functions_found = true;
  }
  if (pipeline_registrar)
  {
    (*pipeline_registrar)();
    functions_found = true;
  }
  if (process_registrar)
  {
    (*process_registrar)();
    functions_found = true;
  }

  if (!functions_found)
  {
    int const ret = dlclose(library);

    if (ret)
    {
      /// \todo Log the error.
    }
  }
}

bool is_separator(char ch)
{
  char const separator =
#if defined(_WIN32) || defined(_WIN64)
    ';';
#else
    ':';
#endif

  return (ch == separator);
}

} // end namespace vistk
<|endoftext|>


<commit_before>
// Copyright (c) 2012, 2013 Pierre MOULON.

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include "openMVG/image/image.hpp"
#include "openMVG/sfm/sfm.hpp"

/// Feature/Regions & Image describer interfaces
#include "openMVG/features/features.hpp"
#include "nonFree/sift/SIFT_describer.hpp"
#include <cereal/archives/json.hpp>
#include "openMVG/system/timer.hpp"

#include "third_party/cmdLine/cmdLine.h"
#include "third_party/stlplus3/filesystemSimplified/file_system.hpp"
#include "third_party/progress/progress.hpp"

#include <cstdlib>
#include <fstream>

#ifdef OPENMVG_USE_OPENMP
#include <omp.h>
#endif

using namespace openMVG;
using namespace openMVG::image;
using namespace openMVG::features;
using namespace openMVG::sfm;
using namespace std;

features::EDESCRIBER_PRESET stringToEnum(const std::string & sPreset)
{
  features::EDESCRIBER_PRESET preset;
  if(sPreset == "NORMAL")
    preset = features::NORMAL_PRESET;
  else
  if (sPreset == "HIGH")
    preset = features::HIGH_PRESET;
  else
  if (sPreset == "ULTRA")
    preset = features::ULTRA_PRESET;
  else
    preset = features::EDESCRIBER_PRESET(-1);
  return preset;
}

/// - Compute view image description (feature & descriptor extraction)
/// - Export computed data
int main(int argc, char **argv)
{
  CmdLine cmd;

  std::string sSfM_Data_Filename;
  std::string sOutDir = "";
  bool bUpRight = false;
  std::string sImage_Describer_Method = "SIFT";
  bool bForce = false;
  std::string sFeaturePreset = "";
#ifdef OPENMVG_USE_OPENMP
  int iNumThreads = 0;
#endif

  // required
  cmd.add( make_option('i', sSfM_Data_Filename, "input_file") );
  cmd.add( make_option('o', sOutDir, "outdir") );
  // Optional
  cmd.add( make_option('m', sImage_Describer_Method, "describerMethod") );
  cmd.add( make_option('u', bUpRight, "upright") );
  cmd.add( make_option('f', bForce, "force") );
  cmd.add( make_option('p', sFeaturePreset, "describerPreset") );

#ifdef OPENMVG_USE_OPENMP
  cmd.add( make_option('n', iNumThreads, "numThreads") );
#endif

  try {
      if (argc == 1) throw std::string("Invalid command line parameter.");
      cmd.process(argc, argv);
  } catch(const std::string& s) {
      std::cerr << "Usage: " << argv[0] << '\n'
      << "[-i|--input_file] a SfM_Data file \n"
      << "[-o|--outdir path] \n"
      << "\n[Optional]\n"
      << "[-f|--force] Force to recompute data\n"
      << "[-m|--describerMethod]\n"
      << "  (method to use to describe an image):\n"
      << "   SIFT (default),\n"
      << "   AKAZE_FLOAT: AKAZE with floating point descriptors,\n"
      << "   AKAZE_MLDB:  AKAZE with binary descriptors\n"
      << "[-u|--upright] Use Upright feature 0 or 1\n"
      << "[-p|--describerPreset]\n"
      << "  (used to control the Image_describer configuration):\n"
      << "   NORMAL (default),\n"
      << "   HIGH,\n"
      << "   ULTRA: !!Can take long time!!\n"
#ifdef OPENMVG_USE_OPENMP
      << "[-n|--numThreads] number of parallel computations\n"
#endif
      << std::endl;

      std::cerr << s << std::endl;
      return EXIT_FAILURE;
  }

  std::cout << " You called : " <<std::endl
            << argv[0] << std::endl
            << "--input_file " << sSfM_Data_Filename << std::endl
            << "--outdir " << sOutDir << std::endl
            << "--describerMethod " << sImage_Describer_Method << std::endl
            << "--upright " << bUpRight << std::endl
            << "--describerPreset " << (sFeaturePreset.empty() ? "NORMAL" : sFeaturePreset) << std::endl
            << "--force " << bForce << std::endl
#ifdef OPENMVG_USE_OPENMP
            << "--numThreads " << iNumThreads << std::endl
#endif
            << std::endl;

  if (sOutDir.empty())  {
    std::cerr << "\nIt is an invalid output directory" << std::endl;
    return EXIT_FAILURE;
  }

  // Create output dir
  if (!stlplus::folder_exists(sOutDir))
  {
    if (!stlplus::folder_create(sOutDir))
    {
      std::cerr << "Cannot create output directory" << std::endl;
      return EXIT_FAILURE;
    }
  }

  //---------------------------------------
  // a. Load input scene
  //---------------------------------------
  SfM_Data sfm_data;
  if (!Load(sfm_data, sSfM_Data_Filename, ESfM_Data(VIEWS|INTRINSICS))) {
    std::cerr << std::endl
      << "The input file \""<< sSfM_Data_Filename << "\" cannot be read" << std::endl;
    return false;
  }

  // b. Init the image_describer
  // - retrieve the used one in case of pre-computed features
  // - else create the desired one

  using namespace openMVG::features;
  std::unique_ptr<Image_describer> image_describer;

  const std::string sImage_describer = stlplus::create_filespec(sOutDir, "image_describer", "json");
  if (!bForce && stlplus::is_file(sImage_describer))
  {
    // Dynamically load the image_describer from the file (will restore old used settings)
    std::ifstream stream(sImage_describer.c_str());
    if (!stream.is_open())
      return false;

    try
    {
      cereal::JSONInputArchive archive(stream);
      archive(cereal::make_nvp("image_describer", image_describer));
    }
    catch (const cereal::Exception & e)
    {
      std::cerr << e.what() << std::endl
        << "Cannot dynamically allocate the Image_describer interface." << std::endl;
      return EXIT_FAILURE;
    }
  }
  else
  {
    // Create the desired Image_describer method.
    // Don't use a factory, perform direct allocation
    if (sImage_Describer_Method == "SIFT")
    {
      image_describer.reset(new SIFT_Image_describer
        (SIFT_Image_describer::Params(), !bUpRight));
    }
    else
    if (sImage_Describer_Method == "AKAZE_FLOAT")
    {
      image_describer.reset(new AKAZE_Image_describer
        (AKAZE_Image_describer::Params(AKAZE::Params(), AKAZE_MSURF), !bUpRight));
    }
    else
    if (sImage_Describer_Method == "AKAZE_MLDB")
    {
      image_describer.reset(new AKAZE_Image_describer
        (AKAZE_Image_describer::Params(AKAZE::Params(), AKAZE_MLDB), !bUpRight));
    }
    if (!image_describer)
    {
      std::cerr << "Cannot create the designed Image_describer:"
        << sImage_Describer_Method << "." << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      if (!sFeaturePreset.empty())
      if (!image_describer->Set_configuration_preset(stringToEnum(sFeaturePreset)))
      {
        std::cerr << "Preset configuration failed." << std::endl;
        return EXIT_FAILURE;
      }
    }

    // Export the used Image_describer and region type for:
    // - dynamic future regions computation and/or loading
    {
      std::ofstream stream(sImage_describer.c_str());
      if (!stream.is_open())
        return false;

      cereal::JSONOutputArchive archive(stream);
      archive(cereal::make_nvp("image_describer", image_describer));
      std::unique_ptr<Regions> regionsType;
      image_describer->Allocate(regionsType);
      archive(cereal::make_nvp("regions_type", regionsType));
    }
  }

  // Feature extraction routines
  // For each View of the SfM_Data container:
  // - if regions file exists continue,
  // - if no file, compute features
  {
    system::Timer timer;
    Image<unsigned char> imageGray, globalMask, imageMask;

    const std::string sGlobalMask_filename = stlplus::create_filespec(sOutDir, "mask.png");
    if(stlplus::file_exists(sGlobalMask_filename))
      ReadImage(sGlobalMask_filename.c_str(), &globalMask);

    C_Progress_display my_progress_bar( sfm_data.GetViews().size(),
      std::cout, "\n- EXTRACT FEATURES -\n" );

    #ifdef OPENMVG_USE_OPENMP
    const unsigned int nb_max_thread = omp_get_max_threads();
    #endif

#ifdef OPENMVG_USE_OPENMP
    omp_set_num_threads(iNumThreads);
    #pragma omp parallel for schedule(dynamic) if(iNumThreads > 0) private(imageMask)
#endif
    for(int i = 0; i < sfm_data.views.size(); ++i)
    {
#ifdef OPENMVG_USE_OPENMP
      if(iNumThreads == 0) omp_set_num_threads(nb_max_thread);
#endif
      Views::const_iterator iterViews = sfm_data.views.begin();
      std::advance(iterViews, i);
      const View * view = iterViews->second.get();
      const std::string
        sView_filename = stlplus::create_filespec(sfm_data.s_root_path, view->s_Img_path),
        sFeat = stlplus::create_filespec(sOutDir, stlplus::basename_part(sView_filename), "feat"),
        sDesc = stlplus::create_filespec(sOutDir, stlplus::basename_part(sView_filename), "desc");

      //If features or descriptors file are missing, compute them
      if (bForce || !stlplus::file_exists(sFeat) || !stlplus::file_exists(sDesc))
      {
        if (!ReadImage(sView_filename.c_str(), &imageGray))
          continue;

        Image<unsigned char> * mask = nullptr; // The mask is null by default

        const std::string sImageMask_filename =
          stlplus::create_filespec(sfm_data.s_root_path,
            stlplus::basename_part(sView_filename) + "_mask", "png");

        if(stlplus::file_exists(sImageMask_filename))
          ReadImage(sImageMask_filename.c_str(), &imageMask);

        // The mask point to the globalMask, if a valid one exists for the current image
        if(globalMask.Width() == imageGray.Width() && globalMask.Height() == imageGray.Height())
          mask = &globalMask;
        // The mask point to the imageMask (individual mask) if a valid one exists for the current image
        if(imageMask.Width() == imageGray.Width() && imageMask.Height() == imageGray.Height())
          mask = &imageMask;

        Image<unsigned char> imageGray;
        if (ReadImage(sView_filename.c_str(), &imageGray))
        {
          // Compute features and descriptors and export them to files
          std::unique_ptr<Regions> regions;
          image_describer->Describe(imageGray, regions, mask);
          image_describer->Save(regions.get(), sFeat, sDesc);
        }
      }
#ifdef OPENMVG_USE_OPENMP
      #pragma omp critical
#endif
      ++my_progress_bar;
    }
    std::cout << "Task done in (s): " << timer.elapsed() << std::endl;
  }
  return EXIT_SUCCESS;
}
<commit_msg>Fixed bug with OMP when computing features<commit_after>
// Copyright (c) 2012, 2013 Pierre MOULON.

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include "openMVG/image/image.hpp"
#include "openMVG/sfm/sfm.hpp"

/// Feature/Regions & Image describer interfaces
#include "openMVG/features/features.hpp"
#include "nonFree/sift/SIFT_describer.hpp"
#include <cereal/archives/json.hpp>
#include "openMVG/system/timer.hpp"

#include "third_party/cmdLine/cmdLine.h"
#include "third_party/stlplus3/filesystemSimplified/file_system.hpp"
#include "third_party/progress/progress.hpp"

#include <cstdlib>
#include <fstream>

#ifdef OPENMVG_USE_OPENMP
#include <omp.h>
#endif

using namespace openMVG;
using namespace openMVG::image;
using namespace openMVG::features;
using namespace openMVG::sfm;
using namespace std;

features::EDESCRIBER_PRESET stringToEnum(const std::string & sPreset)
{
  features::EDESCRIBER_PRESET preset;
  if(sPreset == "NORMAL")
    preset = features::NORMAL_PRESET;
  else
  if (sPreset == "HIGH")
    preset = features::HIGH_PRESET;
  else
  if (sPreset == "ULTRA")
    preset = features::ULTRA_PRESET;
  else
    preset = features::EDESCRIBER_PRESET(-1);
  return preset;
}

/// - Compute view image description (feature & descriptor extraction)
/// - Export computed data
int main(int argc, char **argv)
{
  CmdLine cmd;

  std::string sSfM_Data_Filename;
  std::string sOutDir = "";
  bool bUpRight = false;
  std::string sImage_Describer_Method = "SIFT";
  bool bForce = false;
  std::string sFeaturePreset = "";
#ifdef OPENMVG_USE_OPENMP
  int iNumThreads = 0;
#endif

  // required
  cmd.add( make_option('i', sSfM_Data_Filename, "input_file") );
  cmd.add( make_option('o', sOutDir, "outdir") );
  // Optional
  cmd.add( make_option('m', sImage_Describer_Method, "describerMethod") );
  cmd.add( make_option('u', bUpRight, "upright") );
  cmd.add( make_option('f', bForce, "force") );
  cmd.add( make_option('p', sFeaturePreset, "describerPreset") );

#ifdef OPENMVG_USE_OPENMP
  cmd.add( make_option('n', iNumThreads, "numThreads") );
#endif

  try {
      if (argc == 1) throw std::string("Invalid command line parameter.");
      cmd.process(argc, argv);
  } catch(const std::string& s) {
      std::cerr << "Usage: " << argv[0] << '\n'
      << "[-i|--input_file] a SfM_Data file \n"
      << "[-o|--outdir path] \n"
      << "\n[Optional]\n"
      << "[-f|--force] Force to recompute data\n"
      << "[-m|--describerMethod]\n"
      << "  (method to use to describe an image):\n"
      << "   SIFT (default),\n"
      << "   AKAZE_FLOAT: AKAZE with floating point descriptors,\n"
      << "   AKAZE_MLDB:  AKAZE with binary descriptors\n"
      << "[-u|--upright] Use Upright feature 0 or 1\n"
      << "[-p|--describerPreset]\n"
      << "  (used to control the Image_describer configuration):\n"
      << "   NORMAL (default),\n"
      << "   HIGH,\n"
      << "   ULTRA: !!Can take long time!!\n"
#ifdef OPENMVG_USE_OPENMP
      << "[-n|--numThreads] number of parallel computations\n"
#endif
      << std::endl;

      std::cerr << s << std::endl;
      return EXIT_FAILURE;
  }

  std::cout << " You called : " <<std::endl
            << argv[0] << std::endl
            << "--input_file " << sSfM_Data_Filename << std::endl
            << "--outdir " << sOutDir << std::endl
            << "--describerMethod " << sImage_Describer_Method << std::endl
            << "--upright " << bUpRight << std::endl
            << "--describerPreset " << (sFeaturePreset.empty() ? "NORMAL" : sFeaturePreset) << std::endl
            << "--force " << bForce << std::endl
#ifdef OPENMVG_USE_OPENMP
            << "--numThreads " << iNumThreads << std::endl
#endif
            << std::endl;

  if (sOutDir.empty())  {
    std::cerr << "\nIt is an invalid output directory" << std::endl;
    return EXIT_FAILURE;
  }

  // Create output dir
  if (!stlplus::folder_exists(sOutDir))
  {
    if (!stlplus::folder_create(sOutDir))
    {
      std::cerr << "Cannot create output directory" << std::endl;
      return EXIT_FAILURE;
    }
  }

  //---------------------------------------
  // a. Load input scene
  //---------------------------------------
  SfM_Data sfm_data;
  if (!Load(sfm_data, sSfM_Data_Filename, ESfM_Data(VIEWS|INTRINSICS))) {
    std::cerr << std::endl
      << "The input file \""<< sSfM_Data_Filename << "\" cannot be read" << std::endl;
    return false;
  }

  // b. Init the image_describer
  // - retrieve the used one in case of pre-computed features
  // - else create the desired one

  using namespace openMVG::features;
  std::unique_ptr<Image_describer> image_describer;

  const std::string sImage_describer = stlplus::create_filespec(sOutDir, "image_describer", "json");
  if (!bForce && stlplus::is_file(sImage_describer))
  {
    // Dynamically load the image_describer from the file (will restore old used settings)
    std::ifstream stream(sImage_describer.c_str());
    if (!stream.is_open())
      return false;

    try
    {
      cereal::JSONInputArchive archive(stream);
      archive(cereal::make_nvp("image_describer", image_describer));
    }
    catch (const cereal::Exception & e)
    {
      std::cerr << e.what() << std::endl
        << "Cannot dynamically allocate the Image_describer interface." << std::endl;
      return EXIT_FAILURE;
    }
  }
  else
  {
    // Create the desired Image_describer method.
    // Don't use a factory, perform direct allocation
    if (sImage_Describer_Method == "SIFT")
    {
      image_describer.reset(new SIFT_Image_describer
        (SIFT_Image_describer::Params(), !bUpRight));
    }
    else
    if (sImage_Describer_Method == "AKAZE_FLOAT")
    {
      image_describer.reset(new AKAZE_Image_describer
        (AKAZE_Image_describer::Params(AKAZE::Params(), AKAZE_MSURF), !bUpRight));
    }
    else
    if (sImage_Describer_Method == "AKAZE_MLDB")
    {
      image_describer.reset(new AKAZE_Image_describer
        (AKAZE_Image_describer::Params(AKAZE::Params(), AKAZE_MLDB), !bUpRight));
    }
    if (!image_describer)
    {
      std::cerr << "Cannot create the designed Image_describer:"
        << sImage_Describer_Method << "." << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      if (!sFeaturePreset.empty())
      if (!image_describer->Set_configuration_preset(stringToEnum(sFeaturePreset)))
      {
        std::cerr << "Preset configuration failed." << std::endl;
        return EXIT_FAILURE;
      }
    }

    // Export the used Image_describer and region type for:
    // - dynamic future regions computation and/or loading
    {
      std::ofstream stream(sImage_describer.c_str());
      if (!stream.is_open())
        return false;

      cereal::JSONOutputArchive archive(stream);
      archive(cereal::make_nvp("image_describer", image_describer));
      std::unique_ptr<Regions> regionsType;
      image_describer->Allocate(regionsType);
      archive(cereal::make_nvp("regions_type", regionsType));
    }
  }

  // Feature extraction routines
  // For each View of the SfM_Data container:
  // - if regions file exists continue,
  // - if no file, compute features
  {
    system::Timer timer;
    Image<unsigned char> imageGray, globalMask, imageMask;

    const std::string sGlobalMask_filename = stlplus::create_filespec(sOutDir, "mask.png");
    if(stlplus::file_exists(sGlobalMask_filename))
      ReadImage(sGlobalMask_filename.c_str(), &globalMask);

    C_Progress_display my_progress_bar( sfm_data.GetViews().size(),
      std::cout, "\n- EXTRACT FEATURES -\n" );

#ifdef OPENMVG_USE_OPENMP
    const unsigned int nb_max_thread = omp_get_max_threads();

    if (iNumThreads > 0) {
        omp_set_num_threads(iNumThreads);
    } else {
        omp_set_num_threads(nb_max_thread);
    }

    #pragma omp parallel for schedule(dynamic) if(iNumThreads > 0) private(imageMask)
#endif
    for(int i = 0; i < sfm_data.views.size(); ++i)
    {
      Views::const_iterator iterViews = sfm_data.views.begin();
      std::advance(iterViews, i);
      const View * view = iterViews->second.get();
      const std::string
        sView_filename = stlplus::create_filespec(sfm_data.s_root_path, view->s_Img_path),
        sFeat = stlplus::create_filespec(sOutDir, stlplus::basename_part(sView_filename), "feat"),
        sDesc = stlplus::create_filespec(sOutDir, stlplus::basename_part(sView_filename), "desc");

      //If features or descriptors file are missing, compute them
      if (bForce || !stlplus::file_exists(sFeat) || !stlplus::file_exists(sDesc))
      {
        if (!ReadImage(sView_filename.c_str(), &imageGray))
          continue;

        Image<unsigned char> * mask = nullptr; // The mask is null by default

        const std::string sImageMask_filename =
          stlplus::create_filespec(sfm_data.s_root_path,
            stlplus::basename_part(sView_filename) + "_mask", "png");

        if(stlplus::file_exists(sImageMask_filename))
          ReadImage(sImageMask_filename.c_str(), &imageMask);

        // The mask point to the globalMask, if a valid one exists for the current image
        if(globalMask.Width() == imageGray.Width() && globalMask.Height() == imageGray.Height())
          mask = &globalMask;
        // The mask point to the imageMask (individual mask) if a valid one exists for the current image
        if(imageMask.Width() == imageGray.Width() && imageMask.Height() == imageGray.Height())
          mask = &imageMask;

        Image<unsigned char> imageGray;
        if (ReadImage(sView_filename.c_str(), &imageGray))
        {
          // Compute features and descriptors and export them to files
          std::unique_ptr<Regions> regions;
          image_describer->Describe(imageGray, regions, mask);
          image_describer->Save(regions.get(), sFeat, sDesc);
        }
      }
#ifdef OPENMVG_USE_OPENMP
      #pragma omp critical
#endif
      ++my_progress_bar;
    }
    std::cout << "Task done in (s): " << timer.elapsed() << std::endl;
  }
  return EXIT_SUCCESS;
}
<|endoftext|>