# HG changeset patch # User Benjamin Golinvaux # Date 1551213196 -3600 # Node ID 801d2697a1b1e47239d0877ad248d89ef2f47f91 # Parent 4cc7bb55bd495a2d83bc406b304636847cfcff73# Parent 77e0eb83ff637295f1570729f53a1ea5af7519e7 Merge with am-touch-events diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Commands/BaseCommandBuilder.cpp --- a/Applications/Commands/BaseCommandBuilder.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Applications/Commands/BaseCommandBuilder.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -34,13 +34,12 @@ if (commandJson["commandType"].isString() && commandJson["commandType"].asString() == "generic-no-arg-command") { - printf("creating a simple command\n"); - return new GenericNoArgCommand(commandJson["command"].asString().c_str()); + return new GenericNoArgCommand(commandJson["command"].asString().c_str()); } else if (commandJson["commandType"].isString() && commandJson["commandType"].asString() == "generic-one-string-arg-command") { - printf("creating a simple command\n"); - return new GenericNoArgCommand(commandJson["command"].asString().c_str()); + // TODO: we should create a command with a string arg ! + return new GenericNoArgCommand(commandJson["command"].asString().c_str()); } return NULL; diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Commands/ICommand.h --- a/Applications/Commands/ICommand.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Applications/Commands/ICommand.h Tue Feb 26 21:33:16 2019 +0100 @@ -38,6 +38,7 @@ virtual ~ICommand() {} virtual void Execute() = 0; + virtual ~ICommand() {} // virtual void Configure(const Json::Value& arguments) = 0; const std::string& GetName() const { diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Qt/QCairoWidget.cpp --- a/Applications/Qt/QCairoWidget.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Applications/Qt/QCairoWidget.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -125,7 +125,7 @@ { OrthancStone::NativeStoneApplicationContext::GlobalMutexLocker locker(*context_); - locker.GetCentralViewport().MouseDown(button, event->pos().x(), event->pos().y(), stoneModifiers); + locker.GetCentralViewport().MouseDown(button, event->pos().x(), event->pos().y(), stoneModifiers, std::vector()); } } @@ -140,7 +140,7 @@ void QCairoWidget::mouseMoveEvent(QMouseEvent* event) { OrthancStone::NativeStoneApplicationContext::GlobalMutexLocker locker(*context_); - locker.GetCentralViewport().MouseMove(event->pos().x(), event->pos().y()); + locker.GetCentralViewport().MouseMove(event->pos().x(), event->pos().y(), std::vector()); } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Samples/CMakeLists.txt diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp --- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -32,7 +32,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector& displayTouches) { if (button == MouseButton_Left) { diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Samples/SimpleViewer/MainWidgetInteractor.h --- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.h Tue Feb 26 21:33:16 2019 +0100 @@ -50,7 +50,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar); + IStatusBar* statusBar, + const std::vector& displayTouches); virtual void MouseOver(CairoContext& context, WorldSceneWidget& widget, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp --- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -32,7 +32,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector& displayTouches) { if (button == MouseButton_Left) { diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Samples/SimpleViewer/ThumbnailInteractor.h --- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Applications/Samples/SimpleViewer/ThumbnailInteractor.h Tue Feb 26 21:33:16 2019 +0100 @@ -47,7 +47,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar); + IStatusBar* statusBar, + const std::vector& displayTouches); virtual void MouseOver(CairoContext& context, WorldSceneWidget& widget, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Samples/SimpleViewerApplicationSingleFile.h --- a/Applications/Samples/SimpleViewerApplicationSingleFile.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Applications/Samples/SimpleViewerApplicationSingleFile.h Tue Feb 26 21:33:16 2019 +0100 @@ -66,7 +66,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector& displayTouches) { if (button == MouseButton_Left) { @@ -121,7 +122,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector& displayTouches) { if (button == MouseButton_Left) { diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Samples/SingleFrameApplication.h --- a/Applications/Samples/SingleFrameApplication.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Applications/Samples/SingleFrameApplication.h Tue Feb 26 21:33:16 2019 +0100 @@ -60,7 +60,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector& displayTouches) { return NULL; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Samples/SingleFrameEditorApplication.h diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Samples/build-wasm.sh diff -r 4cc7bb55bd49 -r 801d2697a1b1 Applications/Sdl/SdlEngine.cpp --- a/Applications/Sdl/SdlEngine.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Applications/Sdl/SdlEngine.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -146,15 +146,15 @@ switch (event.button.button) { case SDL_BUTTON_LEFT: - locker.GetCentralViewport().MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers); + locker.GetCentralViewport().MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers, std::vector()); break; case SDL_BUTTON_RIGHT: - locker.GetCentralViewport().MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers); + locker.GetCentralViewport().MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers, std::vector()); break; case SDL_BUTTON_MIDDLE: - locker.GetCentralViewport().MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers); + locker.GetCentralViewport().MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers, std::vector()); break; default: @@ -163,7 +163,7 @@ } else if (event.type == SDL_MOUSEMOTION) { - locker.GetCentralViewport().MouseMove(event.button.x, event.button.y); + locker.GetCentralViewport().MouseMove(event.button.x, event.button.y, std::vector()); } else if (event.type == SDL_MOUSEBUTTONUP) { diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Layers/CircleMeasureTracker.cpp --- a/Framework/Layers/CircleMeasureTracker.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Layers/CircleMeasureTracker.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -91,7 +91,9 @@ void CircleMeasureTracker::MouseMove(int displayX, int displayY, double x, - double y) + double y, + const std::vector& displayTouches, + const std::vector& sceneTouches) { x2_ = x; y2_ = y; diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Layers/CircleMeasureTracker.h --- a/Framework/Layers/CircleMeasureTracker.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Layers/CircleMeasureTracker.h Tue Feb 26 21:33:16 2019 +0100 @@ -72,6 +72,8 @@ virtual void MouseMove(int displayX, int displayY, double x, - double y); + double y, + const std::vector& displayTouches, + const std::vector& sceneTouches); }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Layers/LineMeasureTracker.cpp --- a/Framework/Layers/LineMeasureTracker.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Layers/LineMeasureTracker.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -87,7 +87,9 @@ void LineMeasureTracker::MouseMove(int displayX, int displayY, double x, - double y) + double y, + const std::vector& displayTouches, + const std::vector& sceneTouches) { x2_ = x; y2_ = y; diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Layers/LineMeasureTracker.h --- a/Framework/Layers/LineMeasureTracker.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Layers/LineMeasureTracker.h Tue Feb 26 21:33:16 2019 +0100 @@ -71,6 +71,8 @@ virtual void MouseMove(int displayX, int displayY, double x, - double y); + double y, + const std::vector& displayTouches, + const std::vector& sceneTouches); }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyAlphaLayer.cpp --- a/Framework/Radiography/RadiographyAlphaLayer.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyAlphaLayer.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -45,6 +45,8 @@ SetSize(image->GetWidth(), image->GetHeight()); alpha_ = raii; + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyAlphaLayer::Render(Orthanc::ImageAccessor& buffer, @@ -84,7 +86,7 @@ if (useWindowing_) { float center, width; - if (scene_.GetWindowing(center, width)) + if (GetScene().GetWindowing(center, width)) { value = center + width / 2.0f; // set it to the maximum pixel value of the image } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyAlphaLayer.h --- a/Framework/Radiography/RadiographyAlphaLayer.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyAlphaLayer.h Tue Feb 26 21:33:16 2019 +0100 @@ -33,14 +33,13 @@ class RadiographyAlphaLayer : public RadiographyLayer { private: - const RadiographyScene& scene_; std::auto_ptr alpha_; // Grayscale8 bool useWindowing_; float foreground_; public: - RadiographyAlphaLayer(const RadiographyScene& scene) : - scene_(scene), + RadiographyAlphaLayer(MessageBroker& broker, const RadiographyScene& scene) : + RadiographyLayer(broker, scene), useWindowing_(true), foreground_(0) { diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyDicomLayer.cpp --- a/Framework/Radiography/RadiographyDicomLayer.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyDicomLayer.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -102,6 +102,8 @@ source_ = raii; ApplyConverter(); + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyDicomLayer::Render(Orthanc::ImageAccessor& buffer, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyDicomLayer.h --- a/Framework/Radiography/RadiographyDicomLayer.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyDicomLayer.h Tue Feb 26 21:33:16 2019 +0100 @@ -41,6 +41,11 @@ void ApplyConverter(); public: + RadiographyDicomLayer(MessageBroker& broker, const RadiographyScene& scene) + : RadiographyLayer(broker, scene) + { + } + void SetInstance(const std::string& instanceId, unsigned int frame) { instanceId_ = instanceId; diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayer.cpp --- a/Framework/Radiography/RadiographyLayer.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyLayer.cpp Tue Feb 26 21:33:16 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(cropX); - double dy = static_cast(cropY); - double dwidth = static_cast(cropWidth); - double dheight = static_cast(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,53 +96,35 @@ void RadiographyLayer::DrawBorders(CairoContext& context, double zoom) { - unsigned int cx, cy, width, height; - GetCrop(cx, cy, width, height); - - double dx = static_cast(cx); - double dy = static_cast(cy); - double dwidth = static_cast(width); - double dheight = static_cast(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); } - RadiographyLayer::RadiographyLayer() : + RadiographyLayer::RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene) : + IObservable(broker), index_(0), hasSize_(false), width_(0), height_(0), - prefferedPhotometricDisplayMode_(PhotometricDisplayMode_Default) + prefferedPhotometricDisplayMode_(PhotometricDisplayMode_Default), + scene_(scene) { UpdateTransform(); } @@ -197,6 +135,13 @@ UpdateTransform(); } + void RadiographyLayer::SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode prefferedPhotometricDisplayMode) + { + prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode; + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + } + void RadiographyLayer::SetCrop(unsigned int x, unsigned int y, unsigned int width, @@ -215,6 +160,8 @@ geometry_.SetCrop(x, y, width, height); UpdateTransform(); + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyLayer::SetGeometry(const Geometry& geometry) @@ -225,6 +172,8 @@ { UpdateTransform(); } + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -237,7 +186,7 @@ { GetGeometry().GetCrop(x, y, width, height); } - else + else { x = 0; y = 0; @@ -251,6 +200,8 @@ { geometry_.SetAngle(angle); UpdateTransform(); + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -269,6 +220,7 @@ height_ = height; UpdateTransform(); + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -305,7 +257,7 @@ } else { - transformInverse_.Apply(sceneX, sceneY); + GetTransformInverse().Apply(sceneX, sceneY); int x = static_cast(std::floor(sceneX)); int y = static_cast(std::floor(sceneY)); @@ -346,6 +298,7 @@ { geometry_.SetPan(x, y); UpdateTransform(); + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -354,6 +307,7 @@ { geometry_.SetPixelSpacing(x, y); UpdateTransform(); + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -362,48 +316,65 @@ { centerX = static_cast(width_) / 2.0; centerY = static_cast(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::GetControlPoint(ControlPoint& cpScene /* out in scene coordinates */, + 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); + ControlPoint cp; + switch (index) + { + case ControlPoint_TopLeftCorner: + cp = ControlPoint(cropX, cropY, ControlPoint_TopLeftCorner); + break; + + case ControlPoint_TopRightCorner: + cp = ControlPoint(cropX + cropWidth, cropY, ControlPoint_TopRightCorner); + break; + + case ControlPoint_BottomLeftCorner: + cp = ControlPoint(cropX, cropY + cropHeight, ControlPoint_BottomLeftCorner); + break; + + case ControlPoint_BottomRightCorner: + cp = ControlPoint(cropX + cropWidth, cropY + cropHeight, ControlPoint_BottomRightCorner); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + // transforms image coordinates into scene coordinates + GetTransform().Apply(cp.x, cp.y); + cpScene = cp; + } + + bool RadiographyLayer::LookupControlPoint(ControlPoint& cpScene /* 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]; + cpScene = cp; return true; } } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayer.h --- a/Framework/Radiography/RadiographyLayer.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyLayer.h Tue Feb 26 21:33:16 2019 +0100 @@ -24,14 +24,50 @@ #include "../Toolbox/AffineTransform2D.h" #include "../Toolbox/Extent2D.h" #include "../Viewport/CairoContext.h" +#include "../Messages/IMessage.h" +#include "../Messages/IObservable.h" namespace OrthancStone { - class RadiographyLayer : public boost::noncopyable + class RadiographyScene; + + 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::max()) + {} + }; + + class RadiographyLayer : public IObservable { friend class RadiographyScene; public: + class LayerEditedMessage : + public OriginMessage + { + private: + + public: + LayerEditedMessage(const RadiographyLayer& origin) : + OriginMessage(origin) + { + } + }; + + class Geometry { bool hasCrop_; @@ -141,47 +177,41 @@ AffineTransform2D transformInverse_; Geometry geometry_; PhotometricDisplayMode prefferedPhotometricDisplayMode_; - + const RadiographyScene& scene_; protected: - const AffineTransform2D& GetTransform() const + virtual const AffineTransform2D& GetTransform() const { return transform_; } - void SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode prefferedPhotometricDisplayMode) + virtual const AffineTransform2D& GetTransformInverse() const { - prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode; + return transformInverse_; } + void SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode prefferedPhotometricDisplayMode); + 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); public: - RadiographyLayer(); + RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene); virtual ~RadiographyLayer() { @@ -192,6 +222,11 @@ return index_; } + const RadiographyScene& GetScene() const + { + return scene_; + } + const Geometry& GetGeometry() const { return geometry_; @@ -232,19 +267,19 @@ unsigned int GetWidth() const { return width_; - } + } unsigned int GetHeight() const { return height_; - } + } Extent2D GetExtent() const; - bool GetPixel(unsigned int& imageX, - unsigned int& imageY, - double sceneX, - double sceneY) const; + virtual bool GetPixel(unsigned int& imageX, + unsigned int& imageY, + double sceneX, + double sceneY) const; void SetPixelSpacing(double x, double y); @@ -252,15 +287,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; + virtual void GetControlPoint(ControlPoint& cpScene /* out in scene coordinates */, + size_t index) const; + + virtual size_t GetControlPointCount() const; + + bool LookupControlPoint(ControlPoint& cpScene /* out */, + double x, + double y, + double zoom, + double viewportDistance) const; virtual bool GetDefaultWindowing(float& center, float& width) const = 0; @@ -276,5 +312,7 @@ virtual bool GetRange(float& minValue, float& maxValue) const = 0; - }; + + friend class RadiographyMaskLayer; // because it needs to GetTransform on the dicomLayer it relates to + }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayerCropTracker.cpp --- a/Framework/Radiography/RadiographyLayerCropTracker.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyLayerCropTracker.cpp Tue Feb 26 21:33:16 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()) { @@ -101,7 +99,9 @@ void RadiographyLayerCropTracker::MouseMove(int displayX, int displayY, double sceneX, - double sceneY) + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches) { if (accessor_.IsValid()) { @@ -112,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; @@ -126,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; diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayerCropTracker.h --- a/Framework/Radiography/RadiographyLayerCropTracker.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyLayerCropTracker.h Tue Feb 26 21:33:16 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 { @@ -63,6 +61,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY); + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches); }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayerMaskTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Radiography/RadiographyLayerMaskTracker.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -0,0 +1,140 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "RadiographyLayerMaskTracker.h" +#include "RadiographyMaskLayer.h" + +#include "RadiographySceneCommand.h" + +#include + +namespace OrthancStone +{ + class RadiographyLayerMaskTracker::UndoRedoCommand : public RadiographySceneCommand + { + private: + ControlPoint sourceSceneCp_; + ControlPoint targetSceneCp_; + + protected: + virtual void UndoInternal(RadiographyLayer& layer) const + { + RadiographyMaskLayer* maskLayer = dynamic_cast(&layer); + if (maskLayer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + unsigned int ix, iy; // image coordinates + if (maskLayer->GetPixel(ix, iy, sourceSceneCp_.x, sourceSceneCp_.y)) + { + maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), sourceSceneCp_.index); + } + } + + virtual void RedoInternal(RadiographyLayer& layer) const + { + RadiographyMaskLayer* maskLayer = dynamic_cast(&layer); + if (maskLayer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + unsigned int ix, iy; // image coordinates + if (maskLayer->GetPixel(ix, iy, targetSceneCp_.x, targetSceneCp_.y)) + { + maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), targetSceneCp_.index); + } + } + + public: + UndoRedoCommand(const RadiographyLayerMaskTracker& tracker) : + RadiographySceneCommand(tracker.accessor_), + sourceSceneCp_(tracker.startSceneCp_), + targetSceneCp_(tracker.endSceneCp_) + { + RadiographyMaskLayer* maskLayer = dynamic_cast(&(tracker.accessor_.GetLayer())); + if (maskLayer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + unsigned int ix, iy; // image coordinates + if (maskLayer->GetPixel(ix, iy, targetSceneCp_.x, targetSceneCp_.y)) + { + maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), targetSceneCp_.index); + } + } + }; + + + RadiographyLayerMaskTracker::RadiographyLayerMaskTracker(UndoRedoStack& undoRedoStack, + RadiographyScene& scene, + const ViewportGeometry& view, + size_t layer, + const ControlPoint& startSceneControlPoint) : + undoRedoStack_(undoRedoStack), + accessor_(scene, layer), + startSceneCp_(startSceneControlPoint), + endSceneCp_(startSceneControlPoint) + { + } + + + void RadiographyLayerMaskTracker::Render(CairoContext& context, + double zoom) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + + void RadiographyLayerMaskTracker::MouseUp() + { + if (accessor_.IsValid() && startSceneCp_.x != endSceneCp_.x && startSceneCp_.y != endSceneCp_.y) + { + undoRedoStack_.Add(new UndoRedoCommand(*this)); + } + } + + + void RadiographyLayerMaskTracker::MouseMove(int displayX, + int displayY, + double sceneX, + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches) + { + if (accessor_.IsValid()) + { + unsigned int ix, iy; // image coordinates + + RadiographyLayer& layer = accessor_.GetLayer(); + if (layer.GetPixel(ix, iy, sceneX, sceneY)) + { + endSceneCp_ = ControlPoint(sceneX, sceneY, startSceneCp_.index); + + RadiographyMaskLayer* maskLayer = dynamic_cast(&layer); + if (maskLayer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), startSceneCp_.index); + } + } + } +} diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayerMaskTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Radiography/RadiographyLayerMaskTracker.h Tue Feb 26 21:33:16 2019 +0100 @@ -0,0 +1,65 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Toolbox/UndoRedoStack.h" +#include "../Toolbox/ViewportGeometry.h" +#include "../Widgets/IWorldSceneMouseTracker.h" +#include "RadiographyScene.h" + +namespace OrthancStone +{ + class RadiographyLayerMaskTracker : public IWorldSceneMouseTracker + { + private: + class UndoRedoCommand; + + UndoRedoStack& undoRedoStack_; + RadiographyScene::LayerAccessor accessor_; + ControlPoint startSceneCp_; + ControlPoint endSceneCp_; + + public: + RadiographyLayerMaskTracker(UndoRedoStack& undoRedoStack, + RadiographyScene& scene, + const ViewportGeometry& view, + size_t layer, + const ControlPoint& startSceneControlPoint); + + 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, + const std::vector& displayTouches, + const std::vector& sceneTouches); + }; +} diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayerMoveTracker.cpp --- a/Framework/Radiography/RadiographyLayerMoveTracker.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyLayerMoveTracker.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -97,7 +97,9 @@ void RadiographyLayerMoveTracker::MouseMove(int displayX, int displayY, double sceneX, - double sceneY) + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches) { if (accessor_.IsValid()) { diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayerMoveTracker.h --- a/Framework/Radiography/RadiographyLayerMoveTracker.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyLayerMoveTracker.h Tue Feb 26 21:33:16 2019 +0100 @@ -61,6 +61,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY); + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches); }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayerResizeTracker.cpp --- a/Framework/Radiography/RadiographyLayerResizeTracker.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyLayerResizeTracker.cpp Tue Feb 26 21:33:16 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::epsilon()) { baseScaling_ = 1.0 / d; @@ -159,14 +158,16 @@ void RadiographyLayerResizeTracker::MouseMove(int displayX, int displayY, double sceneX, - double sceneY) + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches) { static const double ROUND_SCALING = 0.1; 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_) { @@ -178,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); } } } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayerResizeTracker.h --- a/Framework/Radiography/RadiographyLayerResizeTracker.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyLayerResizeTracker.h Tue Feb 26 21:33:16 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 @@ -66,6 +62,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY); + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches); }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayerRotateTracker.cpp --- a/Framework/Radiography/RadiographyLayerRotateTracker.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyLayerRotateTracker.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -132,7 +132,9 @@ void RadiographyLayerRotateTracker::MouseMove(int displayX, int displayY, double sceneX, - double sceneY) + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches) { static const double ROUND_ANGLE = 15.0 / 180.0 * boost::math::constants::pi(); diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyLayerRotateTracker.h --- a/Framework/Radiography/RadiographyLayerRotateTracker.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyLayerRotateTracker.h Tue Feb 26 21:33:16 2019 +0100 @@ -68,6 +68,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY); + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches); }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyMaskLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Radiography/RadiographyMaskLayer.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -0,0 +1,143 @@ +/** + * 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 . + **/ + + +#include "RadiographyMaskLayer.h" +#include "RadiographyDicomLayer.h" + +#include "RadiographyScene.h" +#include "Core/Images/Image.h" +#include "Core/Images/ImageProcessing.h" +#include + +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(); + } + + bool RadiographyMaskLayer::GetPixel(unsigned int& imageX, + unsigned int& imageY, + double sceneX, + double sceneY) const + { + return dicomLayer_.GetPixel(imageX, imageY, sceneX, sceneY); + } + + std::string RadiographyMaskLayer::GetInstanceId() const + { + return dicomLayer_.GetInstanceId(); + } + + void RadiographyMaskLayer::SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index) + { + if (index < corners_.size()) + corners_[index] = corner; + else + corners_.push_back(corner); + invalidated_ = true; + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + } + + void RadiographyMaskLayer::SetCorners(const std::vector& corners) + { + corners_ = corners; + invalidated_ = true; + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + } + + void RadiographyMaskLayer::Render(Orthanc::ImageAccessor& buffer, + const AffineTransform2D& viewTransform, + ImageInterpolation interpolation) const + { + if (dicomLayer_.GetWidth() == 0) // nothing to do if the DICOM layer is not displayed (or not loaded) + return; + + if (invalidated_) + { + mask_.reset(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, dicomLayer_.GetWidth(), dicomLayer_.GetHeight(), false)); + + DrawMask(); + + invalidated_ = false; + } + + {// rendering + if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + unsigned int cropX, cropY, cropWidth, cropHeight; + dicomLayer_.GetCrop(cropX, cropY, cropWidth, cropHeight); + + const AffineTransform2D t = AffineTransform2D::Combine( + viewTransform, dicomLayer_.GetTransform(), + AffineTransform2D::CreateOffset(cropX, cropY)); + + Orthanc::ImageAccessor cropped; + mask_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); + + Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false); + + t.Apply(tmp, cropped, interpolation, true /* clear */); + + // Blit + const unsigned int width = buffer.GetWidth(); + const unsigned int height = buffer.GetHeight(); + + for (unsigned int y = 0; y < height; y++) + { + float *q = reinterpret_cast(buffer.GetRow(y)); + const uint8_t *p = reinterpret_cast(tmp.GetRow(y)); + + for (unsigned int x = 0; x < width; x++, p++, q++) + { + if (*p == OUT_MASK_VALUE) + *q = foreground_; + // else keep the underlying pixel value + } + } + + } + } + + void RadiographyMaskLayer::DrawMask() const + { + // first fill the complete image + Orthanc::ImageProcessing::Set(*mask_, OUT_MASK_VALUE); + + // fill mask + Orthanc::ImageProcessing::FillPolygon(*mask_, corners_, IN_MASK_VALUE); + + } + +} diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyMaskLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Radiography/RadiographyMaskLayer.h Tue Feb 26 21:33:16 2019 +0100 @@ -0,0 +1,127 @@ +/** + * 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 . + **/ + + +#pragma once + +#include "RadiographyLayer.h" +#include "Core/Images/Image.h" +#include "Core/Images/ImageProcessing.h" + +namespace OrthancStone +{ + class RadiographyScene; + class RadiographyDicomLayer; + + class RadiographyMaskLayer : public RadiographyLayer + { + private: + std::vector corners_; + const RadiographyDicomLayer& dicomLayer_; + mutable bool invalidated_; + float foreground_; + + mutable std::auto_ptr mask_; + public: + RadiographyMaskLayer(MessageBroker& broker, const RadiographyScene& scene, const RadiographyDicomLayer& dicomLayer, + float foreground) : + RadiographyLayer(broker, scene), + dicomLayer_(dicomLayer), + invalidated_(true), + foreground_(foreground) + { + } + + void SetCorners(const std::vector& corners); + void SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index); + + const std::vector& GetCorners() const + { + return corners_; + } + + float GetForeground() const + { + return foreground_; + } + + virtual void Render(Orthanc::ImageAccessor& buffer, + const AffineTransform2D& viewTransform, + ImageInterpolation interpolation) const; + + std::string GetInstanceId() const; + + virtual size_t GetControlPointCount() const + { + return corners_.size(); + } + + virtual void GetControlPoint(ControlPoint& cpScene, + size_t index) const + { + ControlPoint cp(corners_[index].GetX(), corners_[index].GetY(), index); + + // transforms image coordinates into scene coordinates + GetTransform().Apply(cp.x, cp.y); + cpScene = cp; + } + + virtual bool GetDefaultWindowing(float& center, + float& width) const + { + return false; + } + + virtual bool GetRange(float& minValue, + float& maxValue) const + { + minValue = 0; + maxValue = 0; + + if (foreground_ < 0) + { + minValue = foreground_; + } + + if (foreground_ > 0) + { + maxValue = foreground_; + } + + return true; + + } + + virtual bool GetPixel(unsigned int& imageX, + unsigned int& imageY, + double sceneX, + double sceneY) const; + + protected: + virtual const AffineTransform2D& GetTransform() const; + + virtual const AffineTransform2D& GetTransformInverse() const; + + + private: + void DrawMask() const; + + }; +} diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyScene.cpp --- a/Framework/Radiography/RadiographyScene.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyScene.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -24,6 +24,7 @@ #include "RadiographyAlphaLayer.h" #include "RadiographyDicomLayer.h" #include "RadiographyTextLayer.h" +#include "RadiographyMaskLayer.h" #include "../Toolbox/DicomFrameConverter.h" #include @@ -141,10 +142,15 @@ EmitMessage(GeometryChangedMessage(*this, *layer)); EmitMessage(ContentChangedMessage(*this, *layer)); + layer->RegisterObserverCallback(new Callable(*this, &RadiographyScene::OnLayerEdited)); return *layer; } + void RadiographyScene::OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message) + { + EmitMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin())); + } RadiographyScene::RadiographyScene(MessageBroker& broker) : IObserver(broker), @@ -246,6 +252,8 @@ hasWindowing_ = true; windowingCenter_ = center; windowingWidth_ = width; + + EmitMessage(RadiographyScene::WindowingChangedMessage(*this)); } @@ -253,7 +261,7 @@ const std::string& utf8, RadiographyLayer::Geometry* geometry) { - std::auto_ptr alpha(new RadiographyTextLayer(*this)); + std::auto_ptr alpha(new RadiographyTextLayer(IObservable::GetBroker(), *this)); alpha->LoadText(font, utf8); if (geometry != NULL) { @@ -292,9 +300,25 @@ return LoadAlphaBitmap(block.release(), geometry); } + RadiographyLayer& RadiographyScene::LoadMask(const std::vector& corners, + const RadiographyDicomLayer& dicomLayer, + float foreground, + RadiographyLayer::Geometry* geometry) + { + std::auto_ptr mask(new RadiographyMaskLayer(IObservable::GetBroker(), *this, dicomLayer, foreground)); + mask->SetCorners(corners); + if (geometry != NULL) + { + mask->SetGeometry(*geometry); + } + + return RegisterLayer(mask.release()); + } + + RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry) { - std::auto_ptr alpha(new RadiographyAlphaLayer(*this)); + std::auto_ptr alpha(new RadiographyAlphaLayer(IObservable::GetBroker(), *this)); alpha->SetAlpha(bitmap); if (geometry != NULL) { @@ -310,7 +334,7 @@ bool httpCompression, RadiographyLayer::Geometry* geometry) { - RadiographyDicomLayer& layer = dynamic_cast(RegisterLayer(new RadiographyDicomLayer)); + RadiographyDicomLayer& layer = dynamic_cast(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this))); layer.SetInstance(instance, frame); if (geometry != NULL) @@ -354,7 +378,7 @@ RadiographyLayer& RadiographyScene::LoadDicomWebFrame(IWebService& web) { - RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer); + RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this)); return layer; @@ -522,41 +546,11 @@ } } - - void RadiographyScene::ExportDicom(OrthancApiClient& orthanc, - const Orthanc::DicomMap& dicom, - const std::string& parentOrthancId, - double pixelSpacingX, - double pixelSpacingY, - bool invert, - ImageInterpolation interpolation, - bool usePam) - { - Json::Value createDicomRequestContent; - - ExportToCreateDicomRequest(createDicomRequestContent, dicom, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam); - - if (!parentOrthancId.empty()) - { - createDicomRequestContent["Parent"] = parentOrthancId; - } - - orthanc.PostJsonAsyncExpectJson( - "/tools/create-dicom", createDicomRequestContent, - new Callable - (*this, &RadiographyScene::OnDicomExported), - NULL, NULL); - } - - // Export using PAM is faster than using PNG, but requires Orthanc - // core >= 1.4.3 - void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent, - const Orthanc::DicomMap& dicom, - double pixelSpacingX, - double pixelSpacingY, - bool invert, - ImageInterpolation interpolation, - bool usePam) + Orthanc::Image* RadiographyScene::ExportToImage(double pixelSpacingX, + double pixelSpacingY, + ImageInterpolation interpolation, + bool invert, + int64_t maxValue /* for inversion */) { if (pixelSpacingX <= 0 || pixelSpacingY <= 0) @@ -589,9 +583,27 @@ Render(layers, view, interpolation); - Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, - layers.GetWidth(), layers.GetHeight(), false); - Orthanc::ImageProcessing::Convert(rendered, layers); + std::auto_ptr rendered(new Orthanc::Image(Orthanc::PixelFormat_Grayscale16, + layers.GetWidth(), layers.GetHeight(), false)); + + Orthanc::ImageProcessing::Convert(*rendered, layers); + if (invert) + Orthanc::ImageProcessing::Invert(*rendered, maxValue); + + return rendered.release(); + } + + + void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent, + const Json::Value& dicomTags, + const std::string& parentOrthancId, + double pixelSpacingX, + double pixelSpacingY, + bool invert, + ImageInterpolation interpolation, + bool usePam) + { + std::auto_ptr rendered(ExportToImage(pixelSpacingX, pixelSpacingY, interpolation)); // note: we don't invert the image in the pixels data because we'll set the PhotometricDisplayMode correctly in the DICOM tags std::string base64; @@ -601,42 +613,28 @@ if (usePam) { Orthanc::PamWriter writer; - writer.WriteToMemory(content, rendered); + writer.WriteToMemory(content, *rendered); } else { Orthanc::PngWriter writer; - writer.WriteToMemory(content, rendered); + writer.WriteToMemory(content, *rendered); } Orthanc::Toolbox::EncodeBase64(base64, content); } - std::set tags; - dicom.GetTags(tags); - - createDicomRequestContent["Tags"] = Json::objectValue; - - for (std::set::const_iterator - tag = tags.begin(); tag != tags.end(); ++tag) - { - const Orthanc::DicomValue& value = dicom.GetValue(*tag); - if (!value.IsNull() && - !value.IsBinary()) - { - createDicomRequestContent["Tags"][tag->Format()] = value.GetContent(); - } - } + createDicomRequestContent["Tags"] = dicomTags; PhotometricDisplayMode photometricMode = GetPreferredPhotomotricDisplayMode(); if ((invert && photometricMode != PhotometricDisplayMode_Monochrome2) || (!invert && photometricMode == PhotometricDisplayMode_Monochrome1)) { - createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = "MONOCHROME1"; + createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME1"; } else { - createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = "MONOCHROME2"; + createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME2"; } // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to @@ -646,15 +644,15 @@ char buf[32]; sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX); - createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf; + createDicomRequestContent["Tags"]["PixelSpacing"] = buf; float center, width; if (GetWindowing(center, width)) { - createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] = + createDicomRequestContent["Tags"]["WindowCenter"] = boost::lexical_cast(boost::math::iround(center)); - createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] = + createDicomRequestContent["Tags"]["WindowWidth"] = boost::lexical_cast(boost::math::iround(width)); } @@ -663,9 +661,67 @@ createDicomRequestContent["Content"] = ("data:" + std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) + ";base64," + base64); + + if (!parentOrthancId.empty()) + { + createDicomRequestContent["Parent"] = parentOrthancId; + } + + } + + + void RadiographyScene::ExportDicom(OrthancApiClient& orthanc, + const Json::Value& dicomTags, + const std::string& parentOrthancId, + double pixelSpacingX, + double pixelSpacingY, + bool invert, + ImageInterpolation interpolation, + bool usePam) + { + Json::Value createDicomRequestContent; + + ExportToCreateDicomRequest(createDicomRequestContent, dicomTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam); + + orthanc.PostJsonAsyncExpectJson( + "/tools/create-dicom", createDicomRequestContent, + new Callable + (*this, &RadiographyScene::OnDicomExported), + NULL, NULL); + } + // Export using PAM is faster than using PNG, but requires Orthanc + // core >= 1.4.3 + void RadiographyScene::ExportDicom(OrthancApiClient& orthanc, + const Orthanc::DicomMap& dicom, + const std::string& parentOrthancId, + double pixelSpacingX, + double pixelSpacingY, + bool invert, + ImageInterpolation interpolation, + bool usePam) + { + std::set tags; + dicom.GetTags(tags); + + Json::Value jsonTags = Json::objectValue; + + for (std::set::const_iterator + tag = tags.begin(); tag != tags.end(); ++tag) + { + const Orthanc::DicomValue& value = dicom.GetValue(*tag); + if (!value.IsNull() && + !value.IsBinary()) + { + jsonTags[tag->Format()] = value.GetContent(); + } + } + + ExportDicom(orthanc, jsonTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam); + } + void RadiographyScene::OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message) { LOG(INFO) << "DICOM export was successful: " diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyScene.h --- a/Framework/Radiography/RadiographyScene.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyScene.h Tue Feb 26 21:33:16 2019 +0100 @@ -24,16 +24,20 @@ #include "RadiographyLayer.h" #include "../Toolbox/OrthancApiClient.h" #include "Framework/StoneEnumerations.h" +#include "Core/Images/Image.h" +#include "Core/Images/ImageProcessing.h" namespace OrthancStone { + class RadiographyDicomLayer; + class RadiographyScene : public IObserver, public IObservable { public: class GeometryChangedMessage : - public OriginMessage + public OriginMessage { private: RadiographyLayer& layer_; @@ -53,7 +57,7 @@ }; class ContentChangedMessage : - public OriginMessage + public OriginMessage { private: RadiographyLayer& layer_; @@ -72,6 +76,37 @@ } }; + class LayerEditedMessage : + public OriginMessage + { + private: + const RadiographyLayer& layer_; + + public: + LayerEditedMessage(const RadiographyScene& origin, + const RadiographyLayer& layer) : + OriginMessage(origin), + layer_(layer) + { + } + + const RadiographyLayer& GetLayer() const + { + return layer_; + } + + }; + + class WindowingChangedMessage : + public OriginMessage + { + + public: + WindowingChangedMessage(const RadiographyScene& origin) : + OriginMessage(origin) + { + } + }; class LayerAccessor : public boost::noncopyable { @@ -126,6 +161,7 @@ void OnDicomWebReceived(const IWebService::HttpRequestSuccessMessage& message); + void OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message); public: RadiographyScene(MessageBroker& broker); @@ -150,6 +186,11 @@ unsigned int height, RadiographyLayer::Geometry* geometry); + RadiographyLayer& LoadMask(const std::vector& corners, + const RadiographyDicomLayer& dicomLayer, + float foreground, + RadiographyLayer::Geometry* geometry); + RadiographyLayer& LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, // takes ownership RadiographyLayer::Geometry* geometry); @@ -165,6 +206,54 @@ const RadiographyLayer& GetLayer(size_t layerIndex) const; + template + TypeLayer* GetLayer(size_t index = 0) + { + std::vector layerIndexes; + GetLayersIndexes(layerIndexes); + + size_t count = 0; + + for (size_t i = 0; i < layerIndexes.size(); ++i) + { + TypeLayer* typedLayer = dynamic_cast(layers_[layerIndexes[i]]); + if (typedLayer != NULL) + { + if (count == index) + { + return typedLayer; + } + count++; + } + } + + return NULL; + } + + template + const TypeLayer* GetLayer(size_t index = 0) const + { + std::vector layerIndexes; + GetLayersIndexes(layerIndexes); + + size_t count = 0; + + for (size_t i = 0; i < layerIndexes.size(); ++i) + { + const TypeLayer* typedLayer = dynamic_cast(layers_.at(layerIndexes[i])); + if (typedLayer != NULL) + { + if (count == index) + { + return typedLayer; + } + count++; + } + } + + return NULL; + } + void GetLayersIndexes(std::vector& output) const; Extent2D GetSceneExtent() const; @@ -195,13 +284,35 @@ ImageInterpolation interpolation, bool usePam); - // temporary version used by VSOL because we need to send the same request at another url + void ExportDicom(OrthancApiClient& orthanc, + const Json::Value& dicomTags, + const std::string& parentOrthancId, + double pixelSpacingX, + double pixelSpacingY, + bool invert, + ImageInterpolation interpolation, + bool usePam); + void ExportToCreateDicomRequest(Json::Value& createDicomRequestContent, - const Orthanc::DicomMap& dicom, + const Json::Value& dicomTags, + const std::string& parentOrthancId, double pixelSpacingX, double pixelSpacingY, bool invert, ImageInterpolation interpolation, bool usePam); + + Orthanc::Image* ExportToImage(double pixelSpacingX, + double pixelSpacingY, + ImageInterpolation interpolation) + { + return ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, false, 0); + } + + Orthanc::Image* ExportToImage(double pixelSpacingX, + double pixelSpacingY, + ImageInterpolation interpolation, + bool invert, + int64_t maxValue /* for inversion */); }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographySceneReader.cpp --- a/Framework/Radiography/RadiographySceneReader.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographySceneReader.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -35,6 +35,7 @@ if (version != 1) throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + RadiographyDicomLayer* dicomLayer = NULL; for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++) { const Json::Value& jsonLayer = input["layers"][(int)layerIndex]; @@ -43,7 +44,26 @@ if (jsonLayer["type"].asString() == "dicom") { ReadLayerGeometry(geometry, jsonLayer); - scene_.LoadDicomFrame(orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry); + dicomLayer = dynamic_cast(&(scene_.LoadDicomFrame(orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry))); + } + else if (jsonLayer["type"].asString() == "mask") + { + if (dicomLayer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // we always assumed the dicom layer was read before the mask + } + ReadLayerGeometry(geometry, jsonLayer); + + float foreground = jsonLayer["foreground"].asFloat(); + std::vector corners; + for (size_t i = 0; i < jsonLayer["corners"].size(); i++) + { + Orthanc::ImageProcessing::ImagePoint corner(jsonLayer["corners"][(int)i]["x"].asInt(), + jsonLayer["corners"][(int)i]["y"].asInt()); + corners.push_back(corner); + } + + scene_.LoadMask(corners, *dicomLayer, foreground, &geometry); } else if (jsonLayer["type"].asString() == "text") { diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographySceneReader.h --- a/Framework/Radiography/RadiographySceneReader.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographySceneReader.h Tue Feb 26 21:33:16 2019 +0100 @@ -24,6 +24,7 @@ #include "RadiographyScene.h" #include "RadiographyAlphaLayer.h" #include "RadiographyDicomLayer.h" +#include "RadiographyMaskLayer.h" #include "RadiographyTextLayer.h" #include #include diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographySceneWriter.cpp --- a/Framework/Radiography/RadiographySceneWriter.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographySceneWriter.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -57,6 +57,22 @@ output["fontName"] = layer.GetFontName(); } + void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyMaskLayer& layer) + { + output["type"] = "mask"; + output["instanceId"] = layer.GetInstanceId(); // the dicom layer it's being linked to + output["foreground"] = layer.GetForeground(); + output["corners"] = Json::arrayValue; + const std::vector& corners = layer.GetCorners(); + for (size_t i = 0; i < corners.size(); i++) + { + Json::Value corner; + corner["x"] = corners[i].GetX(); + corner["y"] = corners[i].GetY(); + output["corners"].append(corner); + } + } + void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer) { output["type"] = "alpha"; @@ -128,6 +144,10 @@ { WriteLayer(output, dynamic_cast(layer)); } + else if (dynamic_cast(&layer) != NULL) + { + WriteLayer(output, dynamic_cast(layer)); + } else { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographySceneWriter.h --- a/Framework/Radiography/RadiographySceneWriter.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographySceneWriter.h Tue Feb 26 21:33:16 2019 +0100 @@ -25,6 +25,7 @@ #include "RadiographyAlphaLayer.h" #include "RadiographyDicomLayer.h" #include "RadiographyTextLayer.h" +#include "RadiographyMaskLayer.h" #include namespace OrthancStone @@ -46,5 +47,6 @@ void WriteLayer(Json::Value& output, const RadiographyDicomLayer& layer); void WriteLayer(Json::Value& output, const RadiographyTextLayer& layer); void WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer); + void WriteLayer(Json::Value& output, const RadiographyMaskLayer& layer); }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyTextLayer.h --- a/Framework/Radiography/RadiographyTextLayer.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyTextLayer.h Tue Feb 26 21:33:16 2019 +0100 @@ -34,8 +34,8 @@ std::string fontName_; public: - RadiographyTextLayer(const RadiographyScene& scene) : - RadiographyAlphaLayer(scene) + RadiographyTextLayer(MessageBroker& broker, const RadiographyScene& scene) : + RadiographyAlphaLayer(broker, scene) { } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyWidget.cpp --- a/Framework/Radiography/RadiographyWidget.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyWidget.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -25,6 +25,7 @@ #include #include +#include "RadiographyMaskLayer.h" namespace OrthancStone { @@ -194,6 +195,28 @@ selectedLayer_ = layer; } + bool RadiographyWidget::SelectMaskLayer(size_t index) + { + std::vector layerIndexes; + size_t count = 0; + scene_->GetLayersIndexes(layerIndexes); + + for (size_t i = 0; i < layerIndexes.size(); ++i) + { + const RadiographyMaskLayer* maskLayer = dynamic_cast(&(scene_->GetLayer(layerIndexes[i]))); + if (maskLayer != NULL) + { + if (count == index) + { + Select(layerIndexes[i]); + return true; + } + count++; + } + } + + return false; + } bool RadiographyWidget::LookupSelectedLayer(size_t& layer) { diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyWidget.h --- a/Framework/Radiography/RadiographyWidget.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyWidget.h Tue Feb 26 21:33:16 2019 +0100 @@ -27,6 +27,8 @@ namespace OrthancStone { + class RadiographyMaskLayer; + class RadiographyWidget : public WorldSceneWidget, public IObserver @@ -76,6 +78,8 @@ void Select(size_t layer); + bool SelectMaskLayer(size_t index = 0); + bool LookupSelectedLayer(size_t& layer); void OnGeometryChanged(const RadiographyScene::GeometryChangedMessage& message); diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyWindowingTracker.cpp --- a/Framework/Radiography/RadiographyWindowingTracker.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyWindowingTracker.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -163,7 +163,9 @@ void RadiographyWindowingTracker::MouseMove(int displayX, int displayY, double sceneX, - double sceneY) + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches) { // 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 diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Radiography/RadiographyWindowingTracker.h --- a/Framework/Radiography/RadiographyWindowingTracker.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Radiography/RadiographyWindowingTracker.h Tue Feb 26 21:33:16 2019 +0100 @@ -82,6 +82,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY); + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches); }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/StoneEnumerations.h --- a/Framework/StoneEnumerations.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/StoneEnumerations.h Tue Feb 26 21:33:16 2019 +0100 @@ -157,8 +157,12 @@ MessageType_OrthancApi_GenericHttpError_Ready, MessageType_OrthancApi_GenericEmptyResponse_Ready, - MessageType_Scene_GeometryChanged, - MessageType_Scene_ContentChanged, + MessageType_RadiographyScene_GeometryChanged, + MessageType_RadiographyScene_ContentChanged, + MessageType_RadiographyScene_LayerEdited, + MessageType_RadiographyScene_WindowingChanged, + + MessageType_RadiographyLayer_Edited, MessageType_ViewportChanged, @@ -172,12 +176,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 diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Toolbox/OrthancApiClient.cpp --- a/Framework/Toolbox/OrthancApiClient.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Toolbox/OrthancApiClient.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -248,6 +248,21 @@ web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, NULL, NULL, NULL); } + void OrthancApiClient::PostBinaryAsync( + const std::string& uri, + const std::string& body, + MessageHandler* successCallback, + MessageHandler* failureCallback, + Orthanc::IDynamicObject* payload /* takes ownership */) + { + web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, + new WebServicePayload(successCallback, failureCallback, payload), + new Callable + (*this, &OrthancApiClient::NotifyHttpSuccess), + new Callable + (*this, &OrthancApiClient::NotifyHttpError)); + } + void OrthancApiClient::PostJsonAsyncExpectJson( const std::string& uri, const Json::Value& data, @@ -269,6 +284,18 @@ return PostBinaryAsync(uri, body); } + void OrthancApiClient::PostJsonAsync( + const std::string& uri, + const Json::Value& data, + MessageHandler* successCallback, + MessageHandler* failureCallback, + Orthanc::IDynamicObject* payload /* takes ownership */) + { + std::string body; + MessagingToolbox::JsonToString(body, data); + return PostBinaryAsync(uri, body, successCallback, failureCallback, payload); + } + void OrthancApiClient::DeleteAsync( const std::string& uri, MessageHandler* successCallback, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Toolbox/OrthancApiClient.h --- a/Framework/Toolbox/OrthancApiClient.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Toolbox/OrthancApiClient.h Tue Feb 26 21:33:16 2019 +0100 @@ -201,10 +201,25 @@ void PostJsonAsync(const std::string& uri, const Json::Value& data); + // schedule a POST request and don't expect any response. + void PostJsonAsync(const std::string& uri, + const Json::Value& data, + MessageHandler* successCallback, + MessageHandler* failureCallback = NULL, + Orthanc::IDynamicObject* payload = NULL /* takes ownership */); + + // schedule a POST request and don't mind the response. void PostBinaryAsync(const std::string& uri, const std::string& body); + // schedule a POST request and don't expect any response. + void PostBinaryAsync(const std::string& uri, + const std::string& body, + MessageHandler* successCallback, + MessageHandler* failureCallback = NULL, + Orthanc::IDynamicObject* payload = NULL /* takes ownership */); + // schedule a DELETE request expecting an empty response. void DeleteAsync(const std::string& uri, MessageHandler* successCallback, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Toolbox/ViewportGeometry.cpp --- a/Framework/Toolbox/ViewportGeometry.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Toolbox/ViewportGeometry.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -121,6 +121,18 @@ } + void ViewportGeometry::MapPixelCenterToScene(std::vector& sceneTouches /* out */, + const std::vector& displayTouches) const + { + double sceneX, sceneY; + sceneTouches.clear(); + for (size_t t = 0; t < displayTouches.size(); t++) + { + MapPixelCenterToScene(sceneX, sceneY, displayTouches[t].x, displayTouches[t].y); + sceneTouches.push_back(Touch((float)sceneX, (float)sceneY)); + } + } + void ViewportGeometry::MapPixelCenterToScene(double& sceneX, double& sceneY, int x, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Toolbox/ViewportGeometry.h --- a/Framework/Toolbox/ViewportGeometry.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Toolbox/ViewportGeometry.h Tue Feb 26 21:33:16 2019 +0100 @@ -24,6 +24,7 @@ #include "../Viewport/CairoContext.h" #include "Extent2D.h" #include "LinearAlgebra.h" +#include "../Viewport/IMouseTracker.h" // to include "Touch" definition namespace OrthancStone { @@ -69,6 +70,9 @@ int x, int y) const; + void MapPixelCenterToScene(std::vector& sceneTouches /* out */, + const std::vector& displayTouches) const; + void MapSceneToDisplay(int& displayX /* out */, int& displayY /* out */, double x, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Viewport/IMouseTracker.h --- a/Framework/Viewport/IMouseTracker.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Viewport/IMouseTracker.h Tue Feb 26 21:33:16 2019 +0100 @@ -22,9 +22,28 @@ #pragma once #include "CairoSurface.h" +#include namespace OrthancStone { + struct Touch + { + float x; + float y; + + Touch(float x, float y) + : x(x), + y(y) + { + } + Touch() + : x(0.0f), + y(0.0f) + { + } + }; + + // this is tracking a mouse in screen coordinates/pixels unlike // the IWorldSceneMouseTracker that is tracking a mouse // in scene coordinates/mm. @@ -41,6 +60,9 @@ // Returns "true" iff. the background scene must be repainted virtual void MouseMove(int x, - int y) = 0; + int y, + const std::vector& displayTouches) = 0; + + virtual bool IsTouchTracker() const {return false;} }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Viewport/IViewport.h --- a/Framework/Viewport/IViewport.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Viewport/IViewport.h Tue Feb 26 21:33:16 2019 +0100 @@ -26,6 +26,7 @@ #include "../Messages/IObservable.h" #include +#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition namespace OrthancStone { @@ -58,12 +59,14 @@ virtual void MouseDown(MouseButton button, int x, int y, - KeyboardModifiers modifiers) = 0; + KeyboardModifiers modifiers, + const std::vector& touches) = 0; virtual void MouseUp() = 0; virtual void MouseMove(int x, - int y) = 0; + int y, + const std::vector& displayTouches) = 0; virtual void MouseEnter() = 0; diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Viewport/WidgetViewport.cpp --- a/Framework/Viewport/WidgetViewport.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Viewport/WidgetViewport.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -137,18 +137,36 @@ return true; } + void WidgetViewport::TouchStart(const std::vector& displayTouches) + { + MouseDown(MouseButton_Left, (int)displayTouches[0].x, (int)displayTouches[0].y, KeyboardModifiers_None, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates + } + + void WidgetViewport::TouchMove(const std::vector& displayTouches) + { + MouseMove((int)displayTouches[0].x, (int)displayTouches[0].y, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates + } + + void WidgetViewport::TouchEnd(const std::vector& displayTouches) + { + // note: TouchEnd is not triggered when a single touch gesture ends (it is only triggered when + // going from 2 touches to 1 touch, ...) + MouseUp(); + } void WidgetViewport::MouseDown(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector& displayTouches + ) { lastMouseX_ = x; lastMouseY_ = y; if (centralWidget_.get() != NULL) { - mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers)); + mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers, displayTouches)); } else { @@ -171,7 +189,8 @@ void WidgetViewport::MouseMove(int x, - int y) + int y, + const std::vector& displayTouches) { if (centralWidget_.get() == NULL) { @@ -185,7 +204,7 @@ if (mouseTracker_.get() != NULL) { - mouseTracker_->MouseMove(x, y); + mouseTracker_->MouseMove(x, y, displayTouches); repaint = true; } else diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Viewport/WidgetViewport.h --- a/Framework/Viewport/WidgetViewport.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Viewport/WidgetViewport.h Tue Feb 26 21:33:16 2019 +0100 @@ -59,17 +59,25 @@ virtual void MouseDown(MouseButton button, int x, int y, - KeyboardModifiers modifiers); + KeyboardModifiers modifiers, + const std::vector& displayTouches); virtual void MouseUp(); virtual void MouseMove(int x, - int y); + int y, + const std::vector& displayTouches); virtual void MouseEnter(); virtual void MouseLeave(); + virtual void TouchStart(const std::vector& touches); + + virtual void TouchMove(const std::vector& touches); + + virtual void TouchEnd(const std::vector& touches); + virtual void MouseWheel(MouseWheelDirection direction, int x, int y, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/EmptyWidget.h --- a/Framework/Widgets/EmptyWidget.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/EmptyWidget.h Tue Feb 26 21:33:16 2019 +0100 @@ -76,7 +76,8 @@ virtual IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector& touches) { return NULL; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/IWidget.h --- a/Framework/Widgets/IWidget.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/IWidget.h Tue Feb 26 21:33:16 2019 +0100 @@ -52,7 +52,8 @@ virtual IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) = 0; + KeyboardModifiers modifiers, + const std::vector& touches) = 0; virtual void RenderMouseOver(Orthanc::ImageAccessor& target, int x, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/IWorldSceneInteractor.h --- a/Framework/Widgets/IWorldSceneInteractor.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/IWorldSceneInteractor.h Tue Feb 26 21:33:16 2019 +0100 @@ -46,7 +46,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) = 0; + IStatusBar* statusBar, + const std::vector& touches) = 0; virtual void MouseOver(CairoContext& context, WorldSceneWidget& widget, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/IWorldSceneMouseTracker.h --- a/Framework/Widgets/IWorldSceneMouseTracker.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/IWorldSceneMouseTracker.h Tue Feb 26 21:33:16 2019 +0100 @@ -22,6 +22,7 @@ #pragma once #include "../Viewport/CairoContext.h" +#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition namespace OrthancStone { @@ -46,6 +47,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY) = 0; + double sceneY, + const std::vector& displayTouches, + const std::vector& sceneTouches) = 0; }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/LayoutWidget.cpp --- a/Framework/Widgets/LayoutWidget.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/LayoutWidget.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -68,9 +68,16 @@ } virtual void MouseMove(int x, - int y) + int y, + const std::vector& displayTouches) { - tracker_->MouseMove(x - left_, y - top_); + std::vector relativeTouches; + for (size_t t = 0; t < displayTouches.size(); t++) + { + relativeTouches.push_back(Touch((int)displayTouches[t].x - left_, (int)displayTouches[t].y - top_)); + } + + tracker_->MouseMove(x - left_, y - top_, relativeTouches); } }; @@ -150,14 +157,16 @@ IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector& touches) { if (Contains(x, y)) { IMouseTracker* tracker = widget_->CreateMouseTracker(button, x - left_, y - top_, - modifiers); + modifiers, + touches); if (tracker) { return new LayoutMouseTracker(tracker, left_, top_, width_, height_); @@ -413,11 +422,12 @@ IMouseTracker* LayoutWidget::CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector& touches) { for (size_t i = 0; i < children_.size(); i++) { - IMouseTracker* tracker = children_[i]->CreateMouseTracker(button, x, y, modifiers); + IMouseTracker* tracker = children_[i]->CreateMouseTracker(button, x, y, modifiers, touches); if (tracker != NULL) { return tracker; diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/LayoutWidget.h --- a/Framework/Widgets/LayoutWidget.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/LayoutWidget.h Tue Feb 26 21:33:16 2019 +0100 @@ -106,7 +106,8 @@ virtual IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers); + KeyboardModifiers modifiers, + const std::vector& touches); virtual void RenderMouseOver(Orthanc::ImageAccessor& target, int x, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/PanMouseTracker.cpp --- a/Framework/Widgets/PanMouseTracker.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/PanMouseTracker.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -46,7 +46,9 @@ void PanMouseTracker::MouseMove(int displayX, int displayY, double x, - double y) + double y, + const std::vector& displayTouches, + const std::vector& sceneTouches) { ViewportGeometry view = that_.GetView(); view.SetPan(originalPanX_ + (x - downX_) * view.GetZoom(), diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/PanMouseTracker.h --- a/Framework/Widgets/PanMouseTracker.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/PanMouseTracker.h Tue Feb 26 21:33:16 2019 +0100 @@ -54,6 +54,8 @@ virtual void MouseMove(int displayX, int displayY, double x, - double y); + double y, + const std::vector& displayTouches, + const std::vector& sceneTouches); }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/PanZoomMouseTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Widgets/PanZoomMouseTracker.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -0,0 +1,137 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "PanZoomMouseTracker.h" + +#include +#include +#include + +namespace OrthancStone +{ + Touch GetCenter(const std::vector& touches) + { + return Touch((touches[0].x + touches[1].x) / 2.0f, (touches[0].y + touches[1].y) / 2.0f); + } + + double GetDistance(const std::vector& touches) + { + float dx = touches[0].x - touches[1].x; + float dy = touches[0].y - touches[1].y; + return sqrt((double)(dx * dx) + (double)(dy * dy)); + } + + + PanZoomMouseTracker::PanZoomMouseTracker(WorldSceneWidget& that, + const std::vector& startTouches) + : that_(that), + originalZoom_(that.GetView().GetZoom()) + { + that.GetView().GetPan(originalPanX_, originalPanY_); + that.GetView().MapPixelCenterToScene(originalSceneTouches_, startTouches); + + originalDisplayCenter_ = GetCenter(startTouches); + originalSceneCenter_ = GetCenter(originalSceneTouches_); + originalDisplayDistanceBetweenTouches_ = GetDistance(startTouches); + +// printf("original Pan %f %f\n", originalPanX_, originalPanY_); +// printf("original Zoom %f \n", originalZoom_); +// printf("original distance %f \n", (float)originalDisplayDistanceBetweenTouches_); +// printf("original display touches 0 %f %f\n", startTouches[0].x, startTouches[0].y); +// printf("original display touches 1 %f %f\n", startTouches[1].x, startTouches[1].y); +// printf("original Scene center %f %f\n", originalSceneCenter_.x, originalSceneCenter_.y); + + unsigned int height = that.GetView().GetDisplayHeight(); + + if (height <= 3) + { + idle_ = true; + LOG(WARNING) << "image is too small to zoom (current height = " << height << ")"; + } + else + { + idle_ = false; + normalization_ = 1.0 / static_cast(height - 1); + } + + } + + + void PanZoomMouseTracker::Render(CairoContext& context, + double zoom) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + + void PanZoomMouseTracker::MouseMove(int displayX, + int displayY, + double x, + double y, + const std::vector& displayTouches, + const std::vector& sceneTouches) + { + ViewportGeometry view = that_.GetView(); + +// printf("Display touches 0 %f %f\n", displayTouches[0].x, displayTouches[0].y); +// printf("Display touches 1 %f %f\n", displayTouches[1].x, displayTouches[1].y); +// printf("Scene touches 0 %f %f\n", sceneTouches[0].x, sceneTouches[0].y); +// printf("Scene touches 1 %f %f\n", sceneTouches[1].x, sceneTouches[1].y); + +// printf("zoom = %f\n", view.GetZoom()); + Touch currentSceneCenter = GetCenter(sceneTouches); + double panX = originalPanX_ + (currentSceneCenter.x - originalSceneCenter_.x) * view.GetZoom(); + double panY = originalPanY_ + (currentSceneCenter.y - originalSceneCenter_.y) * view.GetZoom(); + + view.SetPan(panX, panY); + + static const double MIN_ZOOM = -4; + static const double MAX_ZOOM = 4; + + if (!idle_) + { + double currentDistanceBetweenTouches = GetDistance(displayTouches); + + double dy = static_cast(currentDistanceBetweenTouches - originalDisplayDistanceBetweenTouches_) * normalization_; // In the range [-1,1] + double z; + + // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM] + if (dy < -1.0) + { + z = MIN_ZOOM; + } + else if (dy > 1.0) + { + z = MAX_ZOOM; + } + else + { + z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0; + } + + z = pow(2.0, z); + + view.SetZoom(z * originalZoom_); + } + + that_.SetView(view); + } +} diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/PanZoomMouseTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Widgets/PanZoomMouseTracker.h Tue Feb 26 21:33:16 2019 +0100 @@ -0,0 +1,65 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "WorldSceneWidget.h" + +namespace OrthancStone +{ + class PanZoomMouseTracker : public IWorldSceneMouseTracker + { + private: + WorldSceneWidget& that_; + std::vector originalSceneTouches_; + Touch originalSceneCenter_; + Touch originalDisplayCenter_; + double originalPanX_; + double originalPanY_; + double originalZoom_; + double originalDisplayDistanceBetweenTouches_; + bool idle_; + double normalization_; + + public: + PanZoomMouseTracker(WorldSceneWidget& that, + const std::vector& startTouches); + + virtual bool HasRender() const + { + return false; + } + + virtual void MouseUp() + { + } + + virtual void Render(CairoContext& context, + double zoom); + + virtual void MouseMove(int displayX, + int displayY, + double x, + double y, + const std::vector& displayTouches, + const std::vector& sceneTouches); + }; +} diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/TestCairoWidget.cpp --- a/Framework/Widgets/TestCairoWidget.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/TestCairoWidget.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -99,7 +99,8 @@ IMouseTracker* TestCairoWidget::CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector& touches) { UpdateStatusBar("Click"); return NULL; diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/TestCairoWidget.h --- a/Framework/Widgets/TestCairoWidget.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/TestCairoWidget.h Tue Feb 26 21:33:16 2019 +0100 @@ -51,7 +51,8 @@ virtual IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers); + KeyboardModifiers modifiers, + const std::vector& touches); virtual void MouseWheel(MouseWheelDirection direction, int x, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/TestWorldSceneWidget.cpp --- a/Framework/Widgets/TestWorldSceneWidget.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/TestWorldSceneWidget.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -41,7 +41,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector& touches) { if (statusBar) { diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/WorldSceneWidget.cpp --- a/Framework/Widgets/WorldSceneWidget.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/WorldSceneWidget.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -23,6 +23,7 @@ #include "PanMouseTracker.h" #include "ZoomMouseTracker.h" +#include "PanZoomMouseTracker.h" #include #include @@ -72,11 +73,20 @@ } virtual void MouseMove(int x, - int y) + int y, + const std::vector& displayTouches) { double sceneX, sceneY; view_.MapPixelCenterToScene(sceneX, sceneY, x, y); - tracker_->MouseMove(x, y, sceneX, sceneY); + + std::vector sceneTouches; + for (size_t t = 0; t < displayTouches.size(); t++) + { + double sx, sy; + view_.MapPixelCenterToScene(sx, sy, (int)displayTouches[t].x, (int)displayTouches[t].y); + sceneTouches.push_back(Touch(sx, sy)); + } + tracker_->MouseMove(x, y, sceneX, sceneY, displayTouches, sceneTouches); } }; @@ -145,7 +155,8 @@ IMouseTracker* WorldSceneWidget::CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector& touches) { double sceneX, sceneY; view_.MapPixelCenterToScene(sceneX, sceneY, x, y); @@ -155,7 +166,7 @@ if (interactor_) { - tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar())); + tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar(), touches)); } if (tracker.get() != NULL) @@ -164,17 +175,26 @@ } else if (hasDefaultMouseEvents_) { - switch (button) + printf("has default mouse events\n"); + if (touches.size() == 2) { - case MouseButton_Middle: - return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y)); + printf("2 touches !\n"); + return new SceneMouseTracker(view_, new PanZoomMouseTracker(*this, touches)); + } + else + { + switch (button) + { + case MouseButton_Middle: + return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y)); - case MouseButton_Right: - return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y)); + case MouseButton_Right: + return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y)); - default: - return NULL; - } + default: + return NULL; + } + } } else { diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/WorldSceneWidget.h --- a/Framework/Widgets/WorldSceneWidget.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/WorldSceneWidget.h Tue Feb 26 21:33:16 2019 +0100 @@ -88,7 +88,8 @@ virtual IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers); + KeyboardModifiers modifiers, + const std::vector& touches); virtual void MouseWheel(MouseWheelDirection direction, int x, diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/ZoomMouseTracker.cpp --- a/Framework/Widgets/ZoomMouseTracker.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/ZoomMouseTracker.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -61,7 +61,9 @@ void ZoomMouseTracker::MouseMove(int displayX, int displayY, double x, - double y) + double y, + const std::vector& displayTouches, + const std::vector& sceneTouches) { static const double MIN_ZOOM = -4; static const double MAX_ZOOM = 4; diff -r 4cc7bb55bd49 -r 801d2697a1b1 Framework/Widgets/ZoomMouseTracker.h --- a/Framework/Widgets/ZoomMouseTracker.h Tue Feb 26 21:26:47 2019 +0100 +++ b/Framework/Widgets/ZoomMouseTracker.h Tue Feb 26 21:33:16 2019 +0100 @@ -57,6 +57,8 @@ virtual void MouseMove(int displayX, int displayY, double x, - double y); + double y, + const std::vector& displayTouches, + const std::vector& sceneTouches); }; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Platforms/Wasm/Defaults.cpp --- a/Platforms/Wasm/Defaults.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/Platforms/Wasm/Defaults.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -49,7 +49,7 @@ viewports_.push_back(viewport); - printf("There are now %d viewports in C++\n", viewports_.size()); + printf("There are now %lu viewports in C++\n", viewports_.size()); viewport->SetStatusBar(statusBar_); @@ -64,7 +64,7 @@ void EMSCRIPTEN_KEEPALIVE ReleaseCppViewport(ViewportHandle viewport) { viewports_.remove_if([viewport](const std::shared_ptr& v) { return v.get() == viewport;}); - printf("There are now %d viewports in C++\n", viewports_.size()); + printf("There are now %lu viewports in C++\n", viewports_.size()); } void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle viewport) { @@ -191,7 +191,7 @@ return; // Unknown button } - viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None /* TODO */); + viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None, std::vector()); } @@ -222,9 +222,82 @@ int x, int y) { - viewport->MouseMove(x, y); + viewport->MouseMove(x, y, std::vector()); + } + + void GetTouchVector(std::vector& output, + int touchCount, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2) + { + // TODO: it might be nice to try to pass all the x0,y0 coordinates as arrays but that's not so easy to pass array between JS and C++ + if (touchCount > 0) + { + output.push_back(OrthancStone::Touch(x0, y0)); + } + if (touchCount > 1) + { + output.push_back(OrthancStone::Touch(x1, y1)); + } + if (touchCount > 2) + { + output.push_back(OrthancStone::Touch(x2, y2)); + } + } - + + void EMSCRIPTEN_KEEPALIVE ViewportTouchStart(ViewportHandle viewport, + int touchCount, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2) + { + printf("touch start with %d touches\n", touchCount); + + std::vector touches; + GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); + viewport->TouchStart(touches); + } + + void EMSCRIPTEN_KEEPALIVE ViewportTouchMove(ViewportHandle viewport, + int touchCount, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2) + { + printf("touch move with %d touches\n", touchCount); + + std::vector touches; + GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); + viewport->TouchMove(touches); + } + + void EMSCRIPTEN_KEEPALIVE ViewportTouchEnd(ViewportHandle viewport, + int touchCount, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2) + { + printf("touch end with %d touches remaining\n", touchCount); + + std::vector touches; + GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); + viewport->TouchEnd(touches); + } + void EMSCRIPTEN_KEEPALIVE ViewportKeyPressed(ViewportHandle viewport, int key, const char* keyChar, @@ -273,15 +346,13 @@ { static std::string output; // we don't want the string to be deallocated when we return to JS code so we always use the same string (this is fine since JS is single-thread) - printf("SendMessageToStoneApplication\n"); - printf("%s", message); + printf("SendMessageToStoneApplication (JS -> C++): %s\n", message); if (applicationWasmAdapter.get() != NULL) { - printf("sending message to C++\n"); applicationWasmAdapter->HandleMessageFromWeb(output, std::string(message)); return output.c_str(); } - printf("This stone application does not have a Web Adapter"); + printf("This stone application does not have a Web Adapter\n"); return NULL; } diff -r 4cc7bb55bd49 -r 801d2697a1b1 Platforms/Wasm/wasm-viewport.ts --- a/Platforms/Wasm/wasm-viewport.ts Tue Feb 26 21:26:47 2019 +0100 +++ b/Platforms/Wasm/wasm-viewport.ts Tue Feb 26 21:33:16 2019 +0100 @@ -65,6 +65,9 @@ private ViewportMouseLeave : Function; private ViewportMouseWheel : Function; private ViewportKeyPressed : Function; + private ViewportTouchStart : Function; + private ViewportTouchMove : Function; + private ViewportTouchEnd : Function; private pimpl_ : any; // Private pointer to the underlying WebAssembly C++ object @@ -91,6 +94,9 @@ this.ViewportMouseLeave = this.module_.cwrap('ViewportMouseLeave', null, [ 'number' ]); this.ViewportMouseWheel = this.module_.cwrap('ViewportMouseWheel', null, [ 'number', 'number', 'number', 'number', 'number' ]); this.ViewportKeyPressed = this.module_.cwrap('ViewportKeyPressed', null, [ 'number', 'number', 'string', 'number', 'number' ]); + this.ViewportTouchStart = this.module_.cwrap('ViewportTouchStart', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]); + this.ViewportTouchMove = this.module_.cwrap('ViewportTouchMove', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]); + this.ViewportTouchEnd = this.module_.cwrap('ViewportTouchEnd', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]); } public GetCppViewport() : number { @@ -197,7 +203,8 @@ this.htmlCanvas_.addEventListener('mousedown', function(event) { var x = event.pageX - this.offsetLeft; var y = event.pageY - this.offsetTop; - that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO detect modifier keys*/); + + that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO detect modifier keys*/); }); this.htmlCanvas_.addEventListener('mousemove', function(event) { @@ -229,12 +236,32 @@ event.preventDefault(); }); - this.htmlCanvas_.addEventListener('touchstart', function(event) { + this.htmlCanvas_.addEventListener('touchstart', function(event: TouchEvent) { // don't propagate events to the whole body (this could zoom the entire page instead of zooming the viewport) event.preventDefault(); event.stopPropagation(); - that.ResetTouch(); + // TODO: find a way to pass the coordinates as an array between JS and C++ + var x0 = 0; + var y0 = 0; + var x1 = 0; + var y1 = 0; + var x2 = 0; + var y2 = 0; + if (event.targetTouches.length > 0) { + x0 = event.targetTouches[0].pageX; + y0 = event.targetTouches[0].pageY; + } + if (event.targetTouches.length > 1) { + x1 = event.targetTouches[1].pageX; + y1 = event.targetTouches[1].pageY; + } + if (event.targetTouches.length > 2) { + x2 = event.targetTouches[2].pageX; + y2 = event.targetTouches[2].pageY; + } + + that.ViewportTouchStart(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2); }); this.htmlCanvas_.addEventListener('touchend', function(event) { @@ -242,7 +269,27 @@ event.preventDefault(); event.stopPropagation(); - that.ResetTouch(); + // TODO: find a way to pass the coordinates as an array between JS and C++ + var x0 = 0; + var y0 = 0; + var x1 = 0; + var y1 = 0; + var x2 = 0; + var y2 = 0; + if (event.targetTouches.length > 0) { + x0 = event.targetTouches[0].pageX; + y0 = event.targetTouches[0].pageY; + } + if (event.targetTouches.length > 1) { + x1 = event.targetTouches[1].pageX; + y1 = event.targetTouches[1].pageY; + } + if (event.targetTouches.length > 2) { + x2 = event.targetTouches[2].pageX; + y2 = event.targetTouches[2].pageY; + } + + that.ViewportTouchEnd(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2); }); this.htmlCanvas_.addEventListener('touchmove', function(event: TouchEvent) { @@ -251,6 +298,30 @@ event.preventDefault(); event.stopPropagation(); + + // TODO: find a way to pass the coordinates as an array between JS and C++ + var x0 = 0; + var y0 = 0; + var x1 = 0; + var y1 = 0; + var x2 = 0; + var y2 = 0; + if (event.targetTouches.length > 0) { + x0 = event.targetTouches[0].pageX; + y0 = event.targetTouches[0].pageY; + } + if (event.targetTouches.length > 1) { + x1 = event.targetTouches[1].pageX; + y1 = event.targetTouches[1].pageY; + } + if (event.targetTouches.length > 2) { + x2 = event.targetTouches[2].pageX; + y2 = event.targetTouches[2].pageY; + } + + that.ViewportTouchMove(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2); + return; + // if (!that.touchGestureInProgress_) { // // starting a new gesture // that.touchCount_ = event.targetTouches.length; diff -r 4cc7bb55bd49 -r 801d2697a1b1 README.md diff -r 4cc7bb55bd49 -r 801d2697a1b1 Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Tue Feb 26 21:26:47 2019 +0100 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Tue Feb 26 21:33:16 2019 +0100 @@ -30,6 +30,7 @@ include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake) include_directories(${ORTHANC_ROOT}) +include_directories(${ORTHANC_ROOT}/Core/Images) # hack for the numerous #include "../Enumerations.h" in Orthanc to work ##################################################################### @@ -255,9 +256,11 @@ ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyDicomLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerCropTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerMaskTracker.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/RadiographyMaskLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyScene.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographySceneCommand.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographySceneReader.cpp @@ -294,6 +297,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ViewportGeometry.cpp ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoContext.cpp ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoSurface.cpp + ${ORTHANC_STONE_ROOT}/Framework/Viewport/IMouseTracker.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/IStatusBar.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/IViewport.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/WidgetViewport.cpp @@ -307,6 +311,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWorldSceneMouseTracker.h ${ORTHANC_STONE_ROOT}/Framework/Widgets/LayoutWidget.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/PanMouseTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Widgets/PanZoomMouseTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/SliceViewerWidget.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestCairoWidget.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestWorldSceneWidget.cpp diff -r 4cc7bb55bd49 -r 801d2697a1b1 UnitTestsSources/TestMessageBroker.cpp --- a/UnitTestsSources/TestMessageBroker.cpp Tue Feb 26 21:26:47 2019 +0100 +++ b/UnitTestsSources/TestMessageBroker.cpp Tue Feb 26 21:33:16 2019 +0100 @@ -312,7 +312,7 @@ ASSERT_EQ(0, testCounter); } -#if __cplusplus >= 201103L +#if 0 //__cplusplus >= 201103L TEST(MessageBroker, TestLambdaSimpleUseCase) {