view Samples/Sdl/RtViewer/RtViewerSdl.cpp @ 1404:3e644f6fadd4

Three-viewport is now OK in SDL and Wasm
author Benjamin Golinvaux <bgo@osimis.io>
date Wed, 29 Apr 2020 22:06:24 +0200
parents 27e0a00bd3e8
children 5d7ee14dc1eb
line wrap: on
line source

/**
 * Stone of Orthanc
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2020 Osimis S.A., Belgium
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 **/

#include "RtViewerApp.h"
#include "RtViewerView.h"
#include "../SdlHelpers.h"

// Stone of Orthanc includes
#include <Framework/Loaders/GenericLoadersContext.h>
#include <Framework/OpenGL/SdlOpenGLContext.h>
#include <Framework/StoneException.h>
#include <Framework/StoneInitialization.h>

// Orthanc (a.o. for screenshot capture)
#include <Core/Images/Image.h>
#include <Core/Images/ImageProcessing.h>
#include <Core/Images/PngWriter.h>


#include <boost/program_options.hpp>
#include <boost/shared_ptr.hpp>

// #include <boost/pointer_cast.hpp> this include might be necessary in more recent boost versions

#include <SDL.h>

#include <string>

static void GLAPIENTRY
OpenGLMessageCallback(GLenum source,
                      GLenum type,
                      GLuint id,
                      GLenum severity,
                      GLsizei length,
                      const GLchar* message,
                      const void* userParam)
{
  if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
  {
    fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
            (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
            type, severity, message);
  }
}

namespace OrthancStone
{
  void RtViewerView::EnableGLDebugOutput()
  {
    glEnable(GL_DEBUG_OUTPUT);
    glDebugMessageCallback(OpenGLMessageCallback, 0);
  }

  boost::shared_ptr<IViewport> RtViewerView::CreateViewport(const std::string& canvasId)
  {
    // False means we do NOT let Windows treat this as a legacy application that needs to be scaled
    return SdlOpenGLViewport::Create(canvasId, 1024, 1024, false);
  }

  void RtViewerApp::ProcessOptions(int argc, char* argv[])
  {
    namespace po = boost::program_options;
    po::options_description desc("Usage:");

    desc.add_options()
      ("loglevel", po::value<std::string>()->default_value("WARNING"),
       "You can choose WARNING, INFO or TRACE for the logging level: Errors and warnings will always be displayed. (default: WARNING)")

      ("orthanc", po::value<std::string>()->default_value("http://localhost:8042"),
       "Base URL of the Orthanc instance")

      ("ctseries", po::value<std::string>()->default_value("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"),
       "Orthanc ID of the CT series to load")

      ("rtdose", po::value<std::string>()->default_value("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"),
       "Orthanc ID of the RTDOSE instance to load")

      ("rtstruct", po::value<std::string>()->default_value("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"),
       "Orthanc ID of the RTSTRUCT instance to load")
      ;

    po::variables_map vm;
    try
    {
      po::store(po::parse_command_line(argc, argv, desc), vm);
      po::notify(vm);
    }
    catch (std::exception& e)
    {
      std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl;
    }

    for (po::variables_map::iterator it = vm.begin(); it != vm.end(); ++it)
    {
      std::string key = it->first;
      const po::variable_value& value = it->second;
      const std::string& strValue = value.as<std::string>();
      SetArgument(key, strValue);
    }
  }

  void RtViewerApp::RunSdl(int argc, char* argv[])
  {
    ProcessOptions(argc, argv);

    /**
    Create the shared loaders context
    */
    loadersContext_.reset(new GenericLoadersContext(1, 4, 1));

    // we are in SDL --> downcast to concrete type
    boost::shared_ptr<GenericLoadersContext> loadersContext = boost::dynamic_pointer_cast<GenericLoadersContext>(loadersContext_);

    /**
      Url of the Orthanc instance
      Typically, in a native application (Qt, SDL), it will be an absolute URL like "http://localhost:8042". In 
      wasm on the browser, it could be an absolute URL, provided you do not have cross-origin problems, or a relative
      URL. In our wasm samples, it is set to "..", because we set up either a reverse proxy or an Orthanc ServeFolders
      plugin that serves the main web application from an URL like "http://localhost:8042/rtviewer" (with ".." leading 
      to the main Orthanc root URL)
    */
    std::string orthancUrl = arguments_["orthanc"];

    {
      Orthanc::WebServiceParameters p;
      if (HasArgument("orthanc"))
      {
        p.SetUrl(orthancUrl);
      }
      if (HasArgument("user"))
      {
        ORTHANC_ASSERT(HasArgument("password"));
        p.SetCredentials(GetArgument("user"), GetArgument("password"));
      } 
      else
      {
        ORTHANC_ASSERT(!HasArgument("password"));
      }
      loadersContext->SetOrthancParameters(p);
    }

    loadersContext->StartOracle();

    CreateLoaders();

    /**
    Create viewports
    */
    CreateView("RtViewer Axial", VolumeProjection_Axial);
    CreateView("RtViewer Coronal", VolumeProjection_Coronal);
    CreateView("RtViewer Sagittal", VolumeProjection_Sagittal);

    for (size_t i = 0; i < views_.size(); ++i)
    {
      views_[i]->PrepareViewport();
      views_[i]->EnableGLDebugOutput();
    }

    DefaultViewportInteractor interactor;

    /**
    It is very important that the Oracle (responsible for network I/O) be started before creating and firing the 
    loaders, for any command scheduled by the loader before the oracle is started will be lost.
    */
    StartLoaders();


    std::vector<boost::shared_ptr<SdlViewport>> sdlViewports;
    for(size_t i = 0; i < views_.size(); ++i)
    {
      boost::shared_ptr<RtViewerView> view = views_[i];
      boost::shared_ptr<IViewport> viewport = view->GetViewport();
      boost::shared_ptr<SdlViewport> sdlViewport = 
        boost::dynamic_pointer_cast<SdlViewport>(viewport);
      sdlViewports.push_back(sdlViewport);
    }
    OrthancStoneHelpers::SdlRunLoop(sdlViewports, interactor);
    loadersContext->StopOracle();
  }

  void RtViewerView::TakeScreenshot(const std::string& target,
                                   unsigned int canvasWidth,
                                   unsigned int canvasHeight)
  {
    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
    ViewportController& controller = lock->GetController();
    Scene2D& scene = controller.GetScene();

    CairoCompositor compositor(canvasWidth, canvasHeight);
    compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE_0, Orthanc::Encoding_Latin1);
    compositor.Refresh(scene);

    Orthanc::ImageAccessor canvas;
    compositor.GetCanvas().GetReadOnlyAccessor(canvas);

    Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false);
    Orthanc::ImageProcessing::Convert(png, canvas);

    Orthanc::PngWriter writer;
    writer.WriteToFile(target, png);
  }
}

boost::weak_ptr<OrthancStone::RtViewerApp> g_app;

/**
 * IMPORTANT: The full arguments to "main()" are needed for SDL on
 * Windows. Otherwise, one gets the linking error "undefined reference
 * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
 **/
int main(int argc, char* argv[])
{
  using namespace OrthancStone;

  StoneInitialize();

  try
  {
    boost::shared_ptr<RtViewerApp> app = RtViewerApp::Create();
    g_app = app;
    app->RunSdl(argc,argv);
  }
  catch (Orthanc::OrthancException& e)
  {
    LOG(ERROR) << "EXCEPTION: " << e.What();
  }

  StoneFinalize();

  return 0;
}