changeset 1804:5a872e69c74f

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 20 May 2021 14:48:51 +0200
parents d1849468729b
children de7cea710008
files Applications/Platforms/Sdl/OrthancStoneSdlConfiguration.cmake Applications/Platforms/Sdl/SdlViewport.cpp Applications/Platforms/Sdl/SdlViewport.h Applications/Platforms/Sdl/SdlWindow.cpp Applications/Platforms/Sdl/SdlWindow.h Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.cpp OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.h OrthancStone/Sources/Scene2D/ScenePoint2D.cpp OrthancStone/Sources/Scene2D/ScenePoint2D.h
diffstat 11 files changed, 1920 insertions(+), 1717 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Platforms/Sdl/OrthancStoneSdlConfiguration.cmake	Thu May 20 14:15:00 2021 +0200
+++ b/Applications/Platforms/Sdl/OrthancStoneSdlConfiguration.cmake	Thu May 20 14:48:51 2021 +0200
@@ -62,6 +62,7 @@
 #####################################################################
 
 list(APPEND ORTHANC_STONE_SOURCES
+  ${CMAKE_CURRENT_LIST_DIR}/SdlViewport.cpp
   ${CMAKE_CURRENT_LIST_DIR}/SdlWindow.cpp
   ${SDL_SOURCES}
   )
@@ -69,6 +70,5 @@
 if (ENABLE_OPENGL)
   list(APPEND ORTHANC_STONE_SOURCES
     ${CMAKE_CURRENT_LIST_DIR}/SdlOpenGLContext.cpp
-    ${CMAKE_CURRENT_LIST_DIR}/SdlViewport.cpp
     )
 endif()
--- a/Applications/Platforms/Sdl/SdlViewport.cpp	Thu May 20 14:15:00 2021 +0200
+++ b/Applications/Platforms/Sdl/SdlViewport.cpp	Thu May 20 14:48:51 2021 +0200
@@ -85,6 +85,7 @@
   }
 
 
+#if ORTHANC_ENABLE_OPENGL == 1
   SdlOpenGLViewport::SdlOpenGLViewport(const std::string& title,
                                        unsigned int width,
                                        unsigned int height,
@@ -93,26 +94,32 @@
   {
     AcquireCompositor(new OpenGLCompositor(context_));  // (*)
   }
+#endif
 
 
+#if ORTHANC_ENABLE_OPENGL == 1
   void SdlOpenGLViewport::RefreshCanvasSize()
   {
     UpdateSize(context_.GetCanvasWidth(), context_.GetCanvasHeight());
   }
+#endif
 
 
-  boost::shared_ptr<SdlOpenGLViewport> SdlOpenGLViewport::Create(
-    const std::string& title,
-    unsigned int width,
-    unsigned int height,
-    bool allowDpiScaling)
+#if ORTHANC_ENABLE_OPENGL == 1
+  boost::shared_ptr<SdlOpenGLViewport> SdlOpenGLViewport::Create(const std::string& title,
+                                                                 unsigned int width,
+                                                                 unsigned int height,
+                                                                 bool allowDpiScaling)
   {
     boost::shared_ptr<SdlOpenGLViewport> that =
       boost::shared_ptr<SdlOpenGLViewport>(new SdlOpenGLViewport(title, width, height, allowDpiScaling));
     that->SdlViewport::PostConstructor();
     return that;
   }
+#endif
 
+
+#if ORTHANC_ENABLE_OPENGL == 1
   uint32_t SdlOpenGLViewport::GetSdlWindowId()
   {
     const SdlWindow& sdlWindowWrapper = context_.GetWindow();
@@ -120,22 +127,29 @@
     Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow);
     return sdlWindowId;
   }
+#endif
+  
 
+#if ORTHANC_ENABLE_OPENGL == 1
   SdlOpenGLViewport::~SdlOpenGLViewport()
   {
     // Make sure that the "OpenGLCompositor" is destroyed BEFORE the
     // "OpenGLContext" it references (*)
     ClearCompositor();
   }
+#endif
 
 
+#if ORTHANC_ENABLE_OPENGL == 1
   void SdlOpenGLViewport::Paint()
   {
     SdlLock lock(*this);
     lock.GetCompositor().Refresh(lock.GetController().GetScene());
   }
+#endif
 
 
+#if ORTHANC_ENABLE_OPENGL == 1
   void SdlOpenGLViewport::ToggleMaximize()
   {
     // No need to call "Invalidate()" here, as "UpdateSize()" will
@@ -143,7 +157,7 @@
     SdlLock lock(*this);
     context_.ToggleMaximize();
   }
-
+#endif
 
 
   void SdlCairoViewport::RefreshCanvasSize()
@@ -151,7 +165,7 @@
     UpdateSize(window_.GetWidth(), window_.GetHeight());
   }
 
-  SdlCairoViewport::SdlCairoViewport(const char* title,
+  SdlCairoViewport::SdlCairoViewport(const std::string& title,
                                      unsigned int width,
                                      unsigned int height,
                                      bool allowDpiScaling) :
@@ -233,4 +247,16 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
     }
   }
+
+
+  boost::shared_ptr<SdlCairoViewport> SdlCairoViewport::Create(const std::string& title,
+                                                               unsigned int width,
+                                                               unsigned int height,
+                                                               bool allowDpiScaling)
+  {
+    boost::shared_ptr<SdlCairoViewport> that =
+      boost::shared_ptr<SdlCairoViewport>(new SdlCairoViewport(title, width, height, allowDpiScaling));
+    that->SdlViewport::PostConstructor();
+    return that;
+  }
 }
--- a/Applications/Platforms/Sdl/SdlViewport.h	Thu May 20 14:15:00 2021 +0200
+++ b/Applications/Platforms/Sdl/SdlViewport.h	Thu May 20 14:48:51 2021 +0200
@@ -32,15 +32,14 @@
 #  error The macro ORTHANC_ENABLE_OPENGL must be defined
 #endif
 
-#if ORTHANC_ENABLE_OPENGL != 1
-#  error Support for OpenGL is disabled
-#endif
-
 #include "SdlOpenGLContext.h"
-#include "../../../OrthancStone/Sources/Scene2D/OpenGLCompositor.h"
 #include "../../../OrthancStone/Sources/Scene2D/CairoCompositor.h"
 #include "../../../OrthancStone/Sources/Viewport/IViewport.h"
 
+#if ORTHANC_ENABLE_OPENGL == 1
+#  include "../../../OrthancStone/Sources/Scene2D/OpenGLCompositor.h"
+#endif
+
 #include <SDL_events.h>
 
 // TODO: required for UndoStack injection
@@ -139,6 +138,7 @@
   };
 
 
+#if ORTHANC_ENABLE_OPENGL == 1
   class SdlOpenGLViewport : public SdlViewport
   {
   private:
@@ -153,7 +153,7 @@
     virtual void RefreshCanvasSize() ORTHANC_OVERRIDE;
     
   public:
-    static boost::shared_ptr<SdlOpenGLViewport> Create(const std::string&,
+    static boost::shared_ptr<SdlOpenGLViewport> Create(const std::string& title,
                                                        unsigned int width,
                                                        unsigned int height,
                                                        bool allowDpiScaling = true);
@@ -167,6 +167,7 @@
 
     virtual void ToggleMaximize() ORTHANC_OVERRIDE;
   };
+#endif
 
 
   class SdlCairoViewport : public SdlViewport
@@ -177,20 +178,19 @@
 
     void CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor);
 
-    SdlCairoViewport(const char* title,
+    SdlCairoViewport(const std::string& title,
                      unsigned int width,
                      unsigned int height,
-                     bool allowDpiScaling = true);
+                     bool allowDpiScaling);
 
   protected:
     virtual void RefreshCanvasSize() ORTHANC_OVERRIDE;
     
   public:
-    static boost::shared_ptr<SdlCairoViewport> Create(const char* title,
-                     unsigned int width,
-                     unsigned int height,
-                     bool allowDpiScaling = true);
-
+    static boost::shared_ptr<SdlCairoViewport> Create(const std::string& title,
+                                                      unsigned int width,
+                                                      unsigned int height,
+                                                      bool allowDpiScaling = true);
 
     virtual ~SdlCairoViewport();
 
--- a/Applications/Platforms/Sdl/SdlWindow.cpp	Thu May 20 14:15:00 2021 +0200
+++ b/Applications/Platforms/Sdl/SdlWindow.cpp	Thu May 20 14:48:51 2021 +0200
@@ -37,7 +37,7 @@
 
 namespace OrthancStone
 {
-  SdlWindow::SdlWindow(const char* title,
+  SdlWindow::SdlWindow(const std::string& title,
                        unsigned int width,
                        unsigned int height,
                        bool enableOpenGl,
@@ -87,7 +87,7 @@
 #endif 
 // WIN32
     
-    window_ = SDL_CreateWindow(title,
+    window_ = SDL_CreateWindow(title.c_str(),
                                SDL_WINDOWPOS_UNDEFINED,
                                SDL_WINDOWPOS_UNDEFINED,
                                width, height, windowFlags);
--- a/Applications/Platforms/Sdl/SdlWindow.h	Thu May 20 14:15:00 2021 +0200
+++ b/Applications/Platforms/Sdl/SdlWindow.h	Thu May 20 14:48:51 2021 +0200
@@ -24,6 +24,7 @@
 #if ORTHANC_ENABLE_SDL == 1
 
 #include <boost/noncopyable.hpp>
+#include <string>
 
 // Forward declaration of SDL type to avoid clashes with DCMTK headers
 // on "typedef Sint8", in "StoneInitialization.cpp"
@@ -41,7 +42,7 @@
     bool                 maximized_;
 
   public:
-    SdlWindow(const char* title,
+    SdlWindow(const std::string& title,
               unsigned int width,
               unsigned int height,
               bool enableOpenGl,
--- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Thu May 20 14:15:00 2021 +0200
+++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Thu May 20 14:48:51 2021 +0200
@@ -24,10 +24,10 @@
 #include "../../Common/SampleHelpers.h"
 
 #include "../../../../OrthancStone/Sources/Loaders/GenericLoadersContext.h"
+#include "../../../../OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.h"
 #include "../../../../OrthancStone/Sources/Scene2DViewport/AngleMeasureTool.h"
 #include "../../../../OrthancStone/Sources/Scene2DViewport/LineMeasureTool.h"
 #include "../../../../OrthancStone/Sources/Scene2DViewport/UndoStack.h"
-#include "../../../../OrthancStone/Sources/StoneEnumerations.h"
 #include "../../../../OrthancStone/Sources/StoneException.h"
 #include "../../../../OrthancStone/Sources/StoneInitialization.h"
 #include "../../../../OrthancStone/Sources/Viewport/DefaultViewportInteractor.h"
@@ -43,1585 +43,9 @@
 #include <string>
 
 
-
-#if 1
-#include "../../../../OrthancStone/Sources/Scene2D/MacroSceneLayer.h"
-
-#include <boost/math/constants/constants.hpp>
-
-static const double HANDLE_SIZE = 10.0;
-static const double PI = boost::math::constants::pi<double>();
-
-static const char* const KEY_ANNOTATIONS = "annotations";
-static const char* const KEY_TYPE = "type";
-static const char* const KEY_X1 = "x1";
-static const char* const KEY_Y1 = "y1";
-static const char* const KEY_X2 = "x2";
-static const char* const KEY_Y2 = "y2";
-static const char* const KEY_X3 = "x3";
-static const char* const KEY_Y3 = "y3";
-
-static const char* const VALUE_ANGLE = "angle";
-static const char* const VALUE_CIRCLE = "circle";
-static const char* const VALUE_SEGMENT = "segment";
-
-
-namespace OrthancStone
-{
-  class AnnotationsLayer : public IObservable
-  {
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, AnnotationAddedMessage, AnnotationsLayer);
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, AnnotationRemovedMessage, AnnotationsLayer);
-
-    enum Tool
-    {
-      Tool_Edit,
-      Tool_None,
-      Tool_Segment,
-      Tool_Angle,
-      Tool_Circle,
-      Tool_Erase
-    };
-    
-  private:
-    class Annotation;
-    
-    class GeometricPrimitive : public boost::noncopyable
-    {
-    private:
-      bool         modified_;
-      Annotation&  parentAnnotation_;
-      Color        color_;
-      Color        hoverColor_;
-      bool         isHover_;
-      int          depth_;
-
-    public:
-      GeometricPrimitive(Annotation& parentAnnotation,
-                         int depth) :
-        modified_(true),
-        parentAnnotation_(parentAnnotation),
-        color_(192, 192, 192),
-        hoverColor_(0, 255, 0),
-        isHover_(false),
-        depth_(depth)
-      {
-      }
-      
-      virtual ~GeometricPrimitive()
-      {
-      }
-
-      Annotation& GetParentAnnotation() const
-      {
-        return parentAnnotation_;
-      }
-      
-      int GetDepth() const
-      {
-        return depth_;
-      }
-
-      void SetHover(bool hover)
-      {
-        if (hover != isHover_)
-        {
-          isHover_ = hover;
-          modified_ = true;
-        }
-      }
-
-      bool IsHover() const
-      {
-        return isHover_;
-      }
-      
-      void SetModified(bool modified)
-      {
-        modified_ = modified;
-      }
-      
-      bool IsModified() const
-      {
-        return modified_;
-      }
-
-      void SetColor(const Color& color)
-      {
-        SetModified(true);
-        color_ = color;
-      }
-
-      void SetHoverColor(const Color& color)
-      {
-        SetModified(true);
-        hoverColor_ = color;
-      }
-
-      const Color& GetColor() const
-      {
-        return color_;
-      }
-
-      const Color& GetHoverColor() const
-      {
-        return hoverColor_;
-      }
-
-      virtual bool IsHit(const ScenePoint2D& p,
-                         const Scene2D& scene) const = 0;
-
-      // Always called, even if not modified
-      virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
-                                       const Scene2D& scene) = 0;
-
-      // Only called if modified
-      virtual void RenderOtherLayers(MacroSceneLayer& macro,
-                                     const Scene2D& scene) = 0;
-
-      virtual void MovePreview(const ScenePoint2D& delta) = 0;
-
-      virtual void MoveDone(const ScenePoint2D& delta) = 0;
-    };
-    
-
-    class Annotation : public boost::noncopyable
-    {
-    private:
-      typedef std::list<GeometricPrimitive*>  GeometricPrimitives;
-      
-      AnnotationsLayer&    that_;
-      GeometricPrimitives  primitives_;
-      
-    public:
-      Annotation(AnnotationsLayer& that) :
-        that_(that)
-      {
-        that.AddAnnotation(this);
-      }
-      
-      virtual ~Annotation()
-      {
-        for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
-        {
-          that_.DeletePrimitive(*it);
-        }
-      }
-
-      GeometricPrimitive* AddPrimitive(GeometricPrimitive* primitive)
-      {
-        if (primitive == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-        }
-        else
-        {
-          assert(that_.primitives_.find(primitive) == that_.primitives_.end());
-          primitives_.push_back(primitive);  // For automated deallocation
-          that_.primitives_.insert(primitive);
-          return primitive;
-        }
-      }
-
-      template <typename T>
-      T& AddTypedPrimitive(T* primitive)
-      {
-        AddPrimitive(primitive);
-        return *primitive;
-      }
-
-      virtual void SignalMove(GeometricPrimitive& primitive) = 0;
-
-      virtual void Serialize(Json::Value& target) = 0;
-    };
-
-
-    class Handle : public GeometricPrimitive
-    {
-    private:
-      ScenePoint2D  center_;
-      ScenePoint2D  delta_;
-
-    public:
-      explicit Handle(Annotation& parentAnnotation,
-                      const ScenePoint2D& center) :
-        GeometricPrimitive(parentAnnotation, 0),  // Highest priority
-        center_(center),
-        delta_(0, 0)
-      {
-      }
-
-      void SetSize(unsigned int size)
-      {
-        SetModified(true);
-      }
-
-      void SetCenter(const ScenePoint2D& center)
-      {
-        SetModified(true);
-        center_ = center;
-        delta_ = ScenePoint2D(0, 0);
-      }
-
-      ScenePoint2D GetCenter() const
-      {
-        return center_ + delta_;
-      }
-
-      virtual bool IsHit(const ScenePoint2D& p,
-                         const Scene2D& scene) const
-      {
-        const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom();
-
-        double dx = (center_.GetX() + delta_.GetX() - p.GetX()) * zoom;
-        double dy = (center_.GetY() + delta_.GetY() - p.GetY()) * zoom;
-
-        return (std::abs(dx) <= HANDLE_SIZE / 2.0 &&
-                std::abs(dy) <= HANDLE_SIZE / 2.0);
-      }
-
-      virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
-                                       const Scene2D& scene) ORTHANC_OVERRIDE
-      {
-        const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom();
-
-        // TODO: take DPI into account 
-        double x1 = center_.GetX() + delta_.GetX() - (HANDLE_SIZE / 2.0) / zoom;
-        double y1 = center_.GetY() + delta_.GetY() - (HANDLE_SIZE / 2.0) / zoom;
-        double x2 = center_.GetX() + delta_.GetX() + (HANDLE_SIZE / 2.0) / zoom;
-        double y2 = center_.GetY() + delta_.GetY() + (HANDLE_SIZE / 2.0) / zoom;
-
-        PolylineSceneLayer::Chain chain;
-        chain.reserve(4);
-        chain.push_back(ScenePoint2D(x1, y1));
-        chain.push_back(ScenePoint2D(x2, y1));
-        chain.push_back(ScenePoint2D(x2, y2));
-        chain.push_back(ScenePoint2D(x1, y2));
-
-        if (IsHover())
-        {
-          polyline.AddChain(chain, true /* closed */, GetHoverColor());
-        }
-        else
-        {
-          polyline.AddChain(chain, true /* closed */, GetColor());
-        }
-      }
-      
-      virtual void RenderOtherLayers(MacroSceneLayer& macro,
-                                     const Scene2D& scene) ORTHANC_OVERRIDE
-      {
-      }
-
-      virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
-      {
-        SetModified(true);
-        delta_ = delta;
-        GetParentAnnotation().SignalMove(*this);
-      }
-
-      virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
-      {
-        SetModified(true);
-        center_ = center_ + delta;
-        delta_ = ScenePoint2D(0, 0);
-        GetParentAnnotation().SignalMove(*this);
-      }
-    };
-
-    
-    class Segment : public GeometricPrimitive
-    {
-    private:
-      ScenePoint2D  p1_;
-      ScenePoint2D  p2_;
-      ScenePoint2D  delta_;
-      
-    public:
-      Segment(Annotation& parentAnnotation,
-              const ScenePoint2D& p1,
-              const ScenePoint2D& p2) :
-        GeometricPrimitive(parentAnnotation, 1),  // Can only be selected if no handle matches
-        p1_(p1),
-        p2_(p2),
-        delta_(0, 0)
-      {
-      }
-
-      void SetPosition(const ScenePoint2D& p1,
-                       const ScenePoint2D& p2)
-      {
-        SetModified(true);
-        p1_ = p1;
-        p2_ = p2;
-        delta_ = ScenePoint2D(0, 0);
-      }
-
-      ScenePoint2D GetPosition1() const
-      {
-        return p1_ + delta_;
-      }
-
-      ScenePoint2D GetPosition2() const
-      {
-        return p2_ + delta_;
-      }
-
-      virtual bool IsHit(const ScenePoint2D& p,
-                         const Scene2D& scene) const
-      {
-        const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom();
-        return (ScenePoint2D::SquaredDistancePtSegment(p1_ + delta_, p2_ + delta_, p) * zoom * zoom <=
-                (HANDLE_SIZE / 2.0) * (HANDLE_SIZE / 2.0));
-      }
-
-      virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
-                                       const Scene2D& scene) ORTHANC_OVERRIDE
-      {
-        PolylineSceneLayer::Chain chain;
-        chain.reserve(2);
-        chain.push_back(p1_ + delta_);
-        chain.push_back(p2_ + delta_);
-
-        if (IsHover())
-        {
-          polyline.AddChain(chain, false /* closed */, GetHoverColor());
-        }
-        else
-        {
-          polyline.AddChain(chain, false /* closed */, GetColor());
-        }
-      }
-      
-      virtual void RenderOtherLayers(MacroSceneLayer& macro,
-                                     const Scene2D& scene) ORTHANC_OVERRIDE
-      {
-      }
-
-      virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
-      {
-        SetModified(true);
-        delta_ = delta;
-        GetParentAnnotation().SignalMove(*this);
-      }
-
-      virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
-      {
-        SetModified(true);
-        p1_ = p1_ + delta;
-        p2_ = p2_ + delta;
-        delta_ = ScenePoint2D(0, 0);
-        GetParentAnnotation().SignalMove(*this);
-      }
-    };
-
-    
-    class Circle : public GeometricPrimitive
-    {
-    private:
-      ScenePoint2D  p1_;
-      ScenePoint2D  p2_;
-      
-    public:
-      Circle(Annotation& parentAnnotation,
-             const ScenePoint2D& p1,
-             const ScenePoint2D& p2) :
-        GeometricPrimitive(parentAnnotation, 2),
-        p1_(p1),
-        p2_(p2)
-      {
-      }
-
-      void SetPosition(const ScenePoint2D& p1,
-                       const ScenePoint2D& p2)
-      {
-        SetModified(true);
-        p1_ = p1;
-        p2_ = p2;
-      }
-
-      ScenePoint2D GetPosition1() const
-      {
-        return p1_;
-      }
-
-      ScenePoint2D GetPosition2() const
-      {
-        return p2_;
-      }
-
-      virtual bool IsHit(const ScenePoint2D& p,
-                         const Scene2D& scene) const
-      {
-        return false;
-      }
-
-      virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
-                                       const Scene2D& scene) ORTHANC_OVERRIDE
-      {
-        static unsigned int NUM_SEGMENTS = 128;
-
-        ScenePoint2D middle((p1_.GetX() + p2_.GetX()) / 2.0,
-                            (p1_.GetY() + p2_.GetY()) / 2.0);
-        
-        const double radius = ScenePoint2D::DistancePtPt(middle, p1_);
-
-        double increment = 2.0 * PI / static_cast<double>(NUM_SEGMENTS - 1);
-
-        PolylineSceneLayer::Chain chain;
-        chain.reserve(NUM_SEGMENTS);
-
-        double theta = 0;
-        for (unsigned int i = 0; i < NUM_SEGMENTS; i++)
-        {
-          chain.push_back(ScenePoint2D(middle.GetX() + radius * cos(theta),
-                                       middle.GetY() + radius * sin(theta)));
-          theta += increment;
-        }
-        
-        if (IsHover())
-        {
-          polyline.AddChain(chain, false /* closed */, GetHoverColor());
-        }
-        else
-        {
-          polyline.AddChain(chain, false /* closed */, GetColor());
-        }
-      }
-      
-      virtual void RenderOtherLayers(MacroSceneLayer& macro,
-                                     const Scene2D& scene) ORTHANC_OVERRIDE
-      {
-      }
-
-      virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
-      }
-
-      virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
-      }
-    };
-
-    
-    class Arc : public GeometricPrimitive
-    {
-    private:
-      ScenePoint2D  start_;
-      ScenePoint2D  middle_;
-      ScenePoint2D  end_;
-      double        radius_;  // in pixels
-
-      void ComputeAngles(double& fullAngle,
-                         double& startAngle,
-                         double& endAngle) const
-      {
-        const double x1 = start_.GetX();
-        const double y1 = start_.GetY();
-        const double xc = middle_.GetX();
-        const double yc = middle_.GetY();
-        const double x2 = end_.GetX();
-        const double y2 = end_.GetY();
-        
-        startAngle = atan2(y1 - yc, x1 - xc);
-        endAngle = atan2(y2 - yc, x2 - xc);
-
-        fullAngle = endAngle - startAngle;
-        
-        while (fullAngle < -PI)
-        {
-          fullAngle += 2.0 * PI;
-        }
-        
-        while (fullAngle >= PI)
-        {
-          fullAngle -= 2.0 * PI;
-        }
-      }
-      
-    public:
-      Arc(Annotation& parentAnnotation,
-          const ScenePoint2D& start,
-          const ScenePoint2D& middle,
-          const ScenePoint2D& end) :
-        GeometricPrimitive(parentAnnotation, 2),
-        start_(start),
-        middle_(middle),
-        end_(end),
-        radius_(20)
-      {
-      }
-
-      double GetAngle() const
-      {
-        double fullAngle, startAngle, endAngle;
-        ComputeAngles(fullAngle, startAngle, endAngle);
-        return fullAngle;
-      }
-
-      void SetStart(const ScenePoint2D& p)
-      {
-        SetModified(true);
-        start_ = p;
-      }
-
-      void SetMiddle(const ScenePoint2D& p)
-      {
-        SetModified(true);
-        middle_ = p;
-      }
-
-      void SetEnd(const ScenePoint2D& p)
-      {
-        SetModified(true);
-        end_ = p;
-      }
-
-      virtual bool IsHit(const ScenePoint2D& p,
-                         const Scene2D& scene) const
-      {
-        return false;
-      }
-
-      virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
-                                       const Scene2D& scene) ORTHANC_OVERRIDE
-      {
-        static unsigned int NUM_SEGMENTS = 64;
-
-        const double radius = radius_ / scene.GetSceneToCanvasTransform().ComputeZoom();
-
-        double fullAngle, startAngle, endAngle;
-        ComputeAngles(fullAngle, startAngle, endAngle);
-
-        double increment = fullAngle / static_cast<double>(NUM_SEGMENTS - 1);
-
-        PolylineSceneLayer::Chain chain;
-        chain.reserve(NUM_SEGMENTS);
-
-        double theta = startAngle;
-        for (unsigned int i = 0; i < NUM_SEGMENTS; i++)
-        {
-          chain.push_back(ScenePoint2D(middle_.GetX() + radius * cos(theta),
-                                       middle_.GetY() + radius * sin(theta)));
-          theta += increment;
-        }
-        
-        if (IsHover())
-        {
-          polyline.AddChain(chain, false /* closed */, GetHoverColor());
-        }
-        else
-        {
-          polyline.AddChain(chain, false /* closed */, GetColor());
-        }
-      }
-      
-      virtual void RenderOtherLayers(MacroSceneLayer& macro,
-                                     const Scene2D& scene) ORTHANC_OVERRIDE
-      {
-      }
-
-      virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
-      }
-
-      virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
-      }
-    };
-
-    
-    class Text : public GeometricPrimitive
-    {
-    private:
-      AnnotationsLayer&                that_;
-      bool                             first_;
-      size_t                           subLayer_;
-      std::unique_ptr<TextSceneLayer>  content_;
-
-    public:
-      Text(AnnotationsLayer& that,
-           Annotation& parentAnnotation) :
-        GeometricPrimitive(parentAnnotation, 2),
-        that_(that),
-        first_(true)
-      {
-      }
-
-      virtual ~Text()
-      {
-        if (!first_)
-        {
-          that_.TagSubLayerToRemove(subLayer_);
-        }
-      }
-      
-      void SetContent(const TextSceneLayer& content)
-      {
-        SetModified(true);
-        content_.reset(dynamic_cast<TextSceneLayer*>(content.Clone()));
-      }        
-
-      virtual bool IsHit(const ScenePoint2D& p,
-                         const Scene2D& scene) const
-      {
-        return false;
-      }
-
-      virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
-                                       const Scene2D& scene) ORTHANC_OVERRIDE
-      {
-      }
-      
-      virtual void RenderOtherLayers(MacroSceneLayer& macro,
-                                     const Scene2D& scene) ORTHANC_OVERRIDE
-      {
-        if (content_.get() != NULL)
-        {
-          std::unique_ptr<TextSceneLayer> layer(reinterpret_cast<TextSceneLayer*>(content_->Clone()));
-
-          layer->SetColor(IsHover() ? GetHoverColor() : GetColor());
-          
-          if (first_)
-          {
-            subLayer_ = macro.AddLayer(layer.release());
-            first_ = false;
-          }
-          else
-          {
-            macro.UpdateLayer(subLayer_, layer.release());
-          }
-        }
-      }
-
-      virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
-      }
-
-      virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
-      }
-    };
-
-
-    class EditPrimitiveTracker : public IFlexiblePointerTracker
-    {
-    private:
-      GeometricPrimitive&  primitive_;
-      ScenePoint2D         sceneClick_;
-      AffineTransform2D    canvasToScene_;
-      bool                 alive_;
-      
-    public:
-      EditPrimitiveTracker(GeometricPrimitive& primitive,
-                           const ScenePoint2D& sceneClick,
-                           const AffineTransform2D& canvasToScene) :
-        primitive_(primitive),
-        sceneClick_(sceneClick),
-        canvasToScene_(canvasToScene),
-        alive_(true)
-      {
-      }
-
-      virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-        primitive_.MovePreview(event.GetMainPosition().Apply(canvasToScene_) - sceneClick_);
-      }
-      
-      virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-        primitive_.MoveDone(event.GetMainPosition().Apply(canvasToScene_) - sceneClick_);
-        alive_ = false;
-      }
-
-      virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-      }
-
-      virtual bool IsAlive() const ORTHANC_OVERRIDE
-      {
-        return alive_;
-      }
-
-      virtual void Cancel() ORTHANC_OVERRIDE
-      {
-        primitive_.MoveDone(ScenePoint2D(0, 0));
-      }
-    };
-
-
-    class SegmentAnnotation : public Annotation
-    {
-    private:
-      bool      showLabel_;
-      Handle&   handle1_;
-      Handle&   handle2_;
-      Segment&  segment_;
-      Text&     label_;
-
-      void UpdateLabel()
-      {
-        if (showLabel_)
-        {
-          TextSceneLayer content;
-
-          double x1 = handle1_.GetCenter().GetX();
-          double y1 = handle1_.GetCenter().GetY();
-          double x2 = handle2_.GetCenter().GetX();
-          double y2 = handle2_.GetCenter().GetY();
-        
-          // Put the label to the right of the right-most handle
-          if (x1 < x2)
-          {
-            content.SetPosition(x2, y2);
-          }
-          else
-          {
-            content.SetPosition(x1, y1);
-          }
-
-          content.SetAnchor(BitmapAnchor_CenterLeft);
-          content.SetBorder(10);
-
-          double dx = x1 - x2;
-          double dy = y1 - y2;
-          char buf[32];
-          sprintf(buf, "%0.2f cm", sqrt(dx * dx + dy * dy) / 10.0);
-          content.SetText(buf);
-
-          label_.SetContent(content);
-        }
-      }
-
-    public:
-      SegmentAnnotation(AnnotationsLayer& that,
-                        bool showLabel,
-                        const ScenePoint2D& p1,
-                        const ScenePoint2D& p2) :
-        Annotation(that),
-        showLabel_(showLabel),
-        handle1_(AddTypedPrimitive<Handle>(new Handle(*this, p1))),
-        handle2_(AddTypedPrimitive<Handle>(new Handle(*this, p2))),
-        segment_(AddTypedPrimitive<Segment>(new Segment(*this, p1, p2))),
-        label_(AddTypedPrimitive<Text>(new Text(that, *this)))
-      {
-        label_.SetColor(Color(255, 0, 0));
-        UpdateLabel();
-      }
-
-      Handle& GetHandle1() const
-      {
-        return handle1_;
-      }
-
-      Handle& GetHandle2() const
-      {
-        return handle2_;
-      }
-
-      virtual void SignalMove(GeometricPrimitive& primitive) ORTHANC_OVERRIDE
-      {
-        if (&primitive == &handle1_ ||
-            &primitive == &handle2_)
-        {
-          segment_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter());
-        }
-        else if (&primitive == &segment_)
-        {
-          handle1_.SetCenter(segment_.GetPosition1());
-          handle2_.SetCenter(segment_.GetPosition2());
-        }
-        
-        UpdateLabel();
-      }
-
-      virtual void Serialize(Json::Value& target) ORTHANC_OVERRIDE
-      {
-        target = Json::objectValue;
-        target[KEY_TYPE] = VALUE_SEGMENT;
-        target[KEY_X1] = handle1_.GetCenter().GetX();
-        target[KEY_Y1] = handle1_.GetCenter().GetY();
-        target[KEY_X2] = handle2_.GetCenter().GetX();
-        target[KEY_Y2] = handle2_.GetCenter().GetY();
-      }
-
-      static void Unserialize(AnnotationsLayer& target,
-                              const Json::Value& source)
-      {
-        if (source.isMember(KEY_X1) &&
-            source.isMember(KEY_Y1) &&
-            source.isMember(KEY_X2) &&
-            source.isMember(KEY_Y2) &&
-            source[KEY_X1].isNumeric() &&
-            source[KEY_Y1].isNumeric() &&
-            source[KEY_X2].isNumeric() &&
-            source[KEY_Y2].isNumeric())
-        {
-          new SegmentAnnotation(target, true,
-                                ScenePoint2D(source[KEY_X1].asDouble(), source[KEY_Y1].asDouble()),
-                                ScenePoint2D(source[KEY_X2].asDouble(), source[KEY_Y2].asDouble()));
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize an segment annotation");
-        }
-      }
-    };
-
-    
-    class AngleAnnotation : public Annotation
-    {
-    private:
-      Handle&   startHandle_;
-      Handle&   middleHandle_;
-      Handle&   endHandle_;
-      Segment&  segment1_;
-      Segment&  segment2_;
-      Arc&      arc_;
-      Text&     label_;
-
-      void UpdateLabel()
-      {
-        TextSceneLayer content;
-
-        const double x1 = startHandle_.GetCenter().GetX();
-        const double x2 = middleHandle_.GetCenter().GetX();
-        const double y2 = middleHandle_.GetCenter().GetY();
-        const double x3 = endHandle_.GetCenter().GetX();
-        
-        if (x2 < x1 &&
-            x2 < x3)
-        {
-          content.SetAnchor(BitmapAnchor_CenterRight);
-        }
-        else
-        {
-          content.SetAnchor(BitmapAnchor_CenterLeft);
-        }
-
-        content.SetPosition(x2, y2);
-        content.SetBorder(10);
-
-        char buf[32];
-        sprintf(buf, "%.01f%c%c", std::abs(arc_.GetAngle()) / PI * 180.0,
-                0xc2, 0xb0 /* two bytes corresponding to degree symbol in UTF-8 */);
-        content.SetText(buf);
-
-        label_.SetContent(content);
-      }
-
-    public:
-      AngleAnnotation(AnnotationsLayer& that,
-                      const ScenePoint2D& start,
-                      const ScenePoint2D& middle,
-                      const ScenePoint2D& end) :
-        Annotation(that),
-        startHandle_(AddTypedPrimitive<Handle>(new Handle(*this, start))),
-        middleHandle_(AddTypedPrimitive<Handle>(new Handle(*this, middle))),
-        endHandle_(AddTypedPrimitive<Handle>(new Handle(*this, end))),
-        segment1_(AddTypedPrimitive<Segment>(new Segment(*this, start, middle))),
-        segment2_(AddTypedPrimitive<Segment>(new Segment(*this, middle, end))),
-        arc_(AddTypedPrimitive<Arc>(new Arc(*this, start, middle, end))),
-        label_(AddTypedPrimitive<Text>(new Text(that, *this)))
-      {
-        label_.SetColor(Color(255, 0, 0));
-        UpdateLabel();
-      }
-
-      Handle& GetEndHandle() const
-      {
-        return endHandle_;
-      }
-
-      virtual void SignalMove(GeometricPrimitive& primitive) ORTHANC_OVERRIDE
-      {
-        if (&primitive == &startHandle_)
-        {
-          segment1_.SetPosition(startHandle_.GetCenter(), middleHandle_.GetCenter());
-          arc_.SetStart(startHandle_.GetCenter());
-        }
-        else if (&primitive == &middleHandle_)
-        {
-          segment1_.SetPosition(startHandle_.GetCenter(), middleHandle_.GetCenter());
-          segment2_.SetPosition(middleHandle_.GetCenter(), endHandle_.GetCenter());
-          arc_.SetMiddle(middleHandle_.GetCenter());
-        }
-        else if (&primitive == &endHandle_)
-        {
-          segment2_.SetPosition(middleHandle_.GetCenter(), endHandle_.GetCenter());
-          arc_.SetEnd(endHandle_.GetCenter());
-        }
-        else if (&primitive == &segment1_)
-        {
-          startHandle_.SetCenter(segment1_.GetPosition1());
-          middleHandle_.SetCenter(segment1_.GetPosition2());
-          segment2_.SetPosition(segment1_.GetPosition2(), segment2_.GetPosition2());
-          arc_.SetStart(segment1_.GetPosition1());
-          arc_.SetMiddle(segment1_.GetPosition2());
-        }
-        else if (&primitive == &segment2_)
-        {
-          middleHandle_.SetCenter(segment2_.GetPosition1());
-          endHandle_.SetCenter(segment2_.GetPosition2());
-          segment1_.SetPosition(segment1_.GetPosition1(), segment2_.GetPosition1());
-          arc_.SetMiddle(segment2_.GetPosition1());
-          arc_.SetEnd(segment2_.GetPosition2());
-        }
-
-        UpdateLabel();
-      }
-
-      virtual void Serialize(Json::Value& target) ORTHANC_OVERRIDE
-      {
-        target = Json::objectValue;
-        target[KEY_TYPE] = VALUE_ANGLE;
-        target[KEY_X1] = startHandle_.GetCenter().GetX();
-        target[KEY_Y1] = startHandle_.GetCenter().GetY();
-        target[KEY_X2] = middleHandle_.GetCenter().GetX();
-        target[KEY_Y2] = middleHandle_.GetCenter().GetY();
-        target[KEY_X3] = endHandle_.GetCenter().GetX();
-        target[KEY_Y3] = endHandle_.GetCenter().GetY();
-      }
-
-      static void Unserialize(AnnotationsLayer& target,
-                              const Json::Value& source)
-      {
-        if (source.isMember(KEY_X1) &&
-            source.isMember(KEY_Y1) &&
-            source.isMember(KEY_X2) &&
-            source.isMember(KEY_Y2) &&
-            source.isMember(KEY_X3) &&
-            source.isMember(KEY_Y3) &&
-            source[KEY_X1].isNumeric() &&
-            source[KEY_Y1].isNumeric() &&
-            source[KEY_X2].isNumeric() &&
-            source[KEY_Y2].isNumeric() &&
-            source[KEY_X3].isNumeric() &&
-            source[KEY_Y3].isNumeric())
-        {
-          new AngleAnnotation(target,
-                              ScenePoint2D(source[KEY_X1].asDouble(), source[KEY_Y1].asDouble()),
-                              ScenePoint2D(source[KEY_X2].asDouble(), source[KEY_Y2].asDouble()),
-                              ScenePoint2D(source[KEY_X3].asDouble(), source[KEY_Y3].asDouble()));
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize an angle annotation");
-        }
-      }
-    };
-
-    
-    class CircleAnnotation : public Annotation
-    {
-    private:
-      Handle&   handle1_;
-      Handle&   handle2_;
-      Segment&  segment_;
-      Circle&   circle_;
-      Text&     label_;
-
-      void UpdateLabel()
-      {
-        TextSceneLayer content;
-
-        double x1 = handle1_.GetCenter().GetX();
-        double y1 = handle1_.GetCenter().GetY();
-        double x2 = handle2_.GetCenter().GetX();
-        double y2 = handle2_.GetCenter().GetY();
-        
-        // Put the label to the right of the right-most handle
-        if (x1 < x2)
-        {
-          content.SetPosition(x2, y2);
-        }
-        else
-        {
-          content.SetPosition(x1, y1);
-        }
-
-        content.SetAnchor(BitmapAnchor_CenterLeft);
-        content.SetBorder(10);
-
-        double dx = x1 - x2;
-        double dy = y1 - y2;
-        double diameter = sqrt(dx * dx + dy * dy);  // in millimeters
-
-        double area = PI * diameter * diameter / 4.0;
-        
-        char buf[32];
-        sprintf(buf, "%0.2f cm\n%0.2f cm%c%c",
-                diameter / 10.0,
-                area / 100.0,
-                0xc2, 0xb2 /* two bytes corresponding to two power in UTF-8 */);
-        content.SetText(buf);
-
-        label_.SetContent(content);
-      }
-
-    public:
-      CircleAnnotation(AnnotationsLayer& that,
-                       const ScenePoint2D& p1,
-                       const ScenePoint2D& p2) :
-        Annotation(that),
-        handle1_(AddTypedPrimitive<Handle>(new Handle(*this, p1))),
-        handle2_(AddTypedPrimitive<Handle>(new Handle(*this, p2))),
-        segment_(AddTypedPrimitive<Segment>(new Segment(*this, p1, p2))),
-        circle_(AddTypedPrimitive<Circle>(new Circle(*this, p1, p2))),
-        label_(AddTypedPrimitive<Text>(new Text(that, *this)))
-      {
-        label_.SetColor(Color(255, 0, 0));
-        UpdateLabel();
-      }
-
-      Handle& GetHandle2() const
-      {
-        return handle2_;
-      }
-
-      virtual void SignalMove(GeometricPrimitive& primitive) ORTHANC_OVERRIDE
-      {
-        if (&primitive == &handle1_ ||
-            &primitive == &handle2_)
-        {
-          segment_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter());
-          circle_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter());          
-        }
-        else if (&primitive == &segment_)
-        {
-          handle1_.SetCenter(segment_.GetPosition1());
-          handle2_.SetCenter(segment_.GetPosition2());
-          circle_.SetPosition(segment_.GetPosition1(), segment_.GetPosition2());
-        }
-        
-        UpdateLabel();
-      }
-
-      virtual void Serialize(Json::Value& target) ORTHANC_OVERRIDE
-      {
-        target = Json::objectValue;
-        target[KEY_TYPE] = VALUE_CIRCLE;
-        target[KEY_X1] = handle1_.GetCenter().GetX();
-        target[KEY_Y1] = handle1_.GetCenter().GetY();
-        target[KEY_X2] = handle2_.GetCenter().GetX();
-        target[KEY_Y2] = handle2_.GetCenter().GetY();
-      }
-
-      static void Unserialize(AnnotationsLayer& target,
-                              const Json::Value& source)
-      {
-        if (source.isMember(KEY_X1) &&
-            source.isMember(KEY_Y1) &&
-            source.isMember(KEY_X2) &&
-            source.isMember(KEY_Y2) &&
-            source[KEY_X1].isNumeric() &&
-            source[KEY_Y1].isNumeric() &&
-            source[KEY_X2].isNumeric() &&
-            source[KEY_Y2].isNumeric())
-        {
-          new CircleAnnotation(target,
-                               ScenePoint2D(source[KEY_X1].asDouble(), source[KEY_Y1].asDouble()),
-                               ScenePoint2D(source[KEY_X2].asDouble(), source[KEY_Y2].asDouble()));
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize an circle annotation");
-        }
-      }
-    };
-
-    
-    class CreateSegmentOrCircleTracker : public IFlexiblePointerTracker
-    {
-    private:
-      AnnotationsLayer&  that_;
-      Annotation*        annotation_;
-      AffineTransform2D  canvasToScene_;
-      Handle*            handle2_;
-      
-    public:
-      CreateSegmentOrCircleTracker(AnnotationsLayer& that,
-                                   bool isCircle,
-                                   const ScenePoint2D& sceneClick,
-                                   const AffineTransform2D& canvasToScene) :
-        that_(that),
-        annotation_(NULL),
-        canvasToScene_(canvasToScene),
-        handle2_(NULL)
-      {
-        if (isCircle)
-        {
-          annotation_ = new CircleAnnotation(that, sceneClick, sceneClick);
-          handle2_ = &dynamic_cast<CircleAnnotation*>(annotation_)->GetHandle2();
-        }
-        else
-        {
-          annotation_ = new SegmentAnnotation(that, true /* show label */, sceneClick, sceneClick);
-          handle2_ = &dynamic_cast<SegmentAnnotation*>(annotation_)->GetHandle2();
-        }
-        
-        assert(annotation_ != NULL &&
-               handle2_ != NULL);
-      }
-
-      virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-        if (annotation_ != NULL)
-        {
-          assert(handle2_ != NULL);
-          handle2_->SetCenter(event.GetMainPosition().Apply(canvasToScene_));
-          annotation_->SignalMove(*handle2_);
-        }
-      }
-      
-      virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-        annotation_ = NULL;  // IsAlive() becomes false
-
-        that_.BroadcastMessage(AnnotationAddedMessage(that_));
-      }
-
-      virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-      }
-
-      virtual bool IsAlive() const ORTHANC_OVERRIDE
-      {
-        return (annotation_ != NULL);
-      }
-
-      virtual void Cancel() ORTHANC_OVERRIDE
-      {
-        if (annotation_ != NULL)
-        {
-          that_.DeleteAnnotation(annotation_);
-          annotation_ = NULL;
-        }
-      }
-    };
-
-
-    class CreateAngleTracker : public IFlexiblePointerTracker
-    {
-    private:
-      AnnotationsLayer&   that_;
-      SegmentAnnotation*  segment_;
-      AngleAnnotation*    angle_;
-      AffineTransform2D   canvasToScene_;
-      
-    public:
-      CreateAngleTracker(AnnotationsLayer& that,
-                         const ScenePoint2D& sceneClick,
-                         const AffineTransform2D& canvasToScene) :
-        that_(that),
-        segment_(NULL),
-        angle_(NULL),
-        canvasToScene_(canvasToScene)
-      {
-        segment_ = new SegmentAnnotation(that, false /* no length label */, sceneClick, sceneClick);
-      }
-
-      virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-        if (segment_ != NULL)
-        {
-          segment_->GetHandle2().SetCenter(event.GetMainPosition().Apply(canvasToScene_));
-          segment_->SignalMove(segment_->GetHandle2());
-        }
-
-        if (angle_ != NULL)
-        {
-          angle_->GetEndHandle().SetCenter(event.GetMainPosition().Apply(canvasToScene_));
-          angle_->SignalMove(angle_->GetEndHandle());
-        }
-      }
-      
-      virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-        if (segment_ != NULL)
-        {
-          // End of first step: The first segment is available, now create the angle
-
-          angle_ = new AngleAnnotation(that_, segment_->GetHandle1().GetCenter(),
-                                       segment_->GetHandle2().GetCenter(),
-                                       segment_->GetHandle2().GetCenter());
-          
-          that_.DeleteAnnotation(segment_);
-          segment_ = NULL;
-        }
-        else
-        {
-          angle_ = NULL;  // IsAlive() becomes false
-
-          that_.BroadcastMessage(AnnotationAddedMessage(that_));
-        }
-      }
-
-      virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-      }
-
-      virtual bool IsAlive() const ORTHANC_OVERRIDE
-      {
-        return (segment_ != NULL ||
-                angle_ != NULL);
-      }
-
-      virtual void Cancel() ORTHANC_OVERRIDE
-      {
-        if (segment_ != NULL)
-        {
-          that_.DeleteAnnotation(segment_);
-          segment_ = NULL;
-        }
-
-        if (angle_ != NULL)
-        {
-          that_.DeleteAnnotation(angle_);
-          angle_ = NULL;
-        }
-      }
-    };
-
-
-    // Dummy tracker that is only used for deletion, in order to warn
-    // the caller that the mouse action was taken into consideration
-    class EraseTracker : public IFlexiblePointerTracker
-    {
-    public:
-      EraseTracker()
-      {
-      }
-
-      virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-      }
-      
-      virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-      }
-
-      virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-      }
-
-      virtual bool IsAlive() const ORTHANC_OVERRIDE
-      {
-        return false;
-      }
-
-      virtual void Cancel() ORTHANC_OVERRIDE
-      {
-      }
-    };
-
-
-    typedef std::set<GeometricPrimitive*>  GeometricPrimitives;
-    typedef std::set<Annotation*>          Annotations;
-    typedef std::set<size_t>               SubLayers;
-
-    Tool                 activeTool_;
-    size_t               macroLayerIndex_;
-    size_t               polylineSubLayer_;
-    GeometricPrimitives  primitives_;
-    Annotations          annotations_;
-    SubLayers            subLayersToRemove_;
-
-    void AddAnnotation(Annotation* annotation)
-    {
-      assert(annotation != NULL);
-      assert(annotations_.find(annotation) == annotations_.end());
-      annotations_.insert(annotation);
-    }
-
-    void DeleteAnnotation(Annotation* annotation)
-    {
-      if (annotation != NULL)
-      {
-        assert(annotations_.find(annotation) != annotations_.end());
-        annotations_.erase(annotation);
-        delete annotation;
-      }
-    }
-
-    void DeletePrimitive(GeometricPrimitive* primitive)
-    {
-      if (primitive != NULL)
-      {
-        assert(primitives_.find(primitive) != primitives_.end());
-        primitives_.erase(primitive);
-        delete primitive;
-      }
-    }
-
-    void TagSubLayerToRemove(size_t subLayerIndex)
-    {
-      assert(subLayersToRemove_.find(subLayerIndex) == subLayersToRemove_.end());
-      subLayersToRemove_.insert(subLayerIndex);
-    }
-    
-  public:
-    AnnotationsLayer(size_t macroLayerIndex) :
-      activeTool_(Tool_Edit),
-      macroLayerIndex_(macroLayerIndex),
-      polylineSubLayer_(0)  // dummy initialization
-    {
-      annotations_.insert(new SegmentAnnotation(*this, true /* show label */, ScenePoint2D(0, 0), ScenePoint2D(100, 100)));
-      annotations_.insert(new AngleAnnotation(*this, ScenePoint2D(100, 50), ScenePoint2D(150, 40), ScenePoint2D(200, 50)));
-      annotations_.insert(new CircleAnnotation(*this, ScenePoint2D(50, 200), ScenePoint2D(100, 250)));
-    }
-    
-    ~AnnotationsLayer()
-    {
-      Clear();
-    }
-
-    void Clear()
-    {
-      for (Annotations::iterator it = annotations_.begin(); it != annotations_.end(); ++it)
-      {
-        assert(*it != NULL);
-        delete *it;
-      }
-
-      annotations_.clear();
-    }
-
-    void SetActiveTool(Tool tool)
-    {
-      activeTool_ = tool;
-    }
-
-    Tool GetActiveTool() const
-    {
-      return activeTool_;
-    }
-
-    void Render(Scene2D& scene)
-    {
-      MacroSceneLayer* macro = NULL;
-
-      if (scene.HasLayer(macroLayerIndex_))
-      {
-        macro = &dynamic_cast<MacroSceneLayer&>(scene.GetLayer(macroLayerIndex_));
-      }
-      else
-      {
-        macro = &dynamic_cast<MacroSceneLayer&>(scene.SetLayer(macroLayerIndex_, new MacroSceneLayer));
-        polylineSubLayer_ = macro->AddLayer(new PolylineSceneLayer);
-      }
-
-      for (SubLayers::const_iterator it = subLayersToRemove_.begin(); it != subLayersToRemove_.end(); ++it)
-      {
-        assert(macro->HasLayer(*it));
-        macro->DeleteLayer(*it);
-      }
-
-      subLayersToRemove_.clear();
-
-      std::unique_ptr<PolylineSceneLayer> polyline(new PolylineSceneLayer);
-
-      for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
-      {
-        assert(*it != NULL);
-        GeometricPrimitive& primitive = **it;        
-        
-        primitive.RenderPolylineLayer(*polyline, scene);
-
-        if (primitive.IsModified())
-        {
-          primitive.RenderOtherLayers(*macro, scene);
-          primitive.SetModified(false);
-        }
-      }
-
-      macro->UpdateLayer(polylineSubLayer_, polyline.release());
-    }
-
-    bool ClearHover()
-    {
-      bool needsRefresh = false;
-      
-      for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
-      {
-        assert(*it != NULL);
-        if ((*it)->IsHover())
-        {
-          (*it)->SetHover(false);
-          needsRefresh = true;
-        }
-      }
-
-      return needsRefresh;
-    }
-
-    bool SetMouseHover(const ScenePoint2D& p /* expressed in canvas coordinates */,
-                       const Scene2D& scene)
-    {
-      if (activeTool_ == Tool_None)
-      {
-        return ClearHover();
-      }
-      else
-      {
-        bool needsRefresh = false;
-      
-        const ScenePoint2D s = p.Apply(scene.GetCanvasToSceneTransform());
-      
-        for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
-        {
-          assert(*it != NULL);
-          bool hover = (*it)->IsHit(s, scene);
-
-          if ((*it)->IsHover() != hover)
-          {
-            needsRefresh = true;
-          }
-        
-          (*it)->SetHover(hover);
-        }
-
-        return needsRefresh;
-      }
-    }
-
-
-    IFlexiblePointerTracker* CreateTracker(const ScenePoint2D& p /* expressed in canvas coordinates */,
-                                           const Scene2D& scene)
-    {
-      if (activeTool_ == Tool_None)
-      {
-        return NULL;
-      }
-      else
-      {
-        const ScenePoint2D s = p.Apply(scene.GetCanvasToSceneTransform());
-
-        GeometricPrimitive* bestHit = NULL;
-      
-        for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
-        {
-          assert(*it != NULL);
-          if ((*it)->IsHit(s, scene))
-          {
-            if (bestHit == NULL ||
-                bestHit->GetDepth() > (*it)->GetDepth())
-            {
-              bestHit = *it;
-            }
-          }
-        }
-
-        if (bestHit != NULL)
-        {
-          if (activeTool_ == Tool_Erase)
-          {
-            DeleteAnnotation(&bestHit->GetParentAnnotation());
-            BroadcastMessage(AnnotationRemovedMessage(*this));
-            return new EraseTracker;
-          }
-          else
-          {
-            return new EditPrimitiveTracker(*bestHit, s, scene.GetCanvasToSceneTransform());
-          }
-        }
-        else
-        {
-          switch (activeTool_)
-          {
-            case Tool_Segment:
-              return new CreateSegmentOrCircleTracker(*this, false /* segment */, s, scene.GetCanvasToSceneTransform());
-
-            case Tool_Circle:
-              return new CreateSegmentOrCircleTracker(*this, true /* circle */, s, scene.GetCanvasToSceneTransform());
-
-            case Tool_Angle:
-              return new CreateAngleTracker(*this, s, scene.GetCanvasToSceneTransform());
-
-            default:
-              return NULL;
-          }
-        }
-      }
-    }
-
-
-    void Serialize(Json::Value& target) const
-    {
-      Json::Value annotations = Json::arrayValue;
-      
-      for (Annotations::const_iterator it = annotations_.begin(); it != annotations_.end(); ++it)
-      {
-        assert(*it != NULL);
-
-        Json::Value item;
-        (*it)->Serialize(item);
-        annotations.append(item);
-      }
-
-      target = Json::objectValue;
-      target[KEY_ANNOTATIONS] = annotations;
-    }
-
-
-    void Unserialize(const Json::Value& serialized)
-    {
-      Clear();
-      
-      if (serialized.type() != Json::objectValue ||
-          !serialized.isMember(KEY_ANNOTATIONS) ||
-          serialized[KEY_ANNOTATIONS].type() != Json::arrayValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize a set of annotations");
-      }
-
-      const Json::Value& annotations = serialized[KEY_ANNOTATIONS];
-
-      for (Json::Value::ArrayIndex i = 0; i < annotations.size(); i++)
-      {
-        if (annotations[i].type() != Json::objectValue ||
-            !annotations[i].isMember(KEY_TYPE) ||
-            annotations[i][KEY_TYPE].type() != Json::stringValue)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-
-        const std::string& type = annotations[i][KEY_TYPE].asString();
-
-        if (type == VALUE_ANGLE)
-        {
-          AngleAnnotation::Unserialize(*this, annotations[i]);
-        }
-        else if (type == VALUE_CIRCLE)
-        {
-          CircleAnnotation::Unserialize(*this, annotations[i]);
-        }
-        else if (type == VALUE_SEGMENT)
-        {
-          SegmentAnnotation::Unserialize(*this, annotations[i]);
-        }
-        else
-        {
-          LOG(ERROR) << "Cannot unserialize unknown type of annotation: " << type;
-        }
-      }
-    }
-  };
-}
-#endif
-
-
-
-std::string orthancUrl;
-std::string instanceId;
-int frameIndex = 0;
+static std::string orthancUrl;
+static std::string instanceId;
+static int frameIndex = 0;
 
 
 static void ProcessOptions(int argc, char* argv[])
@@ -1721,7 +145,7 @@
 
     {
 
-#if 1
+#if 0
       boost::shared_ptr<OrthancStone::SdlViewport> viewport =
         OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600);
 #else
@@ -1759,8 +183,8 @@
         bool angleMeasureFirst = true;
         angleMeasureTool->Disable();
 
-        OrthancStone::AnnotationsLayer annotations(10);
-        annotations.SetActiveTool(OrthancStone::AnnotationsLayer::Tool_Angle);
+        OrthancStone::AnnotationsSceneLayer annotations(10);
+        annotations.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Angle);
 
         {
           Json::Value v;
--- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Thu May 20 14:15:00 2021 +0200
+++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Thu May 20 14:48:51 2021 +0200
@@ -257,6 +257,8 @@
   ${ORTHANC_STONE_ROOT}/Oracle/ParseDicomFromFileCommand.cpp
   ${ORTHANC_STONE_ROOT}/Oracle/ParseDicomFromWadoCommand.cpp
 
+  ${ORTHANC_STONE_ROOT}/Scene2D/AnnotationsSceneLayer.cpp
+  ${ORTHANC_STONE_ROOT}/Scene2D/AnnotationsSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/ArrowSceneLayer.cpp
   ${ORTHANC_STONE_ROOT}/Scene2D/ArrowSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/CairoCompositor.cpp
@@ -295,6 +297,7 @@
   ${ORTHANC_STONE_ROOT}/Scene2D/RotateSceneTracker.h
   ${ORTHANC_STONE_ROOT}/Scene2D/Scene2D.cpp
   ${ORTHANC_STONE_ROOT}/Scene2D/Scene2D.h
+  ${ORTHANC_STONE_ROOT}/Scene2D/ScenePoint2D.cpp
   ${ORTHANC_STONE_ROOT}/Scene2D/ScenePoint2D.h
   ${ORTHANC_STONE_ROOT}/Scene2D/TextSceneLayer.cpp
   ${ORTHANC_STONE_ROOT}/Scene2D/TextSceneLayer.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.cpp	Thu May 20 14:48:51 2021 +0200
@@ -0,0 +1,1560 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "AnnotationsSceneLayer.h"
+
+#include "MacroSceneLayer.h"
+#include "PolylineSceneLayer.h"
+#include "TextSceneLayer.h"
+
+#include <OrthancException.h>
+
+#include <boost/math/constants/constants.hpp>
+#include <list>
+
+static const double HANDLE_SIZE = 10.0;
+static const double PI = boost::math::constants::pi<double>();
+
+static const char* const KEY_ANNOTATIONS = "annotations";
+static const char* const KEY_TYPE = "type";
+static const char* const KEY_X1 = "x1";
+static const char* const KEY_Y1 = "y1";
+static const char* const KEY_X2 = "x2";
+static const char* const KEY_Y2 = "y2";
+static const char* const KEY_X3 = "x3";
+static const char* const KEY_Y3 = "y3";
+
+static const char* const VALUE_ANGLE = "angle";
+static const char* const VALUE_CIRCLE = "circle";
+static const char* const VALUE_SEGMENT = "segment";
+
+
+namespace OrthancStone
+{
+  class AnnotationsSceneLayer::GeometricPrimitive : public boost::noncopyable
+  {
+  private:
+    bool         modified_;
+    Annotation&  parentAnnotation_;
+    Color        color_;
+    Color        hoverColor_;
+    bool         isHover_;
+    int          depth_;
+
+  public:
+    GeometricPrimitive(Annotation& parentAnnotation,
+                       int depth) :
+      modified_(true),
+      parentAnnotation_(parentAnnotation),
+      color_(192, 192, 192),
+      hoverColor_(0, 255, 0),
+      isHover_(false),
+      depth_(depth)
+    {
+    }
+      
+    virtual ~GeometricPrimitive()
+    {
+    }
+
+    Annotation& GetParentAnnotation() const
+    {
+      return parentAnnotation_;
+    }
+      
+    int GetDepth() const
+    {
+      return depth_;
+    }
+
+    void SetHover(bool hover)
+    {
+      if (hover != isHover_)
+      {
+        isHover_ = hover;
+        modified_ = true;
+      }
+    }
+
+    bool IsHover() const
+    {
+      return isHover_;
+    }
+      
+    void SetModified(bool modified)
+    {
+      modified_ = modified;
+    }
+      
+    bool IsModified() const
+    {
+      return modified_;
+    }
+
+    void SetColor(const Color& color)
+    {
+      SetModified(true);
+      color_ = color;
+    }
+
+    void SetHoverColor(const Color& color)
+    {
+      SetModified(true);
+      hoverColor_ = color;
+    }
+
+    const Color& GetColor() const
+    {
+      return color_;
+    }
+
+    const Color& GetHoverColor() const
+    {
+      return hoverColor_;
+    }
+
+    virtual bool IsHit(const ScenePoint2D& p,
+                       const Scene2D& scene) const = 0;
+
+    // Always called, even if not modified
+    virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
+                                     const Scene2D& scene) = 0;
+
+    // Only called if modified
+    virtual void RenderOtherLayers(MacroSceneLayer& macro,
+                                   const Scene2D& scene) = 0;
+
+    virtual void MovePreview(const ScenePoint2D& delta) = 0;
+
+    virtual void MoveDone(const ScenePoint2D& delta) = 0;
+  };
+    
+
+  class AnnotationsSceneLayer::Annotation : public boost::noncopyable
+  {
+  private:
+    typedef std::list<GeometricPrimitive*>  GeometricPrimitives;
+      
+    AnnotationsSceneLayer&  that_;
+    GeometricPrimitives     primitives_;
+      
+  public:
+    Annotation(AnnotationsSceneLayer& that) :
+      that_(that)
+    {
+      that.AddAnnotation(this);
+    }
+      
+    virtual ~Annotation()
+    {
+      for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
+      {
+        that_.DeletePrimitive(*it);
+      }
+    }
+
+    GeometricPrimitive* AddPrimitive(GeometricPrimitive* primitive)
+    {
+      if (primitive == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+      else
+      {
+        assert(that_.primitives_.find(primitive) == that_.primitives_.end());
+        primitives_.push_back(primitive);  // For automated deallocation
+        that_.primitives_.insert(primitive);
+        return primitive;
+      }
+    }
+
+    template <typename T>
+    T& AddTypedPrimitive(T* primitive)
+    {
+      AddPrimitive(primitive);
+      return *primitive;
+    }
+
+    virtual void SignalMove(GeometricPrimitive& primitive) = 0;
+
+    virtual void Serialize(Json::Value& target) = 0;
+  };
+
+
+  class AnnotationsSceneLayer::Handle : public GeometricPrimitive
+  {
+  private:
+    ScenePoint2D  center_;
+    ScenePoint2D  delta_;
+
+  public:
+    explicit Handle(Annotation& parentAnnotation,
+                    const ScenePoint2D& center) :
+      GeometricPrimitive(parentAnnotation, 0),  // Highest priority
+      center_(center),
+      delta_(0, 0)
+    {
+    }
+
+    void SetSize(unsigned int size)
+    {
+      SetModified(true);
+    }
+
+    void SetCenter(const ScenePoint2D& center)
+    {
+      SetModified(true);
+      center_ = center;
+      delta_ = ScenePoint2D(0, 0);
+    }
+
+    ScenePoint2D GetCenter() const
+    {
+      return center_ + delta_;
+    }
+
+    virtual bool IsHit(const ScenePoint2D& p,
+                       const Scene2D& scene) const
+    {
+      const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom();
+
+      double dx = (center_.GetX() + delta_.GetX() - p.GetX()) * zoom;
+      double dy = (center_.GetY() + delta_.GetY() - p.GetY()) * zoom;
+
+      return (std::abs(dx) <= HANDLE_SIZE / 2.0 &&
+              std::abs(dy) <= HANDLE_SIZE / 2.0);
+    }
+
+    virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
+                                     const Scene2D& scene) ORTHANC_OVERRIDE
+    {
+      const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom();
+
+      // TODO: take DPI into account 
+      double x1 = center_.GetX() + delta_.GetX() - (HANDLE_SIZE / 2.0) / zoom;
+      double y1 = center_.GetY() + delta_.GetY() - (HANDLE_SIZE / 2.0) / zoom;
+      double x2 = center_.GetX() + delta_.GetX() + (HANDLE_SIZE / 2.0) / zoom;
+      double y2 = center_.GetY() + delta_.GetY() + (HANDLE_SIZE / 2.0) / zoom;
+
+      PolylineSceneLayer::Chain chain;
+      chain.reserve(4);
+      chain.push_back(ScenePoint2D(x1, y1));
+      chain.push_back(ScenePoint2D(x2, y1));
+      chain.push_back(ScenePoint2D(x2, y2));
+      chain.push_back(ScenePoint2D(x1, y2));
+
+      if (IsHover())
+      {
+        polyline.AddChain(chain, true /* closed */, GetHoverColor());
+      }
+      else
+      {
+        polyline.AddChain(chain, true /* closed */, GetColor());
+      }
+    }
+      
+    virtual void RenderOtherLayers(MacroSceneLayer& macro,
+                                   const Scene2D& scene) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
+    {
+      SetModified(true);
+      delta_ = delta;
+      GetParentAnnotation().SignalMove(*this);
+    }
+
+    virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
+    {
+      SetModified(true);
+      center_ = center_ + delta;
+      delta_ = ScenePoint2D(0, 0);
+      GetParentAnnotation().SignalMove(*this);
+    }
+  };
+
+    
+  class AnnotationsSceneLayer::Segment : public GeometricPrimitive
+  {
+  private:
+    ScenePoint2D  p1_;
+    ScenePoint2D  p2_;
+    ScenePoint2D  delta_;
+      
+  public:
+    Segment(Annotation& parentAnnotation,
+            const ScenePoint2D& p1,
+            const ScenePoint2D& p2) :
+      GeometricPrimitive(parentAnnotation, 1),  // Can only be selected if no handle matches
+      p1_(p1),
+      p2_(p2),
+      delta_(0, 0)
+    {
+    }
+
+    void SetPosition(const ScenePoint2D& p1,
+                     const ScenePoint2D& p2)
+    {
+      SetModified(true);
+      p1_ = p1;
+      p2_ = p2;
+      delta_ = ScenePoint2D(0, 0);
+    }
+
+    ScenePoint2D GetPosition1() const
+    {
+      return p1_ + delta_;
+    }
+
+    ScenePoint2D GetPosition2() const
+    {
+      return p2_ + delta_;
+    }
+
+    virtual bool IsHit(const ScenePoint2D& p,
+                       const Scene2D& scene) const
+    {
+      const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom();
+      return (ScenePoint2D::SquaredDistancePtSegment(p1_ + delta_, p2_ + delta_, p) * zoom * zoom <=
+              (HANDLE_SIZE / 2.0) * (HANDLE_SIZE / 2.0));
+    }
+
+    virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
+                                     const Scene2D& scene) ORTHANC_OVERRIDE
+    {
+      PolylineSceneLayer::Chain chain;
+      chain.reserve(2);
+      chain.push_back(p1_ + delta_);
+      chain.push_back(p2_ + delta_);
+
+      if (IsHover())
+      {
+        polyline.AddChain(chain, false /* closed */, GetHoverColor());
+      }
+      else
+      {
+        polyline.AddChain(chain, false /* closed */, GetColor());
+      }
+    }
+      
+    virtual void RenderOtherLayers(MacroSceneLayer& macro,
+                                   const Scene2D& scene) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
+    {
+      SetModified(true);
+      delta_ = delta;
+      GetParentAnnotation().SignalMove(*this);
+    }
+
+    virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
+    {
+      SetModified(true);
+      p1_ = p1_ + delta;
+      p2_ = p2_ + delta;
+      delta_ = ScenePoint2D(0, 0);
+      GetParentAnnotation().SignalMove(*this);
+    }
+  };
+
+    
+  class AnnotationsSceneLayer::Circle : public GeometricPrimitive
+  {
+  private:
+    ScenePoint2D  p1_;
+    ScenePoint2D  p2_;
+      
+  public:
+    Circle(Annotation& parentAnnotation,
+           const ScenePoint2D& p1,
+           const ScenePoint2D& p2) :
+      GeometricPrimitive(parentAnnotation, 2),
+      p1_(p1),
+      p2_(p2)
+    {
+    }
+
+    void SetPosition(const ScenePoint2D& p1,
+                     const ScenePoint2D& p2)
+    {
+      SetModified(true);
+      p1_ = p1;
+      p2_ = p2;
+    }
+
+    ScenePoint2D GetPosition1() const
+    {
+      return p1_;
+    }
+
+    ScenePoint2D GetPosition2() const
+    {
+      return p2_;
+    }
+
+    virtual bool IsHit(const ScenePoint2D& p,
+                       const Scene2D& scene) const
+    {
+      return false;
+    }
+
+    virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
+                                     const Scene2D& scene) ORTHANC_OVERRIDE
+    {
+      static unsigned int NUM_SEGMENTS = 128;
+
+      ScenePoint2D middle((p1_.GetX() + p2_.GetX()) / 2.0,
+                          (p1_.GetY() + p2_.GetY()) / 2.0);
+        
+      const double radius = ScenePoint2D::DistancePtPt(middle, p1_);
+
+      double increment = 2.0 * PI / static_cast<double>(NUM_SEGMENTS - 1);
+
+      PolylineSceneLayer::Chain chain;
+      chain.reserve(NUM_SEGMENTS);
+
+      double theta = 0;
+      for (unsigned int i = 0; i < NUM_SEGMENTS; i++)
+      {
+        chain.push_back(ScenePoint2D(middle.GetX() + radius * cos(theta),
+                                     middle.GetY() + radius * sin(theta)));
+        theta += increment;
+      }
+        
+      if (IsHover())
+      {
+        polyline.AddChain(chain, false /* closed */, GetHoverColor());
+      }
+      else
+      {
+        polyline.AddChain(chain, false /* closed */, GetColor());
+      }
+    }
+      
+    virtual void RenderOtherLayers(MacroSceneLayer& macro,
+                                   const Scene2D& scene) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
+    }
+
+    virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
+    }
+  };
+
+    
+  class AnnotationsSceneLayer::Arc : public GeometricPrimitive
+  {
+  private:
+    ScenePoint2D  start_;
+    ScenePoint2D  middle_;
+    ScenePoint2D  end_;
+    double        radius_;  // in pixels
+
+    void ComputeAngles(double& fullAngle,
+                       double& startAngle,
+                       double& endAngle) const
+    {
+      const double x1 = start_.GetX();
+      const double y1 = start_.GetY();
+      const double xc = middle_.GetX();
+      const double yc = middle_.GetY();
+      const double x2 = end_.GetX();
+      const double y2 = end_.GetY();
+        
+      startAngle = atan2(y1 - yc, x1 - xc);
+      endAngle = atan2(y2 - yc, x2 - xc);
+
+      fullAngle = endAngle - startAngle;
+        
+      while (fullAngle < -PI)
+      {
+        fullAngle += 2.0 * PI;
+      }
+        
+      while (fullAngle >= PI)
+      {
+        fullAngle -= 2.0 * PI;
+      }
+    }
+      
+  public:
+    Arc(Annotation& parentAnnotation,
+        const ScenePoint2D& start,
+        const ScenePoint2D& middle,
+        const ScenePoint2D& end) :
+      GeometricPrimitive(parentAnnotation, 2),
+      start_(start),
+      middle_(middle),
+      end_(end),
+      radius_(20)
+    {
+    }
+
+    double GetAngle() const
+    {
+      double fullAngle, startAngle, endAngle;
+      ComputeAngles(fullAngle, startAngle, endAngle);
+      return fullAngle;
+    }
+
+    void SetStart(const ScenePoint2D& p)
+    {
+      SetModified(true);
+      start_ = p;
+    }
+
+    void SetMiddle(const ScenePoint2D& p)
+    {
+      SetModified(true);
+      middle_ = p;
+    }
+
+    void SetEnd(const ScenePoint2D& p)
+    {
+      SetModified(true);
+      end_ = p;
+    }
+
+    virtual bool IsHit(const ScenePoint2D& p,
+                       const Scene2D& scene) const
+    {
+      return false;
+    }
+
+    virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
+                                     const Scene2D& scene) ORTHANC_OVERRIDE
+    {
+      static unsigned int NUM_SEGMENTS = 64;
+
+      const double radius = radius_ / scene.GetSceneToCanvasTransform().ComputeZoom();
+
+      double fullAngle, startAngle, endAngle;
+      ComputeAngles(fullAngle, startAngle, endAngle);
+
+      double increment = fullAngle / static_cast<double>(NUM_SEGMENTS - 1);
+
+      PolylineSceneLayer::Chain chain;
+      chain.reserve(NUM_SEGMENTS);
+
+      double theta = startAngle;
+      for (unsigned int i = 0; i < NUM_SEGMENTS; i++)
+      {
+        chain.push_back(ScenePoint2D(middle_.GetX() + radius * cos(theta),
+                                     middle_.GetY() + radius * sin(theta)));
+        theta += increment;
+      }
+        
+      if (IsHover())
+      {
+        polyline.AddChain(chain, false /* closed */, GetHoverColor());
+      }
+      else
+      {
+        polyline.AddChain(chain, false /* closed */, GetColor());
+      }
+    }
+      
+    virtual void RenderOtherLayers(MacroSceneLayer& macro,
+                                   const Scene2D& scene) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
+    }
+
+    virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
+    }
+  };
+
+    
+  class AnnotationsSceneLayer::Text : public GeometricPrimitive
+  {
+  private:
+    AnnotationsSceneLayer&           that_;
+    bool                             first_;
+    size_t                           subLayer_;
+    std::unique_ptr<TextSceneLayer>  content_;
+
+  public:
+    Text(AnnotationsSceneLayer& that,
+         Annotation& parentAnnotation) :
+      GeometricPrimitive(parentAnnotation, 2),
+      that_(that),
+      first_(true)
+    {
+    }
+
+    virtual ~Text()
+    {
+      if (!first_)
+      {
+        that_.TagSubLayerToRemove(subLayer_);
+      }
+    }
+      
+    void SetContent(const TextSceneLayer& content)
+    {
+      SetModified(true);
+      content_.reset(dynamic_cast<TextSceneLayer*>(content.Clone()));
+    }        
+
+    virtual bool IsHit(const ScenePoint2D& p,
+                       const Scene2D& scene) const
+    {
+      return false;
+    }
+
+    virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
+                                     const Scene2D& scene) ORTHANC_OVERRIDE
+    {
+    }
+      
+    virtual void RenderOtherLayers(MacroSceneLayer& macro,
+                                   const Scene2D& scene) ORTHANC_OVERRIDE
+    {
+      if (content_.get() != NULL)
+      {
+        std::unique_ptr<TextSceneLayer> layer(reinterpret_cast<TextSceneLayer*>(content_->Clone()));
+
+        layer->SetColor(IsHover() ? GetHoverColor() : GetColor());
+          
+        if (first_)
+        {
+          subLayer_ = macro.AddLayer(layer.release());
+          first_ = false;
+        }
+        else
+        {
+          macro.UpdateLayer(subLayer_, layer.release());
+        }
+      }
+    }
+
+    virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
+    }
+
+    virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);  // No hit is possible
+    }
+  };
+
+
+  class AnnotationsSceneLayer::EditPrimitiveTracker : public IFlexiblePointerTracker
+  {
+  private:
+    GeometricPrimitive&  primitive_;
+    ScenePoint2D         sceneClick_;
+    AffineTransform2D    canvasToScene_;
+    bool                 alive_;
+      
+  public:
+    EditPrimitiveTracker(GeometricPrimitive& primitive,
+                         const ScenePoint2D& sceneClick,
+                         const AffineTransform2D& canvasToScene) :
+      primitive_(primitive),
+      sceneClick_(sceneClick),
+      canvasToScene_(canvasToScene),
+      alive_(true)
+    {
+    }
+
+    virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+      primitive_.MovePreview(event.GetMainPosition().Apply(canvasToScene_) - sceneClick_);
+    }
+      
+    virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+      primitive_.MoveDone(event.GetMainPosition().Apply(canvasToScene_) - sceneClick_);
+      alive_ = false;
+    }
+
+    virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual bool IsAlive() const ORTHANC_OVERRIDE
+    {
+      return alive_;
+    }
+
+    virtual void Cancel() ORTHANC_OVERRIDE
+    {
+      primitive_.MoveDone(ScenePoint2D(0, 0));
+    }
+  };
+
+
+  class AnnotationsSceneLayer::SegmentAnnotation : public Annotation
+  {
+  private:
+    bool      showLabel_;
+    Handle&   handle1_;
+    Handle&   handle2_;
+    Segment&  segment_;
+    Text&     label_;
+
+    void UpdateLabel()
+    {
+      if (showLabel_)
+      {
+        TextSceneLayer content;
+
+        double x1 = handle1_.GetCenter().GetX();
+        double y1 = handle1_.GetCenter().GetY();
+        double x2 = handle2_.GetCenter().GetX();
+        double y2 = handle2_.GetCenter().GetY();
+        
+        // Put the label to the right of the right-most handle
+        if (x1 < x2)
+        {
+          content.SetPosition(x2, y2);
+        }
+        else
+        {
+          content.SetPosition(x1, y1);
+        }
+
+        content.SetAnchor(BitmapAnchor_CenterLeft);
+        content.SetBorder(10);
+
+        double dx = x1 - x2;
+        double dy = y1 - y2;
+        char buf[32];
+        sprintf(buf, "%0.2f cm", sqrt(dx * dx + dy * dy) / 10.0);
+        content.SetText(buf);
+
+        label_.SetContent(content);
+      }
+    }
+
+  public:
+    SegmentAnnotation(AnnotationsSceneLayer& that,
+                      bool showLabel,
+                      const ScenePoint2D& p1,
+                      const ScenePoint2D& p2) :
+      Annotation(that),
+      showLabel_(showLabel),
+      handle1_(AddTypedPrimitive<Handle>(new Handle(*this, p1))),
+      handle2_(AddTypedPrimitive<Handle>(new Handle(*this, p2))),
+      segment_(AddTypedPrimitive<Segment>(new Segment(*this, p1, p2))),
+      label_(AddTypedPrimitive<Text>(new Text(that, *this)))
+    {
+      label_.SetColor(Color(255, 0, 0));
+      UpdateLabel();
+    }
+
+    Handle& GetHandle1() const
+    {
+      return handle1_;
+    }
+
+    Handle& GetHandle2() const
+    {
+      return handle2_;
+    }
+
+    virtual void SignalMove(GeometricPrimitive& primitive) ORTHANC_OVERRIDE
+    {
+      if (&primitive == &handle1_ ||
+          &primitive == &handle2_)
+      {
+        segment_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter());
+      }
+      else if (&primitive == &segment_)
+      {
+        handle1_.SetCenter(segment_.GetPosition1());
+        handle2_.SetCenter(segment_.GetPosition2());
+      }
+        
+      UpdateLabel();
+    }
+
+    virtual void Serialize(Json::Value& target) ORTHANC_OVERRIDE
+    {
+      target = Json::objectValue;
+      target[KEY_TYPE] = VALUE_SEGMENT;
+      target[KEY_X1] = handle1_.GetCenter().GetX();
+      target[KEY_Y1] = handle1_.GetCenter().GetY();
+      target[KEY_X2] = handle2_.GetCenter().GetX();
+      target[KEY_Y2] = handle2_.GetCenter().GetY();
+    }
+
+    static void Unserialize(AnnotationsSceneLayer& target,
+                            const Json::Value& source)
+    {
+      if (source.isMember(KEY_X1) &&
+          source.isMember(KEY_Y1) &&
+          source.isMember(KEY_X2) &&
+          source.isMember(KEY_Y2) &&
+          source[KEY_X1].isNumeric() &&
+          source[KEY_Y1].isNumeric() &&
+          source[KEY_X2].isNumeric() &&
+          source[KEY_Y2].isNumeric())
+      {
+        new SegmentAnnotation(target, true,
+                              ScenePoint2D(source[KEY_X1].asDouble(), source[KEY_Y1].asDouble()),
+                              ScenePoint2D(source[KEY_X2].asDouble(), source[KEY_Y2].asDouble()));
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize an segment annotation");
+      }
+    }
+  };
+
+    
+  class AnnotationsSceneLayer::AngleAnnotation : public Annotation
+  {
+  private:
+    Handle&   startHandle_;
+    Handle&   middleHandle_;
+    Handle&   endHandle_;
+    Segment&  segment1_;
+    Segment&  segment2_;
+    Arc&      arc_;
+    Text&     label_;
+
+    void UpdateLabel()
+    {
+      TextSceneLayer content;
+
+      const double x1 = startHandle_.GetCenter().GetX();
+      const double x2 = middleHandle_.GetCenter().GetX();
+      const double y2 = middleHandle_.GetCenter().GetY();
+      const double x3 = endHandle_.GetCenter().GetX();
+        
+      if (x2 < x1 &&
+          x2 < x3)
+      {
+        content.SetAnchor(BitmapAnchor_CenterRight);
+      }
+      else
+      {
+        content.SetAnchor(BitmapAnchor_CenterLeft);
+      }
+
+      content.SetPosition(x2, y2);
+      content.SetBorder(10);
+
+      char buf[32];
+      sprintf(buf, "%.01f%c%c", std::abs(arc_.GetAngle()) / PI * 180.0,
+              0xc2, 0xb0 /* two bytes corresponding to degree symbol in UTF-8 */);
+      content.SetText(buf);
+
+      label_.SetContent(content);
+    }
+
+  public:
+    AngleAnnotation(AnnotationsSceneLayer& that,
+                    const ScenePoint2D& start,
+                    const ScenePoint2D& middle,
+                    const ScenePoint2D& end) :
+      Annotation(that),
+      startHandle_(AddTypedPrimitive<Handle>(new Handle(*this, start))),
+      middleHandle_(AddTypedPrimitive<Handle>(new Handle(*this, middle))),
+      endHandle_(AddTypedPrimitive<Handle>(new Handle(*this, end))),
+      segment1_(AddTypedPrimitive<Segment>(new Segment(*this, start, middle))),
+      segment2_(AddTypedPrimitive<Segment>(new Segment(*this, middle, end))),
+      arc_(AddTypedPrimitive<Arc>(new Arc(*this, start, middle, end))),
+      label_(AddTypedPrimitive<Text>(new Text(that, *this)))
+    {
+      label_.SetColor(Color(255, 0, 0));
+      UpdateLabel();
+    }
+
+    Handle& GetEndHandle() const
+    {
+      return endHandle_;
+    }
+
+    virtual void SignalMove(GeometricPrimitive& primitive) ORTHANC_OVERRIDE
+    {
+      if (&primitive == &startHandle_)
+      {
+        segment1_.SetPosition(startHandle_.GetCenter(), middleHandle_.GetCenter());
+        arc_.SetStart(startHandle_.GetCenter());
+      }
+      else if (&primitive == &middleHandle_)
+      {
+        segment1_.SetPosition(startHandle_.GetCenter(), middleHandle_.GetCenter());
+        segment2_.SetPosition(middleHandle_.GetCenter(), endHandle_.GetCenter());
+        arc_.SetMiddle(middleHandle_.GetCenter());
+      }
+      else if (&primitive == &endHandle_)
+      {
+        segment2_.SetPosition(middleHandle_.GetCenter(), endHandle_.GetCenter());
+        arc_.SetEnd(endHandle_.GetCenter());
+      }
+      else if (&primitive == &segment1_)
+      {
+        startHandle_.SetCenter(segment1_.GetPosition1());
+        middleHandle_.SetCenter(segment1_.GetPosition2());
+        segment2_.SetPosition(segment1_.GetPosition2(), segment2_.GetPosition2());
+        arc_.SetStart(segment1_.GetPosition1());
+        arc_.SetMiddle(segment1_.GetPosition2());
+      }
+      else if (&primitive == &segment2_)
+      {
+        middleHandle_.SetCenter(segment2_.GetPosition1());
+        endHandle_.SetCenter(segment2_.GetPosition2());
+        segment1_.SetPosition(segment1_.GetPosition1(), segment2_.GetPosition1());
+        arc_.SetMiddle(segment2_.GetPosition1());
+        arc_.SetEnd(segment2_.GetPosition2());
+      }
+
+      UpdateLabel();
+    }
+
+    virtual void Serialize(Json::Value& target) ORTHANC_OVERRIDE
+    {
+      target = Json::objectValue;
+      target[KEY_TYPE] = VALUE_ANGLE;
+      target[KEY_X1] = startHandle_.GetCenter().GetX();
+      target[KEY_Y1] = startHandle_.GetCenter().GetY();
+      target[KEY_X2] = middleHandle_.GetCenter().GetX();
+      target[KEY_Y2] = middleHandle_.GetCenter().GetY();
+      target[KEY_X3] = endHandle_.GetCenter().GetX();
+      target[KEY_Y3] = endHandle_.GetCenter().GetY();
+    }
+
+    static void Unserialize(AnnotationsSceneLayer& target,
+                            const Json::Value& source)
+    {
+      if (source.isMember(KEY_X1) &&
+          source.isMember(KEY_Y1) &&
+          source.isMember(KEY_X2) &&
+          source.isMember(KEY_Y2) &&
+          source.isMember(KEY_X3) &&
+          source.isMember(KEY_Y3) &&
+          source[KEY_X1].isNumeric() &&
+          source[KEY_Y1].isNumeric() &&
+          source[KEY_X2].isNumeric() &&
+          source[KEY_Y2].isNumeric() &&
+          source[KEY_X3].isNumeric() &&
+          source[KEY_Y3].isNumeric())
+      {
+        new AngleAnnotation(target,
+                            ScenePoint2D(source[KEY_X1].asDouble(), source[KEY_Y1].asDouble()),
+                            ScenePoint2D(source[KEY_X2].asDouble(), source[KEY_Y2].asDouble()),
+                            ScenePoint2D(source[KEY_X3].asDouble(), source[KEY_Y3].asDouble()));
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize an angle annotation");
+      }
+    }
+  };
+
+    
+  class AnnotationsSceneLayer::CircleAnnotation : public Annotation
+  {
+  private:
+    Handle&   handle1_;
+    Handle&   handle2_;
+    Segment&  segment_;
+    Circle&   circle_;
+    Text&     label_;
+
+    void UpdateLabel()
+    {
+      TextSceneLayer content;
+
+      double x1 = handle1_.GetCenter().GetX();
+      double y1 = handle1_.GetCenter().GetY();
+      double x2 = handle2_.GetCenter().GetX();
+      double y2 = handle2_.GetCenter().GetY();
+        
+      // Put the label to the right of the right-most handle
+      if (x1 < x2)
+      {
+        content.SetPosition(x2, y2);
+      }
+      else
+      {
+        content.SetPosition(x1, y1);
+      }
+
+      content.SetAnchor(BitmapAnchor_CenterLeft);
+      content.SetBorder(10);
+
+      double dx = x1 - x2;
+      double dy = y1 - y2;
+      double diameter = sqrt(dx * dx + dy * dy);  // in millimeters
+
+      double area = PI * diameter * diameter / 4.0;
+        
+      char buf[32];
+      sprintf(buf, "%0.2f cm\n%0.2f cm%c%c",
+              diameter / 10.0,
+              area / 100.0,
+              0xc2, 0xb2 /* two bytes corresponding to two power in UTF-8 */);
+      content.SetText(buf);
+
+      label_.SetContent(content);
+    }
+
+  public:
+    CircleAnnotation(AnnotationsSceneLayer& that,
+                     const ScenePoint2D& p1,
+                     const ScenePoint2D& p2) :
+      Annotation(that),
+      handle1_(AddTypedPrimitive<Handle>(new Handle(*this, p1))),
+      handle2_(AddTypedPrimitive<Handle>(new Handle(*this, p2))),
+      segment_(AddTypedPrimitive<Segment>(new Segment(*this, p1, p2))),
+      circle_(AddTypedPrimitive<Circle>(new Circle(*this, p1, p2))),
+      label_(AddTypedPrimitive<Text>(new Text(that, *this)))
+    {
+      label_.SetColor(Color(255, 0, 0));
+      UpdateLabel();
+    }
+
+    Handle& GetHandle2() const
+    {
+      return handle2_;
+    }
+
+    virtual void SignalMove(GeometricPrimitive& primitive) ORTHANC_OVERRIDE
+    {
+      if (&primitive == &handle1_ ||
+          &primitive == &handle2_)
+      {
+        segment_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter());
+        circle_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter());          
+      }
+      else if (&primitive == &segment_)
+      {
+        handle1_.SetCenter(segment_.GetPosition1());
+        handle2_.SetCenter(segment_.GetPosition2());
+        circle_.SetPosition(segment_.GetPosition1(), segment_.GetPosition2());
+      }
+        
+      UpdateLabel();
+    }
+
+    virtual void Serialize(Json::Value& target) ORTHANC_OVERRIDE
+    {
+      target = Json::objectValue;
+      target[KEY_TYPE] = VALUE_CIRCLE;
+      target[KEY_X1] = handle1_.GetCenter().GetX();
+      target[KEY_Y1] = handle1_.GetCenter().GetY();
+      target[KEY_X2] = handle2_.GetCenter().GetX();
+      target[KEY_Y2] = handle2_.GetCenter().GetY();
+    }
+
+    static void Unserialize(AnnotationsSceneLayer& target,
+                            const Json::Value& source)
+    {
+      if (source.isMember(KEY_X1) &&
+          source.isMember(KEY_Y1) &&
+          source.isMember(KEY_X2) &&
+          source.isMember(KEY_Y2) &&
+          source[KEY_X1].isNumeric() &&
+          source[KEY_Y1].isNumeric() &&
+          source[KEY_X2].isNumeric() &&
+          source[KEY_Y2].isNumeric())
+      {
+        new CircleAnnotation(target,
+                             ScenePoint2D(source[KEY_X1].asDouble(), source[KEY_Y1].asDouble()),
+                             ScenePoint2D(source[KEY_X2].asDouble(), source[KEY_Y2].asDouble()));
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize an circle annotation");
+      }
+    }
+  };
+
+    
+  class AnnotationsSceneLayer::CreateSegmentOrCircleTracker : public IFlexiblePointerTracker
+  {
+  private:
+    AnnotationsSceneLayer&  that_;
+    Annotation*             annotation_;
+    AffineTransform2D       canvasToScene_;
+    Handle*                 handle2_;
+      
+  public:
+    CreateSegmentOrCircleTracker(AnnotationsSceneLayer& that,
+                                 bool isCircle,
+                                 const ScenePoint2D& sceneClick,
+                                 const AffineTransform2D& canvasToScene) :
+      that_(that),
+      annotation_(NULL),
+      canvasToScene_(canvasToScene),
+      handle2_(NULL)
+    {
+      if (isCircle)
+      {
+        annotation_ = new CircleAnnotation(that, sceneClick, sceneClick);
+        handle2_ = &dynamic_cast<CircleAnnotation*>(annotation_)->GetHandle2();
+      }
+      else
+      {
+        annotation_ = new SegmentAnnotation(that, true /* show label */, sceneClick, sceneClick);
+        handle2_ = &dynamic_cast<SegmentAnnotation*>(annotation_)->GetHandle2();
+      }
+        
+      assert(annotation_ != NULL &&
+             handle2_ != NULL);
+    }
+
+    virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+      if (annotation_ != NULL)
+      {
+        assert(handle2_ != NULL);
+        handle2_->SetCenter(event.GetMainPosition().Apply(canvasToScene_));
+        annotation_->SignalMove(*handle2_);
+      }
+    }
+      
+    virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+      annotation_ = NULL;  // IsAlive() becomes false
+
+      that_.BroadcastMessage(AnnotationAddedMessage(that_));
+    }
+
+    virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual bool IsAlive() const ORTHANC_OVERRIDE
+    {
+      return (annotation_ != NULL);
+    }
+
+    virtual void Cancel() ORTHANC_OVERRIDE
+    {
+      if (annotation_ != NULL)
+      {
+        that_.DeleteAnnotation(annotation_);
+        annotation_ = NULL;
+      }
+    }
+  };
+
+
+  class AnnotationsSceneLayer::CreateAngleTracker : public IFlexiblePointerTracker
+  {
+  private:
+    AnnotationsSceneLayer&  that_;
+    SegmentAnnotation*      segment_;
+    AngleAnnotation*        angle_;
+    AffineTransform2D       canvasToScene_;
+      
+  public:
+    CreateAngleTracker(AnnotationsSceneLayer& that,
+                       const ScenePoint2D& sceneClick,
+                       const AffineTransform2D& canvasToScene) :
+      that_(that),
+      segment_(NULL),
+      angle_(NULL),
+      canvasToScene_(canvasToScene)
+    {
+      segment_ = new SegmentAnnotation(that, false /* no length label */, sceneClick, sceneClick);
+    }
+
+    virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+      if (segment_ != NULL)
+      {
+        segment_->GetHandle2().SetCenter(event.GetMainPosition().Apply(canvasToScene_));
+        segment_->SignalMove(segment_->GetHandle2());
+      }
+
+      if (angle_ != NULL)
+      {
+        angle_->GetEndHandle().SetCenter(event.GetMainPosition().Apply(canvasToScene_));
+        angle_->SignalMove(angle_->GetEndHandle());
+      }
+    }
+      
+    virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+      if (segment_ != NULL)
+      {
+        // End of first step: The first segment is available, now create the angle
+
+        angle_ = new AngleAnnotation(that_, segment_->GetHandle1().GetCenter(),
+                                     segment_->GetHandle2().GetCenter(),
+                                     segment_->GetHandle2().GetCenter());
+          
+        that_.DeleteAnnotation(segment_);
+        segment_ = NULL;
+      }
+      else
+      {
+        angle_ = NULL;  // IsAlive() becomes false
+
+        that_.BroadcastMessage(AnnotationAddedMessage(that_));
+      }
+    }
+
+    virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual bool IsAlive() const ORTHANC_OVERRIDE
+    {
+      return (segment_ != NULL ||
+              angle_ != NULL);
+    }
+
+    virtual void Cancel() ORTHANC_OVERRIDE
+    {
+      if (segment_ != NULL)
+      {
+        that_.DeleteAnnotation(segment_);
+        segment_ = NULL;
+      }
+
+      if (angle_ != NULL)
+      {
+        that_.DeleteAnnotation(angle_);
+        angle_ = NULL;
+      }
+    }
+  };
+
+
+  // Dummy tracker that is only used for deletion, in order to warn
+  // the caller that the mouse action was taken into consideration
+  class AnnotationsSceneLayer::EraseTracker : public IFlexiblePointerTracker
+  {
+  public:
+    EraseTracker()
+    {
+    }
+
+    virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+    }
+      
+    virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual bool IsAlive() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
+    virtual void Cancel() ORTHANC_OVERRIDE
+    {
+    }
+  };
+
+
+  void AnnotationsSceneLayer::AddAnnotation(Annotation* annotation)
+  {
+    assert(annotation != NULL);
+    assert(annotations_.find(annotation) == annotations_.end());
+    annotations_.insert(annotation);
+  }
+  
+
+  void AnnotationsSceneLayer::DeleteAnnotation(Annotation* annotation)
+  {
+    if (annotation != NULL)
+    {
+      assert(annotations_.find(annotation) != annotations_.end());
+      annotations_.erase(annotation);
+      delete annotation;
+    }
+  }
+
+  
+  void AnnotationsSceneLayer::DeletePrimitive(GeometricPrimitive* primitive)
+  {
+    if (primitive != NULL)
+    {
+      assert(primitives_.find(primitive) != primitives_.end());
+      primitives_.erase(primitive);
+      delete primitive;
+    }
+  }
+
+  
+  void AnnotationsSceneLayer::TagSubLayerToRemove(size_t subLayerIndex)
+  {
+    assert(subLayersToRemove_.find(subLayerIndex) == subLayersToRemove_.end());
+    subLayersToRemove_.insert(subLayerIndex);
+  }
+    
+
+  AnnotationsSceneLayer::AnnotationsSceneLayer(size_t macroLayerIndex) :
+    activeTool_(Tool_Edit),
+    macroLayerIndex_(macroLayerIndex),
+    polylineSubLayer_(0)  // dummy initialization
+  {
+    annotations_.insert(new SegmentAnnotation(*this, true /* show label */, ScenePoint2D(0, 0), ScenePoint2D(100, 100)));
+    annotations_.insert(new AngleAnnotation(*this, ScenePoint2D(100, 50), ScenePoint2D(150, 40), ScenePoint2D(200, 50)));
+    annotations_.insert(new CircleAnnotation(*this, ScenePoint2D(50, 200), ScenePoint2D(100, 250)));
+  }
+    
+
+  void AnnotationsSceneLayer::Clear()
+  {
+    for (Annotations::iterator it = annotations_.begin(); it != annotations_.end(); ++it)
+    {
+      assert(*it != NULL);
+      delete *it;
+    }
+
+    annotations_.clear();
+  }
+
+
+  void AnnotationsSceneLayer::Render(Scene2D& scene)
+  {
+    MacroSceneLayer* macro = NULL;
+
+    if (scene.HasLayer(macroLayerIndex_))
+    {
+      macro = &dynamic_cast<MacroSceneLayer&>(scene.GetLayer(macroLayerIndex_));
+    }
+    else
+    {
+      macro = &dynamic_cast<MacroSceneLayer&>(scene.SetLayer(macroLayerIndex_, new MacroSceneLayer));
+      polylineSubLayer_ = macro->AddLayer(new PolylineSceneLayer);
+    }
+
+    for (SubLayers::const_iterator it = subLayersToRemove_.begin(); it != subLayersToRemove_.end(); ++it)
+    {
+      assert(macro->HasLayer(*it));
+      macro->DeleteLayer(*it);
+    }
+
+    subLayersToRemove_.clear();
+
+    std::unique_ptr<PolylineSceneLayer> polyline(new PolylineSceneLayer);
+
+    for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
+    {
+      assert(*it != NULL);
+      GeometricPrimitive& primitive = **it;        
+        
+      primitive.RenderPolylineLayer(*polyline, scene);
+
+      if (primitive.IsModified())
+      {
+        primitive.RenderOtherLayers(*macro, scene);
+        primitive.SetModified(false);
+      }
+    }
+
+    macro->UpdateLayer(polylineSubLayer_, polyline.release());
+  }
+
+  
+  bool AnnotationsSceneLayer::ClearHover()
+  {
+    bool needsRefresh = false;
+      
+    for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
+    {
+      assert(*it != NULL);
+      if ((*it)->IsHover())
+      {
+        (*it)->SetHover(false);
+        needsRefresh = true;
+      }
+    }
+
+    return needsRefresh;
+  }
+  
+
+  bool AnnotationsSceneLayer::SetMouseHover(const ScenePoint2D& p,
+                                            const Scene2D& scene)
+  {
+    if (activeTool_ == Tool_None)
+    {
+      return ClearHover();
+    }
+    else
+    {
+      bool needsRefresh = false;
+      
+      const ScenePoint2D s = p.Apply(scene.GetCanvasToSceneTransform());
+      
+      for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
+      {
+        assert(*it != NULL);
+        bool hover = (*it)->IsHit(s, scene);
+
+        if ((*it)->IsHover() != hover)
+        {
+          needsRefresh = true;
+        }
+        
+        (*it)->SetHover(hover);
+      }
+
+      return needsRefresh;
+    }
+  }
+
+
+  IFlexiblePointerTracker* AnnotationsSceneLayer::CreateTracker(const ScenePoint2D& p,
+                                                                const Scene2D& scene)
+  {
+    if (activeTool_ == Tool_None)
+    {
+      return NULL;
+    }
+    else
+    {
+      const ScenePoint2D s = p.Apply(scene.GetCanvasToSceneTransform());
+
+      GeometricPrimitive* bestHit = NULL;
+      
+      for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
+      {
+        assert(*it != NULL);
+        if ((*it)->IsHit(s, scene))
+        {
+          if (bestHit == NULL ||
+              bestHit->GetDepth() > (*it)->GetDepth())
+          {
+            bestHit = *it;
+          }
+        }
+      }
+
+      if (bestHit != NULL)
+      {
+        if (activeTool_ == Tool_Erase)
+        {
+          DeleteAnnotation(&bestHit->GetParentAnnotation());
+          BroadcastMessage(AnnotationRemovedMessage(*this));
+          return new EraseTracker;
+        }
+        else
+        {
+          return new EditPrimitiveTracker(*bestHit, s, scene.GetCanvasToSceneTransform());
+        }
+      }
+      else
+      {
+        switch (activeTool_)
+        {
+          case Tool_Segment:
+            return new CreateSegmentOrCircleTracker(*this, false /* segment */, s, scene.GetCanvasToSceneTransform());
+
+          case Tool_Circle:
+            return new CreateSegmentOrCircleTracker(*this, true /* circle */, s, scene.GetCanvasToSceneTransform());
+
+          case Tool_Angle:
+            return new CreateAngleTracker(*this, s, scene.GetCanvasToSceneTransform());
+
+          default:
+            return NULL;
+        }
+      }
+    }
+  }
+
+
+  void AnnotationsSceneLayer::Serialize(Json::Value& target) const
+  {
+    Json::Value annotations = Json::arrayValue;
+      
+    for (Annotations::const_iterator it = annotations_.begin(); it != annotations_.end(); ++it)
+    {
+      assert(*it != NULL);
+
+      Json::Value item;
+      (*it)->Serialize(item);
+      annotations.append(item);
+    }
+
+    target = Json::objectValue;
+    target[KEY_ANNOTATIONS] = annotations;
+  }
+
+
+  void AnnotationsSceneLayer::Unserialize(const Json::Value& serialized)
+  {
+    Clear();
+      
+    if (serialized.type() != Json::objectValue ||
+        !serialized.isMember(KEY_ANNOTATIONS) ||
+        serialized[KEY_ANNOTATIONS].type() != Json::arrayValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize a set of annotations");
+    }
+
+    const Json::Value& annotations = serialized[KEY_ANNOTATIONS];
+
+    for (Json::Value::ArrayIndex i = 0; i < annotations.size(); i++)
+    {
+      if (annotations[i].type() != Json::objectValue ||
+          !annotations[i].isMember(KEY_TYPE) ||
+          annotations[i][KEY_TYPE].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      const std::string& type = annotations[i][KEY_TYPE].asString();
+
+      if (type == VALUE_ANGLE)
+      {
+        AngleAnnotation::Unserialize(*this, annotations[i]);
+      }
+      else if (type == VALUE_CIRCLE)
+      {
+        CircleAnnotation::Unserialize(*this, annotations[i]);
+      }
+      else if (type == VALUE_SEGMENT)
+      {
+        SegmentAnnotation::Unserialize(*this, annotations[i]);
+      }
+      else
+      {
+        LOG(ERROR) << "Cannot unserialize unknown type of annotation: " << type;
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.h	Thu May 20 14:48:51 2021 +0200
@@ -0,0 +1,116 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../Messages/IObservable.h"
+#include "Scene2D.h"
+#include "../Scene2DViewport/IFlexiblePointerTracker.h"
+
+namespace OrthancStone
+{
+  class AnnotationsSceneLayer : public IObservable
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, AnnotationAddedMessage, AnnotationsSceneLayer);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, AnnotationRemovedMessage, AnnotationsSceneLayer);
+
+    enum Tool
+    {
+      Tool_Edit,
+      Tool_None,
+      Tool_Segment,
+      Tool_Angle,
+      Tool_Circle,
+      Tool_Erase
+    };
+    
+  private:
+    class GeometricPrimitive;    
+    class Handle;    
+    class Segment;
+    class Circle;    
+    class Arc;
+    class Text;
+
+    class Annotation;
+    class SegmentAnnotation;
+    class AngleAnnotation;
+    class CircleAnnotation;
+    
+    class EditPrimitiveTracker;
+    class CreateSegmentOrCircleTracker;
+    class CreateAngleTracker;
+    class EraseTracker;
+
+    typedef std::set<GeometricPrimitive*>  GeometricPrimitives;
+    typedef std::set<Annotation*>          Annotations;
+    typedef std::set<size_t>               SubLayers;
+
+    Tool                 activeTool_;
+    size_t               macroLayerIndex_;
+    size_t               polylineSubLayer_;
+    GeometricPrimitives  primitives_;
+    Annotations          annotations_;
+    SubLayers            subLayersToRemove_;
+
+    void AddAnnotation(Annotation* annotation);
+    
+    void DeleteAnnotation(Annotation* annotation);
+
+    void DeletePrimitive(GeometricPrimitive* primitive);
+    
+    void TagSubLayerToRemove(size_t subLayerIndex);
+    
+  public:
+    AnnotationsSceneLayer(size_t macroLayerIndex);
+    
+    ~AnnotationsSceneLayer()
+    {
+      Clear();
+    }
+
+    void Clear();
+
+    void SetActiveTool(Tool tool)
+    {
+      activeTool_ = tool;
+    }
+
+    Tool GetActiveTool() const
+    {
+      return activeTool_;
+    }
+
+    void Render(Scene2D& scene);
+    
+    bool ClearHover();
+
+    bool SetMouseHover(const ScenePoint2D& p /* expressed in canvas coordinates */,
+                       const Scene2D& scene);
+
+    IFlexiblePointerTracker* CreateTracker(const ScenePoint2D& p /* expressed in canvas coordinates */,
+                                           const Scene2D& scene);
+    
+    void Serialize(Json::Value& target) const;
+    
+    void Unserialize(const Json::Value& serialized);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Scene2D/ScenePoint2D.cpp	Thu May 20 14:48:51 2021 +0200
@@ -0,0 +1,162 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ScenePoint2D.h"
+
+
+namespace OrthancStone
+{
+  ScenePoint2D ScenePoint2D::Apply(const AffineTransform2D& t) const
+  {
+    double x = x_;
+    double y = y_;
+    t.Apply(x, y);
+    return ScenePoint2D(x, y);
+  }
+
+  
+  const ScenePoint2D ScenePoint2D::operator-(const ScenePoint2D& a) const
+  {
+    ScenePoint2D v;
+    v.x_ = x_ - a.x_;
+    v.y_ = y_ - a.y_;
+    return v;
+  }
+
+  
+  const ScenePoint2D ScenePoint2D::operator+(const ScenePoint2D& a) const
+  {
+    ScenePoint2D v;
+    v.x_ = x_ + a.x_;
+    v.y_ = y_ + a.y_;
+    return v;
+  }
+
+  
+  const ScenePoint2D ScenePoint2D::operator*(double a) const
+  {
+    ScenePoint2D v;
+    v.x_ = x_ * a;
+    v.y_ = y_ * a;
+    return v;
+  }
+
+  
+  const ScenePoint2D ScenePoint2D::operator/(double a) const
+  {
+    ScenePoint2D v;
+    v.x_ = x_ / a;
+    v.y_ = y_ / a;
+    return v;
+  }
+
+
+  void ScenePoint2D::MidPoint(ScenePoint2D& result,
+                              const ScenePoint2D& a,
+                              const ScenePoint2D& b)
+  {
+    result.x_ = 0.5 * (a.x_ + b.x_);
+    result.y_ = 0.5 * (a.y_ + b.y_);
+  }
+
+  
+  double ScenePoint2D::Dot(const ScenePoint2D& a, const ScenePoint2D& b)
+  {
+    return a.x_ * b.x_ + a.y_ * b.y_;
+  }
+
+  
+  double ScenePoint2D::SquaredMagnitude(const ScenePoint2D& v)
+  {
+    return v.x_ * v.x_ + v.y_ * v.y_;
+  }
+
+
+  double ScenePoint2D::Magnitude(const ScenePoint2D& v)
+  {
+    double squaredMagnitude = SquaredMagnitude(v);
+      
+    if (LinearAlgebra::IsCloseToZero(squaredMagnitude))
+    {
+      return 0.0;
+    }
+    else
+    {
+      return sqrt(squaredMagnitude);
+    }
+  }
+
+
+  double ScenePoint2D::SquaredDistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b)
+  {
+    ScenePoint2D n = b - a;
+    return Dot(n, n);
+  }
+
+  
+  double ScenePoint2D::DistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b)
+  {
+    double squaredDist = SquaredDistancePtPt(a, b);
+    return sqrt(squaredDist);
+  }
+
+
+  double ScenePoint2D::SquaredDistancePtSegment(const ScenePoint2D& a, const ScenePoint2D& b, const ScenePoint2D& p)
+  {
+    // Rewritten from https://www.randygaul.net/2014/07/23/distance-point-to-line-segment/
+
+    ScenePoint2D n = b - a;
+    ScenePoint2D pa = a - p;
+
+    double c = Dot(n, pa);
+
+    // Closest point is a
+    if (c > 0.0)
+    {
+      return Dot(pa, pa);
+    }
+
+    ScenePoint2D bp = p - b;
+
+    // Closest point is b
+    if (Dot(n, bp) > 0.0)
+    {
+      return Dot(bp, bp);
+    }
+
+    // if segment length is very short, we approximate distance to the
+    // distance with a
+    double nq = Dot(n, n);
+    if (LinearAlgebra::IsCloseToZero(nq))
+    {
+      // segment is very small: approximate distance from point to segment
+      // with distance from p to a
+      return Dot(pa, pa);
+    }
+    else
+    {
+      // Closest point is between a and b
+      ScenePoint2D e = pa - n * (c / nq);
+      return Dot(e, e);
+    }
+  }
+}
--- a/OrthancStone/Sources/Scene2D/ScenePoint2D.h	Thu May 20 14:15:00 2021 +0200
+++ b/OrthancStone/Sources/Scene2D/ScenePoint2D.h	Thu May 20 14:48:51 2021 +0200
@@ -23,7 +23,6 @@
 #pragma once
 
 #include "../Toolbox/AffineTransform2D.h"
-#include "../Toolbox/LinearAlgebra.h"
 
 namespace OrthancStone
 {
@@ -41,7 +40,7 @@
     }
 
     ScenePoint2D(double x,
-      double y) :
+                 double y) :
       x_(x),
       y_(y)
     {
@@ -57,124 +56,36 @@
       return y_;
     }
 
-    ScenePoint2D Apply(const AffineTransform2D& t) const
-    {
-      double x = x_;
-      double y = y_;
-      t.Apply(x, y);
-      return ScenePoint2D(x, y);
-    }
+    ScenePoint2D Apply(const AffineTransform2D& t) const;
 
-    const ScenePoint2D operator-(const ScenePoint2D& a) const
-    {
-      ScenePoint2D v;
-      v.x_ = x_ - a.x_;
-      v.y_ = y_ - a.y_;
-
-      return v;
-    }
+    const ScenePoint2D operator-(const ScenePoint2D& a) const;
 
-    const ScenePoint2D operator+(const ScenePoint2D& a) const
-    {
-      ScenePoint2D v;
-      v.x_ = x_ + a.x_;
-      v.y_ = y_ + a.y_;
-
-      return v;
-    }
+    const ScenePoint2D operator+(const ScenePoint2D& a) const;
 
-    const ScenePoint2D operator*(double a) const
-    {
-      ScenePoint2D v;
-      v.x_ = x_ * a;
-      v.y_ = y_ * a;
+    const ScenePoint2D operator*(double a) const;
 
-      return v;
-    }
+    const ScenePoint2D operator/(double a) const;
 
-    const ScenePoint2D operator/(double a) const
-    {
-      ScenePoint2D v;
-      v.x_ = x_ / a;
-      v.y_ = y_ / a;
-
-      return v;
-    }
-
-    static void MidPoint(ScenePoint2D& result, const ScenePoint2D& a, const ScenePoint2D& b)
-    {
-      result.x_ = 0.5 * (a.x_ + b.x_);
-      result.y_ = 0.5 * (a.y_ + b.y_);
-    }
-
-    static double Dot(const ScenePoint2D& a, const ScenePoint2D& b)
-    {
-      return a.x_ * b.x_ + a.y_ * b.y_;
-    }
+    static void MidPoint(ScenePoint2D& result,
+                         const ScenePoint2D& a,
+                         const ScenePoint2D& b);
 
-    static double SquaredMagnitude(const ScenePoint2D& v)
-    {
-      return v.x_ * v.x_ + v.y_ * v.y_;
-    }
-
-    static double Magnitude(const ScenePoint2D& v)
-    {
-      double squaredMagnitude = SquaredMagnitude(v);
-      if (LinearAlgebra::IsCloseToZero(squaredMagnitude))
-        return 0.0;
-      return sqrt(squaredMagnitude);
-    }
+    static double Dot(const ScenePoint2D& a,
+                      const ScenePoint2D& b);
 
-    static double SquaredDistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b)
-    {
-      ScenePoint2D n = b - a;
-      return Dot(n, n);
-    }
+    static double SquaredMagnitude(const ScenePoint2D& v);
 
-    static double DistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b)
-    {
-      double squaredDist = SquaredDistancePtPt(a, b);
-      return sqrt(squaredDist);
-    }
-
-    /**
-    Distance from point p to [a,b] segment
+    static double Magnitude(const ScenePoint2D& v);
 
-    Rewritten from https://www.randygaul.net/2014/07/23/distance-point-to-line-segment/
-    */
-    static double SquaredDistancePtSegment(const ScenePoint2D& a, const ScenePoint2D& b, const ScenePoint2D& p)
-    {
-      ScenePoint2D n = b - a;
-      ScenePoint2D pa = a - p;
-
-      double c = Dot(n, pa);
-
-      // Closest point is a
-      if (c > 0.0)
-        return Dot(pa, pa);
-
-      ScenePoint2D bp = p - b;
+    static double SquaredDistancePtPt(const ScenePoint2D& a,
+                                      const ScenePoint2D& b);
 
-      // Closest point is b
-      if (Dot(n, bp) > 0.0)
-        return Dot(bp, bp);
+    static double DistancePtPt(const ScenePoint2D& a,
+                               const ScenePoint2D& b);
 
-      // if segment length is very short, we approximate distance to the
-      // distance with a
-      double nq = Dot(n, n);
-      if (LinearAlgebra::IsCloseToZero(nq))
-      {
-        // segment is very small: approximate distance from point to segment
-        // with distance from p to a
-        return Dot(pa, pa);
-      }
-      else
-      {
-        // Closest point is between a and b
-        ScenePoint2D e = pa - n * (c / nq);
-        return Dot(e, e);
-      }
-    }
+    // Distance from point p to [a,b] segment
+    static double SquaredDistancePtSegment(const ScenePoint2D& a,
+                                           const ScenePoint2D& b,
+                                           const ScenePoint2D& p);
   };
 }
-