Mercurial > hg > orthanc-stone
changeset 632:500c3f70b6c2
- Added a ClearAllChains method to PolylineSceneLayer --> revision must change
when calling it ==> BumpRevision has been added to base class
- Added some docs
= Added GetMinDepth + GetMaxDepth to Scene2D (to alleviate the need for app-
specific "Z depth registry" : clients may simply add a new layer on top or at
the bottom of the existing layer set.
- Added the line tracker measurement tools, commands and trackers. Generic base
classes + Line measure
- started work on the line measure handles
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Thu, 09 May 2019 10:41:31 +0200 |
parents | 0925b27e8750 |
children | b0652595b62a |
files | Framework/Scene2D/ColorSceneLayer.h Framework/Scene2D/IPointerTracker.h Framework/Scene2D/PolylineSceneLayer.cpp Framework/Scene2D/PolylineSceneLayer.h Framework/Scene2D/Scene2D.cpp Framework/Scene2D/Scene2D.h Samples/Common/MeasureCommands.cpp Samples/Common/MeasureCommands.h Samples/Common/MeasureTools.cpp Samples/Common/MeasureTools.h Samples/Common/MeasureTrackers.cpp Samples/Common/MeasureTrackers.h Samples/Sdl/CMakeLists.txt Samples/Sdl/TrackerSample.cpp |
diffstat | 14 files changed, 1295 insertions(+), 8 deletions(-) [+] |
line wrap: on
line diff
--- a/Framework/Scene2D/ColorSceneLayer.h Thu May 02 18:58:46 2019 +0200 +++ b/Framework/Scene2D/ColorSceneLayer.h Thu May 09 10:41:31 2019 +0200 @@ -22,6 +22,7 @@ #pragma once #include "ISceneLayer.h" +#include <Core/Enumerations.h> #include <stdint.h> @@ -33,7 +34,13 @@ uint8_t red_; uint8_t green_; uint8_t blue_; - + uint64_t revision_; + protected: + void BumpRevision() + { + // this is *not* thread-safe!!! + revision_++; + } public: ColorSceneLayer() : red_(255), @@ -42,6 +49,11 @@ { } + virtual uint64_t GetRevision() const ORTHANC_OVERRIDE + { + return revision_; + } + void SetColor(uint8_t red, uint8_t green, uint8_t blue) @@ -49,6 +61,7 @@ red_ = red; green_ = green; blue_ = blue; + BumpRevision(); } uint8_t GetRed() const
--- a/Framework/Scene2D/IPointerTracker.h Thu May 02 18:58:46 2019 +0200 +++ b/Framework/Scene2D/IPointerTracker.h Thu May 09 10:41:31 2019 +0200 @@ -32,8 +32,15 @@ { } + /** + This method will be repeatedly called during user interaction + */ virtual void Update(const PointerEvent& event) = 0; + /** + This method will be called if the tracker is to be abandoned without + committing its result + */ virtual void Release() = 0; }; }
--- a/Framework/Scene2D/PolylineSceneLayer.cpp Thu May 02 18:58:46 2019 +0200 +++ b/Framework/Scene2D/PolylineSceneLayer.cpp Thu May 09 10:41:31 2019 +0200 @@ -42,6 +42,7 @@ else { thickness_ = thickness; + BumpRevision(); } } @@ -52,6 +53,7 @@ chains_ = from.chains_; closed_ = from.closed_; thickness_ = from.thickness_; + BumpRevision(); } @@ -69,10 +71,18 @@ { chains_.push_back(chain); closed_.push_back(isClosed); + BumpRevision(); } } + void PolylineSceneLayer::ClearAllChains() + { + chains_.clear(); + closed_.clear(); + BumpRevision(); + } + const PolylineSceneLayer::Chain& PolylineSceneLayer::GetChain(size_t i) const { if (i < chains_.size())
--- a/Framework/Scene2D/PolylineSceneLayer.h Thu May 02 18:58:46 2019 +0200 +++ b/Framework/Scene2D/PolylineSceneLayer.h Thu May 09 10:41:31 2019 +0200 @@ -60,6 +60,8 @@ void AddChain(const Chain& chain, bool isClosed); + void ClearAllChains(); + size_t GetChainsCount() const { return chains_.size(); @@ -75,10 +77,6 @@ } virtual bool GetBoundingBox(Extent2D& target) const; - - virtual uint64_t GetRevision() const - { - return 0; - } + }; }
--- a/Framework/Scene2D/Scene2D.cpp Thu May 02 18:58:46 2019 +0200 +++ b/Framework/Scene2D/Scene2D.cpp Thu May 09 10:41:31 2019 +0200 @@ -102,6 +102,8 @@ void Scene2D::SetLayer(int depth, ISceneLayer* layer) // Takes ownership { + LOG(INFO) << "SetLayer(" << depth << ", " << + reinterpret_cast<intptr_t>(layer) << ")"; std::auto_ptr<Item> item(new Item(layer, layerCounter_++)); if (layer == NULL) @@ -126,10 +128,12 @@ void Scene2D::DeleteLayer(int depth) { + Content::iterator found = content_.find(depth); if (found != content_.end()) { + LOG(INFO) << "DeleteLayer --found-- (" << depth << ")"; assert(found->second != NULL); delete found->second; content_.erase(found); @@ -159,6 +163,23 @@ } + int Scene2D::GetMinDepth() const + { + if (content_.size() == 0) + return 0; + else + return content_.begin()->first; + } + + + int Scene2D::GetMaxDepth() const + { + if (content_.size() == 0) + return 0; + else + return content_.rbegin()->first; + } + ISceneLayer* Scene2D::ReleaseLayer(int depth) { Content::iterator found = content_.find(depth);
--- a/Framework/Scene2D/Scene2D.h Thu May 02 18:58:46 2019 +0200 +++ b/Framework/Scene2D/Scene2D.h Thu May 09 10:41:31 2019 +0200 @@ -23,7 +23,6 @@ #include "ISceneLayer.h" #include "../Toolbox/AffineTransform2D.h" - #include <map> namespace OrthancStone @@ -71,12 +70,29 @@ void SetLayer(int depth, ISceneLayer* layer); // Takes ownership + /** + Removes the layer at specified depth and deletes the underlying object + */ void DeleteLayer(int depth); bool HasLayer(int depth) const; ISceneLayer& GetLayer(int depth) const; + /** + Returns the minimum depth among all layers or 0 if there are no layers + */ + int GetMinDepth() const; + + /** + Returns the minimum depth among all layers or 0 if there are no layers + */ + int GetMaxDepth() const; + + /** + Removes the layer at specified depth and transfers the object + ownership to the caller + */ ISceneLayer* ReleaseLayer(int depth); void Apply(IVisitor& visitor) const;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureCommands.cpp Thu May 09 10:41:31 2019 +0200 @@ -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 <http://www.gnu.org/licenses/>. + **/ + +#include "MeasureCommands.h" + +namespace OrthancStone +{ + void CreateMeasureCommand::Undo() + { + // simply disable the measure tool upon undo + GetMeasureTool()->Disable(); + } + + void CreateMeasureCommand::Redo() + { + GetMeasureTool()->Enable(); + } + + CreateMeasureCommand::CreateMeasureCommand( + Scene2D& scene, MeasureToolList& measureTools) + : TrackerCommand(scene) + , measureTools_(measureTools) + { + + } + + CreateMeasureCommand::~CreateMeasureCommand() + { + // deleting the command should not change the model state + // we thus leave it as is + } + + CreateLineMeasureCommand::CreateLineMeasureCommand( + Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point) + : CreateMeasureCommand(scene, measureTools) + , measureTool_(new LineMeasureTool(scene)) + { + measureTool_ = LineMeasureToolPtr(new LineMeasureTool(scene)); + measureTools_.push_back(measureTool_); + measureTool_->Set(point, point); + } + + void CreateLineMeasureCommand::Update(ScenePoint2D scenePos) + { + measureTool_->SetEnd(scenePos); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureCommands.h Thu May 09 10:41:31 2019 +0200 @@ -0,0 +1,91 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ +#pragma once + +#include <Framework/Scene2D/Scene2D.h> +#include <boost/shared_ptr.hpp> + +// to be moved into Stone +#include "MeasureTools.h" + +namespace OrthancStone +{ + class TrackerCommand + { + public: + TrackerCommand(Scene2D& scene) : scene_(scene) + { + + } + virtual void Undo() = 0; + virtual void Redo() = 0; + virtual void Update(ScenePoint2D scenePos) = 0; + + Scene2D& GetScene() + { + return scene_; + } + + protected: + Scene2D& scene_; + private: + TrackerCommand(const TrackerCommand&); + TrackerCommand& operator=(const TrackerCommand&); + }; + + typedef boost::shared_ptr<TrackerCommand> TrackerCommandPtr; + + class CreateMeasureCommand : public TrackerCommand + { + public: + CreateMeasureCommand(Scene2D& scene, MeasureToolList& measureTools); + ~CreateMeasureCommand(); + virtual void Undo() ORTHANC_OVERRIDE; + virtual void Redo() ORTHANC_OVERRIDE; + protected: + MeasureToolList& measureTools_; + private: + /** Must be implemented by the subclasses that create the actual tool */ + virtual MeasureToolPtr GetMeasureTool() = 0; + }; + + typedef boost::shared_ptr<CreateMeasureCommand> CreateMeasureCommandPtr; + + class CreateLineMeasureCommand : public CreateMeasureCommand + { + public: + CreateLineMeasureCommand::CreateLineMeasureCommand( + Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point); + + void Update(ScenePoint2D scenePos) ORTHANC_OVERRIDE; + + private: + virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE + { + return measureTool_; + } + LineMeasureToolPtr measureTool_; + ScenePoint2D start_; + ScenePoint2D end_; + }; + + typedef boost::shared_ptr<CreateLineMeasureCommand> CreateLineMeasureCommandPtr; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureTools.cpp Thu May 09 10:41:31 2019 +0200 @@ -0,0 +1,196 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + +#include <Core/Logging.h> +#include "MeasureTools.h" + +namespace OrthancStone +{ + void MeasureTool::Enable() + { + enabled_ = true; + RefreshScene(); + } + + void MeasureTool::Disable() + { + enabled_ = false; + RefreshScene(); + } + + LineMeasureTool::~LineMeasureTool() + { + // this measuring tool is a RABI for the corresponding visual layers + // stored in the 2D scene + Disable(); + RemoveFromScene(); + } + + void LineMeasureTool::RemoveFromScene() + { + if (layersCreated) + { + assert(GetScene().HasLayer(polylineZIndex_)); + assert(GetScene().HasLayer(textZIndex_)); + GetScene().DeleteLayer(polylineZIndex_); + GetScene().DeleteLayer(textZIndex_); + } + } + + + void LineMeasureTool::SetStart(ScenePoint2D start) + { + start_ = start; + RefreshScene(); + } + + void LineMeasureTool::SetEnd(ScenePoint2D end) + { + end_ = end; + RefreshScene(); + } + + void LineMeasureTool::Set(ScenePoint2D start, ScenePoint2D end) + { + start_ = start; + end_ = end; + RefreshScene(); + } + + PolylineSceneLayer* LineMeasureTool::GetPolylineLayer() + { + assert(GetScene().HasLayer(polylineZIndex_)); + ISceneLayer* layer = &(GetScene().GetLayer(polylineZIndex_)); + PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer); + assert(concreteLayer != NULL); + return concreteLayer; + } + + TextSceneLayer* LineMeasureTool::GetTextLayer() + { + assert(GetScene().HasLayer(textZIndex_)); + ISceneLayer* layer = &(GetScene().GetLayer(textZIndex_)); + TextSceneLayer* concreteLayer = dynamic_cast<TextSceneLayer*>(layer); + assert(concreteLayer != NULL); + return concreteLayer; + } + + void LineMeasureTool::RefreshScene() + { + if (IsEnabled()) + { + if (!layersCreated) + { + // Create the layers if need be + + assert(textZIndex_ == -1); + { + polylineZIndex_ = GetScene().GetMaxDepth() + 100; + LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_; + std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer()); + GetScene().SetLayer(polylineZIndex_, layer.release()); + } + { + textZIndex_ = GetScene().GetMaxDepth() + 100; + LOG(INFO) << "set textZIndex_ to: " << textZIndex_; + std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer()); + GetScene().SetLayer(textZIndex_, layer.release()); + } + layersCreated = true; + } + else + { + assert(GetScene().HasLayer(polylineZIndex_)); + assert(GetScene().HasLayer(textZIndex_)); + } + { + // Fill the polyline layer with the measurement line + + PolylineSceneLayer* polylineLayer = GetPolylineLayer(); + polylineLayer->ClearAllChains(); + polylineLayer->SetColor(0, 223, 21); + + { + PolylineSceneLayer::Chain chain; + chain.push_back(start_); + chain.push_back(end_); + polylineLayer->AddChain(chain, false); + } + + // handles + { + auto startC = start_.Apply(GetScene().GetSceneToCanvasTransform()); + auto squareSize = 10; //TODO: take DPI into account + auto startHandleLX = startC.GetX() - squareSize/2; + auto startHandleTY = startC.GetY() - squareSize / 2; + auto startHandleRX = startC.GetX() + squareSize / 2; + auto startHandleBY = startC.GetY() + squareSize / 2; + auto startLTC = ScenePoint2D(startHandleLX, startHandleTY); + auto startRTC = ScenePoint2D(startHandleRX, startHandleTY); + auto startRBC = ScenePoint2D(startHandleRX, startHandleBY); + auto startLBC = ScenePoint2D(startHandleLX, startHandleBY); + + auto startLT = startLTC.Apply(GetScene().GetCanvasToSceneTransform()); + auto startRT = startRTC.Apply(GetScene().GetCanvasToSceneTransform()); + auto startRB = startRBC.Apply(GetScene().GetCanvasToSceneTransform()); + auto startLB = startLBC.Apply(GetScene().GetCanvasToSceneTransform()); + + PolylineSceneLayer::Chain chain; + chain.push_back(startLT); + chain.push_back(startRT); + chain.push_back(startRB); + chain.push_back(startLB); + polylineLayer->AddChain(chain, true); + } + + + + } + { + // Set the text layer proporeties + + TextSceneLayer* textLayer = GetTextLayer(); + double deltaX = end_.GetX() - start_.GetX(); + double deltaY = end_.GetY() - start_.GetY(); + double squareDist = deltaX * deltaX + deltaY * deltaY; + double dist = sqrt(squareDist); + char buf[64]; + sprintf(buf, "%0.02f units", dist); + textLayer->SetText(buf); + textLayer->SetColor(0, 223, 21); + + // TODO: for now we simply position the text overlay at the middle + // of the measuring segment + double midX = 0.5*(end_.GetX() + start_.GetX()); + double midY = 0.5*(end_.GetY() + start_.GetY()); + textLayer->SetPosition(midX, midY); + } + } + else + { + if (layersCreated) + { + RemoveFromScene(); + layersCreated = false; + } + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureTools.h Thu May 09 10:41:31 2019 +0200 @@ -0,0 +1,126 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ +#pragma once + +#include <Framework/Scene2D/Scene2D.h> +#include <Framework/Scene2D/ScenePoint2D.h> +#include <Framework/Scene2D/PolylineSceneLayer.h> +#include <Framework/Scene2D/TextSceneLayer.h> + +#include <boost/shared_ptr.hpp> + +#include <vector> +#include <cmath> + +namespace OrthancStone +{ + class MeasureTool + { + public: + virtual ~MeasureTool() {} + + /** + Enabled tools are rendered in the scene. + */ + void Enable(); + + /** + Disabled tools are not rendered in the scene. This is useful to be able + to use them as their own memento in command stacks (when a measure tool + creation command has been undone, the measure remains alive in the + command object but is disabled so that it can be redone later on easily) + */ + void Disable(); + + protected: + MeasureTool(Scene2D& scene) + : scene_(scene) + , enabled_(true) + { + } + + + + /** + This is the meat of the tool: this method must [create (if needed) and] + update the layers and their data according to the measure tool kind and + current state. This is repeatedly called during user interaction + */ + virtual void RefreshScene() = 0; + + + Scene2D& GetScene() + { + return scene_; + } + + /** + enabled_ is not accessible by subclasses because there is a state machine + that we do not wanna mess with + */ + bool IsEnabled() const + { + return enabled_; + } + + private: + Scene2D& scene_; + bool enabled_; + }; + + typedef boost::shared_ptr<MeasureTool> MeasureToolPtr; + + class LineMeasureTool : public MeasureTool + { + public: + LineMeasureTool(Scene2D& scene) + : MeasureTool(scene) + , layersCreated(false) + , polylineZIndex_(-1) + , textZIndex_(-1) + { + + } + + ~LineMeasureTool(); + + void SetStart(ScenePoint2D start); + void SetEnd(ScenePoint2D end); + void Set(ScenePoint2D start, ScenePoint2D end); + + private: + PolylineSceneLayer* GetPolylineLayer(); + TextSceneLayer* GetTextLayer(); + virtual void RefreshScene() ORTHANC_OVERRIDE; + void RemoveFromScene(); + + private: + ScenePoint2D start_; + ScenePoint2D end_; + bool layersCreated; + int polylineZIndex_; + int textZIndex_; + }; + + typedef boost::shared_ptr<LineMeasureTool> LineMeasureToolPtr; + typedef std::vector<MeasureToolPtr> MeasureToolList; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureTrackers.cpp Thu May 09 10:41:31 2019 +0200 @@ -0,0 +1,92 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + +#include "MeasureTrackers.h" +#include <Core/OrthancException.h> + +using namespace Orthanc; + +namespace OrthancStone +{ + + CreateMeasureTracker::CreateMeasureTracker( + Scene2D& scene, + std::vector<TrackerCommandPtr>& undoStack, + std::vector<MeasureToolPtr>& measureTools) + : scene_(scene) + , undoStack_(undoStack) + , measureTools_(measureTools) + , commitResult_(true) + { + } + + CreateMeasureTracker::~CreateMeasureTracker() + { + // if the tracker completes successfully, we add the command + // to the undo stack + + // otherwise, we simply undo it + if (commitResult_) + undoStack_.push_back(command_); + else + command_->Undo(); + } + + CreateLineMeasureTracker::CreateLineMeasureTracker( + Scene2D& scene, + std::vector<TrackerCommandPtr>& undoStack, + std::vector<MeasureToolPtr>& measureTools, + const PointerEvent& e) + : CreateMeasureTracker(scene, undoStack, measureTools) + { + command_.reset( + new CreateLineMeasureCommand( + scene, + measureTools, + e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()))); + } + + CreateLineMeasureTracker::~CreateLineMeasureTracker() + { + + } + + void CreateMeasureTracker::Update(const PointerEvent& event) + { + ScenePoint2D scenePos = event.GetMainPosition().Apply( + scene_.GetCanvasToSceneTransform()); + + LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << " " << + "scenePos.GetY() = " << scenePos.GetY(); + + CreateLineMeasureTracker* concreteThis = + dynamic_cast<CreateLineMeasureTracker*>(this); + assert(concreteThis != NULL); + command_->Update(scenePos); + } + + void CreateMeasureTracker::Release() + { + commitResult_ = false; + } + +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureTrackers.h Thu May 09 10:41:31 2019 +0200 @@ -0,0 +1,74 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + +#pragma once + +#include "../../Framework/Scene2D/IPointerTracker.h" +#include "../../Framework/Scene2D/Scene2D.h" + +#include "MeasureTools.h" +#include "MeasureCommands.h" + +#include <vector> + +namespace OrthancStone +{ + class CreateMeasureTracker : public IPointerTracker + { + public: + virtual void Update(const PointerEvent& e) ORTHANC_OVERRIDE; + virtual void Release() ORTHANC_OVERRIDE; + protected: + CreateMeasureTracker( + Scene2D& scene, + std::vector<TrackerCommandPtr>& undoStack, + std::vector<MeasureToolPtr>& measureTools); + + ~CreateMeasureTracker(); + + private: + Scene2D& scene_; + std::vector<TrackerCommandPtr>& undoStack_; + std::vector<MeasureToolPtr>& measureTools_; + bool commitResult_; + + protected: + CreateMeasureCommandPtr command_; + }; + + class CreateLineMeasureTracker : public CreateMeasureTracker + { + public: + /** + When you create this tracker, you need to supply it with the undo stack + where it will store the commands that perform the actual measure tool + creation and modification. + In turn, a container for these commands to store the actual measuring + must be supplied, too + */ + CreateLineMeasureTracker( + Scene2D& scene, + std::vector<TrackerCommandPtr>& undoStack, + std::vector<MeasureToolPtr>& measureTools, + const PointerEvent& e); + + ~CreateLineMeasureTracker(); + }; +}
--- a/Samples/Sdl/CMakeLists.txt Thu May 02 18:58:46 2019 +0200 +++ b/Samples/Sdl/CMakeLists.txt Thu May 09 10:41:31 2019 +0200 @@ -37,6 +37,7 @@ UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf ) +SET(ENABLE_SDL_CONSOLE OFF CACHE BOOL "Enable the use of the MIT-licensed SDL_Console") SET(ENABLE_GOOGLE_TEST OFF) SET(ENABLE_LOCALE ON) SET(ENABLE_SDL ON) @@ -46,7 +47,6 @@ include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) - ##################################################################### ## Build the samples ##################################################################### @@ -60,3 +60,26 @@ ) target_link_libraries(BasicScene OrthancStone) + +if(ENABLE_SDL_CONSOLE) + add_definitions( + -DENABLE_SDL_CONSOLE=1 + ) + LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.c") + LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.h") +endif() + +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureCommands.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureCommands.h") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTools.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTools.h") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTrackers.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTrackers.h") +LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSample.cpp") + +add_executable(TrackerSample + ${TRACKERSAMPLE_SOURCE} + ) + +target_link_libraries(TrackerSample OrthancStone) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/TrackerSample.cpp Thu May 09 10:41:31 2019 +0200 @@ -0,0 +1,555 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + + // From Stone +#include "../../Applications/Sdl/SdlOpenGLWindow.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2D/Scene2D.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/StoneInitialization.h" + +// From Orthanc framework +#include <Core/Logging.h> +#include <Core/OrthancException.h> +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Images/PngWriter.h> + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> + +#include <SDL.h> +#include <stdio.h> + + +// to be moved into Stone +#include "../Common/MeasureTrackers.h" +#include "../Common/MeasureCommands.h" + +/* +TODO: + +- to decouple the trackers from the sample, we need to supply them with + the scene rather than the app + +- in order to do that, we need a GetNextFreeZIndex function (or something + along those lines) in the scene object + +*/ + + +using namespace Orthanc; +using namespace OrthancStone; + +namespace OrthancStone +{ + enum GuiTool + { + GuiTool_Rotate = 0, + GuiTool_Pan, + GuiTool_Zoom, + GuiTool_LineMeasure, + GuiTool_CircleMeasure, + GuiTool_AngleMeasure, + GuiTool_EllipseMeasure, + GuiTool_LAST + }; + + const char* MeasureToolToString(size_t i) + { + static const char* descs[] = { + "GuiTool_Rotate", + "GuiTool_Pan", + "GuiTool_Zoom", + "GuiTool_LineMeasure", + "GuiTool_CircleMeasure", + "GuiTool_AngleMeasure", + "GuiTool_EllipseMeasure", + "GuiTool_LAST" + }; + if (i >= GuiTool_LAST) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong tool index"); + } + return descs[i]; + } +} + +class TrackerSampleApp +{ +public: + // 12 because. + TrackerSampleApp() : currentTool_(GuiTool_Rotate) + { + TEXTURE_2x2_1_ZINDEX = 1; + TEXTURE_1x1_ZINDEX = 2; + TEXTURE_2x2_2_ZINDEX = 3; + LINESET_1_ZINDEX = 4; + LINESET_2_ZINDEX = 5; + INFOTEXT_LAYER_ZINDEX = 6; + } + void PrepareScene(); + void Run(); + +private: + Scene2D& GetScene() + { + return scene_; + } + + void SelectNextTool() + { + currentTool_ = static_cast<GuiTool>(currentTool_ + 1); + if (currentTool_ == GuiTool_LAST) + currentTool_ = static_cast<GuiTool>(0);; + printf("Current tool is now: %s\n", MeasureToolToString(currentTool_)); + } + + void HandleApplicationEvent( + const OpenGLCompositor& compositor, + const SDL_Event& event, + std::auto_ptr<IPointerTracker>& activeTracker); + + IPointerTracker* TrackerSampleApp::TrackerHitTest(const PointerEvent& e); + + IPointerTracker* CreateSuitableTracker( + const SDL_Event& event, + const PointerEvent& e, + const OpenGLCompositor& compositor); + + void TakeScreenshot( + const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight); + + /** + This adds the command at the top of the undo stack + */ + void Commit(TrackerCommandPtr cmd); + void Undo(); + void Redo(); + +private: + static const unsigned int FONT_SIZE = 32; + + std::vector<TrackerCommandPtr> undoStack_; + + // we store the measure tools here so that they don't get deleted + std::vector<MeasureToolPtr> measureTools_; + + //static const int LAYER_POSITION = 150; +#if 0 + int TEXTURE_2x2_1_ZINDEX = 12; + int TEXTURE_1x1_ZINDEX = 13; + int TEXTURE_2x2_2_ZINDEX = 14; + int LINESET_1_ZINDEX = 50; + int LINESET_2_ZINDEX = 100; + int INFOTEXT_LAYER_ZINDEX = 150; +#else + int TEXTURE_2x2_1_ZINDEX; + int TEXTURE_1x1_ZINDEX; + int TEXTURE_2x2_2_ZINDEX; + int LINESET_1_ZINDEX; + int LINESET_2_ZINDEX; + int INFOTEXT_LAYER_ZINDEX; +#endif + Scene2D scene_; + GuiTool currentTool_; +}; + + +void TrackerSampleApp::PrepareScene() +{ + // Texture of 2x2 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t *p = reinterpret_cast<uint8_t*>(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast<uint8_t*>(i.GetRow(1)); + p[0] = 0; + p[1] = 0; + p[2] = 255; + + p[3] = 255; + p[4] = 0; + p[5] = 0; + + scene_.SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i)); + + std::auto_ptr<ColorTextureSceneLayer> l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-3, 2); + l->SetPixelSpacing(1.5, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene_.SetLayer(TEXTURE_2x2_2_ZINDEX, l.release()); + } + + // Texture of 1x1 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); + + uint8_t *p = reinterpret_cast<uint8_t*>(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + std::auto_ptr<ColorTextureSceneLayer> l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-2, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene_.SetLayer(TEXTURE_1x1_ZINDEX, l.release()); + } + + // Some lines + { + std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer); + + layer->SetThickness(1); + + PolylineSceneLayer::Chain chain; + chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5)); + chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5)); + layer->AddChain(chain, true); + + chain.clear(); + chain.push_back(ScenePoint2D(-5, -5)); + chain.push_back(ScenePoint2D(5, -5)); + chain.push_back(ScenePoint2D(5, 5)); + chain.push_back(ScenePoint2D(-5, 5)); + layer->AddChain(chain, true); + + double dy = 1.01; + chain.clear(); + chain.push_back(ScenePoint2D(-4, -4)); + chain.push_back(ScenePoint2D(4, -4 + dy)); + chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy)); + chain.push_back(ScenePoint2D(4, 2)); + layer->AddChain(chain, false); + + layer->SetColor(0, 255, 255); + scene_.SetLayer(LINESET_1_ZINDEX, layer.release()); + } + + // Some text + { + std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer); + layer->SetText("Hello"); + scene_.SetLayer(LINESET_2_ZINDEX, layer.release()); + } +} + + +void TrackerSampleApp::TakeScreenshot(const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight) +{ + // Take a screenshot, then save it as PNG file + CairoCompositor compositor(scene_, canvasWidth, canvasHeight); + compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE, Orthanc::Encoding_Latin1); + compositor.Refresh(); + + Orthanc::ImageAccessor canvas; + compositor.GetCanvas().GetReadOnlyAccessor(canvas); + + Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false); + Orthanc::ImageProcessing::Convert(png, canvas); + + Orthanc::PngWriter writer; + writer.WriteToFile(target, png); +} + + +IPointerTracker* TrackerSampleApp::TrackerHitTest(const PointerEvent& e) +{ + // std::vector<MeasureToolPtr> measureTools_; + return nullptr; +} + +IPointerTracker* TrackerSampleApp::CreateSuitableTracker( + const SDL_Event& event, + const PointerEvent& e, + const OpenGLCompositor& compositor) +{ + switch (event.button.button) + { + case SDL_BUTTON_MIDDLE: + return new PanSceneTracker(scene_, e); + + case SDL_BUTTON_RIGHT: + return new ZoomSceneTracker( + scene_, e, compositor.GetCanvasHeight()); + + case SDL_BUTTON_LEFT: + { + // TODO: we need to iterate on the set of measuring tool and perform + // a hit test to check if a tracker needs to be created for edition. + // Otherwise, depending upon the active tool, we might want to create + // a "measuring tool creation" tracker + + // TODO: if there are conflicts, we should prefer a tracker that + // pertains to the type of measuring tool currently selected (TBD?) + IPointerTracker* hitTestTracker = TrackerHitTest(e); + + if (hitTestTracker != NULL) + { + return hitTestTracker; + } + else + { + switch (currentTool_) + { + case GuiTool_Rotate: + return new RotateSceneTracker(scene_, e); + case GuiTool_LineMeasure: + return new CreateLineMeasureTracker( + scene_, undoStack_, measureTools_, e); + //case GuiTool_AngleMeasure: + // return new AngleMeasureTracker(scene_, measureTools_, undoStack_, e); + //case GuiTool_CircleMeasure: + // return new CircleMeasureTracker(scene_, measureTools_, undoStack_, e); + //case GuiTool_EllipseMeasure: + // return new EllipseMeasureTracker(scene_, measureTools_, undoStack_, e); + default: + throw OrthancException(ErrorCode_InternalError, "Wrong tool!"); + } + } + } + default: + return NULL; + } +} + +void TrackerSampleApp::HandleApplicationEvent( + const OpenGLCompositor& compositor, + const SDL_Event& event, + std::auto_ptr<IPointerTracker>& activeTracker) +{ + if (event.type == SDL_MOUSEMOTION) + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + if (activeTracker.get() == NULL && + SDL_SCANCODE_LCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_LCTRL]) + { + // The "left-ctrl" key is down, while no tracker is present + + PointerEvent e; + e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + + ScenePoint2D p = e.GetMainPosition().Apply(scene_.GetCanvasToSceneTransform()); + + char buf[64]; + sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY()); + + if (scene_.HasLayer(INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = + dynamic_cast<TextSceneLayer&>(scene_.GetLayer(INFOTEXT_LAYER_ZINDEX)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer); + layer->SetColor(0, 255, 0); + layer->SetText(buf); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_BottomCenter); + layer->SetPosition(p.GetX(), p.GetY()); + scene_.SetLayer(INFOTEXT_LAYER_ZINDEX, layer.release()); + } + } + else + { + scene_.DeleteLayer(INFOTEXT_LAYER_ZINDEX); + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + PointerEvent e; + e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + + activeTracker.reset(CreateSuitableTracker(event, e, compositor)); + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_s: + scene_.FitContent(compositor.GetCanvasWidth(), + compositor.GetCanvasHeight()); + break; + + case SDLK_c: + TakeScreenshot( + "screenshot.png", + compositor.GetCanvasWidth(), + compositor.GetCanvasHeight()); + break; + + default: + break; + } + } +} + + +static void GLAPIENTRY +OpenGLMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) +{ + if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) + { + fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", + (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), + type, severity, message); + } +} + +bool g_stopApplication = false; + +void TrackerSampleApp::Run() +{ + SdlOpenGLWindow window("Hello", 1024, 768); + + scene_.FitContent(window.GetCanvasWidth(), window.GetCanvasHeight()); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + OpenGLCompositor compositor(window, scene_); + compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE, Orthanc::Encoding_Latin1); + + // this will either be empty or contain the current tracker, if any + std::auto_ptr<IPointerTracker> tracker; + + + while (!g_stopApplication) + { + compositor.Refresh(); + + SDL_Event event; + while (!g_stopApplication && SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + g_stopApplication = true; + break; + } + else if (event.type == SDL_MOUSEMOTION) + { + if (tracker.get() != NULL) + { + PointerEvent e; + e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + LOG(TRACE) << "event.button.x = " << event.button.x << " " << + "event.button.y = " << event.button.y; + tracker->Update(e); + } + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + if (tracker.get() != NULL) + { + tracker->Release(); + tracker.reset(NULL); + } + } + else if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + tracker.reset(NULL); + compositor.UpdateSize(); + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + window.GetWindow().ToggleMaximize(); + break; + + case SDLK_q: + g_stopApplication = true; + break; + + case SDLK_t: + SelectNextTool(); + break; + + default: + break; + } + } + HandleApplicationEvent(compositor, event, tracker); + } + SDL_Delay(1); + } +} + + +/** + * IMPORTANT: The full arguments to "main()" are needed for SDL on + * Windows. Otherwise, one gets the linking error "undefined reference + * to `SDL_main'". https://wiki.libsdl.org/FAQWindows + **/ +int main(int argc, char* argv[]) +{ + StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + + try + { + TrackerSampleApp app; + app.PrepareScene(); + app.Run(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +}