changeset 1989:e8b9a2ba1df1

Added left/right rotation buttons
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 31 Oct 2022 20:59:59 +0100
parents 3c84c34322d7
children f03a827f8b47
files Applications/StoneWebViewer/NEWS Applications/StoneWebViewer/WebApplication/app.js Applications/StoneWebViewer/WebApplication/index.html Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Sources/Scene2D/Scene2D.cpp OrthancStone/Sources/Scene2D/Scene2D.h OrthancStone/Sources/Toolbox/AffineTransform2D.cpp OrthancStone/Sources/Toolbox/AffineTransform2D.h
diffstat 8 files changed, 189 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/StoneWebViewer/NEWS	Mon Oct 31 17:45:53 2022 +0100
+++ b/Applications/StoneWebViewer/NEWS	Mon Oct 31 20:59:59 2022 +0100
@@ -5,6 +5,7 @@
   - Pixel probe
   - Rectangle probe
   - Ellipse probe
+* Added left/right rotation buttons
 * Added vertical slider showing position of the current frame inside the series
 * Display of orientation markers
 * The text field with the instance number is editable to go to a specific instance
--- a/Applications/StoneWebViewer/WebApplication/app.js	Mon Oct 31 17:45:53 2022 +0100
+++ b/Applications/StoneWebViewer/WebApplication/app.js	Mon Oct 31 20:59:59 2022 +0100
@@ -899,6 +899,20 @@
       }
     },
 
+    RotateLeft: function() {
+      var canvas = this.GetActiveCanvas();
+      if (canvas != '') {
+        stone.RotateLeft(canvas);
+      }
+    },
+
+    RotateRight: function() {
+      var canvas = this.GetActiveCanvas();
+      if (canvas != '') {
+        stone.RotateRight(canvas);
+      }
+    },
+
     ApplyPreferences: function() {
       this.modalPreferences = false;
 
--- a/Applications/StoneWebViewer/WebApplication/index.html	Mon Oct 31 17:45:53 2022 +0100
+++ b/Applications/StoneWebViewer/WebApplication/index.html	Mon Oct 31 20:59:59 2022 +0100
@@ -485,6 +485,22 @@
 
             <div class="ng-scope inline-object">
               <button class="wvButton--underline text-center"
+                      data-toggle="tooltip" data-title="Rotate to the left"
+                      v-on:click="RotateLeft()">
+                <i class="fas fa-undo"></i>
+              </button>
+            </div>
+
+            <div class="ng-scope inline-object">
+              <button class="wvButton--underline text-center"
+                      data-toggle="tooltip" data-title="Rotate to the right"
+                      v-on:click="RotateRight()">
+                <i class="fas fa-undo fa-flip-horizontal"></i>
+              </button>
+            </div>
+            
+            <div class="ng-scope inline-object">
+              <button class="wvButton--underline text-center"
                       data-toggle="tooltip" data-title="Flip horizontally"
                       v-on:click="FlipX()">
                 <i class="fas fa-exchange-alt"></i>
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Mon Oct 31 17:45:53 2022 +0100
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Mon Oct 31 20:59:59 2022 +0100
@@ -96,10 +96,13 @@
 #include "../../../OrthancStone/Sources/Platforms/WebAssembly/WebGLViewport.h"
 
 
-#include <boost/math/special_functions/round.hpp>
+#include <algorithm>
 #include <boost/make_shared.hpp>
+#include <boost/math/constants/constants.hpp>
+#include <boost/math/special_functions/round.hpp>
 #include <stdio.h>
-#include <algorithm>
+
+static const double PI = boost::math::constants::pi<double>();
 
 #if !defined(STONE_WEB_VIEWER_EXPORT)
 // We are not running ParseWebAssemblyExports.py, but we're compiling the wasm
@@ -1993,8 +1996,6 @@
   std::vector<float>                           windowingPresetWidths_;
   unsigned int                                 cineRate_;
   bool                                         inverted_;
-  bool                                         flipX_;
-  bool                                         flipY_;
   bool                                         fitNextContent_;
   std::list<PrefetchItem>                      prefetchQueue_;
   bool                                         serverSideTranscoding_;
@@ -2155,8 +2156,6 @@
     assert(layer.get() != NULL);
 
     layer->SetLinearInterpolation(true);
-    layer->SetFlipX(flipX_);
-    layer->SetFlipY(flipY_);
 
     double pixelSpacingX, pixelSpacingY;
 
@@ -2210,8 +2209,6 @@
       if (accessor.IsValid())
       {
         overlay.reset(accessor.CreateTexture());
-        overlay->SetFlipX(flipX_);
-        overlay->SetFlipY(flipY_);
       }
     }
 
@@ -2496,25 +2493,6 @@
           lock->GetController().GetScene().GetLayer(LAYER_TEXTURE)).
           SetCustomWindowing(windowingCenter_, windowingWidth_);
       }
-
-      {
-        OrthancStone::TextureBaseSceneLayer& layer = 
-          dynamic_cast<OrthancStone::TextureBaseSceneLayer&>(
-            lock->GetController().GetScene().GetLayer(LAYER_TEXTURE));
-
-        layer.SetFlipX(flipX_);
-        layer.SetFlipY(flipY_);
-      }
-
-      if (lock->GetController().GetScene().HasLayer(LAYER_OVERLAY))
-      {
-        OrthancStone::TextureBaseSceneLayer& layer = 
-          dynamic_cast<OrthancStone::TextureBaseSceneLayer&>(
-            lock->GetController().GetScene().GetLayer(LAYER_OVERLAY));
-
-        layer.SetFlipX(flipX_);
-        layer.SetFlipY(flipY_);
-      }
         
       lock->Invalidate();
     }
@@ -2530,8 +2508,6 @@
     source_(source),
     framesCache_(cache),
     fitNextContent_(true),
-    flipX_(false),
-    flipY_(false),
     hasFocusOnInstance_(false),
     focusFrameNumber_(0),
     synchronizationOffset_(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0)),
@@ -2764,8 +2740,6 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
     }
 
-    flipX_ = false;
-    flipY_ = false;
     fitNextContent_ = true;
     cineRate_ = DEFAULT_CINE_RATE;
     inverted_ = false;
@@ -3097,14 +3071,42 @@
 
   void FlipX()
   {
-    flipX_ = !flipX_;
-    UpdateCurrentTextureParameters();
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+      lock->GetController().GetScene().FlipViewportX(
+        lock->GetCompositor().GetCanvasWidth(), lock->GetCompositor().GetCanvasHeight());
+      lock->Invalidate();
+    }    
   }
 
   void FlipY()
   {
-    flipY_ = !flipY_;
-    UpdateCurrentTextureParameters();
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+      lock->GetController().GetScene().FlipViewportY(
+        lock->GetCompositor().GetCanvasWidth(), lock->GetCompositor().GetCanvasHeight());
+      lock->Invalidate();
+    }
+  }
+
+  void RotateLeft()
+  {
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+      lock->GetController().GetScene().RotateViewport(
+        -PI / 2.0, lock->GetCompositor().GetCanvasWidth(), lock->GetCompositor().GetCanvasHeight());
+      lock->Invalidate();
+    }    
+  }
+
+  void RotateRight()
+  {
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+      lock->GetController().GetScene().RotateViewport(
+        PI / 2.0, lock->GetCompositor().GetCanvasWidth(), lock->GetCompositor().GetCanvasHeight());
+      lock->Invalidate();
+    }
   }
 
   void Invert()
@@ -4212,6 +4214,28 @@
   
 
   EMSCRIPTEN_KEEPALIVE
+  void RotateLeft(const char* canvas)
+  {
+    try
+    {
+      GetViewport(canvas)->RotateLeft();
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }  
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void RotateRight(const char* canvas)
+  {
+    try
+    {
+      GetViewport(canvas)->RotateRight();
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }  
+  
+
+  EMSCRIPTEN_KEEPALIVE
   void SetSoftwareRendering(int softwareRendering)
   {
     softwareRendering_ = softwareRendering;
--- a/OrthancStone/Sources/Scene2D/Scene2D.cpp	Mon Oct 31 17:45:53 2022 +0100
+++ b/OrthancStone/Sources/Scene2D/Scene2D.cpp	Mon Oct 31 20:59:59 2022 +0100
@@ -241,17 +241,34 @@
     }
   }
 
-  void Scene2D::FitContent(unsigned int canvasWidth,
+  
+  static void AddTransformedPoint(Extent2D& extent,
+                                  const AffineTransform2D& forcedTransform,
+                                  double x,
+                                  double y)
+  {
+    forcedTransform.Apply(x, y);
+    extent.AddPoint(x, y);
+  }
+
+  
+  void Scene2D::FitContent(const AffineTransform2D& forcedTransform,
+                           unsigned int canvasWidth,
                            unsigned int canvasHeight)
   {
     Extent2D extent;
-
     GetBoundingBox(extent);
 
     if (!extent.IsEmpty())
     {
-      double zoomX = static_cast<double>(canvasWidth) / extent.GetWidth();
-      double zoomY = static_cast<double>(canvasHeight) / extent.GetHeight();
+      Extent2D extent2;
+      AddTransformedPoint(extent2, forcedTransform, extent.GetX1(), extent.GetY1());
+      AddTransformedPoint(extent2, forcedTransform, extent.GetX1(), extent.GetY2());
+      AddTransformedPoint(extent2, forcedTransform, extent.GetX2(), extent.GetY2());
+      AddTransformedPoint(extent2, forcedTransform, extent.GetX2(), extent.GetY1());
+
+      double zoomX = static_cast<double>(canvasWidth) / extent2.GetWidth();
+      double zoomY = static_cast<double>(canvasHeight) / extent2.GetHeight();
 
       double zoom = std::min(zoomX, zoomY);
       if (LinearAlgebra::IsCloseToZero(zoom))
@@ -259,8 +276,8 @@
         zoom = 1;
       }
 
-      double panX = extent.GetCenterX();
-      double panY = extent.GetCenterY();
+      double panX = extent2.GetCenterX();
+      double panY = extent2.GetCenterY();
 
       // Bring the center of the scene to (0,0)
       AffineTransform2D t1 = AffineTransform2D::CreateOffset(-panX, -panY);
@@ -268,7 +285,45 @@
       // Scale the scene
       AffineTransform2D t2 = AffineTransform2D::CreateScaling(zoom, zoom);
 
-      SetSceneToCanvasTransform(AffineTransform2D::Combine(t2, t1));
+      SetSceneToCanvasTransform(AffineTransform2D::Combine(t2, t1, forcedTransform));
     }
   }
+
+
+  void Scene2D::FitContent(unsigned int canvasWidth,
+                           unsigned int canvasHeight)
+  {
+    FitContent(AffineTransform2D() /* identity transform */, canvasWidth, canvasHeight);
+  }
+
+
+  void Scene2D::RotateViewport(double angle,
+                               unsigned int canvasWidth,
+                               unsigned int canvasHeight)
+  {
+    AffineTransform2D transform = AffineTransform2D::Combine(
+      AffineTransform2D::CreateRotation(angle),
+      GetSceneToCanvasTransform());
+    FitContent(transform, canvasWidth, canvasHeight);
+  }
+
+
+  void Scene2D::FlipViewportX(unsigned int canvasWidth,
+                              unsigned int canvasHeight)
+  {
+    AffineTransform2D transform = AffineTransform2D::Combine(
+      AffineTransform2D::CreateFlipX(),
+      GetSceneToCanvasTransform());
+    FitContent(transform, canvasWidth, canvasHeight);
+  }
+
+
+  void Scene2D::FlipViewportY(unsigned int canvasWidth,
+                              unsigned int canvasHeight)
+  {
+    AffineTransform2D transform = AffineTransform2D::Combine(
+      AffineTransform2D::CreateFlipY(),
+      GetSceneToCanvasTransform());
+    FitContent(transform, canvasWidth, canvasHeight);
+  }
 }
--- a/OrthancStone/Sources/Scene2D/Scene2D.h	Mon Oct 31 17:45:53 2022 +0100
+++ b/OrthancStone/Sources/Scene2D/Scene2D.h	Mon Oct 31 20:59:59 2022 +0100
@@ -60,6 +60,10 @@
 
     Scene2D(const Scene2D& other);
     
+    void FitContent(const AffineTransform2D& forcedTransform,
+                    unsigned int canvasWidth,
+                    unsigned int canvasHeight);
+
   public:
     Scene2D() : layerCounter_(0)
     {
@@ -118,5 +122,15 @@
                     unsigned int canvasHeight);
 
     void GetBoundingBox(Extent2D& target) const;
+
+    void RotateViewport(double angle,
+                        unsigned int canvasWidth,
+                        unsigned int canvasHeight);
+
+    void FlipViewportX(unsigned int canvasWidth,
+                       unsigned int canvasHeight);
+
+    void FlipViewportY(unsigned int canvasWidth,
+                       unsigned int canvasHeight);
   };
 }
--- a/OrthancStone/Sources/Toolbox/AffineTransform2D.cpp	Mon Oct 31 17:45:53 2022 +0100
+++ b/OrthancStone/Sources/Toolbox/AffineTransform2D.cpp	Mon Oct 31 20:59:59 2022 +0100
@@ -293,4 +293,22 @@
       return t;
     }
   }
+
+  
+  AffineTransform2D AffineTransform2D::CreateFlipX()
+  {
+    AffineTransform2D t;
+    t.matrix_(0, 0) = -1;
+    t.matrix_(1, 1) = 1;
+    return t;
+  }
+
+  
+  AffineTransform2D AffineTransform2D::CreateFlipY()
+  {
+    AffineTransform2D t;
+    t.matrix_(0, 0) = 1;
+    t.matrix_(1, 1) = -1;
+    return t;
+  }
 }
--- a/OrthancStone/Sources/Toolbox/AffineTransform2D.h	Mon Oct 31 17:45:53 2022 +0100
+++ b/OrthancStone/Sources/Toolbox/AffineTransform2D.h	Mon Oct 31 20:59:59 2022 +0100
@@ -36,7 +36,7 @@
     Matrix  matrix_;
 
   public:
-    AffineTransform2D();
+    AffineTransform2D();  // Create the identity transform
 
     // The matrix must be 3x3, without perspective effects
     explicit AffineTransform2D(const Matrix& m);
@@ -106,5 +106,9 @@
                                         bool flipY,
                                         unsigned int width,
                                         unsigned int height);
+
+    static AffineTransform2D CreateFlipX();
+
+    static AffineTransform2D CreateFlipY();
   };
 }