changeset 476:a95090305dd4 am-touch-events

Introduced ControlPoint instead of Corner in the trackers and layers + drawing mask from the ControlPoints
author am@osimis.io
date Wed, 13 Feb 2019 12:04:02 +0100
parents 3c28542229a3
children baf8c8d68bbc
files Applications/Samples/SingleFrameEditorApplication.h Framework/Radiography/RadiographyLayer.cpp Framework/Radiography/RadiographyLayer.h Framework/Radiography/RadiographyLayerCropTracker.cpp Framework/Radiography/RadiographyLayerCropTracker.h Framework/Radiography/RadiographyLayerResizeTracker.cpp Framework/Radiography/RadiographyLayerResizeTracker.h Framework/Radiography/RadiographyMaskLayer.cpp Framework/Radiography/RadiographyMaskLayer.h Framework/StoneEnumerations.h
diffstat 10 files changed, 350 insertions(+), 217 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SingleFrameEditorApplication.h	Tue Feb 12 12:22:13 2019 +0100
+++ b/Applications/Samples/SingleFrameEditorApplication.h	Wed Feb 13 12:04:02 2019 +0100
@@ -61,6 +61,7 @@
         Tool_Rotate,
         Tool_Crop,
         Tool_Resize,
+        Tool_Mask,
         Tool_Windowing
       };
 
@@ -68,6 +69,7 @@
       StoneApplicationContext*  context_;
       UndoRedoStack             undoRedoStack_;
       Tool                      tool_;
+      RadiographyMaskLayer*     maskLayer_;
 
 
       static double GetHandleSize()
@@ -80,7 +82,8 @@
       RadiographyEditorInteractor(MessageBroker& broker) :
         IObserver(broker),
         context_(NULL),
-        tool_(Tool_Move)
+        tool_(Tool_Move),
+        maskLayer_(NULL)
       {
       }
 
@@ -89,6 +92,10 @@
         context_ = &context;
       }
 
+      void SetMaskLayer(RadiographyMaskLayer* maskLayer)
+      {
+        maskLayer_ = maskLayer;
+      }
       virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& worldWidget,
                                                           const ViewportGeometry& view,
                                                           MouseButton button,
@@ -127,23 +134,32 @@
 
             return NULL;
           }
+          else if (tool_ == Tool_Mask)
+          {
+//            maskLayer_
+//                case Tool_Mask:
+//                  return new RadiographyLayerMaskTracker
+//                      (undoRedoStack_, widget.GetScene(), view, selected, x, y, corner);
+            return NULL;
+          }
           else if (tool_ == Tool_Crop ||
                    tool_ == Tool_Resize)
           {
             RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected);
             
-            Corner corner;
-            if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
+            ControlPoint controlPoint;
+            if (accessor.GetLayer().LookupControlPoint(controlPoint, x, y, view.GetZoom(), GetHandleSize()))
             {
               switch (tool_)
               {
               case Tool_Crop:
                 return new RadiographyLayerCropTracker
-                    (undoRedoStack_, widget.GetScene(), view, selected, x, y, corner);
+                    (undoRedoStack_, widget.GetScene(), view, selected, controlPoint);
+
 
               case Tool_Resize:
                 return new RadiographyLayerResizeTracker
-                    (undoRedoStack_, widget.GetScene(), selected, x, y, corner,
+                    (undoRedoStack_, widget.GetScene(), selected, controlPoint,
                      (modifiers & KeyboardModifiers_Shift));
 
               default:
@@ -209,6 +225,7 @@
         {
           return NULL;
         }
+        return NULL;
       }
 
       virtual void MouseOver(CairoContext& context,
@@ -233,25 +250,24 @@
 
         if (widget.LookupSelectedLayer(selected) &&
             (tool_ == Tool_Crop ||
-             tool_ == Tool_Resize))
+             tool_ == Tool_Resize) ||
+             tool_ == Tool_Mask)
         {
           RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected);
 
-          Corner corner;
-          if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
+          ControlPoint controlPoint;
+          if (accessor.GetLayer().LookupControlPoint(controlPoint, x, y, view.GetZoom(), GetHandleSize()))
           {
-            accessor.GetLayer().GetCorner(x, y, corner);
-
             double z = 1.0 / view.GetZoom();
 
             context.SetSourceColor(255, 0, 0);
             cairo_t* cr = context.GetObject();
             cairo_set_line_width(cr, 2.0 * z);
-            cairo_move_to(cr, x - GetHandleSize() * z, y - GetHandleSize() * z);
-            cairo_line_to(cr, x + GetHandleSize() * z, y - GetHandleSize() * z);
-            cairo_line_to(cr, x + GetHandleSize() * z, y + GetHandleSize() * z);
-            cairo_line_to(cr, x - GetHandleSize() * z, y + GetHandleSize() * z);
-            cairo_line_to(cr, x - GetHandleSize() * z, y - GetHandleSize() * z);
+            cairo_move_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y - GetHandleSize() * z);
+            cairo_line_to(cr, controlPoint.x + GetHandleSize() * z, controlPoint.y - GetHandleSize() * z);
+            cairo_line_to(cr, controlPoint.x + GetHandleSize() * z, controlPoint.y + GetHandleSize() * z);
+            cairo_line_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y + GetHandleSize() * z);
+            cairo_line_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y - GetHandleSize() * z);
             cairo_stroke(cr);
           }
         }
@@ -282,6 +298,11 @@
           tool_ = Tool_Crop;
           break;
 
+        case 'm':
+          tool_ = Tool_Mask;
+          widget.Select(1);
+          break;
+
         case 'd':
         {
           // dump to json and reload
@@ -340,7 +361,7 @@
           widget.SwitchInvert();
           break;
 
-        case 'm':
+        case 't':
           tool_ = Tool_Move;
           break;
 
@@ -409,6 +430,7 @@
       boost::shared_ptr<RadiographyScene>   scene_;
       RadiographyEditorInteractor           interactor_;
       Orthanc::FontRegistry                 fontRegistry_;
+      RadiographyMaskLayer*                 maskLayer_;
 
     public:
       SingleFrameEditorApplication(MessageBroker& broker) :
@@ -449,10 +471,11 @@
         statusBar.SetMessage("Use the key \"e\" to export DICOM to the Orthanc server");
         statusBar.SetMessage("Use the key \"f\" to switch full screen");
         statusBar.SetMessage("Use the key \"i\" to invert contrast");
-        statusBar.SetMessage("Use the key \"m\" to move objects");
+        statusBar.SetMessage("Use the key \"m\" to modify the mask");
         statusBar.SetMessage("Use the key \"n\" to switch between nearest neighbor and bilinear interpolation");
         statusBar.SetMessage("Use the key \"r\" to rotate objects");
         statusBar.SetMessage("Use the key \"s\" to resize objects (not applicable to DICOM layers)");
+        statusBar.SetMessage("Use the key \"t\" to move (translate) objects");
         statusBar.SetMessage("Use the key \"w\" to change windowing");
         
         statusBar.SetMessage("Use the key \"ctrl-z\" to undo action");
@@ -480,10 +503,13 @@
         //scene_->LoadDicomWebFrame(context->GetWebService());
         
         std::vector<MaskPoint> mask;
-        mask.push_back(MaskPoint(100, 100));
-        mask.push_back(MaskPoint(100, 1000));
-        mask.push_back(MaskPoint(1000, 1000));
-        scene_->LoadMask(mask, dynamic_cast<RadiographyDicomLayer&>(dicomLayer), 128.0f, NULL);
+        mask.push_back(MaskPoint(1100, 100));
+        mask.push_back(MaskPoint(1100, 1000));
+        mask.push_back(MaskPoint(2000, 1000));
+        mask.push_back(MaskPoint(2200, 150));
+        mask.push_back(MaskPoint(1500, 550));
+        maskLayer_ = dynamic_cast<RadiographyMaskLayer*>(&(scene_->LoadMask(mask, dynamic_cast<RadiographyDicomLayer&>(dicomLayer), 128.0f, NULL)));
+        interactor_.SetMaskLayer(maskLayer_);
 
         {
           RadiographyLayer& layer = scene_->LoadText(fontRegistry_.GetFont(0), "Hello\nworld", NULL);
--- a/Framework/Radiography/RadiographyLayer.cpp	Tue Feb 12 12:22:13 2019 +0100
+++ b/Framework/Radiography/RadiographyLayer.cpp	Wed Feb 13 12:04:02 2019 +0100
@@ -76,58 +76,14 @@
                                      double x,
                                      double y) const
   {
-    transform_.Apply(x, y);
+    GetTransform().Apply(x, y);
     extent.AddPoint(x, y);
   }
 
-
-  void RadiographyLayer::GetCornerInternal(double& x,
-                                           double& y,
-                                           Corner corner,
-                                           unsigned int cropX,
-                                           unsigned int cropY,
-                                           unsigned int cropWidth,
-                                           unsigned int cropHeight) const
-  {
-    double dx = static_cast<double>(cropX);
-    double dy = static_cast<double>(cropY);
-    double dwidth = static_cast<double>(cropWidth);
-    double dheight = static_cast<double>(cropHeight);
-
-    switch (corner)
-    {
-    case Corner_TopLeft:
-      x = dx;
-      y = dy;
-      break;
-
-    case Corner_TopRight:
-      x = dx + dwidth;
-      y = dy;
-      break;
-
-    case Corner_BottomLeft:
-      x = dx;
-      y = dy + dheight;
-      break;
-
-    case Corner_BottomRight:
-      x = dx + dwidth;
-      y = dy + dheight;
-      break;
-
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    transform_.Apply(x, y);
-  }
-
-
   bool RadiographyLayer::Contains(double x,
                                   double y) const
   {
-    transformInverse_.Apply(x, y);
+    GetTransformInverse().Apply(x, y);
 
     unsigned int cropX, cropY, cropWidth, cropHeight;
     GetCrop(cropX, cropY, cropWidth, cropHeight);
@@ -140,43 +96,23 @@
   void RadiographyLayer::DrawBorders(CairoContext& context,
                                      double zoom)
   {
-    unsigned int cx, cy, width, height;
-    GetCrop(cx, cy, width, height);
-
-    double dx = static_cast<double>(cx);
-    double dy = static_cast<double>(cy);
-    double dwidth = static_cast<double>(width);
-    double dheight = static_cast<double>(height);
+    if (GetControlPointCount() < 3 )
+      return;
 
     cairo_t* cr = context.GetObject();
     cairo_set_line_width(cr, 2.0 / zoom);
 
-    double x, y;
-    x = dx;
-    y = dy;
-    transform_.Apply(x, y);
-    cairo_move_to(cr, x, y);
-
-    x = dx + dwidth;
-    y = dy;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
+    ControlPoint cp;
+    GetControlPoint(cp, 0);
+    cairo_move_to(cr, cp.x, cp.y);
 
-    x = dx + dwidth;
-    y = dy + dheight;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
+    for (size_t i = 0; i < GetControlPointCount(); i++)
+    {
+      GetControlPoint(cp, i);
+      cairo_line_to(cr, cp.x, cp.y);
+    }
 
-    x = dx;
-    y = dy + dheight;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
-
-    x = dx;
-    y = dy;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
-
+    cairo_close_path(cr);
     cairo_stroke(cr);
   }
 
@@ -237,7 +173,7 @@
     {
       GetGeometry().GetCrop(x, y, width, height);
     }
-    else 
+    else
     {
       x = 0;
       y = 0;
@@ -305,7 +241,7 @@
     }
     else
     {
-      transformInverse_.Apply(sceneX, sceneY);
+      GetTransformInverse().Apply(sceneX, sceneY);
 
       int x = static_cast<int>(std::floor(sceneX));
       int y = static_cast<int>(std::floor(sceneY));
@@ -362,48 +298,71 @@
   {
     centerX = static_cast<double>(width_) / 2.0;
     centerY = static_cast<double>(height_) / 2.0;
-    transform_.Apply(centerX, centerY);
-  }
-
-
-  void RadiographyLayer::GetCorner(double& x /* out */,
-                                   double& y /* out */,
-                                   Corner corner) const
-  {
-    unsigned int cropX, cropY, cropWidth, cropHeight;
-    GetCrop(cropX, cropY, cropWidth, cropHeight);
-    GetCornerInternal(x, y, corner, cropX, cropY, cropWidth, cropHeight);
+    GetTransform().Apply(centerX, centerY);
   }
 
 
-  bool RadiographyLayer::LookupCorner(Corner& corner /* out */,
-                                      double x,
-                                      double y,
-                                      double zoom,
-                                      double viewportDistance) const
+
+  size_t RadiographyLayer::GetControlPointCount() const {return 4;}
+
+  void RadiographyLayer::GetControlPointInternal(ControlPoint& controlPoint,
+                                                 size_t index) const
   {
-    static const Corner CORNERS[] = {
-      Corner_TopLeft,
-      Corner_TopRight,
-      Corner_BottomLeft,
-      Corner_BottomRight
-    };
-
     unsigned int cropX, cropY, cropWidth, cropHeight;
     GetCrop(cropX, cropY, cropWidth, cropHeight);
 
+    switch (index)
+    {
+    case ControlPoint_TopLeftCorner:
+      controlPoint = ControlPoint(cropX, cropY, ControlPoint_TopLeftCorner);
+      break;
+
+    case ControlPoint_TopRightCorner:
+      controlPoint = ControlPoint(cropX + cropWidth, cropY, ControlPoint_TopRightCorner);
+      break;
+
+    case ControlPoint_BottomLeftCorner:
+      controlPoint = ControlPoint(cropX, cropY + cropHeight, ControlPoint_BottomLeftCorner);
+      break;
+
+    case ControlPoint_BottomRightCorner:
+      controlPoint = ControlPoint(cropX + cropWidth, cropY + cropHeight, ControlPoint_BottomRightCorner);
+      break;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+  }
+
+
+
+  void RadiographyLayer::GetControlPoint(ControlPoint& controlPoint /* out */,
+                                         size_t index) const
+  {
+    GetControlPointInternal(controlPoint, index);
+    GetTransform().Apply(controlPoint.x, controlPoint.y);
+  }
+
+
+  bool RadiographyLayer::LookupControlPoint(ControlPoint& controlPoint /* out */,
+                                            double x,
+                                            double y,
+                                            double zoom,
+                                            double viewportDistance) const
+  {
     double threshold = Square(viewportDistance / zoom);
 
-    for (size_t i = 0; i < 4; i++)
+    for (size_t i = 0; i < GetControlPointCount(); i++)
     {
-      double cx, cy;
-      GetCornerInternal(cx, cy, CORNERS[i], cropX, cropY, cropWidth, cropHeight);
+      ControlPoint cp;
+      GetControlPoint(cp, i);
 
-      double d = Square(cx - x) + Square(cy - y);
+      double d = Square(cp.x - x) + Square(cp.y - y);
 
       if (d <= threshold)
       {
-        corner = CORNERS[i];
+        controlPoint = cp;
         return true;
       }
     }
--- a/Framework/Radiography/RadiographyLayer.h	Tue Feb 12 12:22:13 2019 +0100
+++ b/Framework/Radiography/RadiographyLayer.h	Wed Feb 13 12:04:02 2019 +0100
@@ -27,6 +27,25 @@
 
 namespace OrthancStone
 {
+  struct ControlPoint
+  {
+    double x;
+    double y;
+    size_t index;
+
+    ControlPoint(double x, double y, size_t index)
+      : x(x),
+        y(y),
+        index(index)
+    {}
+
+    ControlPoint()
+      : x(0),
+        y(0),
+        index(std::numeric_limits<size_t>::max())
+    {}
+  };
+
   class RadiographyLayer : public boost::noncopyable
   {
     friend class RadiographyScene;
@@ -144,39 +163,39 @@
 
 
   protected:
-    const AffineTransform2D& GetTransform() const
+    virtual const AffineTransform2D& GetTransform() const
     {
       return transform_;
     }
 
+    virtual const AffineTransform2D& GetTransformInverse() const
+    {
+      return transformInverse_;
+    }
+
     void SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode  prefferedPhotometricDisplayMode)
     {
       prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode;
     }
 
+    virtual void GetControlPointInternal(ControlPoint& controlPoint,
+                                         size_t index) const;
+
   private:
     void UpdateTransform();
-      
+
     void AddToExtent(Extent2D& extent,
                      double x,
                      double y) const;
 
-    void GetCornerInternal(double& x,
-                           double& y,
-                           Corner corner,
-                           unsigned int cropX,
-                           unsigned int cropY,
-                           unsigned int cropWidth,
-                           unsigned int cropHeight) const;
-
     void SetIndex(size_t index)
     {
       index_ = index;
     }
-      
+
     bool Contains(double x,
                   double y) const;
-      
+
     void DrawBorders(CairoContext& context,
                      double zoom);
 
@@ -232,12 +251,12 @@
     unsigned int GetWidth() const
     {
       return width_;
-    }        
+    }
 
     unsigned int GetHeight() const
     {
       return height_;
-    }       
+    }
 
     Extent2D GetExtent() const;
 
@@ -252,15 +271,16 @@
     void GetCenter(double& centerX,
                    double& centerY) const;
 
-    void GetCorner(double& x /* out */,
-                   double& y /* out */,
-                   Corner corner) const;
-      
-    bool LookupCorner(Corner& corner /* out */,
-                      double x,
-                      double y,
-                      double zoom,
-                      double viewportDistance) const;
+    void GetControlPoint(ControlPoint& controlPoint,
+                         size_t index) const;
+
+    virtual size_t GetControlPointCount() const;
+
+    bool LookupControlPoint(ControlPoint& controlPoint /* out */,
+                            double x,
+                            double y,
+                            double zoom,
+                            double viewportDistance) const;
 
     virtual bool GetDefaultWindowing(float& center,
                                      float& width) const = 0;
--- a/Framework/Radiography/RadiographyLayerCropTracker.cpp	Tue Feb 12 12:22:13 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerCropTracker.cpp	Wed Feb 13 12:04:02 2019 +0100
@@ -68,12 +68,10 @@
                                                            RadiographyScene& scene,
                                                            const ViewportGeometry& view,
                                                            size_t layer,
-                                                           double x,
-                                                           double y,
-                                                           Corner corner) :
+                                                           const ControlPoint& startControlPoint) :
     undoRedoStack_(undoRedoStack),
     accessor_(scene, layer),
-    corner_(corner)
+    startControlPoint_(startControlPoint)
   {
     if (accessor_.IsValid())
     {
@@ -114,8 +112,8 @@
       {
         unsigned int targetX, targetWidth;
 
-        if (corner_ == Corner_TopLeft ||
-            corner_ == Corner_BottomLeft)
+        if (startControlPoint_.index == ControlPoint_TopLeftCorner ||
+            startControlPoint_.index == ControlPoint_BottomLeftCorner)
         {
           targetX = std::min(x, cropX_ + cropWidth_);
           targetWidth = cropX_ + cropWidth_ - targetX;
@@ -128,8 +126,8 @@
 
         unsigned int targetY, targetHeight;
 
-        if (corner_ == Corner_TopLeft ||
-            corner_ == Corner_TopRight)
+        if (startControlPoint_.index == ControlPoint_TopLeftCorner ||
+            startControlPoint_.index == ControlPoint_TopRightCorner)
         {
           targetY = std::min(y, cropY_ + cropHeight_);
           targetHeight = cropY_ + cropHeight_ - targetY;
--- a/Framework/Radiography/RadiographyLayerCropTracker.h	Tue Feb 12 12:22:13 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerCropTracker.h	Wed Feb 13 12:04:02 2019 +0100
@@ -35,7 +35,7 @@
 
     UndoRedoStack&                   undoRedoStack_;
     RadiographyScene::LayerAccessor  accessor_;
-    Corner                           corner_;
+    ControlPoint                     startControlPoint_;
     unsigned int                     cropX_;
     unsigned int                     cropY_;
     unsigned int                     cropWidth_;
@@ -46,9 +46,7 @@
                                 RadiographyScene& scene,
                                 const ViewportGeometry& view,
                                 size_t layer,
-                                double x,
-                                double y,
-                                Corner corner);
+                                const ControlPoint& startControlPoint);
 
     virtual bool HasRender() const
     {
--- a/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Tue Feb 12 12:22:13 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Wed Feb 13 12:04:02 2019 +0100
@@ -85,9 +85,7 @@
   RadiographyLayerResizeTracker::RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack,
                                                                RadiographyScene& scene,
                                                                size_t layer,
-                                                               double x,
-                                                               double y,
-                                                               Corner corner,
+                                                               const ControlPoint& startControlPoint,
                                                                bool roundScaling) :
     undoRedoStack_(undoRedoStack),
     accessor_(scene, layer),
@@ -101,31 +99,32 @@
       originalPanX_ = accessor_.GetLayer().GetGeometry().GetPanX();
       originalPanY_ = accessor_.GetLayer().GetGeometry().GetPanY();
 
-      switch (corner)
+      size_t oppositeControlPointType;
+      switch (startControlPoint.index)
       {
-        case Corner_TopLeft:
-          oppositeCorner_ = Corner_BottomRight;
+        case ControlPoint_TopLeftCorner:
+          oppositeControlPointType = ControlPoint_BottomRightCorner;
           break;
 
-        case Corner_TopRight:
-          oppositeCorner_ = Corner_BottomLeft;
+        case ControlPoint_TopRightCorner:
+          oppositeControlPointType = ControlPoint_BottomLeftCorner;
           break;
 
-        case Corner_BottomLeft:
-          oppositeCorner_ = Corner_TopRight;
+        case ControlPoint_BottomLeftCorner:
+          oppositeControlPointType = ControlPoint_TopRightCorner;
           break;
 
-        case Corner_BottomRight:
-          oppositeCorner_ = Corner_TopLeft;
+        case ControlPoint_BottomRightCorner:
+          oppositeControlPointType = ControlPoint_TopLeftCorner;
           break;
 
         default:
           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
 
-      accessor_.GetLayer().GetCorner(oppositeX_, oppositeY_, oppositeCorner_);
+      accessor_.GetLayer().GetControlPoint(startOppositeControlPoint_, oppositeControlPointType);
 
-      double d = ComputeDistance(x, y, oppositeX_, oppositeY_);
+      double d = ComputeDistance(startControlPoint.x, startControlPoint.y, startOppositeControlPoint_.x, startOppositeControlPoint_.y);
       if (d >= std::numeric_limits<float>::epsilon())
       {
         baseScaling_ = 1.0 / d;
@@ -168,7 +167,7 @@
     if (accessor_.IsValid() &&
         accessor_.GetLayer().GetGeometry().IsResizeable())
     {
-      double scaling = ComputeDistance(oppositeX_, oppositeY_, sceneX, sceneY) * baseScaling_;
+      double scaling = ComputeDistance(startOppositeControlPoint_.x, startOppositeControlPoint_.y, sceneX, sceneY) * baseScaling_;
 
       if (roundScaling_)
       {
@@ -180,10 +179,10 @@
                             scaling * originalSpacingY_);
 
       // Keep the opposite corner at a fixed location
-      double ox, oy;
-      layer.GetCorner(ox, oy, oppositeCorner_);
-      layer.SetPan(layer.GetGeometry().GetPanX() + oppositeX_ - ox,
-                   layer.GetGeometry().GetPanY() + oppositeY_ - oy);
+      ControlPoint currentOppositeCorner;
+      layer.GetControlPoint(currentOppositeCorner, startOppositeControlPoint_.index);
+      layer.SetPan(layer.GetGeometry().GetPanX() + startOppositeControlPoint_.x - currentOppositeCorner.x,
+                   layer.GetGeometry().GetPanY() + startOppositeControlPoint_.y - currentOppositeCorner.y);
     }
   }
 }
--- a/Framework/Radiography/RadiographyLayerResizeTracker.h	Tue Feb 12 12:22:13 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerResizeTracker.h	Wed Feb 13 12:04:02 2019 +0100
@@ -39,18 +39,14 @@
     double                           originalSpacingY_;
     double                           originalPanX_;
     double                           originalPanY_;
-    Corner                           oppositeCorner_;
-    double                           oppositeX_;
-    double                           oppositeY_;
+    ControlPoint                     startOppositeControlPoint_;
     double                           baseScaling_;
 
   public:
     RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack,
                                   RadiographyScene& scene,
                                   size_t layer,
-                                  double x,
-                                  double y,
-                                  Corner corner,
+                                  const ControlPoint& startControlPoint,
                                   bool roundScaling);
 
     virtual bool HasRender() const
--- a/Framework/Radiography/RadiographyMaskLayer.cpp	Tue Feb 12 12:22:13 2019 +0100
+++ b/Framework/Radiography/RadiographyMaskLayer.cpp	Wed Feb 13 12:04:02 2019 +0100
@@ -29,6 +29,18 @@
 
 namespace OrthancStone
 {
+  const unsigned char IN_MASK_VALUE = 0x00;
+  const unsigned char OUT_MASK_VALUE = 0xFF;
+
+  const AffineTransform2D& RadiographyMaskLayer::GetTransform() const
+  {
+    return dicomLayer_.GetTransform();
+  }
+
+  const AffineTransform2D& RadiographyMaskLayer::GetTransformInverse() const
+  {
+    return dicomLayer_.GetTransformInverse();
+  }
 
   void ComputeMaskExtent(unsigned int& left, unsigned int& right, unsigned int& top, unsigned int& bottom, const std::vector<MaskPoint>& corners)
   {
@@ -66,18 +78,6 @@
 
       DrawMask();
 
-//      for (unsigned int i = 0; i < 100; i++)
-//      {
-//        for (unsigned int j = 0; j < 50; j++)
-//        {
-//          if ((i + j) % 2 == 1)
-//          {
-//            Orthanc::ImageAccessor region;
-//            mask_->GetRegion(region, i* 20, j * 20, 20, 20);
-//            Orthanc::ImageProcessing::Set(region, 255);
-//          }
-//        }
-//      }
       invalidated_ = false;
     }
 
@@ -112,7 +112,7 @@
 
         for (unsigned int x = 0; x < width; x++, p++, q++)
         {
-          if (*p == 0)
+          if (*p == OUT_MASK_VALUE)
             *q = foreground_;
           // else keep the underlying pixel value
         }
@@ -214,6 +214,36 @@
       return count&1;  // Same as (count%2 == 1)
   }
 
+  void RadiographyMaskLayer::DrawLine(const MaskPoint& start, const MaskPoint& end) const
+  {
+    int dx = (int)(end.x) - (int)(start.x);
+    int dy = (int)(end.y) - (int)(start.y);
+
+    if (std::abs(dx) > std::abs(dy))
+    { // the line is closer to horizontal
+
+      int incx = dx / std::abs(dx);
+      double incy = (double)(dy)/(double)(dx);
+      double y = (double)(start.y);
+      for (int x = (int)(start.x); x != (int)(end.x); x += incx, y += incy)
+      {
+        unsigned char* p = reinterpret_cast<unsigned char*>(mask_->GetRow((int)(y + 0.5))) + x;
+        *p = IN_MASK_VALUE;
+      }
+    }
+    else
+    { // the line is closer to vertical
+      int incy = dy / std::abs(dy);
+      double incx = (double)(dx)/(double)(dy);
+      double x = (double)(start.x);
+      for (int y = (int)(start.y); y != (int)(end.y); y += incy, x += incx)
+      {
+        unsigned char* p = reinterpret_cast<unsigned char*>(mask_->GetRow(y)) + (int)(x + 0.5);
+        *p = IN_MASK_VALUE;
+      }
+    }
+  }
+
 
   void RadiographyMaskLayer::DrawMask() const
   {
@@ -222,26 +252,114 @@
     unsigned int top;
     unsigned int bottom;
 
-    ComputeMaskExtent(left, right, top, bottom, corners_);
+//    ComputeMaskExtent(left, right, top, bottom, corners_);
 
-    Orthanc::ImageProcessing::Set(*mask_, 0);
+    left = 0;
+    right = 2500;
+    top = 0;
+    bottom = 2500;
+    // first fill the complete image
+    Orthanc::ImageProcessing::Set(*mask_, OUT_MASK_VALUE);
 
-    MaskPoint p(left, top);
-    for (p.y = top; p.y <= bottom; p.y++)
+
     {
-      unsigned char* q = reinterpret_cast<unsigned char*>(mask_->GetRow(p.y));
-      for (p.x = left; p.x <= right; p.x++, q++)
+      // from http://alienryderflex.com/polygon_fill/
+      std::auto_ptr<int> raiiNodeX(new int(corners_.size()));
+
+      std::vector<int> nodeX;
+      nodeX.resize(corners_.size());
+      int  nodes, pixelX, pixelY, i, j, swap ;
+
+      //  Loop through the rows of the image.
+      for (pixelY = (int)top; pixelY < (int)bottom; pixelY++)
       {
-        if (isInside(corners_, p))
+        //  Build a list of nodes.
+        nodes = 0;
+        j = (int)corners_.size() - 1;
+
+        for (i = 0; i < (int)corners_.size(); i++)
+        {
+          if ((int)corners_[i].y < pixelY && (int)corners_[j].y >=  pixelY
+          ||  (int)corners_[j].y < pixelY && (int)corners_[i].y >= pixelY)
+          {
+            nodeX[nodes++]= (int)((double)corners_[i].x + ((double)pixelY - (double)corners_[i].y)/((double)corners_[j].y - (double)corners_[i].y) *((double)corners_[j].x - (double)corners_[i].x));
+          }
+          j=i;
+        }
+
+        //  Sort the nodes, via a simple “Bubble” sort.
+        i=0;
+        while (i<nodes-1)
         {
-          *q = 255;
+          if (nodeX[i]>nodeX[i+1])
+          {
+            swap=nodeX[i]; nodeX[i]=nodeX[i+1]; nodeX[i+1]=swap; if (i) i--;
+          }
+          else
+          {
+            i++;
+          }
+        }
+
+        unsigned char* row = reinterpret_cast<unsigned char*>(mask_->GetRow(pixelY));
+        //  Fill the pixels between node pairs.
+        for (i=0; i<nodes; i+=2)
+        {
+          if   (nodeX[i  ]>=(int)right)
+            break;
+          if   (nodeX[i+1]> (int)left)
+          {
+            if (nodeX[i  ]< (int)left )
+              nodeX[i  ]=(int)left ;
+            if (nodeX[i+1]> (int)right)
+              nodeX[i+1]=(int)right;
+            for (pixelX=nodeX[i]; pixelX<nodeX[i+1]; pixelX++)
+            {
+              *(row + pixelX) = IN_MASK_VALUE;
+            }
+          }
         }
       }
     }
 
-//    Orthanc::ImageAccessor region;
-//    mask_->GetRegion(region, 100, 100, 1000, 1000);
-//    Orthanc::ImageProcessing::Set(region, 255);
+//    // draw lines
+//    for (size_t i = 1; i < corners_.size(); i++)
+//    {
+//      DrawLine(corners_[i-1], corners_[i]);
+//    }
+//    DrawLine(corners_[corners_.size()-1], corners_[0]);
+
+//    // fill between lines
+//    MaskPoint p(left, top);
+//    for (p.y = top; p.y <= bottom; p.y++)
+//    {
+//      unsigned char* q = reinterpret_cast<unsigned char*>(mask_->GetRow(p.y)) + left;
+//      unsigned char previousPixelValue1 = OUT_MASK_VALUE;
+//      unsigned char previousPixelValue2 = OUT_MASK_VALUE;
+//      for (p.x = left; p.x <= right; p.x++, q++)
+//      {
+//        if (*p == OUT_MASK_VALUE && previousPixelValue1 == IN_MASK_VALUE && previousPixelValue2 == OUT_MASK_VALUE) // we just passed over a single one pixel line => start filling
+//        {
+
+//          *q = IN_MASK_VALUE;
+//        }
+//      }
+//    }
+
+
+//    MaskPoint p(left, top);
+//    for (p.y = top; p.y <= bottom; p.y++)
+//    {
+//      unsigned char* q = reinterpret_cast<unsigned char*>(mask_->GetRow(p.y)) + left;
+//      for (p.x = left; p.x <= right; p.x++, q++)
+//      {
+//        if (isInside(corners_, p))
+//        {
+//          *q = IN_MASK_VALUE;
+//        }
+//      }
+//    }
+
   }
 
 }
--- a/Framework/Radiography/RadiographyMaskLayer.h	Tue Feb 12 12:22:13 2019 +0100
+++ b/Framework/Radiography/RadiographyMaskLayer.h	Wed Feb 13 12:04:02 2019 +0100
@@ -65,6 +65,17 @@
                         const AffineTransform2D& viewTransform,
                         ImageInterpolation interpolation) const;
 
+    virtual size_t GetControlPointCount() const
+    {
+      return corners_.size();
+    }
+
+    virtual void GetControlPointInternal(ControlPoint& controlPoint,
+                                         size_t index) const
+    {
+      controlPoint = ControlPoint(corners_[index].x, corners_[index].y, index);
+    }
+
     virtual bool GetDefaultWindowing(float& center,
                                      float& width) const
     {
@@ -91,7 +102,15 @@
 
     }
 
+  protected:
+    virtual const AffineTransform2D& GetTransform() const;
+
+    virtual const AffineTransform2D& GetTransformInverse() const;
+
+
   private:
     void DrawMask() const;
+    void DrawLine(const MaskPoint& start, const MaskPoint& end) const;
+
   };
 }
--- a/Framework/StoneEnumerations.h	Tue Feb 12 12:22:13 2019 +0100
+++ b/Framework/StoneEnumerations.h	Wed Feb 13 12:04:02 2019 +0100
@@ -172,12 +172,12 @@
   };
 
   
-  enum Corner
+  enum ControlPointType
   {
-    Corner_TopLeft,
-    Corner_TopRight,
-    Corner_BottomLeft,
-    Corner_BottomRight
+    ControlPoint_TopLeftCorner = 0,
+    ControlPoint_TopRightCorner = 1,
+    ControlPoint_BottomRightCorner = 2,
+    ControlPoint_BottomLeftCorner = 3
   };
 
   enum PhotometricDisplayMode