changeset 415:c0589c3173fd

finished reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 13 Nov 2018 10:36:53 +0100
parents f7616c010056
children aee3d7941c9b
files Applications/Samples/SingleFrameEditorApplication.h Framework/Radiography/RadiographyLayerCropTracker.cpp Framework/Radiography/RadiographyLayerCropTracker.h Framework/Radiography/RadiographyLayerResizeTracker.cpp Framework/Radiography/RadiographyLayerResizeTracker.h Framework/Radiography/RadiographyWindowingTracker.cpp Framework/Radiography/RadiographyWindowingTracker.h Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 8 files changed, 760 insertions(+), 518 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SingleFrameEditorApplication.h	Mon Nov 12 18:00:48 2018 +0100
+++ b/Applications/Samples/SingleFrameEditorApplication.h	Tue Nov 13 10:36:53 2018 +0100
@@ -23,25 +23,18 @@
 
 #include "SampleApplicationBase.h"
 
+#include "../../Framework/Radiography/RadiographyLayerCropTracker.h"
 #include "../../Framework/Radiography/RadiographyLayerMoveTracker.h"
+#include "../../Framework/Radiography/RadiographyLayerResizeTracker.h"
 #include "../../Framework/Radiography/RadiographyLayerRotateTracker.h"
 #include "../../Framework/Radiography/RadiographyScene.h"
 #include "../../Framework/Radiography/RadiographySceneCommand.h"
 #include "../../Framework/Radiography/RadiographyWidget.h"
-
-#include "../../Framework/Toolbox/UndoRedoStack.h"
+#include "../../Framework/Radiography/RadiographyWindowingTracker.h"
 
 #include <Core/Images/FontRegistry.h>
-#include <Core/Images/Image.h>
-#include <Core/Images/ImageProcessing.h>
-#include <Core/Images/PamReader.h>
-#include <Core/Images/PamWriter.h>
-#include <Core/Images/PngWriter.h>
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
-#include <Core/Toolbox.h>
-#include <Plugins/Samples/Common/DicomDatasetReader.h>
-#include <Plugins/Samples/Common/FullOrthancDataset.h>
 
 
 // Export using PAM is faster than using PNG, but requires Orthanc
@@ -49,515 +42,8 @@
 #define EXPORT_USING_PAM  1
 
 
-#include <boost/math/special_functions/round.hpp>
-
-
 namespace OrthancStone
 {
-  class RadiographyLayerCropTracker : public IWorldSceneMouseTracker
-  {
-  private:
-    UndoRedoStack&                   undoRedoStack_;
-    RadiographyScene::LayerAccessor  accessor_;
-    Corner                           corner_;
-    unsigned int                     cropX_;
-    unsigned int                     cropY_;
-    unsigned int                     cropWidth_;
-    unsigned int                     cropHeight_;
-
-    class UndoRedoCommand : public RadiographySceneCommand
-    {
-    private:
-      unsigned int  sourceCropX_;
-      unsigned int  sourceCropY_;
-      unsigned int  sourceCropWidth_;
-      unsigned int  sourceCropHeight_;
-      unsigned int  targetCropX_;
-      unsigned int  targetCropY_;
-      unsigned int  targetCropWidth_;
-      unsigned int  targetCropHeight_;
-
-    protected:
-      virtual void UndoInternal(RadiographyLayer& layer) const
-      {
-        layer.SetCrop(sourceCropX_, sourceCropY_, sourceCropWidth_, sourceCropHeight_);
-      }
-
-      virtual void RedoInternal(RadiographyLayer& layer) const
-      {
-        layer.SetCrop(targetCropX_, targetCropY_, targetCropWidth_, targetCropHeight_);
-      }
-
-    public:
-      UndoRedoCommand(const RadiographyLayerCropTracker& tracker) :
-        RadiographySceneCommand(tracker.accessor_),
-        sourceCropX_(tracker.cropX_),
-        sourceCropY_(tracker.cropY_),
-        sourceCropWidth_(tracker.cropWidth_),
-        sourceCropHeight_(tracker.cropHeight_)
-      {
-        tracker.accessor_.GetLayer().GetCrop(targetCropX_, targetCropY_,
-                                             targetCropWidth_, targetCropHeight_);
-      }
-    };
-
-
-  public:
-    RadiographyLayerCropTracker(UndoRedoStack& undoRedoStack,
-                                RadiographyScene& scene,
-                                const ViewportGeometry& view,
-                                size_t layer,
-                                double x,
-                                double y,
-                                Corner corner) :
-      undoRedoStack_(undoRedoStack),
-      accessor_(scene, layer),
-      corner_(corner)
-    {
-      if (accessor_.IsValid())
-      {
-        accessor_.GetLayer().GetCrop(cropX_, cropY_, cropWidth_, cropHeight_);          
-      }
-    }
-
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void Render(CairoContext& context,
-                        double zoom)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    virtual void MouseUp()
-    {
-      if (accessor_.IsValid())
-      {
-        undoRedoStack_.Add(new UndoRedoCommand(*this));
-      }
-    }
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double sceneX,
-                           double sceneY)
-    {
-      if (accessor_.IsValid())
-      {
-        unsigned int x, y;
-        
-        RadiographyLayer& layer = accessor_.GetLayer();
-        if (layer.GetPixel(x, y, sceneX, sceneY))
-        {
-          unsigned int targetX, targetWidth;
-
-          if (corner_ == Corner_TopLeft ||
-              corner_ == Corner_BottomLeft)
-          {
-            targetX = std::min(x, cropX_ + cropWidth_);
-            targetWidth = cropX_ + cropWidth_ - targetX;
-          }
-          else
-          {
-            targetX = cropX_;
-            targetWidth = std::max(x, cropX_) - cropX_;
-          }
-
-          unsigned int targetY, targetHeight;
-
-          if (corner_ == Corner_TopLeft ||
-              corner_ == Corner_TopRight)
-          {
-            targetY = std::min(y, cropY_ + cropHeight_);
-            targetHeight = cropY_ + cropHeight_ - targetY;
-          }
-          else
-          {
-            targetY = cropY_;
-            targetHeight = std::max(y, cropY_) - cropY_;
-          }
-
-          layer.SetCrop(targetX, targetY, targetWidth, targetHeight);
-        }
-      }
-    }
-  };
-    
-    
-  class RadiographyLayerResizeTracker : public IWorldSceneMouseTracker
-  {
-  private:
-    UndoRedoStack&                   undoRedoStack_;
-    RadiographyScene::LayerAccessor  accessor_;
-    bool                             roundScaling_;
-    double                           originalSpacingX_;
-    double                           originalSpacingY_;
-    double                           originalPanX_;
-    double                           originalPanY_;
-    Corner                           oppositeCorner_;
-    double                           oppositeX_;
-    double                           oppositeY_;
-    double                           baseScaling_;
-
-    static double ComputeDistance(double x1,
-                                  double y1,
-                                  double x2,
-                                  double y2)
-    {
-      double dx = x1 - x2;
-      double dy = y1 - y2;
-      return sqrt(dx * dx + dy * dy);
-    }
-      
-    class UndoRedoCommand : public RadiographySceneCommand
-    {
-    private:
-      double   sourceSpacingX_;
-      double   sourceSpacingY_;
-      double   sourcePanX_;
-      double   sourcePanY_;
-      double   targetSpacingX_;
-      double   targetSpacingY_;
-      double   targetPanX_;
-      double   targetPanY_;
-
-    protected:
-      virtual void UndoInternal(RadiographyLayer& layer) const
-      {
-        layer.SetPixelSpacing(sourceSpacingX_, sourceSpacingY_);
-        layer.SetPan(sourcePanX_, sourcePanY_);
-      }
-
-      virtual void RedoInternal(RadiographyLayer& layer) const
-      {
-        layer.SetPixelSpacing(targetSpacingX_, targetSpacingY_);
-        layer.SetPan(targetPanX_, targetPanY_);
-      }
-
-    public:
-      UndoRedoCommand(const RadiographyLayerResizeTracker& tracker) :
-        RadiographySceneCommand(tracker.accessor_),
-        sourceSpacingX_(tracker.originalSpacingX_),
-        sourceSpacingY_(tracker.originalSpacingY_),
-        sourcePanX_(tracker.originalPanX_),
-        sourcePanY_(tracker.originalPanY_),
-        targetSpacingX_(tracker.accessor_.GetLayer().GetPixelSpacingX()),
-        targetSpacingY_(tracker.accessor_.GetLayer().GetPixelSpacingY()),
-        targetPanX_(tracker.accessor_.GetLayer().GetPanX()),
-        targetPanY_(tracker.accessor_.GetLayer().GetPanY())
-      {
-      }
-    };
-
-
-  public:
-    RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack,
-                                  RadiographyScene& scene,
-                                  size_t layer,
-                                  double x,
-                                  double y,
-                                  Corner corner,
-                                  bool roundScaling) :
-      undoRedoStack_(undoRedoStack),
-      accessor_(scene, layer),
-      roundScaling_(roundScaling)
-    {
-      if (accessor_.IsValid() &&
-          accessor_.GetLayer().IsResizeable())
-      {
-        originalSpacingX_ = accessor_.GetLayer().GetPixelSpacingX();
-        originalSpacingY_ = accessor_.GetLayer().GetPixelSpacingY();
-        originalPanX_ = accessor_.GetLayer().GetPanX();
-        originalPanY_ = accessor_.GetLayer().GetPanY();
-
-        switch (corner)
-        {
-          case Corner_TopLeft:
-            oppositeCorner_ = Corner_BottomRight;
-            break;
-
-          case Corner_TopRight:
-            oppositeCorner_ = Corner_BottomLeft;
-            break;
-
-          case Corner_BottomLeft:
-            oppositeCorner_ = Corner_TopRight;
-            break;
-
-          case Corner_BottomRight:
-            oppositeCorner_ = Corner_TopLeft;
-            break;
-
-          default:
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-
-        accessor_.GetLayer().GetCorner(oppositeX_, oppositeY_, oppositeCorner_);
-
-        double d = ComputeDistance(x, y, oppositeX_, oppositeY_);
-        if (d >= std::numeric_limits<float>::epsilon())
-        {
-          baseScaling_ = 1.0 / d;
-        }
-        else
-        {
-          // Avoid division by zero in extreme cases
-          accessor_.Invalidate();
-        }
-      }
-    }
-
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void Render(CairoContext& context,
-                        double zoom)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    virtual void MouseUp()
-    {
-      if (accessor_.IsValid() &&
-          accessor_.GetLayer().IsResizeable())
-      {
-        undoRedoStack_.Add(new UndoRedoCommand(*this));
-      }
-    }
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double sceneX,
-                           double sceneY)
-    {
-      static const double ROUND_SCALING = 0.1;
-        
-      if (accessor_.IsValid() &&
-          accessor_.GetLayer().IsResizeable())
-      {
-        double scaling = ComputeDistance(oppositeX_, oppositeY_, sceneX, sceneY) * baseScaling_;
-
-        if (roundScaling_)
-        {
-          scaling = boost::math::round<double>((scaling / ROUND_SCALING) * ROUND_SCALING);
-        }
-          
-        RadiographyLayer& layer = accessor_.GetLayer();
-        layer.SetPixelSpacing(scaling * originalSpacingX_,
-                              scaling * originalSpacingY_);
-
-        // Keep the opposite corner at a fixed location
-        double ox, oy;
-        layer.GetCorner(ox, oy, oppositeCorner_);
-        layer.SetPan(layer.GetPanX() + oppositeX_ - ox,
-                     layer.GetPanY() + oppositeY_ - oy);
-      }
-    }
-  };
-
-
-  class RadiographyWindowingTracker : public IWorldSceneMouseTracker
-  {   
-  public:
-    enum Action
-    {
-      Action_IncreaseWidth,
-      Action_DecreaseWidth,
-      Action_IncreaseCenter,
-      Action_DecreaseCenter
-    };
-    
-  private:
-    UndoRedoStack&      undoRedoStack_;
-    RadiographyScene&   scene_;
-    int                 clickX_;
-    int                 clickY_;
-    Action              leftAction_;
-    Action              rightAction_;
-    Action              upAction_;
-    Action              downAction_;
-    float               strength_;
-    float               sourceCenter_;
-    float               sourceWidth_;
-
-    static void ComputeAxisEffect(int& deltaCenter,
-                                  int& deltaWidth,
-                                  int delta,
-                                  Action actionNegative,
-                                  Action actionPositive)
-    {
-      if (delta < 0)
-      {
-        switch (actionNegative)
-        {
-          case Action_IncreaseWidth:
-            deltaWidth = -delta;
-            break;
-
-          case Action_DecreaseWidth:
-            deltaWidth = delta;
-            break;
-
-          case Action_IncreaseCenter:
-            deltaCenter = -delta;
-            break;
-
-          case Action_DecreaseCenter:
-            deltaCenter = delta;
-            break;
-
-          default:
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-      }
-      else if (delta > 0)
-      {
-        switch (actionPositive)
-        {
-          case Action_IncreaseWidth:
-            deltaWidth = delta;
-            break;
-
-          case Action_DecreaseWidth:
-            deltaWidth = -delta;
-            break;
-
-          case Action_IncreaseCenter:
-            deltaCenter = delta;
-            break;
-
-          case Action_DecreaseCenter:
-            deltaCenter = -delta;
-            break;
-
-          default:
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-      }
-    }
-    
-    
-    class UndoRedoCommand : public UndoRedoStack::ICommand
-    {
-    private:
-      RadiographyScene&  scene_;
-      float              sourceCenter_;
-      float              sourceWidth_;
-      float              targetCenter_;
-      float              targetWidth_;
-
-    public:
-      UndoRedoCommand(const RadiographyWindowingTracker& tracker) :
-        scene_(tracker.scene_),
-        sourceCenter_(tracker.sourceCenter_),
-        sourceWidth_(tracker.sourceWidth_)
-      {
-        scene_.GetWindowingWithDefault(targetCenter_, targetWidth_);
-      }
-
-      virtual void Undo() const
-      {
-        scene_.SetWindowing(sourceCenter_, sourceWidth_);
-      }
-      
-      virtual void Redo() const
-      {
-        scene_.SetWindowing(targetCenter_, targetWidth_);
-      }
-    };
-
-
-  public:
-    RadiographyWindowingTracker(UndoRedoStack& undoRedoStack,
-                                RadiographyScene& scene,
-                                int x,
-                                int y,
-                                Action leftAction,
-                                Action rightAction,
-                                Action upAction,
-                                Action downAction) :
-      undoRedoStack_(undoRedoStack),
-      scene_(scene),
-      clickX_(x),
-      clickY_(y),
-      leftAction_(leftAction),
-      rightAction_(rightAction),
-      upAction_(upAction),
-      downAction_(downAction)
-    {
-      scene_.GetWindowingWithDefault(sourceCenter_, sourceWidth_);
-
-      float minValue, maxValue;
-      scene.GetRange(minValue, maxValue);
-
-      assert(minValue <= maxValue);
-
-      float tmp;
-      
-      float delta = (maxValue - minValue);
-      if (delta <= 1)
-      {
-        tmp = 0;
-      }
-      else
-      {
-        // NB: Visual Studio 2008 does not provide "log2f()", so we
-        // implement it by ourselves
-        tmp = logf(delta) / logf(2.0f);
-      }
-
-      strength_ = tmp - 7;
-      if (strength_ < 1)
-      {
-        strength_ = 1;
-      }
-    }
-
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void Render(CairoContext& context,
-                        double zoom)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    virtual void MouseUp()
-    {
-      undoRedoStack_.Add(new UndoRedoCommand(*this));
-    }
-
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double sceneX,
-                           double sceneY)
-    {
-      // https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/src/app/viewport/image-plugins/windowing-viewport-tool.class.js
-
-      static const float SCALE = 1.0;
-      
-      int deltaCenter = 0;
-      int deltaWidth = 0;
-
-      ComputeAxisEffect(deltaCenter, deltaWidth, displayX - clickX_, leftAction_, rightAction_);
-      ComputeAxisEffect(deltaCenter, deltaWidth, displayY - clickY_, upAction_, downAction_);
-
-      float newCenter = sourceCenter_ + (deltaCenter / SCALE * strength_);
-      float newWidth = sourceWidth_ + (deltaWidth / SCALE * strength_);
-      scene_.SetWindowing(newCenter, newWidth);
-    }
-  };
-
-
-
-  
   namespace Samples
   {
     class RadiographyEditorInteractor :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyLayerCropTracker.cpp	Tue Nov 13 10:36:53 2018 +0100
@@ -0,0 +1,145 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RadiographyLayerCropTracker.h"
+
+#include "RadiographySceneCommand.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class RadiographyLayerCropTracker::UndoRedoCommand : public RadiographySceneCommand
+  {
+  private:
+    unsigned int  sourceCropX_;
+    unsigned int  sourceCropY_;
+    unsigned int  sourceCropWidth_;
+    unsigned int  sourceCropHeight_;
+    unsigned int  targetCropX_;
+    unsigned int  targetCropY_;
+    unsigned int  targetCropWidth_;
+    unsigned int  targetCropHeight_;
+
+  protected:
+    virtual void UndoInternal(RadiographyLayer& layer) const
+    {
+      layer.SetCrop(sourceCropX_, sourceCropY_, sourceCropWidth_, sourceCropHeight_);
+    }
+
+    virtual void RedoInternal(RadiographyLayer& layer) const
+    {
+      layer.SetCrop(targetCropX_, targetCropY_, targetCropWidth_, targetCropHeight_);
+    }
+
+  public:
+    UndoRedoCommand(const RadiographyLayerCropTracker& tracker) :
+      RadiographySceneCommand(tracker.accessor_),
+      sourceCropX_(tracker.cropX_),
+      sourceCropY_(tracker.cropY_),
+      sourceCropWidth_(tracker.cropWidth_),
+      sourceCropHeight_(tracker.cropHeight_)
+    {
+      tracker.accessor_.GetLayer().GetCrop(targetCropX_, targetCropY_,
+                                           targetCropWidth_, targetCropHeight_);
+    }
+  };
+
+
+  RadiographyLayerCropTracker::RadiographyLayerCropTracker(UndoRedoStack& undoRedoStack,
+                                                           RadiographyScene& scene,
+                                                           const ViewportGeometry& view,
+                                                           size_t layer,
+                                                           double x,
+                                                           double y,
+                                                           Corner corner) :
+    undoRedoStack_(undoRedoStack),
+    accessor_(scene, layer),
+    corner_(corner)
+  {
+    if (accessor_.IsValid())
+    {
+      accessor_.GetLayer().GetCrop(cropX_, cropY_, cropWidth_, cropHeight_);          
+    }
+  }
+
+
+  void RadiographyLayerCropTracker::Render(CairoContext& context,
+                                           double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void RadiographyLayerCropTracker::MouseUp()
+  {
+    if (accessor_.IsValid())
+    {
+      undoRedoStack_.Add(new UndoRedoCommand(*this));
+    }
+  }
+
+  
+  void RadiographyLayerCropTracker::MouseMove(int displayX,
+                                              int displayY,
+                                              double sceneX,
+                                              double sceneY)
+  {
+    if (accessor_.IsValid())
+    {
+      unsigned int x, y;
+        
+      RadiographyLayer& layer = accessor_.GetLayer();
+      if (layer.GetPixel(x, y, sceneX, sceneY))
+      {
+        unsigned int targetX, targetWidth;
+
+        if (corner_ == Corner_TopLeft ||
+            corner_ == Corner_BottomLeft)
+        {
+          targetX = std::min(x, cropX_ + cropWidth_);
+          targetWidth = cropX_ + cropWidth_ - targetX;
+        }
+        else
+        {
+          targetX = cropX_;
+          targetWidth = std::max(x, cropX_) - cropX_;
+        }
+
+        unsigned int targetY, targetHeight;
+
+        if (corner_ == Corner_TopLeft ||
+            corner_ == Corner_TopRight)
+        {
+          targetY = std::min(y, cropY_ + cropHeight_);
+          targetHeight = cropY_ + cropHeight_ - targetY;
+        }
+        else
+        {
+          targetY = cropY_;
+          targetHeight = std::max(y, cropY_) - cropY_;
+        }
+
+        layer.SetCrop(targetX, targetY, targetWidth, targetHeight);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyLayerCropTracker.h	Tue Nov 13 10:36:53 2018 +0100
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Toolbox/UndoRedoStack.h"
+#include "../Toolbox/ViewportGeometry.h"
+#include "../Widgets/IWorldSceneMouseTracker.h"
+#include "RadiographyScene.h"
+
+namespace OrthancStone
+{
+  class RadiographyLayerCropTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    class UndoRedoCommand;
+
+    UndoRedoStack&                   undoRedoStack_;
+    RadiographyScene::LayerAccessor  accessor_;
+    Corner                           corner_;
+    unsigned int                     cropX_;
+    unsigned int                     cropY_;
+    unsigned int                     cropWidth_;
+    unsigned int                     cropHeight_;
+
+  public:
+    RadiographyLayerCropTracker(UndoRedoStack& undoRedoStack,
+                                RadiographyScene& scene,
+                                const ViewportGeometry& view,
+                                size_t layer,
+                                double x,
+                                double y,
+                                Corner corner);
+
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double sceneX,
+                           double sceneY);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Tue Nov 13 10:36:53 2018 +0100
@@ -0,0 +1,187 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RadiographyLayerResizeTracker.h"
+
+#include "RadiographySceneCommand.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/math/special_functions/round.hpp>
+
+
+namespace OrthancStone
+{
+  static double ComputeDistance(double x1,
+                                double y1,
+                                double x2,
+                                double y2)
+  {
+    double dx = x1 - x2;
+    double dy = y1 - y2;
+    return sqrt(dx * dx + dy * dy);
+  }
+      
+
+  class RadiographyLayerResizeTracker::UndoRedoCommand : public RadiographySceneCommand
+  {
+  private:
+    double   sourceSpacingX_;
+    double   sourceSpacingY_;
+    double   sourcePanX_;
+    double   sourcePanY_;
+    double   targetSpacingX_;
+    double   targetSpacingY_;
+    double   targetPanX_;
+    double   targetPanY_;
+
+  protected:
+    virtual void UndoInternal(RadiographyLayer& layer) const
+    {
+      layer.SetPixelSpacing(sourceSpacingX_, sourceSpacingY_);
+      layer.SetPan(sourcePanX_, sourcePanY_);
+    }
+
+    virtual void RedoInternal(RadiographyLayer& layer) const
+    {
+      layer.SetPixelSpacing(targetSpacingX_, targetSpacingY_);
+      layer.SetPan(targetPanX_, targetPanY_);
+    }
+
+  public:
+    UndoRedoCommand(const RadiographyLayerResizeTracker& tracker) :
+      RadiographySceneCommand(tracker.accessor_),
+      sourceSpacingX_(tracker.originalSpacingX_),
+      sourceSpacingY_(tracker.originalSpacingY_),
+      sourcePanX_(tracker.originalPanX_),
+      sourcePanY_(tracker.originalPanY_),
+      targetSpacingX_(tracker.accessor_.GetLayer().GetPixelSpacingX()),
+      targetSpacingY_(tracker.accessor_.GetLayer().GetPixelSpacingY()),
+      targetPanX_(tracker.accessor_.GetLayer().GetPanX()),
+      targetPanY_(tracker.accessor_.GetLayer().GetPanY())
+    {
+    }
+  };
+
+
+  RadiographyLayerResizeTracker::RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack,
+                                                               RadiographyScene& scene,
+                                                               size_t layer,
+                                                               double x,
+                                                               double y,
+                                                               Corner corner,
+                                                               bool roundScaling) :
+    undoRedoStack_(undoRedoStack),
+    accessor_(scene, layer),
+    roundScaling_(roundScaling)
+  {
+    if (accessor_.IsValid() &&
+        accessor_.GetLayer().IsResizeable())
+    {
+      originalSpacingX_ = accessor_.GetLayer().GetPixelSpacingX();
+      originalSpacingY_ = accessor_.GetLayer().GetPixelSpacingY();
+      originalPanX_ = accessor_.GetLayer().GetPanX();
+      originalPanY_ = accessor_.GetLayer().GetPanY();
+
+      switch (corner)
+      {
+        case Corner_TopLeft:
+          oppositeCorner_ = Corner_BottomRight;
+          break;
+
+        case Corner_TopRight:
+          oppositeCorner_ = Corner_BottomLeft;
+          break;
+
+        case Corner_BottomLeft:
+          oppositeCorner_ = Corner_TopRight;
+          break;
+
+        case Corner_BottomRight:
+          oppositeCorner_ = Corner_TopLeft;
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      accessor_.GetLayer().GetCorner(oppositeX_, oppositeY_, oppositeCorner_);
+
+      double d = ComputeDistance(x, y, oppositeX_, oppositeY_);
+      if (d >= std::numeric_limits<float>::epsilon())
+      {
+        baseScaling_ = 1.0 / d;
+      }
+      else
+      {
+        // Avoid division by zero in extreme cases
+        accessor_.Invalidate();
+      }
+    }
+  }
+
+
+  void RadiographyLayerResizeTracker::Render(CairoContext& context,
+                                             double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void RadiographyLayerResizeTracker::MouseUp()
+  {
+    if (accessor_.IsValid() &&
+        accessor_.GetLayer().IsResizeable())
+    {
+      undoRedoStack_.Add(new UndoRedoCommand(*this));
+    }
+  }
+
+  
+  void RadiographyLayerResizeTracker::MouseMove(int displayX,
+                                                int displayY,
+                                                double sceneX,
+                                                double sceneY)
+  {
+    static const double ROUND_SCALING = 0.1;
+        
+    if (accessor_.IsValid() &&
+        accessor_.GetLayer().IsResizeable())
+    {
+      double scaling = ComputeDistance(oppositeX_, oppositeY_, sceneX, sceneY) * baseScaling_;
+
+      if (roundScaling_)
+      {
+        scaling = boost::math::round<double>((scaling / ROUND_SCALING) * ROUND_SCALING);
+      }
+          
+      RadiographyLayer& layer = accessor_.GetLayer();
+      layer.SetPixelSpacing(scaling * originalSpacingX_,
+                            scaling * originalSpacingY_);
+
+      // Keep the opposite corner at a fixed location
+      double ox, oy;
+      layer.GetCorner(ox, oy, oppositeCorner_);
+      layer.SetPan(layer.GetPanX() + oppositeX_ - ox,
+                   layer.GetPanY() + oppositeY_ - oy);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyLayerResizeTracker.h	Tue Nov 13 10:36:53 2018 +0100
@@ -0,0 +1,71 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Toolbox/UndoRedoStack.h"
+#include "../Widgets/IWorldSceneMouseTracker.h"
+#include "RadiographyScene.h"
+
+namespace OrthancStone
+{
+  class RadiographyLayerResizeTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    class UndoRedoCommand;
+
+    UndoRedoStack&                   undoRedoStack_;
+    RadiographyScene::LayerAccessor  accessor_;
+    bool                             roundScaling_;
+    double                           originalSpacingX_;
+    double                           originalSpacingY_;
+    double                           originalPanX_;
+    double                           originalPanY_;
+    Corner                           oppositeCorner_;
+    double                           oppositeX_;
+    double                           oppositeY_;
+    double                           baseScaling_;
+
+  public:
+    RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack,
+                                  RadiographyScene& scene,
+                                  size_t layer,
+                                  double x,
+                                  double y,
+                                  Corner corner,
+                                  bool roundScaling);
+
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double sceneX,
+                           double sceneY);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyWindowingTracker.cpp	Tue Nov 13 10:36:53 2018 +0100
@@ -0,0 +1,195 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RadiographyWindowingTracker.h"
+
+#include <Core/OrthancException.h>
+
+
+namespace OrthancStone
+{
+  class RadiographyWindowingTracker::UndoRedoCommand : public UndoRedoStack::ICommand
+  {
+  private:
+    RadiographyScene&  scene_;
+    float              sourceCenter_;
+    float              sourceWidth_;
+    float              targetCenter_;
+    float              targetWidth_;
+
+  public:
+    UndoRedoCommand(const RadiographyWindowingTracker& tracker) :
+      scene_(tracker.scene_),
+      sourceCenter_(tracker.sourceCenter_),
+      sourceWidth_(tracker.sourceWidth_)
+    {
+      scene_.GetWindowingWithDefault(targetCenter_, targetWidth_);
+    }
+
+    virtual void Undo() const
+    {
+      scene_.SetWindowing(sourceCenter_, sourceWidth_);
+    }
+      
+    virtual void Redo() const
+    {
+      scene_.SetWindowing(targetCenter_, targetWidth_);
+    }
+  };
+
+
+  void RadiographyWindowingTracker::ComputeAxisEffect(int& deltaCenter,
+                                                      int& deltaWidth,
+                                                      int delta,
+                                                      Action actionNegative,
+                                                      Action actionPositive)
+  {
+    if (delta < 0)
+    {
+      switch (actionNegative)
+      {
+        case Action_IncreaseWidth:
+          deltaWidth = -delta;
+          break;
+
+        case Action_DecreaseWidth:
+          deltaWidth = delta;
+          break;
+
+        case Action_IncreaseCenter:
+          deltaCenter = -delta;
+          break;
+
+        case Action_DecreaseCenter:
+          deltaCenter = delta;
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+    else if (delta > 0)
+    {
+      switch (actionPositive)
+      {
+        case Action_IncreaseWidth:
+          deltaWidth = delta;
+          break;
+
+        case Action_DecreaseWidth:
+          deltaWidth = -delta;
+          break;
+
+        case Action_IncreaseCenter:
+          deltaCenter = delta;
+          break;
+
+        case Action_DecreaseCenter:
+          deltaCenter = -delta;
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+  }
+    
+    
+  RadiographyWindowingTracker::RadiographyWindowingTracker(UndoRedoStack& undoRedoStack,
+                                                           RadiographyScene& scene,
+                                                           int x,
+                                                           int y,
+                                                           Action leftAction,
+                                                           Action rightAction,
+                                                           Action upAction,
+                                                           Action downAction) :
+    undoRedoStack_(undoRedoStack),
+    scene_(scene),
+    clickX_(x),
+    clickY_(y),
+    leftAction_(leftAction),
+    rightAction_(rightAction),
+    upAction_(upAction),
+    downAction_(downAction)
+  {
+    scene_.GetWindowingWithDefault(sourceCenter_, sourceWidth_);
+
+    float minValue, maxValue;
+    scene.GetRange(minValue, maxValue);
+
+    assert(minValue <= maxValue);
+
+    float tmp;
+      
+    float delta = (maxValue - minValue);
+    if (delta <= 1)
+    {
+      tmp = 0;
+    }
+    else
+    {
+      // NB: Visual Studio 2008 does not provide "log2f()", so we
+      // implement it by ourselves
+      tmp = logf(delta) / logf(2.0f);
+    }
+
+    strength_ = tmp - 7;
+    if (strength_ < 1)
+    {
+      strength_ = 1;
+    }
+  }
+
+
+  void RadiographyWindowingTracker::Render(CairoContext& context,
+                                           double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+  
+
+  void RadiographyWindowingTracker::MouseUp()
+  {
+    undoRedoStack_.Add(new UndoRedoCommand(*this));
+  }
+
+
+  void RadiographyWindowingTracker::MouseMove(int displayX,
+                                              int displayY,
+                                              double sceneX,
+                                              double sceneY)
+  {
+    // This follows the behavior of the Osimis Web viewer:
+    // https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/src/app/viewport/image-plugins/windowing-viewport-tool.class.js
+
+    static const float SCALE = 1.0;
+      
+    int deltaCenter = 0;
+    int deltaWidth = 0;
+
+    ComputeAxisEffect(deltaCenter, deltaWidth, displayX - clickX_, leftAction_, rightAction_);
+    ComputeAxisEffect(deltaCenter, deltaWidth, displayY - clickY_, upAction_, downAction_);
+
+    float newCenter = sourceCenter_ + (deltaCenter / SCALE * strength_);
+    float newWidth = sourceWidth_ + (deltaWidth / SCALE * strength_);
+    scene_.SetWindowing(newCenter, newWidth);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyWindowingTracker.h	Tue Nov 13 10:36:53 2018 +0100
@@ -0,0 +1,87 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Toolbox/UndoRedoStack.h"
+#include "../Widgets/IWorldSceneMouseTracker.h"
+#include "RadiographyScene.h"
+
+namespace OrthancStone
+{
+  class RadiographyWindowingTracker : public IWorldSceneMouseTracker
+  {   
+  public:
+    enum Action
+    {
+      Action_IncreaseWidth,
+      Action_DecreaseWidth,
+      Action_IncreaseCenter,
+      Action_DecreaseCenter
+    };
+    
+  private:
+    class UndoRedoCommand;
+
+    UndoRedoStack&      undoRedoStack_;
+    RadiographyScene&   scene_;
+    int                 clickX_;
+    int                 clickY_;
+    Action              leftAction_;
+    Action              rightAction_;
+    Action              upAction_;
+    Action              downAction_;
+    float               strength_;
+    float               sourceCenter_;
+    float               sourceWidth_;
+
+    static void ComputeAxisEffect(int& deltaCenter,
+                                  int& deltaWidth,
+                                  int delta,
+                                  Action actionNegative,
+                                  Action actionPositive);    
+    
+  public:
+    RadiographyWindowingTracker(UndoRedoStack& undoRedoStack,
+                                RadiographyScene& scene,
+                                int x,
+                                int y,
+                                Action leftAction,
+                                Action rightAction,
+                                Action upAction,
+                                Action downAction);
+    
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double sceneX,
+                           double sceneY);
+  };
+}
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Mon Nov 12 18:00:48 2018 +0100
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Nov 13 10:36:53 2018 +0100
@@ -246,12 +246,15 @@
   ${ORTHANC_STONE_ROOT}/Framework/Layers/LineMeasureTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/RenderStyle.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/SliceOutlineRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerCropTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerMoveTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerResizeTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerRotateTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyScene.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographySceneCommand.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyWidget.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyWindowingTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/SmartLoader.cpp
   ${ORTHANC_STONE_ROOT}/Framework/StoneEnumerations.cpp
   ${ORTHANC_STONE_ROOT}/Framework/StoneException.h