changeset 813:bc7ee59420a1

merge
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 28 May 2019 18:31:44 +0200
parents abc08ac721d3 (current diff) ffec76a5f7eb (diff)
children aead999345e0
files Framework/Toolbox/ISeriesLoader.h Framework/Toolbox/ParallelSlices.cpp Framework/Toolbox/ParallelSlices.h Framework/Toolbox/ParallelSlicesCursor.cpp Framework/Toolbox/ParallelSlicesCursor.h Framework/Viewport/CairoContext.cpp Framework/Viewport/CairoContext.h Framework/Viewport/CairoFont.cpp Framework/Viewport/CairoFont.h Framework/Viewport/CairoSurface.cpp Framework/Viewport/CairoSurface.h
diffstat 40 files changed, 1302 insertions(+), 1274 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Sdl/SdlCairoSurface.h	Tue May 28 18:31:17 2019 +0200
+++ b/Applications/Sdl/SdlCairoSurface.h	Tue May 28 18:31:44 2019 +0200
@@ -24,7 +24,7 @@
 #if ORTHANC_ENABLE_SDL == 1
 
 #include "SdlWindow.h"
-#include "../../Framework/Viewport/CairoSurface.h"
+#include "../../Framework/Wrappers/CairoSurface.h"
 #include "../../Framework/Deprecated/Viewport/IViewport.h"
 
 #include <boost/thread/mutex.hpp>
--- a/Framework/Deprecated/Layers/ILayerRenderer.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Deprecated/Layers/ILayerRenderer.h	Tue May 28 18:31:44 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Viewport/CairoContext.h"
+#include "../../Wrappers/CairoContext.h"
 #include "../../Toolbox/CoordinateSystem3D.h"
 #include "../Toolbox/ViewportGeometry.h"
 #include "RenderStyle.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ISeriesLoader.h	Tue May 28 18:31:44 2019 +0200
@@ -0,0 +1,59 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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/>.
+ **/
+
+
+#pragma once
+
+#include "ParallelSlices.h"
+
+#include <Images/ImageAccessor.h>
+#include <Plugins/Samples/Common/IDicomDataset.h>
+
+namespace Deprecated
+{
+  class ISeriesLoader : public boost::noncopyable
+  {
+  public:
+    virtual ~ISeriesLoader()
+    {
+    }
+    
+    virtual ParallelSlices& GetGeometry() = 0;
+
+    virtual Orthanc::PixelFormat GetPixelFormat() = 0;
+
+    virtual unsigned int GetWidth() = 0;
+
+    virtual unsigned int GetHeight() = 0;
+
+    virtual OrthancPlugins::IDicomDataset* DownloadDicom(size_t index) = 0;
+
+    // This downloads the frame from Orthanc. The resulting pixel
+    // format must be Grayscale8, Grayscale16, SignedGrayscale16 or
+    // RGB24. Orthanc Stone assumes the conversion of the photometric
+    // interpretation is done by Orthanc.
+    virtual Orthanc::ImageAccessor* DownloadFrame(size_t index) = 0;
+
+    virtual Orthanc::ImageAccessor* DownloadJpegFrame(size_t index,
+                                                      unsigned int quality) = 0;
+
+    virtual bool IsJpegAvailable() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ParallelSlices.cpp	Tue May 28 18:31:44 2019 +0200
@@ -0,0 +1,215 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "ParallelSlices.h"
+
+#include "../../Toolbox/GeometryToolbox.h"
+#include "../../Volumes/ImageBuffer3D.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  ParallelSlices::ParallelSlices()
+  {
+    Clear();
+  }
+
+
+  ParallelSlices::ParallelSlices(const ParallelSlices& other)
+  {
+    normal_ = other.normal_;
+
+    slices_.resize(other.slices_.size());
+
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      assert(other.slices_[i] != NULL);
+      slices_[i] = new OrthancStone::CoordinateSystem3D(*other.slices_[i]);
+    }
+  }
+
+
+  void ParallelSlices::Clear()
+  {
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      if (slices_[i] != NULL)
+      {
+        delete slices_[i];
+        slices_[i] = NULL;
+      }
+    }
+
+    slices_.clear();
+    OrthancStone::LinearAlgebra::AssignVector(normal_, 0, 0, 1);
+  }
+  
+
+  ParallelSlices::~ParallelSlices()
+  {
+    Clear();
+  }
+
+
+  void ParallelSlices::AddSlice(const OrthancStone::CoordinateSystem3D& slice)
+  {
+    if (slices_.empty())
+    {
+      normal_ = slice.GetNormal();
+      slices_.push_back(new OrthancStone::CoordinateSystem3D(slice));
+    }
+    else if (OrthancStone::GeometryToolbox::IsParallel(slice.GetNormal(), normal_))
+    {
+      slices_.push_back(new OrthancStone::CoordinateSystem3D(slice));
+    }
+    else
+    {
+      LOG(ERROR) << "Trying to add a slice that is not parallel to the previous ones";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  void ParallelSlices::AddSlice(const OrthancStone::Vector& origin,
+                                const OrthancStone::Vector& axisX,
+                                const OrthancStone::Vector& axisY)
+  {
+    OrthancStone::CoordinateSystem3D slice(origin, axisX, axisY);
+    AddSlice(slice);
+  }
+
+
+  const OrthancStone::CoordinateSystem3D& ParallelSlices::GetSlice(size_t index) const
+  {
+    if (index >= slices_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return *slices_[index];
+    }
+  }
+
+
+  bool ParallelSlices::ComputeClosestSlice(size_t& closestSlice,
+                                           double& closestDistance,
+                                           const OrthancStone::Vector& origin) const
+  {
+    if (slices_.empty())
+    {
+      return false;
+    }
+
+    double reference = boost::numeric::ublas::inner_prod(origin, normal_);
+
+    closestSlice = 0;
+    closestDistance = std::numeric_limits<double>::infinity();
+
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      double distance = fabs(boost::numeric::ublas::inner_prod(slices_[i]->GetOrigin(), normal_) - reference);
+
+      if (distance < closestDistance)
+      {
+        closestSlice = i;
+        closestDistance = distance;
+      }
+    }
+
+    return true;
+  }
+
+
+  ParallelSlices* ParallelSlices::Reverse() const
+  {
+    std::auto_ptr<ParallelSlices> reversed(new ParallelSlices);
+
+    for (size_t i = slices_.size(); i > 0; i--)
+    {
+      const OrthancStone::CoordinateSystem3D& slice = *slices_[i - 1];
+
+      reversed->AddSlice(slice.GetOrigin(),
+                         -slice.GetAxisX(),
+                         slice.GetAxisY());
+    }
+
+    return reversed.release();
+  }
+
+
+  ParallelSlices* ParallelSlices::FromVolumeImage(const OrthancStone::VolumeImageGeometry& geometry,
+                                                  OrthancStone::VolumeProjection projection)
+  {
+    const OrthancStone::Vector dimensions = geometry.GetVoxelDimensions(OrthancStone::VolumeProjection_Axial);
+    const OrthancStone::CoordinateSystem3D& axial = geometry.GetAxialGeometry();
+    
+    std::auto_ptr<ParallelSlices> result(new ParallelSlices);
+
+    switch (projection)
+    {
+      case OrthancStone::VolumeProjection_Axial:
+        for (unsigned int z = 0; z < geometry.GetDepth(); z++)
+        {
+          OrthancStone::Vector origin = axial.GetOrigin();
+          origin += static_cast<double>(z) * dimensions[2] * axial.GetNormal();
+
+          result->AddSlice(origin,
+                           axial.GetAxisX(),
+                           axial.GetAxisY());
+        }
+        break;
+
+      case OrthancStone::VolumeProjection_Coronal:
+        for (unsigned int y = 0; y < geometry.GetHeight(); y++)
+        {
+          OrthancStone::Vector origin = axial.GetOrigin();
+          origin += static_cast<double>(y) * dimensions[1] * axial.GetAxisY();
+          origin += static_cast<double>(geometry.GetDepth() - 1) * dimensions[2] * axial.GetNormal();
+
+          result->AddSlice(origin,
+                           axial.GetAxisX(),
+                           -axial.GetNormal());
+        }
+        break;
+
+      case OrthancStone::VolumeProjection_Sagittal:
+        for (unsigned int x = 0; x < geometry.GetWidth(); x++)
+        {
+          OrthancStone::Vector origin = axial.GetOrigin();
+          origin += static_cast<double>(x) * dimensions[0] * axial.GetAxisX();
+          origin += static_cast<double>(geometry.GetDepth() - 1) * dimensions[2] * axial.GetNormal();
+
+          result->AddSlice(origin,
+                           axial.GetAxisY(),
+                           -axial.GetNormal());
+        }
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    return result.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ParallelSlices.h	Tue May 28 18:31:44 2019 +0200
@@ -0,0 +1,73 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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/>.
+ **/
+
+
+#pragma once
+
+#include "../../Toolbox/CoordinateSystem3D.h"
+#include "../../Volumes/VolumeImageGeometry.h"
+
+namespace Deprecated
+{
+  class ParallelSlices : public boost::noncopyable
+  {
+  private:
+    OrthancStone::Vector  normal_;
+    std::vector<OrthancStone::CoordinateSystem3D*>  slices_;
+    
+    ParallelSlices& operator= (const ParallelSlices& other);  // Forbidden
+
+    void Clear();
+
+  public:
+    ParallelSlices();
+
+    ParallelSlices(const ParallelSlices& other);
+
+    ~ParallelSlices();
+
+    const OrthancStone::Vector& GetNormal() const
+    {
+      return normal_;
+    }
+
+    void AddSlice(const OrthancStone::CoordinateSystem3D& slice);
+
+    void AddSlice(const OrthancStone::Vector& origin,
+                  const OrthancStone::Vector& axisX,
+                  const OrthancStone::Vector& axisY);
+
+    size_t GetSliceCount() const
+    {
+      return slices_.size();
+    }
+
+    const OrthancStone::CoordinateSystem3D& GetSlice(size_t index) const;
+
+    bool ComputeClosestSlice(size_t& closestSlice,
+                             double& closestDistance,
+                             const OrthancStone::Vector& origin) const;
+
+    ParallelSlices* Reverse() const;
+
+    static ParallelSlices* FromVolumeImage(const OrthancStone::VolumeImageGeometry& geometry,
+                                           OrthancStone::VolumeProjection projection);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ParallelSlicesCursor.cpp	Tue May 28 18:31:44 2019 +0200
@@ -0,0 +1,221 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "ParallelSlicesCursor.h"
+
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  size_t ParallelSlicesCursor::GetDefaultSlice()
+  {
+    if (slices_.get() == NULL)
+    {
+      return 0;
+    }
+    else
+    {
+      return slices_->GetSliceCount() / 2;
+    }
+  }
+
+
+  size_t ParallelSlicesCursor::GetSliceCount()
+  {
+    if (slices_.get() == NULL)
+    {
+      return 0;
+    }
+    else
+    {
+      return slices_->GetSliceCount();
+    }
+  }
+
+
+  OrthancStone::CoordinateSystem3D ParallelSlicesCursor::GetSlice(size_t slice)
+  {
+    if (slices_.get() == NULL)
+    {
+      return OrthancStone::CoordinateSystem3D();
+    }
+    else
+    {
+      return slices_->GetSlice(slice);
+    }
+  }
+
+
+  void ParallelSlicesCursor::SetGeometry(const ParallelSlices& slices)
+  {
+    slices_.reset(new ParallelSlices(slices));
+
+    currentSlice_ = GetDefaultSlice();
+  }
+
+
+  OrthancStone::CoordinateSystem3D ParallelSlicesCursor::GetCurrentSlice()
+  {
+    if (slices_.get() != NULL &&
+        currentSlice_ < slices_->GetSliceCount())
+    {
+      return slices_->GetSlice(currentSlice_);
+    }
+    else
+    {
+      return OrthancStone::CoordinateSystem3D();  // No slice is available, return the canonical geometry
+    }
+  }
+
+
+  bool ParallelSlicesCursor::SetDefaultSlice()
+  {
+    size_t slice = GetDefaultSlice();
+
+    if (currentSlice_ != slice)
+    {
+      currentSlice_ = slice;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool ParallelSlicesCursor::ApplyOffset(OrthancStone::SliceOffsetMode mode,
+                                         int offset)
+  {
+    if (slices_.get() == NULL)
+    {
+      return false;
+    }
+
+    int count = static_cast<int>(slices_->GetSliceCount());
+    if (count == 0)
+    {
+      return false;
+    }
+
+    int slice;
+    if (static_cast<int>(currentSlice_) >= count)
+    {
+      slice = count - 1;
+    }
+    else
+    {
+      slice = static_cast<int>(currentSlice_);
+    }
+
+    switch (mode)
+    {
+      case OrthancStone::SliceOffsetMode_Absolute:
+      {
+        slice = offset;
+        break;
+      }
+
+      case OrthancStone::SliceOffsetMode_Relative:
+      {
+        slice += offset;
+        break;
+      }
+
+      case OrthancStone::SliceOffsetMode_Loop:
+      {
+        slice += offset;
+        while (slice < 0)
+        {
+          slice += count;
+        }
+
+        while (slice >= count)
+        {
+          slice -= count;
+        }
+
+        break;
+      }
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (slice < 0)
+    {
+      slice = 0;
+    }
+
+    if (slice >= count)
+    {
+      slice = count - 1;
+    }
+
+    if (slice != static_cast<int>(currentSlice_))
+    {
+      currentSlice_ = static_cast<int>(slice);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool ParallelSlicesCursor::ApplyWheelEvent(OrthancStone::MouseWheelDirection direction,
+                                             OrthancStone::KeyboardModifiers modifiers)
+  {
+    int offset = (modifiers & OrthancStone::KeyboardModifiers_Control ? 10 : 1);
+
+    switch (direction)
+    {
+      case OrthancStone::MouseWheelDirection_Down:
+        return ApplyOffset(OrthancStone::SliceOffsetMode_Relative, -offset);
+
+      case OrthancStone::MouseWheelDirection_Up:
+        return ApplyOffset(OrthancStone::SliceOffsetMode_Relative, offset);
+
+      default:
+        return false;
+    }
+  }
+
+
+  bool ParallelSlicesCursor::LookupSliceContainingPoint(const OrthancStone::Vector& p)
+  {
+    size_t slice;
+    double distance;
+
+    if (slices_.get() != NULL &&
+        slices_->ComputeClosestSlice(slice, distance, p))
+    {
+      if (currentSlice_ != slice)
+      {
+        currentSlice_ = slice;
+        return true;
+      }
+    }
+
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ParallelSlicesCursor.h	Tue May 28 18:31:44 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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/>.
+ **/
+
+
+#pragma once
+
+#include "ParallelSlices.h"
+#include "../../StoneEnumerations.h"
+
+namespace Deprecated
+{
+  class ParallelSlicesCursor : public boost::noncopyable
+  {
+  private:
+    std::auto_ptr<ParallelSlices>  slices_;
+    size_t                         currentSlice_;
+
+    size_t GetDefaultSlice();
+
+  public:
+    ParallelSlicesCursor() :
+      currentSlice_(0)
+    {
+    }
+
+    void SetGeometry(const ParallelSlices& slices);
+
+    size_t GetSliceCount();
+
+    OrthancStone::CoordinateSystem3D GetSlice(size_t slice);
+
+    OrthancStone::CoordinateSystem3D GetCurrentSlice();
+
+    // Returns "true" iff. the slice has actually changed
+    bool SetDefaultSlice();
+
+    // Returns "true" iff. the slice has actually changed
+    bool ApplyOffset(OrthancStone::SliceOffsetMode mode,
+                     int offset);
+
+    // Returns "true" iff. the slice has actually changed
+    bool ApplyWheelEvent(OrthancStone::MouseWheelDirection direction,
+                         OrthancStone::KeyboardModifiers modifiers);
+
+    // Returns "true" iff. the slice has actually changed
+    bool LookupSliceContainingPoint(const OrthancStone::Vector& p);
+  };
+}
--- a/Framework/Deprecated/Toolbox/ViewportGeometry.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Deprecated/Toolbox/ViewportGeometry.h	Tue May 28 18:31:44 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Viewport/CairoContext.h"
+#include "../../Wrappers/CairoContext.h"
 #include "../../Toolbox/Extent2D.h"
 #include "../../Toolbox/LinearAlgebra.h"
 #include "../Viewport/IMouseTracker.h"  // to include "Touch" definition
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Viewport/CairoFont.cpp	Tue May 28 18:31:44 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "CairoFont.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  CairoFont::CairoFont(const char* family,
+                       cairo_font_slant_t slant,
+                       cairo_font_weight_t weight)
+  {
+    font_ = cairo_toy_font_face_create(family, slant, weight);
+    if (font_ == NULL)
+    {
+      LOG(ERROR) << "Unknown font: " << family;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+  }
+
+
+  CairoFont::~CairoFont()
+  {
+    if (font_ != NULL)
+    {
+      cairo_font_face_destroy(font_);
+    }
+  }
+
+
+  void CairoFont::Draw(OrthancStone::CairoContext& context,
+                       const std::string& text,
+                       double size)
+  {
+    if (size <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    cairo_t* cr = context.GetObject();
+    cairo_set_font_face(cr, font_);
+    cairo_set_font_size(cr, size);
+    cairo_show_text(cr, text.c_str());    
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Viewport/CairoFont.h	Tue May 28 18:31:44 2019 +0200
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#if ORTHANC_SANDBOXED == 1
+#  error The class CairoFont cannot be used in sandboxed environments
+#endif
+
+#include "../../Wrappers/CairoContext.h"
+
+namespace Deprecated
+{
+  class CairoFont : public boost::noncopyable
+  {
+  private:
+    cairo_font_face_t*  font_;
+
+  public:
+    CairoFont(const char* family,
+              cairo_font_slant_t slant,
+              cairo_font_weight_t weight);
+
+    ~CairoFont();
+
+    void Draw(OrthancStone::CairoContext& context,
+              const std::string& text,
+              double size);
+  };
+}
--- a/Framework/Deprecated/Viewport/IMouseTracker.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Deprecated/Viewport/IMouseTracker.h	Tue May 28 18:31:44 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Viewport/CairoSurface.h"
+#include "../../Wrappers/CairoSurface.h"
 #include <vector>
 
 namespace Deprecated
--- a/Framework/Deprecated/Widgets/IWorldSceneMouseTracker.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Deprecated/Widgets/IWorldSceneMouseTracker.h	Tue May 28 18:31:44 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Viewport/CairoContext.h"
+#include "../../Wrappers/CairoContext.h"
 #include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition
 
 namespace Deprecated
--- a/Framework/Deprecated/Widgets/WidgetBase.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Deprecated/Widgets/WidgetBase.h	Tue May 28 18:31:44 2019 +0200
@@ -23,7 +23,7 @@
 
 #include "IWidget.h"
 
-#include "../../Viewport/CairoContext.h"
+#include "../../Wrappers/CairoContext.h"
 #include "../Viewport/WidgetViewport.h"
 
 namespace Deprecated
--- a/Framework/Radiography/RadiographyLayer.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Radiography/RadiographyLayer.h	Tue May 28 18:31:44 2019 +0200
@@ -23,7 +23,7 @@
 
 #include "../Toolbox/AffineTransform2D.h"
 #include "../Toolbox/Extent2D.h"
-#include "../Viewport/CairoContext.h"
+#include "../Wrappers/CairoContext.h"
 #include "../Messages/IMessage.h"
 #include "../Messages/IObservable.h"
 
--- a/Framework/Scene2D/CairoCompositor.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Scene2D/CairoCompositor.h	Tue May 28 18:31:44 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Fonts/GlyphBitmapAlphabet.h"
-#include "../Viewport/CairoContext.h"
+#include "../Wrappers/CairoContext.h"
 #include "Internals/CompositorHelper.h"
 #include "Internals/ICairoContextProvider.h"
 
--- a/Framework/Scene2D/Internals/CairoColorTextureRenderer.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoColorTextureRenderer.h	Tue May 28 18:31:44 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Viewport/CairoSurface.h"
+#include "../../Wrappers/CairoSurface.h"
 #include "CompositorHelper.h"
 #include "ICairoContextProvider.h"
 
--- a/Framework/Scene2D/Internals/CairoFloatTextureRenderer.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.h	Tue May 28 18:31:44 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Viewport/CairoSurface.h"
+#include "../../Wrappers/CairoSurface.h"
 #include "CompositorHelper.h"
 #include "ICairoContextProvider.h"
 
--- a/Framework/Scene2D/Internals/CairoInfoPanelRenderer.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoInfoPanelRenderer.h	Tue May 28 18:31:44 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Viewport/CairoSurface.h"
+#include "../../Wrappers/CairoSurface.h"
 #include "CompositorHelper.h"
 #include "ICairoContextProvider.h"
 
--- a/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.h	Tue May 28 18:31:44 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Viewport/CairoSurface.h"
+#include "../../Wrappers/CairoSurface.h"
 #include "CompositorHelper.h"
 #include "ICairoContextProvider.h"
 
--- a/Framework/Scene2D/Internals/CairoTextRenderer.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoTextRenderer.h	Tue May 28 18:31:44 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../../Fonts/GlyphBitmapAlphabet.h"
-#include "../../Viewport/CairoSurface.h"
+#include "../../Wrappers/CairoSurface.h"
 #include "../TextSceneLayer.h"
 #include "CairoBaseRenderer.h"
 
--- a/Framework/Toolbox/FiniteProjectiveCamera.cpp	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Toolbox/FiniteProjectiveCamera.cpp	Tue May 28 18:31:44 2019 +0200
@@ -23,7 +23,6 @@
 
 #include "GeometryToolbox.h"
 #include "SubpixelReader.h"
-#include "ParallelSlices.h"
 
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
@@ -317,7 +316,7 @@
     LOG(WARNING) << "Output image size: " << target.GetWidth() << "x" << target.GetHeight();
     LOG(WARNING) << "Output pixel format: " << Orthanc::EnumerationToString(target.GetFormat());
 
-    std::auto_ptr<OrthancStone::ParallelSlices> slices(OrthancStone::ParallelSlices::FromVolumeImage(geometry, projection));
+    const unsigned int slicesCount = geometry.GetProjectionDepth(projection);
     const OrthancStone::Vector pixelSpacing = geometry.GetVoxelDimensions(projection);
     const unsigned int targetWidth = target.GetWidth();
     const unsigned int targetHeight = target.GetHeight();
@@ -329,11 +328,11 @@
 
     typedef SubpixelReader<SourceFormat, ImageInterpolation_Nearest>  SourceReader;
     
-    for (size_t z = 0; z < slices->GetSliceCount(); z++)
+    for (unsigned int z = 0; z < slicesCount; z++)
     {
-      LOG(INFO) << "Applying raytracer on slice: " << z << "/" << slices->GetSliceCount();
-      
-      const OrthancStone::CoordinateSystem3D& slice = slices->GetSlice(z);
+      LOG(INFO) << "Applying raytracer on slice: " << z << "/" << slicesCount;
+
+      OrthancStone::CoordinateSystem3D slice = geometry.GetProjectionSlice(projection, z);
       OrthancStone::ImageBuffer3D::SliceReader sliceReader(source, projection, static_cast<unsigned int>(z));
 
       SourceReader pixelReader(sliceReader.GetAccessor());
--- a/Framework/Toolbox/ISeriesLoader.h	Tue May 28 18:31:17 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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/>.
- **/
-
-
-#pragma once
-
-#include "ParallelSlices.h"
-
-#include <Images/ImageAccessor.h>
-#include <Plugins/Samples/Common/IDicomDataset.h>
-
-namespace OrthancStone
-{
-  class ISeriesLoader : public boost::noncopyable
-  {
-  public:
-    virtual ~ISeriesLoader()
-    {
-    }
-    
-    virtual ParallelSlices& GetGeometry() = 0;
-
-    virtual Orthanc::PixelFormat GetPixelFormat() = 0;
-
-    virtual unsigned int GetWidth() = 0;
-
-    virtual unsigned int GetHeight() = 0;
-
-    virtual OrthancPlugins::IDicomDataset* DownloadDicom(size_t index) = 0;
-
-    // This downloads the frame from Orthanc. The resulting pixel
-    // format must be Grayscale8, Grayscale16, SignedGrayscale16 or
-    // RGB24. Orthanc Stone assumes the conversion of the photometric
-    // interpretation is done by Orthanc.
-    virtual Orthanc::ImageAccessor* DownloadFrame(size_t index) = 0;
-
-    virtual Orthanc::ImageAccessor* DownloadJpegFrame(size_t index,
-                                                      unsigned int quality) = 0;
-
-    virtual bool IsJpegAvailable() = 0;
-  };
-}
--- a/Framework/Toolbox/ParallelSlices.cpp	Tue May 28 18:31:17 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,215 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "ParallelSlices.h"
-
-#include "GeometryToolbox.h"
-#include "../Volumes/ImageBuffer3D.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  ParallelSlices::ParallelSlices()
-  {
-    Clear();
-  }
-
-
-  ParallelSlices::ParallelSlices(const ParallelSlices& other)
-  {
-    normal_ = other.normal_;
-
-    slices_.resize(other.slices_.size());
-
-    for (size_t i = 0; i < slices_.size(); i++)
-    {
-      assert(other.slices_[i] != NULL);
-      slices_[i] = new CoordinateSystem3D(*other.slices_[i]);
-    }
-  }
-
-
-  void ParallelSlices::Clear()
-  {
-    for (size_t i = 0; i < slices_.size(); i++)
-    {
-      if (slices_[i] != NULL)
-      {
-        delete slices_[i];
-        slices_[i] = NULL;
-      }
-    }
-
-    slices_.clear();
-    LinearAlgebra::AssignVector(normal_, 0, 0, 1);
-  }
-  
-
-  ParallelSlices::~ParallelSlices()
-  {
-    Clear();
-  }
-
-
-  void ParallelSlices::AddSlice(const CoordinateSystem3D& slice)
-  {
-    if (slices_.empty())
-    {
-      normal_ = slice.GetNormal();
-      slices_.push_back(new CoordinateSystem3D(slice));
-    }
-    else if (GeometryToolbox::IsParallel(slice.GetNormal(), normal_))
-    {
-      slices_.push_back(new CoordinateSystem3D(slice));
-    }
-    else
-    {
-      LOG(ERROR) << "Trying to add a slice that is not parallel to the previous ones";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-
-  void ParallelSlices::AddSlice(const Vector& origin,
-                                const Vector& axisX,
-                                const Vector& axisY)
-  {
-    CoordinateSystem3D slice(origin, axisX, axisY);
-    AddSlice(slice);
-  }
-
-
-  const CoordinateSystem3D& ParallelSlices::GetSlice(size_t index) const
-  {
-    if (index >= slices_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      return *slices_[index];
-    }
-  }
-
-
-  bool ParallelSlices::ComputeClosestSlice(size_t& closestSlice,
-                                           double& closestDistance,
-                                           const Vector& origin) const
-  {
-    if (slices_.empty())
-    {
-      return false;
-    }
-
-    double reference = boost::numeric::ublas::inner_prod(origin, normal_);
-
-    closestSlice = 0;
-    closestDistance = std::numeric_limits<double>::infinity();
-
-    for (size_t i = 0; i < slices_.size(); i++)
-    {
-      double distance = fabs(boost::numeric::ublas::inner_prod(slices_[i]->GetOrigin(), normal_) - reference);
-
-      if (distance < closestDistance)
-      {
-        closestSlice = i;
-        closestDistance = distance;
-      }
-    }
-
-    return true;
-  }
-
-
-  ParallelSlices* ParallelSlices::Reverse() const
-  {
-    std::auto_ptr<ParallelSlices> reversed(new ParallelSlices);
-
-    for (size_t i = slices_.size(); i > 0; i--)
-    {
-      const CoordinateSystem3D& slice = *slices_[i - 1];
-
-      reversed->AddSlice(slice.GetOrigin(),
-                         -slice.GetAxisX(),
-                         slice.GetAxisY());
-    }
-
-    return reversed.release();
-  }
-
-
-  ParallelSlices* ParallelSlices::FromVolumeImage(const VolumeImageGeometry& geometry,
-                                                  VolumeProjection projection)
-  {
-    const Vector dimensions = geometry.GetVoxelDimensions(VolumeProjection_Axial);
-    const CoordinateSystem3D& axial = geometry.GetAxialGeometry();
-    
-    std::auto_ptr<ParallelSlices> result(new ParallelSlices);
-
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        for (unsigned int z = 0; z < geometry.GetDepth(); z++)
-        {
-          Vector origin = axial.GetOrigin();
-          origin += static_cast<double>(z) * dimensions[2] * axial.GetNormal();
-
-          result->AddSlice(origin,
-                           axial.GetAxisX(),
-                           axial.GetAxisY());
-        }
-        break;
-
-      case VolumeProjection_Coronal:
-        for (unsigned int y = 0; y < geometry.GetHeight(); y++)
-        {
-          Vector origin = axial.GetOrigin();
-          origin += static_cast<double>(y) * dimensions[1] * axial.GetAxisY();
-          origin += static_cast<double>(geometry.GetDepth() - 1) * dimensions[2] * axial.GetNormal();
-
-          result->AddSlice(origin,
-                           axial.GetAxisX(),
-                           -axial.GetNormal());
-        }
-        break;
-
-      case VolumeProjection_Sagittal:
-        for (unsigned int x = 0; x < geometry.GetWidth(); x++)
-        {
-          Vector origin = axial.GetOrigin();
-          origin += static_cast<double>(x) * dimensions[0] * axial.GetAxisX();
-          origin += static_cast<double>(geometry.GetDepth() - 1) * dimensions[2] * axial.GetNormal();
-
-          result->AddSlice(origin,
-                           axial.GetAxisY(),
-                           -axial.GetNormal());
-        }
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    return result.release();
-  }
-}
--- a/Framework/Toolbox/ParallelSlices.h	Tue May 28 18:31:17 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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/>.
- **/
-
-
-#pragma once
-
-#include "CoordinateSystem3D.h"
-#include "../Volumes/VolumeImageGeometry.h"
-
-namespace OrthancStone
-{
-  class ParallelSlices : public boost::noncopyable
-  {
-  private:
-    Vector                            normal_;
-    std::vector<CoordinateSystem3D*>  slices_;
-    
-    ParallelSlices& operator= (const ParallelSlices& other);  // Forbidden
-
-    void Clear();
-
-  public:
-    ParallelSlices();
-
-    ParallelSlices(const ParallelSlices& other);
-
-    ~ParallelSlices();
-
-    const Vector& GetNormal() const
-    {
-      return normal_;
-    }
-
-    void AddSlice(const CoordinateSystem3D& slice);
-
-    void AddSlice(const Vector& origin,
-                  const Vector& axisX,
-                  const Vector& axisY);
-
-    size_t GetSliceCount() const
-    {
-      return slices_.size();
-    }
-
-    const CoordinateSystem3D& GetSlice(size_t index) const;
-
-    bool ComputeClosestSlice(size_t& closestSlice,
-                             double& closestDistance,
-                             const Vector& origin) const;
-
-    ParallelSlices* Reverse() const;
-
-    static ParallelSlices* FromVolumeImage(const VolumeImageGeometry& geometry,
-                                           VolumeProjection projection);
-  };
-}
--- a/Framework/Toolbox/ParallelSlicesCursor.cpp	Tue May 28 18:31:17 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,221 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "ParallelSlicesCursor.h"
-
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  size_t ParallelSlicesCursor::GetDefaultSlice()
-  {
-    if (slices_.get() == NULL)
-    {
-      return 0;
-    }
-    else
-    {
-      return slices_->GetSliceCount() / 2;
-    }
-  }
-
-
-  size_t ParallelSlicesCursor::GetSliceCount()
-  {
-    if (slices_.get() == NULL)
-    {
-      return 0;
-    }
-    else
-    {
-      return slices_->GetSliceCount();
-    }
-  }
-
-
-  CoordinateSystem3D ParallelSlicesCursor::GetSlice(size_t slice)
-  {
-    if (slices_.get() == NULL)
-    {
-      return CoordinateSystem3D();
-    }
-    else
-    {
-      return slices_->GetSlice(slice);
-    }
-  }
-
-
-  void ParallelSlicesCursor::SetGeometry(const ParallelSlices& slices)
-  {
-    slices_.reset(new ParallelSlices(slices));
-
-    currentSlice_ = GetDefaultSlice();
-  }
-
-
-  CoordinateSystem3D ParallelSlicesCursor::GetCurrentSlice()
-  {
-    if (slices_.get() != NULL &&
-        currentSlice_ < slices_->GetSliceCount())
-    {
-      return slices_->GetSlice(currentSlice_);
-    }
-    else
-    {
-      return CoordinateSystem3D();  // No slice is available, return the canonical geometry
-    }
-  }
-
-
-  bool ParallelSlicesCursor::SetDefaultSlice()
-  {
-    size_t slice = GetDefaultSlice();
-
-    if (currentSlice_ != slice)
-    {
-      currentSlice_ = slice;
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool ParallelSlicesCursor::ApplyOffset(SliceOffsetMode mode,
-                                         int offset)
-  {
-    if (slices_.get() == NULL)
-    {
-      return false;
-    }
-
-    int count = static_cast<int>(slices_->GetSliceCount());
-    if (count == 0)
-    {
-      return false;
-    }
-
-    int slice;
-    if (static_cast<int>(currentSlice_) >= count)
-    {
-      slice = count - 1;
-    }
-    else
-    {
-      slice = static_cast<int>(currentSlice_);
-    }
-
-    switch (mode)
-    {
-      case SliceOffsetMode_Absolute:
-      {
-        slice = offset;
-        break;
-      }
-
-      case SliceOffsetMode_Relative:
-      {
-        slice += offset;
-        break;
-      }
-
-      case SliceOffsetMode_Loop:
-      {
-        slice += offset;
-        while (slice < 0)
-        {
-          slice += count;
-        }
-
-        while (slice >= count)
-        {
-          slice -= count;
-        }
-
-        break;
-      }
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    if (slice < 0)
-    {
-      slice = 0;
-    }
-
-    if (slice >= count)
-    {
-      slice = count - 1;
-    }
-
-    if (slice != static_cast<int>(currentSlice_))
-    {
-      currentSlice_ = static_cast<int>(slice);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool ParallelSlicesCursor::ApplyWheelEvent(MouseWheelDirection direction,
-                                             KeyboardModifiers modifiers)
-  {
-    int offset = (modifiers & KeyboardModifiers_Control ? 10 : 1);
-
-    switch (direction)
-    {
-      case MouseWheelDirection_Down:
-        return ApplyOffset(SliceOffsetMode_Relative, -offset);
-
-      case MouseWheelDirection_Up:
-        return ApplyOffset(SliceOffsetMode_Relative, offset);
-
-      default:
-        return false;
-    }
-  }
-
-
-  bool ParallelSlicesCursor::LookupSliceContainingPoint(const Vector& p)
-  {
-    size_t slice;
-    double distance;
-
-    if (slices_.get() != NULL &&
-        slices_->ComputeClosestSlice(slice, distance, p))
-    {
-      if (currentSlice_ != slice)
-      {
-        currentSlice_ = slice;
-        return true;
-      }
-    }
-
-    return false;
-  }
-}
--- a/Framework/Toolbox/ParallelSlicesCursor.h	Tue May 28 18:31:17 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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/>.
- **/
-
-
-#pragma once
-
-#include "ParallelSlices.h"
-#include "../StoneEnumerations.h"
-
-namespace OrthancStone
-{
-  class ParallelSlicesCursor : public boost::noncopyable
-  {
-  private:
-    std::auto_ptr<ParallelSlices>  slices_;
-    size_t                         currentSlice_;
-
-    size_t GetDefaultSlice();
-
-  public:
-    ParallelSlicesCursor() :
-      currentSlice_(0)
-    {
-    }
-
-    void SetGeometry(const ParallelSlices& slices);
-
-    size_t GetSliceCount();
-
-    CoordinateSystem3D GetSlice(size_t slice);
-
-    CoordinateSystem3D GetCurrentSlice();
-
-    // Returns "true" iff. the slice has actually changed
-    bool SetDefaultSlice();
-
-    // Returns "true" iff. the slice has actually changed
-    bool ApplyOffset(SliceOffsetMode mode,
-                     int offset);
-
-    // Returns "true" iff. the slice has actually changed
-    bool ApplyWheelEvent(MouseWheelDirection direction,
-                         KeyboardModifiers modifiers);
-
-    // Returns "true" iff. the slice has actually changed
-    bool LookupSliceContainingPoint(const Vector& p);
-  };
-}
--- a/Framework/Viewport/CairoContext.cpp	Tue May 28 18:31:17 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "CairoContext.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-
-namespace OrthancStone
-{
-  CairoContext::CairoContext(CairoSurface& surface) :
-    width_(surface.GetWidth()),
-    height_(surface.GetHeight())
-  {
-    context_ = cairo_create(surface.GetObject());
-    if (!context_)
-    {
-      LOG(ERROR) << "Cannot create Cairo drawing context";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-  CairoContext::~CairoContext()
-  {
-    if (context_ != NULL)
-    {
-      cairo_destroy(context_);
-      context_ = NULL;
-    }
-  }
-
-
-  void CairoContext::SetSourceColor(uint8_t red,
-                                    uint8_t green,
-                                    uint8_t blue)
-  {
-    cairo_set_source_rgb(context_,
-                         static_cast<float>(red) / 255.0f,
-                         static_cast<float>(green) / 255.0f,
-                         static_cast<float>(blue) / 255.0f);
-  }
-
-
-  class CairoContext::AlphaSurface : public boost::noncopyable
-  {
-  private:
-    cairo_surface_t  *surface_;
-
-  public:
-    AlphaSurface(unsigned int width,
-                 unsigned int height)
-    {
-      surface_ = cairo_image_surface_create(CAIRO_FORMAT_A8, width, height);
-      
-      if (!surface_)
-      {
-        // Should never occur
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
-      {
-        LOG(ERROR) << "Cannot create a Cairo surface";
-        cairo_surface_destroy(surface_);
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-    
-    ~AlphaSurface()
-    {
-      cairo_surface_destroy(surface_);
-    }
-
-    void GetAccessor(Orthanc::ImageAccessor& target)
-    {
-      target.AssignWritable(Orthanc::PixelFormat_Grayscale8,
-                            cairo_image_surface_get_width(surface_),
-                            cairo_image_surface_get_height(surface_),
-                            cairo_image_surface_get_stride(surface_),
-                            cairo_image_surface_get_data(surface_));
-    }
-
-    void Blit(cairo_t* cr,
-              double x,
-              double y)
-    {
-      cairo_surface_mark_dirty(surface_);
-      cairo_mask_surface(cr, surface_, x, y);
-      cairo_fill(cr);
-    }
-  };
-
-
-  void CairoContext::DrawText(const Orthanc::Font& font,
-                              const std::string& text,
-                              double x,
-                              double y,
-                              BitmapAnchor anchor)
-  {
-    // Render a bitmap containing the text
-    unsigned int width, height;
-    font.ComputeTextExtent(width, height, text);
-    
-    AlphaSurface surface(width, height);
-
-    Orthanc::ImageAccessor accessor;
-    surface.GetAccessor(accessor);
-    font.Draw(accessor, text, 0, 0, 255);
-
-    // Correct the text location given the anchor location
-    double deltaX, deltaY;
-    ComputeAnchorTranslation(deltaX, deltaY, anchor, width, height);
-
-    // Cancel zoom/rotation before blitting the text onto the surface
-    double pixelX = x;
-    double pixelY = y;
-    cairo_user_to_device(context_, &pixelX, &pixelY);
-
-    cairo_save(context_);
-    cairo_identity_matrix(context_);
-
-    // Blit the text bitmap
-    surface.Blit(context_, pixelX + deltaX, pixelY + deltaY);
-    cairo_restore(context_);
-  }
-}
--- a/Framework/Viewport/CairoContext.h	Tue May 28 18:31:17 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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/>.
- **/
-
-
-#pragma once
-
-#include "CairoSurface.h"
-#include "../StoneEnumerations.h"
-
-#include <Core/Images/Font.h>
-
-namespace OrthancStone
-{
-  // This is a RAII wrapper around the Cairo drawing context
-  class CairoContext : public boost::noncopyable
-  {
-  private:
-    class AlphaSurface;
-    
-    cairo_t*      context_;
-    unsigned int  width_;
-    unsigned int  height_;
-
-  public:
-    CairoContext(CairoSurface& surface);
-
-    ~CairoContext();
-
-    cairo_t* GetObject()
-    {
-      return context_;
-    }
-
-    unsigned int GetWidth() const
-    {
-      return width_;
-    }
-
-    unsigned int GetHeight() const
-    {
-      return height_;
-    }
-
-    void SetSourceColor(uint8_t red,
-                        uint8_t green,
-                        uint8_t blue);
-
-    void SetSourceColor(const uint8_t color[3])
-    {
-      SetSourceColor(color[0], color[1], color[2]);
-    }
-
-    void DrawText(const Orthanc::Font& font,
-                  const std::string& text,
-                  double x,
-                  double y,
-                  BitmapAnchor anchor);      
-  };
-}
--- a/Framework/Viewport/CairoFont.cpp	Tue May 28 18:31:17 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "CairoFont.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  CairoFont::CairoFont(const char* family,
-                       cairo_font_slant_t slant,
-                       cairo_font_weight_t weight)
-  {
-    font_ = cairo_toy_font_face_create(family, slant, weight);
-    if (font_ == NULL)
-    {
-      LOG(ERROR) << "Unknown font: " << family;
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-    }
-  }
-
-
-  CairoFont::~CairoFont()
-  {
-    if (font_ != NULL)
-    {
-      cairo_font_face_destroy(font_);
-    }
-  }
-
-
-  void CairoFont::Draw(CairoContext& context,
-                       const std::string& text,
-                       double size)
-  {
-    if (size <= 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    cairo_t* cr = context.GetObject();
-    cairo_set_font_face(cr, font_);
-    cairo_set_font_size(cr, size);
-    cairo_show_text(cr, text.c_str());    
-  }
-}
--- a/Framework/Viewport/CairoFont.h	Tue May 28 18:31:17 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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/>.
- **/
-
-
-#pragma once
-
-#if !defined(ORTHANC_SANDBOXED)
-#  error The macro ORTHANC_SANDBOXED must be defined
-#endif
-
-#if ORTHANC_SANDBOXED == 1
-#  error The class CairoFont cannot be used in sandboxed environments
-#endif
-
-#include "CairoContext.h"
-
-namespace OrthancStone
-{
-  class CairoFont : public boost::noncopyable
-  {
-  private:
-    cairo_font_face_t*  font_;
-
-  public:
-    CairoFont(const char* family,
-              cairo_font_slant_t slant,
-              cairo_font_weight_t weight);
-
-    ~CairoFont();
-
-    void Draw(CairoContext& context,
-              const std::string& text,
-              double size);
-  };
-}
--- a/Framework/Viewport/CairoSurface.cpp	Tue May 28 18:31:17 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "CairoSurface.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-#include <Core/Images/ImageProcessing.h>
-
-namespace OrthancStone
-{
-  void CairoSurface::Release()
-  {
-    if (surface_)
-    {
-      cairo_surface_destroy(surface_);
-      surface_ = NULL;
-    }
-  }
-
-
-  void CairoSurface::Allocate(unsigned int width,
-                              unsigned int height,
-                              bool hasAlpha)
-  {
-    Release();
-
-    hasAlpha_ = hasAlpha;
-
-    surface_ = cairo_image_surface_create
-      (hasAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, width, height);
-    if (!surface_)
-    {
-      // Should never occur
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
-    {
-      LOG(ERROR) << "Cannot create a Cairo surface";
-      cairo_surface_destroy(surface_);
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    width_ = width;
-    height_ = height;
-    pitch_ = cairo_image_surface_get_stride(surface_);
-    buffer_ = cairo_image_surface_get_data(surface_);
-  }
-
-
-  CairoSurface::CairoSurface(Orthanc::ImageAccessor& accessor,
-                             bool hasAlpha) :
-    hasAlpha_(hasAlpha)
-  {
-    if (accessor.GetFormat() != Orthanc::PixelFormat_BGRA32)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-    }      
-
-    width_ = accessor.GetWidth();
-    height_ = accessor.GetHeight();
-    pitch_ = accessor.GetPitch();
-    buffer_ = accessor.GetBuffer();
-
-    surface_ = cairo_image_surface_create_for_data
-      (reinterpret_cast<unsigned char*>(buffer_),
-       hasAlpha_ ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
-       width_, height_, pitch_);
-    if (!surface_)
-    {
-      // Should never occur
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
-    {
-      LOG(ERROR) << "Bad pitch for a Cairo surface";
-      cairo_surface_destroy(surface_);
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-  void CairoSurface::SetSize(unsigned int width,
-                             unsigned int height,
-                             bool hasAlpha)
-  {
-    if (hasAlpha_ != hasAlpha ||
-        width_ != width ||
-        height_ != height)
-    {
-      Allocate(width, height, hasAlpha);
-    }
-  }
-
-
-  void CairoSurface::Copy(const CairoSurface& other)
-  {
-    SetSize(other.GetWidth(), other.GetHeight(), other.HasAlpha());
-    
-    Orthanc::ImageAccessor source, target;
-
-    other.GetReadOnlyAccessor(source);
-    GetWriteableAccessor(target);
-
-    Orthanc::ImageProcessing::Copy(target, source);
-
-    cairo_surface_mark_dirty(surface_);
-  }
-
-
-  void CairoSurface::Copy(const Orthanc::ImageAccessor& source,
-                          bool hasAlpha)
-  {
-    SetSize(source.GetWidth(), source.GetHeight(), hasAlpha);
-
-    Orthanc::ImageAccessor target;
-    GetWriteableAccessor(target);
-
-    Orthanc::ImageProcessing::Convert(target, source);
-
-    cairo_surface_mark_dirty(surface_);
-  }
-
-
-  void CairoSurface::GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const
-  {
-    target.AssignReadOnly(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_);
-  }
-  
-
-  void CairoSurface::GetWriteableAccessor(Orthanc::ImageAccessor& target)
-  {
-    target.AssignWritable(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_);
-  }
-}
--- a/Framework/Viewport/CairoSurface.h	Tue May 28 18:31:17 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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/>.
- **/
-
-
-#pragma once
-
-#include <Core/Images/ImageAccessor.h>
-
-#include <boost/noncopyable.hpp>
-#include <cairo.h>
-
-namespace OrthancStone
-{
-  class CairoSurface : public boost::noncopyable
-  {
-  private:
-    cairo_surface_t* surface_;
-    unsigned int     width_;
-    unsigned int     height_;
-    unsigned int     pitch_;
-    void*            buffer_;
-    bool             hasAlpha_;
-
-    void Release();
-
-    void Allocate(unsigned int width,
-                  unsigned int height,
-                  bool hasAlpha);
-
-  public:
-    CairoSurface() :
-      surface_(NULL)
-    {
-      Allocate(0, 0, false);
-    }
-
-    CairoSurface(unsigned int width,
-                 unsigned int height,
-                 bool hasAlpha) :
-      surface_(NULL)
-    {
-      Allocate(width, height, hasAlpha);
-    }
-
-    CairoSurface(Orthanc::ImageAccessor& accessor,
-                 bool hasAlpha);
-
-    ~CairoSurface()
-    {
-      Release();
-    }
-
-    void SetSize(unsigned int width,
-                 unsigned int height,
-                 bool hasAlpha);
-
-    void Copy(const CairoSurface& other);
-
-    void Copy(const Orthanc::ImageAccessor& source,
-              bool hasAlpha);
-
-    unsigned int GetWidth() const
-    {
-      return width_;
-    }
-
-    unsigned int GetHeight() const
-    {
-      return height_;
-    }
-
-    unsigned int GetPitch() const
-    {
-      return pitch_;
-    }
-
-    const void* GetBuffer() const
-    {
-      return buffer_;
-    }
-
-    void* GetBuffer()
-    {
-      return buffer_;
-    }
-
-    cairo_surface_t* GetObject()
-    {
-      return surface_;
-    }
-
-    bool HasAlpha() const
-    {
-      return hasAlpha_;
-    }
-
-    void GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const;
-
-    void GetWriteableAccessor(Orthanc::ImageAccessor& target);
-  };
-}
--- a/Framework/Volumes/VolumeImageGeometry.cpp	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Volumes/VolumeImageGeometry.cpp	Tue May 28 18:31:44 2019 +0200
@@ -306,4 +306,21 @@
       return true;
     }
   }
+
+
+  CoordinateSystem3D VolumeImageGeometry::GetProjectionSlice(VolumeProjection projection,
+                                                             unsigned int z) const
+  {
+    if (z >= GetProjectionDepth(projection))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    Vector dim = GetVoxelDimensions(projection);
+    CoordinateSystem3D plane = GetProjectionGeometry(projection);
+
+    plane.SetOrigin(plane.GetOrigin() + static_cast<double>(z) * plane.GetNormal() * dim[2]);
+
+    return plane;
+  }
 }
--- a/Framework/Volumes/VolumeImageGeometry.h	Tue May 28 18:31:17 2019 +0200
+++ b/Framework/Volumes/VolumeImageGeometry.h	Tue May 28 18:31:44 2019 +0200
@@ -127,5 +127,8 @@
     bool DetectSlice(VolumeProjection& projection,
                      unsigned int& slice,
                      const CoordinateSystem3D& plane) const;
+
+    CoordinateSystem3D GetProjectionSlice(VolumeProjection projection,
+                                          unsigned int z) const;
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Wrappers/CairoContext.cpp	Tue May 28 18:31:44 2019 +0200
@@ -0,0 +1,146 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "CairoContext.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+
+namespace OrthancStone
+{
+  CairoContext::CairoContext(CairoSurface& surface) :
+    width_(surface.GetWidth()),
+    height_(surface.GetHeight())
+  {
+    context_ = cairo_create(surface.GetObject());
+    if (!context_)
+    {
+      LOG(ERROR) << "Cannot create Cairo drawing context";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  CairoContext::~CairoContext()
+  {
+    if (context_ != NULL)
+    {
+      cairo_destroy(context_);
+      context_ = NULL;
+    }
+  }
+
+
+  void CairoContext::SetSourceColor(uint8_t red,
+                                    uint8_t green,
+                                    uint8_t blue)
+  {
+    cairo_set_source_rgb(context_,
+                         static_cast<float>(red) / 255.0f,
+                         static_cast<float>(green) / 255.0f,
+                         static_cast<float>(blue) / 255.0f);
+  }
+
+
+  class CairoContext::AlphaSurface : public boost::noncopyable
+  {
+  private:
+    cairo_surface_t  *surface_;
+
+  public:
+    AlphaSurface(unsigned int width,
+                 unsigned int height)
+    {
+      surface_ = cairo_image_surface_create(CAIRO_FORMAT_A8, width, height);
+      
+      if (!surface_)
+      {
+        // Should never occur
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
+      {
+        LOG(ERROR) << "Cannot create a Cairo surface";
+        cairo_surface_destroy(surface_);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+    
+    ~AlphaSurface()
+    {
+      cairo_surface_destroy(surface_);
+    }
+
+    void GetAccessor(Orthanc::ImageAccessor& target)
+    {
+      target.AssignWritable(Orthanc::PixelFormat_Grayscale8,
+                            cairo_image_surface_get_width(surface_),
+                            cairo_image_surface_get_height(surface_),
+                            cairo_image_surface_get_stride(surface_),
+                            cairo_image_surface_get_data(surface_));
+    }
+
+    void Blit(cairo_t* cr,
+              double x,
+              double y)
+    {
+      cairo_surface_mark_dirty(surface_);
+      cairo_mask_surface(cr, surface_, x, y);
+      cairo_fill(cr);
+    }
+  };
+
+
+  void CairoContext::DrawText(const Orthanc::Font& font,
+                              const std::string& text,
+                              double x,
+                              double y,
+                              BitmapAnchor anchor)
+  {
+    // Render a bitmap containing the text
+    unsigned int width, height;
+    font.ComputeTextExtent(width, height, text);
+    
+    AlphaSurface surface(width, height);
+
+    Orthanc::ImageAccessor accessor;
+    surface.GetAccessor(accessor);
+    font.Draw(accessor, text, 0, 0, 255);
+
+    // Correct the text location given the anchor location
+    double deltaX, deltaY;
+    ComputeAnchorTranslation(deltaX, deltaY, anchor, width, height);
+
+    // Cancel zoom/rotation before blitting the text onto the surface
+    double pixelX = x;
+    double pixelY = y;
+    cairo_user_to_device(context_, &pixelX, &pixelY);
+
+    cairo_save(context_);
+    cairo_identity_matrix(context_);
+
+    // Blit the text bitmap
+    surface.Blit(context_, pixelX + deltaX, pixelY + deltaY);
+    cairo_restore(context_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Wrappers/CairoContext.h	Tue May 28 18:31:44 2019 +0200
@@ -0,0 +1,76 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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/>.
+ **/
+
+
+#pragma once
+
+#include "CairoSurface.h"
+#include "../StoneEnumerations.h"
+
+#include <Core/Images/Font.h>
+
+namespace OrthancStone
+{
+  // This is a RAII wrapper around the Cairo drawing context
+  class CairoContext : public boost::noncopyable
+  {
+  private:
+    class AlphaSurface;
+    
+    cairo_t*      context_;
+    unsigned int  width_;
+    unsigned int  height_;
+
+  public:
+    CairoContext(CairoSurface& surface);
+
+    ~CairoContext();
+
+    cairo_t* GetObject()
+    {
+      return context_;
+    }
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    void SetSourceColor(uint8_t red,
+                        uint8_t green,
+                        uint8_t blue);
+
+    void SetSourceColor(const uint8_t color[3])
+    {
+      SetSourceColor(color[0], color[1], color[2]);
+    }
+
+    void DrawText(const Orthanc::Font& font,
+                  const std::string& text,
+                  double x,
+                  double y,
+                  BitmapAnchor anchor);      
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Wrappers/CairoSurface.cpp	Tue May 28 18:31:44 2019 +0200
@@ -0,0 +1,155 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "CairoSurface.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Images/ImageProcessing.h>
+
+namespace OrthancStone
+{
+  void CairoSurface::Release()
+  {
+    if (surface_)
+    {
+      cairo_surface_destroy(surface_);
+      surface_ = NULL;
+    }
+  }
+
+
+  void CairoSurface::Allocate(unsigned int width,
+                              unsigned int height,
+                              bool hasAlpha)
+  {
+    Release();
+
+    hasAlpha_ = hasAlpha;
+
+    surface_ = cairo_image_surface_create
+      (hasAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, width, height);
+    if (!surface_)
+    {
+      // Should never occur
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
+    {
+      LOG(ERROR) << "Cannot create a Cairo surface";
+      cairo_surface_destroy(surface_);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    width_ = width;
+    height_ = height;
+    pitch_ = cairo_image_surface_get_stride(surface_);
+    buffer_ = cairo_image_surface_get_data(surface_);
+  }
+
+
+  CairoSurface::CairoSurface(Orthanc::ImageAccessor& accessor,
+                             bool hasAlpha) :
+    hasAlpha_(hasAlpha)
+  {
+    if (accessor.GetFormat() != Orthanc::PixelFormat_BGRA32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }      
+
+    width_ = accessor.GetWidth();
+    height_ = accessor.GetHeight();
+    pitch_ = accessor.GetPitch();
+    buffer_ = accessor.GetBuffer();
+
+    surface_ = cairo_image_surface_create_for_data
+      (reinterpret_cast<unsigned char*>(buffer_),
+       hasAlpha_ ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
+       width_, height_, pitch_);
+    if (!surface_)
+    {
+      // Should never occur
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
+    {
+      LOG(ERROR) << "Bad pitch for a Cairo surface";
+      cairo_surface_destroy(surface_);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  void CairoSurface::SetSize(unsigned int width,
+                             unsigned int height,
+                             bool hasAlpha)
+  {
+    if (hasAlpha_ != hasAlpha ||
+        width_ != width ||
+        height_ != height)
+    {
+      Allocate(width, height, hasAlpha);
+    }
+  }
+
+
+  void CairoSurface::Copy(const CairoSurface& other)
+  {
+    SetSize(other.GetWidth(), other.GetHeight(), other.HasAlpha());
+    
+    Orthanc::ImageAccessor source, target;
+
+    other.GetReadOnlyAccessor(source);
+    GetWriteableAccessor(target);
+
+    Orthanc::ImageProcessing::Copy(target, source);
+
+    cairo_surface_mark_dirty(surface_);
+  }
+
+
+  void CairoSurface::Copy(const Orthanc::ImageAccessor& source,
+                          bool hasAlpha)
+  {
+    SetSize(source.GetWidth(), source.GetHeight(), hasAlpha);
+
+    Orthanc::ImageAccessor target;
+    GetWriteableAccessor(target);
+
+    Orthanc::ImageProcessing::Convert(target, source);
+
+    cairo_surface_mark_dirty(surface_);
+  }
+
+
+  void CairoSurface::GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const
+  {
+    target.AssignReadOnly(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_);
+  }
+  
+
+  void CairoSurface::GetWriteableAccessor(Orthanc::ImageAccessor& target)
+  {
+    target.AssignWritable(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Wrappers/CairoSurface.h	Tue May 28 18:31:44 2019 +0200
@@ -0,0 +1,118 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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/>.
+ **/
+
+
+#pragma once
+
+#include <Core/Images/ImageAccessor.h>
+
+#include <boost/noncopyable.hpp>
+#include <cairo.h>
+
+namespace OrthancStone
+{
+  class CairoSurface : public boost::noncopyable
+  {
+  private:
+    cairo_surface_t* surface_;
+    unsigned int     width_;
+    unsigned int     height_;
+    unsigned int     pitch_;
+    void*            buffer_;
+    bool             hasAlpha_;
+
+    void Release();
+
+    void Allocate(unsigned int width,
+                  unsigned int height,
+                  bool hasAlpha);
+
+  public:
+    CairoSurface() :
+      surface_(NULL)
+    {
+      Allocate(0, 0, false);
+    }
+
+    CairoSurface(unsigned int width,
+                 unsigned int height,
+                 bool hasAlpha) :
+      surface_(NULL)
+    {
+      Allocate(width, height, hasAlpha);
+    }
+
+    CairoSurface(Orthanc::ImageAccessor& accessor,
+                 bool hasAlpha);
+
+    ~CairoSurface()
+    {
+      Release();
+    }
+
+    void SetSize(unsigned int width,
+                 unsigned int height,
+                 bool hasAlpha);
+
+    void Copy(const CairoSurface& other);
+
+    void Copy(const Orthanc::ImageAccessor& source,
+              bool hasAlpha);
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetPitch() const
+    {
+      return pitch_;
+    }
+
+    const void* GetBuffer() const
+    {
+      return buffer_;
+    }
+
+    void* GetBuffer()
+    {
+      return buffer_;
+    }
+
+    cairo_surface_t* GetObject()
+    {
+      return surface_;
+    }
+
+    bool HasAlpha() const
+    {
+      return hasAlpha_;
+    }
+
+    void GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const;
+
+    void GetWriteableAccessor(Orthanc::ImageAccessor& target);
+  };
+}
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Tue May 28 18:31:17 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Tue May 28 18:31:44 2019 +0200
@@ -252,7 +252,6 @@
 
 if (NOT ORTHANC_SANDBOXED)
   set(PLATFORM_SOURCES
-    ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoFont.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceCommandBase.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceGetCommand.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServicePostCommand.cpp
@@ -263,6 +262,12 @@
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleDelayedCallExecutor.h
     )
 
+  if (ENABLE_STONE_DEPRECATED)
+    list(APPEND PLATFORM_SOURCES
+      ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/CairoFont.cpp
+      )
+  endif()
+
   if (ENABLE_SDL OR ENABLE_QT)
     list(APPEND APPLICATIONS_SOURCES
       ${ORTHANC_STONE_ROOT}/Applications/Generic/NativeStoneApplicationRunner.cpp
@@ -333,6 +338,8 @@
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/MessagingToolbox.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancApiClient.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ParallelSlices.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ParallelSlicesCursor.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/Slice.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ViewportGeometry.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/IMouseTracker.h
@@ -479,13 +486,11 @@
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ImageGeometry.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/LinearAlgebra.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlices.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlicesCursor.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/SlicesSorter.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoContext.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoSurface.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Wrappers/CairoContext.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Wrappers/CairoSurface.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/ImageBuffer3D.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/OrientedVolumeBoundingBox.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeImageGeometry.cpp
--- a/UnitTestsSources/UnitTestsMain.cpp	Tue May 28 18:31:17 2019 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Tue May 28 18:31:44 2019 +0200
@@ -699,18 +699,22 @@
     OrthancStone::VolumeProjection projection = (OrthancStone::VolumeProjection) p;
     const OrthancStone::CoordinateSystem3D& s = g.GetProjectionGeometry(projection);
     
+    ASSERT_THROW(g.GetProjectionSlice(projection, g.GetProjectionDepth(projection)), Orthanc::OrthancException);
+
     for (unsigned int i = 0; i < g.GetProjectionDepth(projection); i++)
     {
-      OrthancStone::CoordinateSystem3D plane(
-        s.GetOrigin() + static_cast<double>(i) * s.GetNormal() * g.GetVoxelDimensions(projection)[2],
-        s.GetAxisX(),
-        s.GetAxisY());
+      OrthancStone::CoordinateSystem3D plane = g.GetProjectionSlice(projection, i);
+
+      ASSERT_TRUE(IsEqualVector(plane.GetOrigin(), s.GetOrigin() + static_cast<double>(i) * 
+                                s.GetNormal() * g.GetVoxelDimensions(projection)[2]));
+      ASSERT_TRUE(IsEqualVector(plane.GetAxisX(), s.GetAxisX()));
+      ASSERT_TRUE(IsEqualVector(plane.GetAxisY(), s.GetAxisY()));
 
       unsigned int slice;
       OrthancStone::VolumeProjection q;
       ASSERT_TRUE(g.DetectSlice(q, slice, plane));
       ASSERT_EQ(projection, q);
-      ASSERT_EQ(i, slice);
+      ASSERT_EQ(i, slice);     
     }
   }
 }