Mercurial > hg > orthanc-stone
changeset 902:fa5b945f8db8 am-dev
Merge
author | Alain Mazy <alain@mazy.be> |
---|---|
date | Wed, 17 Jul 2019 09:53:51 +0200 |
parents | f9ae731fdc25 (diff) 240359ab1651 (current diff) |
children | ef6e425dc79f |
files | |
diffstat | 39 files changed, 2688 insertions(+), 129 deletions(-) [+] |
line wrap: on
line diff
--- a/Applications/Generic/GuiAdapter.cpp Wed Jul 17 09:39:51 2019 +0200 +++ b/Applications/Generic/GuiAdapter.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -41,14 +41,14 @@ widgets_.push_back(widget); } - std::ostream& operator<<( - std::ostream& os, const GuiAdapterKeyboardEvent& event) - { - os << "sym: " << event.sym << " (" << (int)(event.sym[0]) << ") ctrl: " << event.ctrlKey << ", " << - "shift: " << event.shiftKey << ", " << - "alt: " << event.altKey; - return os; - } + std::ostream& operator<<( + std::ostream& os, const GuiAdapterKeyboardEvent& event) + { + os << "sym: " << event.sym << " (" << (int)(event.sym[0]) << ") ctrl: " << event.ctrlKey << ", " << + "shift: " << event.shiftKey << ", " << + "alt: " << event.altKey; + return os; + } #if ORTHANC_ENABLE_WASM == 1 void GuiAdapter::Run() @@ -428,15 +428,15 @@ switch (source.button.button) { case SDL_BUTTON_MIDDLE: - dest.button = 1; + dest.button =GUIADAPTER_MOUSEBUTTON_MIDDLE; break; case SDL_BUTTON_RIGHT: - dest.button = 2; + dest.button = GUIADAPTER_MOUSEBUTTON_RIGHT; break; case SDL_BUTTON_LEFT: - dest.button = 0; + dest.button = GUIADAPTER_MOUSEBUTTON_LEFT; break; default: @@ -573,14 +573,14 @@ // the SDL window name IS the canvas name ("canvas" is used because this lib // is designed for Wasm - SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); - ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); + SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); + ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow); - ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); + ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); std::string windowTitle(windowTitleSz); - ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); + ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); switch (event.mouse.type) { @@ -604,14 +604,14 @@ ORTHANC_ASSERT(event.sym[0] != 0); ORTHANC_ASSERT(event.sym[1] == 0); - SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); - ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); + SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); + ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow); - ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); + ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); std::string windowTitle(windowTitleSz); - ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); + ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); switch (event.type) { @@ -636,23 +636,23 @@ // SDL ONLY void GuiAdapter::OnMouseEvent(uint32_t windowID, const GuiAdapterMouseEvent& event) { - if (windowID == 0) - { - LOG(WARNING) << "GuiAdapter::OnMouseEvent -- windowID == 0 and event won't be routed!"; - } - else - { + if (windowID == 0) + { + LOG(WARNING) << "GuiAdapter::OnMouseEvent -- windowID == 0 and event won't be routed!"; + } + else + { // the SDL window name IS the canvas name ("canvas" is used because this lib // is designed for Wasm - SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); - - ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); + SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); + + ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow); - ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); + ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); std::string windowTitle(windowTitleSz); - ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); + ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); switch (event.type) { @@ -692,7 +692,7 @@ //ORTHANC_ASSERT(foundWidget, "WindowID " << windowID << " was not found in the registered widgets!"); //if(foundWidget) // foundWidget-> - } + } } // SDL ONLY @@ -788,8 +788,8 @@ } #endif } - else if (event.type == SDL_MOUSEWHEEL) - { + else if (event.type == SDL_MOUSEWHEEL) + { int scancodeCount = 0; const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); @@ -810,21 +810,21 @@ GuiAdapterWheelEvent dest; ConvertFromPlatform(dest, ctrlPressed, shiftPressed, altPressed, event); - OnMouseWheelEvent(event.window.windowID, dest); - - //KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); - - //int x, y; - //SDL_GetMouseState(&x, &y); - - //if (event.wheel.y > 0) - //{ - // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers); - //} - //else if (event.wheel.y < 0) - //{ - // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers); - //} + OnMouseWheelEvent(event.window.windowID, dest); + + //KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); + + //int x, y; + //SDL_GetMouseState(&x, &y); + + //if (event.wheel.y > 0) + //{ + // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers); + //} + //else if (event.wheel.y < 0) + //{ + // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers); + //} } else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
--- a/Applications/Generic/GuiAdapter.h Wed Jul 17 09:39:51 2019 +0200 +++ b/Applications/Generic/GuiAdapter.h Wed Jul 17 09:53:51 2019 +0200 @@ -67,6 +67,14 @@ }; + enum GuiAdapterMouseButtonType + { + GUIADAPTER_MOUSEBUTTON_LEFT = 0, + GUIADAPTER_MOUSEBUTTON_MIDDLE = 1, + GUIADAPTER_MOUSEBUTTON_RIGHT = 2 + }; + + enum GuiAdapterHidEventType { GUIADAPTER_EVENT_MOUSEDOWN = 1973, @@ -140,6 +148,14 @@ //long canvasX; //long canvasY; //long padding; + + public: + GuiAdapterMouseEvent() + : ctrlKey(false), + shiftKey(false), + altKey(false) + { + } }; struct GuiAdapterWheelEvent { @@ -162,7 +178,7 @@ bool altKey; }; - std::ostream& operator<<(std::ostream& os, const GuiAdapterKeyboardEvent& event); + std::ostream& operator<<(std::ostream& os, const GuiAdapterKeyboardEvent& event); /* Mousedown event trigger when either the left or right (or middle) mouse is pressed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Generic/Scene2DInteractor.h Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,52 @@ +/** + * 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/PointerEvent.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" +#include "../../Framework/Scene2D/Internals/CompositorHelper.h" +#include "GuiAdapter.h" + + +namespace OrthancStone +{ + + class Scene2DInteractor + { + protected: + boost::shared_ptr<ViewportController> viewportController_; + boost::shared_ptr<ICompositor> compositor_; + + public: + Scene2DInteractor(boost::shared_ptr<ViewportController> viewportController) : + viewportController_(viewportController) + {} + + void SetCompositor(boost::shared_ptr<ICompositor> compositor) + { + compositor_ = compositor; + } + + virtual bool OnMouseEvent(const GuiAdapterMouseEvent& guiEvent, const PointerEvent& pointerEvent) = 0; // returns true if it has handled the event + virtual bool OnKeyboardEvent(const GuiAdapterKeyboardEvent& guiEvent) = 0; // returns true if it has handled the event + virtual bool OnWheelEvent(const GuiAdapterWheelEvent& guiEvent) = 0; // returns true if it has handled the event + + }; +}
--- a/Applications/Qt/QCairoWidget.h Wed Jul 17 09:39:51 2019 +0200 +++ b/Applications/Qt/QCairoWidget.h Wed Jul 17 09:53:51 2019 +0200 @@ -21,8 +21,8 @@ #pragma once #include "../../Applications/Generic/NativeStoneApplicationContext.h" -#include "../../Framework/Viewport/CairoSurface.h" -#include "../../Framework/Widgets/IWidget.h" +#include "../../Framework/Wrappers/CairoSurface.h" +#include "../../Framework/Deprecated/Widgets/IWidget.h" #include <QWidget> #include <memory>
--- a/Applications/Qt/QtStoneApplicationRunner.cpp Wed Jul 17 09:39:51 2019 +0200 +++ b/Applications/Qt/QtStoneApplicationRunner.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -27,7 +27,7 @@ #include <boost/program_options.hpp> #include <QApplication> -#include "../../Framework/Toolbox/MessagingToolbox.h" +#include "../../Framework/Deprecated/Toolbox/MessagingToolbox.h" #include <Core/Logging.h> #include <Core/HttpClient.h>
--- a/Framework/Deprecated/Toolbox/BaseWebService.cpp Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Deprecated/Toolbox/BaseWebService.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -27,6 +27,8 @@ #include <Core/OrthancException.h> #include <boost/shared_ptr.hpp> +#include <algorithm> +#include <Core/Logging.h> namespace Deprecated { @@ -89,7 +91,7 @@ OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, unsigned int timeoutInSeconds) { - if (cache_.find(uri) == cache_.end()) + if (!cacheEnabled_ || cache_.find(uri) == cache_.end()) { GetAsyncInternal(uri, headers, new BaseWebService::BaseWebServicePayload(successCallback, failureCallback, payload), // ownership is transfered @@ -101,6 +103,15 @@ } else { + // put the uri on top of the most recently accessed list + std::deque<std::string>::iterator it = std::find(orderedCacheKeys_.begin(), orderedCacheKeys_.end(), uri); + if (it != orderedCacheKeys_.end()) + { + std::string uri = *it; + orderedCacheKeys_.erase(it); + orderedCacheKeys_.push_front(uri); + } + // create a command and "post" it to the Oracle so it is executed and commited "later" NotifyHttpSuccessLater(cache_[uri], payload, successCallback); } @@ -123,7 +134,28 @@ void BaseWebService::CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message) { - cache_[message.GetUri()] = boost::shared_ptr<CachedHttpRequestSuccessMessage>(new CachedHttpRequestSuccessMessage(message)); + if (cacheEnabled_) + { + while (cacheCurrentSize_ + message.GetAnswerSize() > cacheMaxSize_ && orderedCacheKeys_.size() > 0) + { + VLOG(1) << "BaseWebService: clearing cache: " << cacheCurrentSize_ << "/" << cacheMaxSize_ << "(" << message.GetAnswerSize() << ")"; + const std::string& oldestUri = orderedCacheKeys_.back(); + HttpCache::iterator it = cache_.find(oldestUri); + if (it != cache_.end()) + { + cacheCurrentSize_ -= it->second->GetAnswerSize(); + cache_.erase(it); + } + orderedCacheKeys_.pop_back(); + + } + + boost::shared_ptr<CachedHttpRequestSuccessMessage> cachedMessage(new CachedHttpRequestSuccessMessage(message)); + cache_[message.GetUri()] = cachedMessage; + orderedCacheKeys_.push_front(message.GetUri()); + cacheCurrentSize_ += message.GetAnswerSize(); + } + NotifyHttpSuccess(message); }
--- a/Framework/Deprecated/Toolbox/BaseWebService.h Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Deprecated/Toolbox/BaseWebService.h Wed Jul 17 09:53:51 2019 +0200 @@ -25,6 +25,7 @@ #include <string> #include <map> +#include <deque> namespace Deprecated { @@ -81,14 +82,21 @@ class BaseWebServicePayload; bool cacheEnabled_; - std::map<std::string, boost::shared_ptr<CachedHttpRequestSuccessMessage> > cache_; // TODO: this is currently an infinite cache ! + size_t cacheCurrentSize_; + size_t cacheMaxSize_; + + typedef std::map<std::string, boost::shared_ptr<CachedHttpRequestSuccessMessage> > HttpCache; + HttpCache cache_; + std::deque<std::string> orderedCacheKeys_; public: BaseWebService(OrthancStone::MessageBroker& broker) : IWebService(broker), IObserver(broker), - cacheEnabled_(true) + cacheEnabled_(false), + cacheCurrentSize_(0), + cacheMaxSize_(100*1024*1024) { }
--- a/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -73,7 +73,7 @@ std::auto_ptr< OrthancStone::MessageHandler<BinaryResponseReadyMessage> > binaryHandler_; std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage> > failureHandler_; std::auto_ptr< Orthanc::IDynamicObject > userPayload_; - + OrthancStone::MessageBroker& broker_; void NotifyConversionError(const IWebService::HttpRequestSuccessMessage& message) const { if (failureHandler_.get() != NULL) @@ -84,12 +84,15 @@ } public: - WebServicePayload(OrthancStone::MessageHandler<EmptyResponseReadyMessage>* handler, + WebServicePayload(OrthancStone::MessageBroker& broker, + OrthancStone::MessageHandler<EmptyResponseReadyMessage>* handler, OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, Orthanc::IDynamicObject* userPayload) : emptyHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload) + userPayload_(userPayload), + broker_(broker) + { if (handler == NULL) { @@ -97,12 +100,14 @@ } } - WebServicePayload(OrthancStone::MessageHandler<BinaryResponseReadyMessage>* handler, + WebServicePayload(OrthancStone::MessageBroker& broker, + OrthancStone::MessageHandler<BinaryResponseReadyMessage>* handler, OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, Orthanc::IDynamicObject* userPayload) : binaryHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload) + userPayload_(userPayload), + broker_(broker) { if (handler == NULL) { @@ -110,12 +115,14 @@ } } - WebServicePayload(OrthancStone::MessageHandler<JsonResponseReadyMessage>* handler, + WebServicePayload(OrthancStone::MessageBroker& broker, + OrthancStone::MessageHandler<JsonResponseReadyMessage>* handler, OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, Orthanc::IDynamicObject* userPayload) : jsonHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload) + userPayload_(userPayload), + broker_(broker) { if (handler == NULL) { @@ -127,26 +134,35 @@ { if (emptyHandler_.get() != NULL) { - emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage - (message.GetUri(), userPayload_.get())); + if (broker_.IsActive(*(emptyHandler_->GetObserver()))) + { + emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage + (message.GetUri(), userPayload_.get())); + } } else if (binaryHandler_.get() != NULL) { - binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage - (message.GetUri(), message.GetAnswer(), - message.GetAnswerSize(), userPayload_.get())); + if (broker_.IsActive(*(binaryHandler_->GetObserver()))) + { + binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage + (message.GetUri(), message.GetAnswer(), + message.GetAnswerSize(), userPayload_.get())); + } } else if (jsonHandler_.get() != NULL) { - Json::Value response; - if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) + if (broker_.IsActive(*(jsonHandler_->GetObserver()))) { - jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage - (message.GetUri(), response, userPayload_.get())); - } - else - { - NotifyConversionError(message); + Json::Value response; + if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) + { + jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage + (message.GetUri(), response, userPayload_.get())); + } + else + { + NotifyConversionError(message); + } } } else @@ -186,7 +202,7 @@ IWebService::HttpHeaders emptyHeaders; web_.GetAsync(baseUrl_ + uri, emptyHeaders, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> @@ -216,7 +232,7 @@ // printf("GET [%s] [%s]\n", baseUrl_.c_str(), uri.c_str()); web_.GetAsync(baseUrl_ + uri, headers, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> @@ -232,7 +248,7 @@ Orthanc::IDynamicObject* payload) { web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> @@ -255,7 +271,7 @@ Orthanc::IDynamicObject* payload /* takes ownership */) { web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> @@ -302,7 +318,7 @@ Orthanc::IDynamicObject* payload) { web_.DeleteAsync(baseUrl_ + uri, IWebService::HttpHeaders(), - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
--- a/Framework/Radiography/RadiographyDicomLayer.h Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.h Wed Jul 17 09:53:51 2019 +0200 @@ -60,6 +60,22 @@ return frame_; } + virtual size_t GetApproximateMemoryUsage() const + { + size_t size = 0; + if (source_.get() != NULL) + { + size += source_->GetPitch() * source_->GetHeight(); + } + if (converted_.get() != NULL) + { + size += converted_->GetPitch() * converted_->GetHeight(); + } + + return size; + } + + void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset); void SetSourceImage(Orthanc::ImageAccessor* image); // Takes ownership
--- a/Framework/Radiography/RadiographyLayer.h Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Radiography/RadiographyLayer.h Wed Jul 17 09:53:51 2019 +0200 @@ -355,5 +355,10 @@ float& maxValue) const = 0; friend class RadiographyMaskLayer; // because it needs to GetTransform on the dicomLayer it relates to + + virtual size_t GetApproximateMemoryUsage() const // this is used to limit the number of scenes loaded in RAM when resources are limited (we actually only count the size used by the images, not the C structs) + { + return 0; + } }; }
--- a/Framework/Radiography/RadiographyMaskLayer.h Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Radiography/RadiographyMaskLayer.h Wed Jul 17 09:53:51 2019 +0200 @@ -49,6 +49,18 @@ { } + virtual size_t GetApproximateMemoryUsage() const + { + size_t size = 0; + if (mask_.get() != NULL) + { + size += mask_->GetPitch() * mask_->GetHeight(); + } + + return size; + } + + void SetCorners(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners); void SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index);
--- a/Framework/Radiography/RadiographyScene.cpp Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -147,6 +147,16 @@ return *layer; } + size_t RadiographyScene::GetApproximateMemoryUsage() const + { + size_t size = 0; + for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++) + { + size += it->second->GetApproximateMemoryUsage(); + } + return size; + } + void RadiographyScene::OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message) { BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin()));
--- a/Framework/Radiography/RadiographyScene.h Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.h Wed Jul 17 09:53:51 2019 +0200 @@ -163,6 +163,8 @@ virtual ~RadiographyScene(); + virtual size_t GetApproximateMemoryUsage() const; + bool GetWindowing(float& center, float& width) const;
--- a/Framework/Radiography/RadiographySceneReader.cpp Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Radiography/RadiographySceneReader.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -60,6 +60,11 @@ if (version != 1) throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + if (input.isMember("hasWindowing") && input["hasWindowing"].asBool()) + { + scene_.SetWindowing(input["windowCenter"].asFloat(), input["windowWidth"].asFloat()); + } + RadiographyDicomLayer* dicomLayer = NULL; for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++) { @@ -143,6 +148,11 @@ if (version != 1) throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + if (input.isMember("hasWindowing") && input["hasWindowing"].asBool()) + { + scene_.SetWindowing(input["windowCenter"].asFloat(), input["windowWidth"].asFloat()); + } + RadiographyDicomLayer* dicomLayer = NULL; for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++) {
--- a/Framework/Radiography/RadiographySceneWriter.cpp Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Radiography/RadiographySceneWriter.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -30,6 +30,14 @@ void RadiographySceneWriter::Write(Json::Value& output, const RadiographyScene& scene) { output["version"] = 1; + float windowCenter, windowWidth; + bool hasWindowing = scene.GetWindowing(windowCenter, windowWidth); + output["hasWindowing"] = hasWindowing; + if (hasWindowing) + { + output["windowCenter"] = windowCenter; + output["windowWidth"] = windowWidth; + } output["layers"] = Json::arrayValue; std::vector<size_t> layersIndexes;
--- a/Framework/Scene2D/CairoCompositor.h Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Scene2D/CairoCompositor.h Wed Jul 17 09:53:51 2019 +0200 @@ -29,6 +29,7 @@ namespace OrthancStone { class CairoCompositor : + public ICompositor, private Internals::CompositorHelper::IRendererFactory, private Internals::ICairoContextProvider { @@ -58,12 +59,12 @@ return canvas_; } - unsigned int GetCanvasWidth() const + virtual unsigned int GetCanvasWidth() const { return canvas_.GetWidth(); } - unsigned int GetCanvasHeight() const + virtual unsigned int GetCanvasHeight() const { return canvas_.GetHeight(); } @@ -78,7 +79,7 @@ Orthanc::Encoding codepage); #endif - void Refresh(); + virtual void Refresh(); Orthanc::ImageAccessor* RenderText(size_t fontIndex, const std::string& utf8) const;
--- a/Framework/Scene2D/Internals/CompositorHelper.h Wed Jul 17 09:39:51 2019 +0200 +++ b/Framework/Scene2D/Internals/CompositorHelper.h Wed Jul 17 09:53:51 2019 +0200 @@ -22,13 +22,33 @@ #pragma once #include "../Scene2D.h" - +#include "../ScenePoint2D.h" #include <boost/noncopyable.hpp> #include <map> namespace OrthancStone { + class ICompositor : public boost::noncopyable + { + public: + virtual ~ICompositor() + { + } + + virtual unsigned int GetCanvasWidth() const = 0; + virtual unsigned int GetCanvasHeight() const = 0; + virtual void Refresh() = 0; + + ScenePoint2D GetPixelCenterCoordinates(int x, int y) const + { + return ScenePoint2D( + static_cast<double>(x) + 0.5 - static_cast<double>(GetCanvasWidth()) / 2.0, + static_cast<double>(y) + 0.5 - static_cast<double>(GetCanvasHeight()) / 2.0); + } + + }; + namespace Internals { class CompositorHelper : protected Scene2D::IVisitor
--- a/Platforms/Wasm/Defaults.cpp Wed Jul 17 09:39:51 2019 +0200 +++ b/Platforms/Wasm/Defaults.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -41,6 +41,41 @@ extern "C" { #endif +#if 0 + // rewrite malloc/free in order to monitor allocations. We actually only monitor large allocations (like images ...) + + size_t bigChunksTotalSize = 0; + std::map<void*, size_t> allocatedBigChunks; + + extern void* emscripten_builtin_malloc(size_t bytes); + extern void emscripten_builtin_free(void* mem); + + void * __attribute__((noinline)) malloc(size_t size) + { + void *ptr = emscripten_builtin_malloc(size); + if (size > 100000) + { + bigChunksTotalSize += size; + printf("++ Allocated %zu bytes, got %p. (%zu MB consumed by big chunks)\n", size, ptr, bigChunksTotalSize/(1024*1024)); + allocatedBigChunks[ptr] = size; + } + return ptr; + } + + void __attribute__((noinline)) free(void *ptr) + { + emscripten_builtin_free(ptr); + + std::map<void*, size_t>::iterator it = allocatedBigChunks.find(ptr); + if (it != allocatedBigChunks.end()) + { + bigChunksTotalSize -= it->second; + printf("-- Freed %zu bytes at %p. (%zu MB consumed by big chunks)\n", it->second, ptr, bigChunksTotalSize/(1024*1024)); + allocatedBigChunks.erase(it); + } + } +#endif // 0 + using namespace OrthancStone; // when WASM needs a C++ viewport @@ -275,7 +310,7 @@ float x2, float y2) { - printf("touch start with %d touches\n", touchCount); + // printf("touch start with %d touches\n", touchCount); std::vector<Deprecated::Touch> touches; GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); @@ -291,7 +326,7 @@ float x2, float y2) { - printf("touch move with %d touches\n", touchCount); + // printf("touch move with %d touches\n", touchCount); std::vector<Deprecated::Touch> touches; GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); @@ -307,7 +342,7 @@ float x2, float y2) { - printf("touch end with %d touches remaining\n", touchCount); + // printf("touch end with %d touches remaining\n", touchCount); std::vector<Deprecated::Touch> touches; GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); @@ -362,14 +397,14 @@ { 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("SendSerializedMessageToStoneApplication\n"); - printf("%s", message); + //printf("SendSerializedMessageToStoneApplication\n"); + //printf("%s", message); if (applicationWasmAdapter.get() != NULL) { applicationWasmAdapter->HandleSerializedMessageFromWeb(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, unable to send messages"); return NULL; }
--- a/Platforms/Wasm/logger.ts Wed Jul 17 09:39:51 2019 +0200 +++ b/Platforms/Wasm/logger.ts Wed Jul 17 09:53:51 2019 +0200 @@ -73,7 +73,7 @@ private getOutput(source: LogSource, args: any[]): any[] { var prefix = this.getPrefix(); - var prefixAndSource = []; + var prefixAndSource = Array<string>(); if (prefix != null) { prefixAndSource = [prefix]; @@ -94,7 +94,7 @@ return [...prefixAndSource, ...args]; } - protected getPrefix(): string { + protected getPrefix(): string | null { return null; } }
--- a/Platforms/Wasm/wasm-application-runner.ts Wed Jul 17 09:39:51 2019 +0200 +++ b/Platforms/Wasm/wasm-application-runner.ts Wed Jul 17 09:53:51 2019 +0200 @@ -1,5 +1,5 @@ -import Stone = require('./stone-framework-loader'); -import StoneViewport = require('./wasm-viewport'); +import * as Stone from './stone-framework-loader' +import * as StoneViewport from './wasm-viewport' import * as Logger from './logger' if (!('WebAssembly' in window)) { @@ -130,11 +130,6 @@ Logger.defaultLogger.debug("Connecting C++ methods to JS methods - done"); - // Prevent scrolling - document.body.addEventListener('touchmove', function (event) { - event.preventDefault(); - }, { passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface - _InitializeWasmApplication(orthancBaseUrl); }); }
--- a/Platforms/Wasm/wasm-viewport.ts Wed Jul 17 09:39:51 2019 +0200 +++ b/Platforms/Wasm/wasm-viewport.ts Wed Jul 17 09:53:51 2019 +0200 @@ -1,4 +1,4 @@ -import wasmApplicationRunner = require('./wasm-application-runner'); +import * as wasmApplicationRunner from './wasm-application-runner' import * as Logger from './logger' var isPendingRedraw = false; @@ -10,14 +10,17 @@ Logger.defaultLogger.debug('Scheduling a refresh of the viewport, as its content changed'); window.requestAnimationFrame(function() { isPendingRedraw = false; - WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw(); + let viewport = WasmViewport.GetFromCppViewport(cppViewportHandle); + if (viewport) { + viewport.Redraw(); + } }); } } (<any>window).ScheduleWebViewportRedraw = ScheduleWebViewportRedraw; -declare function UTF8ToString(any): string; +declare function UTF8ToString(v: any): string; function CreateWasmViewport(htmlCanvasId: string) : any { var cppViewportHandle = wasmApplicationRunner.CreateCppViewport(); @@ -38,7 +41,7 @@ private module_ : any; private canvasId_ : string; private htmlCanvas_ : HTMLCanvasElement; - private context_ : CanvasRenderingContext2D; + private context_ : CanvasRenderingContext2D | null; private imageData_ : any = null; private renderingBuffer_ : any = null; @@ -96,20 +99,20 @@ return this.pimpl_; } - public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport { + public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport | null { if (WasmViewport.viewportsMapByCppHandle_[cppViewportHandle] !== undefined) { return WasmViewport.viewportsMapByCppHandle_[cppViewportHandle]; } Logger.defaultLogger.error("WasmViewport not found !"); - return undefined; + return null; } - public static GetFromCanvasId(canvasId: string) : WasmViewport { + public static GetFromCanvasId(canvasId: string) : WasmViewport | null { if (WasmViewport.viewportsMapByCanvasId_[canvasId] !== undefined) { return WasmViewport.viewportsMapByCanvasId_[canvasId]; } Logger.defaultLogger.error("WasmViewport not found !"); - return undefined; + return null; } public static ResizeAll() { @@ -135,7 +138,9 @@ this.renderingBuffer_, this.imageData_.width * this.imageData_.height * 4)); - this.context_.putImageData(this.imageData_, 0, 0); + if (this.context_) { + this.context_.putImageData(this.imageData_, 0, 0); + } } } @@ -147,25 +152,27 @@ } // width/height is defined by the parent width/height - this.htmlCanvas_.width = this.htmlCanvas_.parentElement.offsetWidth; - this.htmlCanvas_.height = this.htmlCanvas_.parentElement.offsetHeight; + if (this.htmlCanvas_.parentElement) { + this.htmlCanvas_.width = this.htmlCanvas_.parentElement.offsetWidth; + this.htmlCanvas_.height = this.htmlCanvas_.parentElement.offsetHeight; - Logger.defaultLogger.debug("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); + Logger.defaultLogger.debug("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); - if (this.imageData_ === null) { - this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height); - this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); - - if (this.renderingBuffer_ != null) { - this.module_._free(this.renderingBuffer_); + if (this.imageData_ === null && this.context_) { + this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height); + this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); + + if (this.renderingBuffer_ != null) { + this.module_._free(this.renderingBuffer_); + } + + this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4); + } else { + this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); } - this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4); - } else { - this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); + this.Redraw(); } - - this.Redraw(); } public Initialize() { @@ -211,7 +218,7 @@ }); window.addEventListener('keydown', function(event) { - var keyChar = event.key; + var keyChar: string | null = event.key; var keyCode = event.keyCode if (keyChar.length == 1) { keyCode = 0; // maps to OrthancStone::KeyboardKeys_Generic @@ -328,7 +335,7 @@ this.touchZoom_ = false; } - public GetTouchTranslation(event) { + public GetTouchTranslation(event: any) { var touch = event.targetTouches[0]; return [ touch.pageX, @@ -336,7 +343,7 @@ ]; } - public GetTouchZoom(event) { + public GetTouchZoom(event: any) { var touch1 = event.targetTouches[0]; var touch2 = event.targetTouches[1]; var dx = (touch1.pageX - touch2.pageX);
--- a/Resources/CodeGeneration/template.in.h.j2 Wed Jul 17 09:39:51 2019 +0200 +++ b/Resources/CodeGeneration/template.in.h.j2 Wed Jul 17 09:53:51 2019 +0200 @@ -51,7 +51,7 @@ inline Json::Value _StoneSerializeValue(int64_t value) { - Json::Value result(value); + Json::Value result(static_cast<Json::Value::Int64>(value)); return result; } @@ -79,7 +79,7 @@ inline Json::Value _StoneSerializeValue(uint64_t value) { - Json::Value result(value); + Json::Value result(static_cast<Json::Value::UInt64>(value)); return result; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/BasicScene.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,116 @@ +/** + * 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/>. + **/ + +#define GLEW_STATIC 1 +// From Stone +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include "../../Applications/Sdl/SdlWindow.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/Scene2DViewport/ViewportController.h" +#include "../../Framework/Scene2DViewport/UndoStack.h" + +#include "../../Framework/StoneInitialization.h" +#include "../../Framework/Messages/MessageBroker.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/make_shared.hpp> +#include <boost/ref.hpp> +#include "EmbeddedResources.h" + +//#include <SDL.h> +#include <stdio.h> +#include <QDebug> +#include <QWindow> + +#include "../Shared/SharedBasicScene.h" + + +using namespace OrthancStone; + + + +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 ); + } +} + +extern void InitGL(); + +#include <QApplication> +#include "BasicSceneWindow.h" + +int main(int argc, char* argv[]) +{ + { + QApplication a(argc, argv); + + QSurfaceFormat requestedFormat; + requestedFormat.setVersion( 2, 0 ); + + OrthancStone::Samples::BasicSceneWindow window; + window.show(); + + MessageBroker broker; + boost::shared_ptr<UndoStack> undoStack(new UndoStack); + boost::shared_ptr<ViewportController> controller = boost::make_shared<ViewportController>( + undoStack, boost::ref(broker)); + PrepareScene(controller->GetScene()); + + boost::shared_ptr<OrthancStone::Scene2DInteractor> interactor(new BasicScene2DInteractor(controller)); + window.GetOpenGlWidget().SetInteractor(interactor); + + QOpenGLContext * context = new QOpenGLContext; + context->setFormat( requestedFormat ); + context->create(); + context->makeCurrent(window.GetOpenGlWidget().context()->surface()); + + boost::shared_ptr<OpenGLCompositor> compositor = boost::make_shared<OpenGLCompositor>(window.GetOpenGlWidget(), *controller->GetScene()); + compositor->SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + BASIC_SCENE_FONT_SIZE, Orthanc::Encoding_Latin1); + + interactor->SetCompositor(compositor); + window.GetOpenGlWidget().SetCompositor(compositor); + + return a.exec(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/BasicSceneWindow.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,58 @@ +/** + * 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 "../../Framework/OpenGL/OpenGLIncludes.h" +#include "BasicSceneWindow.h" + +/** + * Don't use "ui_MainWindow.h" instead of <ui_MainWindow.h> below, as + * this makes CMake unable to detect when the UI file changes. + **/ +#include <ui_BasicSceneWindow.h> +#include "../../Applications/Samples/SampleApplicationBase.h" + +namespace OrthancStone +{ + namespace Samples + { + + BasicSceneWindow::BasicSceneWindow( + QWidget *parent) : + ui_(new Ui::BasicSceneWindow) + { + ui_->setupUi(this); + } + + BasicSceneWindow::~BasicSceneWindow() + { + delete ui_; + } + + QStoneOpenGlWidget& BasicSceneWindow::GetOpenGlWidget() + { + return *(ui_->centralWidget); + } + + void BasicSceneWindow::SetCompositor(boost::shared_ptr<OrthancStone::OpenGLCompositor> compositor) + { + ui_->centralWidget->SetCompositor(compositor); + } + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/BasicSceneWindow.h Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,55 @@ +/** + * 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 <QMainWindow> +#include <QStoneOpenGlWidget.h> +// #include "../../Qt/QCairoWidget.h" +// #include "../../Qt/QStoneMainWindow.h" + +namespace Ui +{ + class BasicSceneWindow; +} + +namespace OrthancStone +{ + namespace Samples + { + + //class SampleSingleCanvasApplicationBase; + + class BasicSceneWindow : public QMainWindow + { + Q_OBJECT + + private: + Ui::BasicSceneWindow* ui_; + //SampleSingleCanvasApplicationBase& stoneSampleApplication_; + + public: + explicit BasicSceneWindow(QWidget *parent = 0); + ~BasicSceneWindow(); + + QStoneOpenGlWidget& GetOpenGlWidget(); + + void SetCompositor(boost::shared_ptr<OpenGLCompositor> compositor); + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/BasicSceneWindow.ui Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BasicSceneWindow</class> + <widget class="QMainWindow" name="BasicSceneWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>903</width> + <height>634</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>500</width> + <height>300</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>500</width> + <height>300</height> + </size> + </property> + <property name="windowTitle"> + <string>Stone of Orthanc</string> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <widget class="QWidget" name="mainWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <widget class="OrthancStone::QStoneOpenGlWidget" name="centralWidget" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>500</height> + </size> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>903</width> + <height>21</height> + </rect> + </property> + <widget class="QMenu" name="menuTest"> + <property name="title"> + <string>Test</string> + </property> + </widget> + <addaction name="menuTest"/> + </widget> + <widget class="QStatusBar" name="statusbar"/> + </widget> + <customwidgets> + <customwidget> + <class>QStoneOpenGlWidget</class> + <extends>QWidget</extends> + <header location="global">QStoneOpenGlWidget.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/CMakeLists.txt Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 2.8.3) + +##################################################################### +## Configuration of the Orthanc framework +##################################################################### + +# This CMake file defines the "ORTHANC_STONE_VERSION" macro, so it +# must be the first inclusion +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/Version.cmake) + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.5.7") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + + +##################################################################### +## Configuration of the Stone framework +##################################################################### + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) + +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +set(ORTHANC_STONE_APPLICATION_RESOURCES + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) +SET(ENABLE_QT ON) +SET(ENABLE_SDL OFF) +SET(ENABLE_WEB_CLIENT ON) +SET(ORTHANC_SANDBOXED OFF) +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) +##################################################################### +## Build the samples +##################################################################### + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +list(APPEND BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.cpp + ) + +ORTHANC_QT_WRAP_UI(BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.ui + ) + +ORTHANC_QT_WRAP_CPP(BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.h + QStoneOpenGlWidget.h + ) + +add_executable(BasicScene + BasicScene.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.h + ${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.cpp + QStoneOpenGlWidget.cpp + ${BASIC_SCENE_APPLICATIONS_SOURCES} + ) + +target_include_directories(BasicScene PUBLIC ${CMAKE_SOURCE_DIR} ${STONE_SOURCES_DIR}) + +target_link_libraries(BasicScene OrthancStone)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/QStoneOpenGlWidget.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,171 @@ +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include "QStoneOpenGlWidget.h" + +#include <QMouseEvent> + +using namespace OrthancStone; + +void QStoneOpenGlWidget::initializeGL() +{ + glewInit(); +} + +void QStoneOpenGlWidget::MakeCurrent() +{ + this->makeCurrent(); +} + +void QStoneOpenGlWidget::resizeGL(int w, int h) +{ + +} + +void QStoneOpenGlWidget::paintGL() +{ + if (compositor_) + { + compositor_->Refresh(); + } + doneCurrent(); +} + +void ConvertFromPlatform( + OrthancStone::GuiAdapterMouseEvent& guiEvent, + PointerEvent& pointerEvent, + const QMouseEvent& qtEvent, + const IViewport& viewport) +{ + guiEvent.targetX = qtEvent.x(); + guiEvent.targetY = qtEvent.y(); + pointerEvent.AddPosition(viewport.GetPixelCenterCoordinates(guiEvent.targetX, guiEvent.targetY)); + + switch (qtEvent.button()) + { + case Qt::LeftButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_LEFT; break; + case Qt::MiddleButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_MIDDLE; break; + case Qt::RightButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_RIGHT; break; + default: + guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_LEFT; + } + + if (qtEvent.modifiers().testFlag(Qt::ShiftModifier)) + { + guiEvent.shiftKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::ControlModifier)) + { + guiEvent.ctrlKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::AltModifier)) + { + guiEvent.altKey = true; + } +} + +void QStoneOpenGlWidget::mouseEvent(QMouseEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType) +{ + OrthancStone::GuiAdapterMouseEvent guiEvent; + PointerEvent pointerEvent; + ConvertFromPlatform(guiEvent, pointerEvent, *qtEvent, *viewport_); + guiEvent.type = guiEventType; + + if (sceneInteractor_.get() != NULL && compositor_.get() != NULL) + { + sceneInteractor_->OnMouseEvent(guiEvent, pointerEvent); + } + + // force redraw of the OpenGL widget + update(); +} + +void QStoneOpenGlWidget::mousePressEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEDOWN); +} + +void QStoneOpenGlWidget::mouseMoveEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEMOVE); +} + +void QStoneOpenGlWidget::mouseReleaseEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEUP); +} + +void ConvertFromPlatform( + OrthancStone::GuiAdapterKeyboardEvent& guiEvent, + const QKeyEvent& qtEvent) +{ + if (qtEvent.text().length() > 0) + { + guiEvent.sym[0] = qtEvent.text()[0].cell(); + } + else + { + guiEvent.sym[0] = 0; + } + guiEvent.sym[1] = 0; + + if (qtEvent.modifiers().testFlag(Qt::ShiftModifier)) + { + guiEvent.shiftKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::ControlModifier)) + { + guiEvent.ctrlKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::AltModifier)) + { + guiEvent.altKey = true; + } + +} + + +bool QStoneOpenGlWidget::keyEvent(QKeyEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType) +{ + bool handled = false; + OrthancStone::GuiAdapterKeyboardEvent guiEvent; + ConvertFromPlatform(guiEvent, *qtEvent); + guiEvent.type = guiEventType; + + if (sceneInteractor_.get() != NULL && compositor_.get() != NULL) + { + handled = sceneInteractor_->OnKeyboardEvent(guiEvent); + + if (handled) + { + // force redraw of the OpenGL widget + update(); + } + } + return handled; +} + +void QStoneOpenGlWidget::keyPressEvent(QKeyEvent *qtEvent) +{ + bool handled = keyEvent(qtEvent, GUIADAPTER_EVENT_KEYDOWN); + if (!handled) + { + QOpenGLWidget::keyPressEvent(qtEvent); + } +} + +void QStoneOpenGlWidget::keyReleaseEvent(QKeyEvent *qtEvent) +{ + bool handled = keyEvent(qtEvent, GUIADAPTER_EVENT_KEYUP); + if (!handled) + { + QOpenGLWidget::keyPressEvent(qtEvent); + } +} + +void QStoneOpenGlWidget::wheelEvent(QWheelEvent *qtEvent) +{ + OrthancStone::GuiAdapterWheelEvent guiEvent; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + + // force redraw of the OpenGL widget + update(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/QStoneOpenGlWidget.h Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,73 @@ +#pragma once +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include <QOpenGLWidget> +#include <QOpenGLFunctions> + +#include <boost/shared_ptr.hpp> +#include "../../Framework/OpenGL/IOpenGLContext.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Viewport/IViewport.h" +#include "../../Applications/Generic/Scene2DInteractor.h" + +namespace OrthancStone +{ + class QStoneOpenGlWidget : public QOpenGLWidget, public OrthancStone::OpenGL::IOpenGLContext + { + boost::shared_ptr<IViewport> viewport_; + boost::shared_ptr<OrthancStone::OpenGLCompositor> compositor_; + boost::shared_ptr<Scene2DInteractor> sceneInteractor_; + + public: + QStoneOpenGlWidget(QWidget *parent) : + QOpenGLWidget(parent) + { + setFocusPolicy(Qt::StrongFocus); // to enable keyPressEvent + setMouseTracking(true); // to enable mouseMoveEvent event when no button is pressed + } + + protected: + + //**** QWidget overrides + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void wheelEvent(QWheelEvent* event) override; + + //**** IOpenGLContext overrides + + virtual void MakeCurrent() override; + virtual void SwapBuffer() override {} + + virtual unsigned int GetCanvasWidth() const override + { + return this->width(); + } + + virtual unsigned int GetCanvasHeight() const override + { + return this->height(); + } + + public: + void SetInteractor(boost::shared_ptr<Scene2DInteractor> sceneInteractor) + { + sceneInteractor_ = sceneInteractor; + } + + void SetCompositor(boost::shared_ptr<OrthancStone::OpenGLCompositor> compositor) + { + compositor_ = compositor; + } + + protected: + void mouseEvent(QMouseEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType); + bool keyEvent(QKeyEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType); + + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/Scene2DInteractor.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,72 @@ +#include "Scene2DInteractor.h" + +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" + + +namespace OrthancStone +{ + +} + +using namespace OrthancStone; + + +bool BasicScene2DInteractor::OnMouseEvent(const GuiAdapterMouseEvent& event, const PointerEvent& pointerEvent) +{ + if (currentTracker_.get() != NULL) + { + switch (event.type) + { + case GUIADAPTER_EVENT_MOUSEUP: + { + currentTracker_->PointerUp(pointerEvent); + if (!currentTracker_->IsAlive()) + { + currentTracker_.reset(); + } + };break; + case GUIADAPTER_EVENT_MOUSEMOVE: + { + currentTracker_->PointerMove(pointerEvent); + };break; + } + return true; + } + else + { + if (event.button == GUIADAPTER_MOUSEBUTTON_LEFT) + { + currentTracker_.reset(new RotateSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_MIDDLE) + { + currentTracker_.reset(new PanSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_RIGHT && compositor_.get() != NULL) + { + currentTracker_.reset(new ZoomSceneTracker(viewportController_, pointerEvent, compositor_->GetHeight())); + } + return true; + } + return false; +} + +bool BasicScene2DInteractor::OnKeyboardEvent(const GuiAdapterKeyboardEvent& guiEvent) +{ + switch (guiEvent.sym[0]) + { + case 's': + { + viewportController_->FitContent(compositor_->GetWidth(), compositor_->GetHeight()); + return true; + }; + } + return false; +} + +bool BasicScene2DInteractor::OnWheelEvent(const GuiAdapterWheelEvent& guiEvent) +{ + return false; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/Scene2DInteractor.h Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,19 @@ +#pragma once + +#include "../../Applications/Generic/Scene2DInteractor.h" +#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" + + +class BasicScene2DInteractor : public OrthancStone::Scene2DInteractor +{ + boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> currentTracker_; +public: + BasicScene2DInteractor(boost::shared_ptr<OrthancStone::ViewportController> viewportController) : + Scene2DInteractor(viewportController) + {} + + virtual bool OnMouseEvent(const OrthancStone::GuiAdapterMouseEvent& event, const OrthancStone::PointerEvent& pointerEvent) override; + virtual bool OnKeyboardEvent(const OrthancStone::GuiAdapterKeyboardEvent& guiEvent); + virtual bool OnWheelEvent(const OrthancStone::GuiAdapterWheelEvent& guiEvent); +}; +
--- a/Samples/Sdl/CMakeLists.txt Wed Jul 17 09:39:51 2019 +0200 +++ b/Samples/Sdl/CMakeLists.txt Wed Jul 17 09:53:51 2019 +0200 @@ -71,7 +71,7 @@ target_link_libraries(BasicScene OrthancStone) # -# BasicScene +# TrackerSample # LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSample.cpp") @@ -108,3 +108,21 @@ ) target_link_libraries(FusionMprSdl OrthancStone) + +# +# RadiographyEditor +# + +LIST(APPEND RADIOGRAPHY_EDITOR_SOURCE "../Shared/RadiographyEditorApp.cpp") +LIST(APPEND RADIOGRAPHY_EDITOR_SOURCE "../Shared/RadiographyEditorApp.h") +LIST(APPEND RADIOGRAPHY_EDITOR_SOURCE "RadiographyEditor.cpp") + +if (MSVC AND MSVC_VERSION GREATER 1700) + LIST(APPEND RADIOGRAPHY_EDITOR_SOURCE "cpp.hint") +endif() + +add_executable(RadiographyEditor + ${RADIOGRAPHY_EDITOR_SOURCE} + ) + +target_link_libraries(RadiographyEditor OrthancStone)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/RadiographyEditor.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,267 @@ +/** + * 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 "../Shared/RadiographyEditorApp.h" + +// From Stone +#include "../../Framework/Oracle/SleepOracleCommand.h" +#include "../../Framework/Oracle/ThreadedOracle.h" +#include "../../Applications/Sdl/SdlOpenGLWindow.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/StoneInitialization.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> + + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> + +#include <SDL.h> +#include <stdio.h> + +using namespace OrthancStone; + +namespace OrthancStone +{ + class NativeApplicationContext : public IMessageEmitter + { + private: + boost::shared_mutex mutex_; + MessageBroker broker_; + IObservable oracleObservable_; + + public: + NativeApplicationContext() : + oracleObservable_(broker_) + { + } + + + virtual void EmitMessage(const IObserver& observer, + const IMessage& message) ORTHANC_OVERRIDE + { + try + { + boost::unique_lock<boost::shared_mutex> lock(mutex_); + oracleObservable_.EmitMessage(observer, message); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while emitting a message: " << e.What(); + } + } + + + class ReaderLock : public boost::noncopyable + { + private: + NativeApplicationContext& that_; + boost::shared_lock<boost::shared_mutex> lock_; + + public: + ReaderLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + }; + + + class WriterLock : public boost::noncopyable + { + private: + NativeApplicationContext& that_; + boost::unique_lock<boost::shared_mutex> lock_; + + public: + WriterLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + + MessageBroker& GetBroker() + { + return that_.broker_; + } + + IObservable& GetOracleObservable() + { + return that_.oracleObservable_; + } + }; + }; +} + +class OpenGlSdlCompositorFactory : public ICompositorFactory +{ + OpenGL::IOpenGLContext& openGlContext_; + +public: + OpenGlSdlCompositorFactory(OpenGL::IOpenGLContext& openGlContext) : + openGlContext_(openGlContext) + {} + + ICompositor* GetCompositor(const Scene2D& scene) + { + + OpenGLCompositor* compositor = new OpenGLCompositor(openGlContext_, scene); + compositor->SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_0, Orthanc::Encoding_Latin1); + compositor->SetFont(1, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_1, Orthanc::Encoding_Latin1); + return compositor; + } +}; + +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); + } +} + + +/** + * 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[]) +{ + using namespace OrthancStone; + + StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + // Orthanc::Logging::EnableTraceLevel(true); + + try + { + OrthancStone::NativeApplicationContext context; + OrthancStone::NativeApplicationContext::WriterLock lock(context); + OrthancStone::ThreadedOracle oracle(context); + + // False means we do NOT let Windows treat this as a legacy application + // that needs to be scaled + SdlOpenGLWindow window("Hello", 1024, 1024, false); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + std::auto_ptr<OpenGlSdlCompositorFactory> compositorFactory(new OpenGlSdlCompositorFactory(window)); + boost::shared_ptr<RadiographyEditorApp> app(new RadiographyEditorApp(oracle, lock.GetOracleObservable(), compositorFactory.release())); + app->PrepareScene(); + app->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight()); + + bool stopApplication = false; + + while (!stopApplication) + { + app->Refresh(); + + SDL_Event event; + while (!stopApplication && SDL_PollEvent(&event)) + { + OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; + if (event.key.keysym.mod & KMOD_CTRL) + modifiers = static_cast<OrthancStone::KeyboardModifiers>(static_cast<int>(modifiers) | static_cast<int>(OrthancStone::KeyboardModifiers_Control)); + if (event.key.keysym.mod & KMOD_ALT) + modifiers = static_cast<OrthancStone::KeyboardModifiers>(static_cast<int>(modifiers) | static_cast<int>(OrthancStone::KeyboardModifiers_Alt)); + if (event.key.keysym.mod & KMOD_SHIFT) + modifiers = static_cast<OrthancStone::KeyboardModifiers>(static_cast<int>(modifiers) | static_cast<int>(OrthancStone::KeyboardModifiers_Shift)); + + OrthancStone::MouseButton button; + if (event.button.button == SDL_BUTTON_LEFT) + button = OrthancStone::MouseButton_Left; + else if (event.button.button == SDL_BUTTON_MIDDLE) + button = OrthancStone::MouseButton_Middle; + else if (event.button.button == SDL_BUTTON_RIGHT) + button = OrthancStone::MouseButton_Right; + + if (event.type == SDL_QUIT) + { + stopApplication = true; + break; + } + else if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + app->DisableTracker(); // was: tracker.reset(NULL); + app->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: + stopApplication = true; + break; + default: + { + app->OnKeyPressed(event.key.keysym.sym, modifiers); + } + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + app->OnMouseDown(event.button.x, event.button.y, modifiers, button); + } + else if (event.type == SDL_MOUSEMOTION) + { + app->OnMouseMove(event.button.x, event.button.y, modifiers); + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + app->OnMouseUp(event.button.x, event.button.y, modifiers, button); + } + } + SDL_Delay(1); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Shared/RadiographyEditorApp.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,786 @@ +/** + * 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 "RadiographyEditorApp.h" + +#include "../../Applications/Sdl/SdlOpenGLWindow.h" + +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2D/Scene2D.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2DViewport/CreateAngleMeasureTracker.h" +#include "../../Framework/Scene2DViewport/CreateLineMeasureTracker.h" +#include "../../Framework/Scene2DViewport/UndoStack.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/ref.hpp> +#include <boost/make_shared.hpp> +#include <SDL.h> + +#include <stdio.h> + +namespace OrthancStone +{ + 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]; + } + + boost::shared_ptr<Scene2D> RadiographyEditorApp::GetScene() + { + return controller_->GetScene(); + } + + boost::shared_ptr<const Scene2D> RadiographyEditorApp::GetScene() const + { + return controller_->GetScene(); + } + + void RadiographyEditorApp::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 RadiographyEditorApp::DisplayInfoText() + { + // do not try to use stuff too early! + if (compositor_.get() == NULL) + return; + + std::stringstream msg; + + for (std::map<std::string, std::string>::const_iterator kv = infoTextMap_.begin(); + kv != infoTextMap_.end(); ++kv) + { + msg << kv->first << " : " << kv->second << std::endl; + } + std::string msgS = msg.str(); + + TextSceneLayer* layerP = NULL; + if (GetScene()->HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = dynamic_cast<TextSceneLayer&>( + GetScene()->GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); + layerP = &layer; + } + else + { + std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer); + layerP = layer.get(); + layer->SetColor(0, 255, 0); + layer->SetFontIndex(1); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_TopLeft); + //layer->SetPosition(0,0); + GetScene()->SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release()); + } + // position the fixed info text in the upper right corner + layerP->SetText(msgS.c_str()); + double cX = compositor_->GetCanvasWidth() * (-0.5); + double cY = compositor_->GetCanvasHeight() * (-0.5); + GetScene()->GetCanvasToSceneTransform().Apply(cX,cY); + layerP->SetPosition(cX, cY); + } + + void RadiographyEditorApp::DisplayFloatingCtrlInfoText(const PointerEvent& e) + { + ScenePoint2D p = e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform()); + + char buf[128]; + sprintf(buf, "S:(%0.02f,%0.02f) C:(%0.02f,%0.02f)", + p.GetX(), p.GetY(), + e.GetMainPosition().GetX(), e.GetMainPosition().GetY()); + + if (GetScene()->HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = + dynamic_cast<TextSceneLayer&>(GetScene()->GetLayer(FLOATING_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()); + GetScene()->SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release()); + } + } + + void RadiographyEditorApp::HideInfoText() + { + GetScene()->DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX); + } + + ScenePoint2D RadiographyEditorApp::GetRandomPointInScene() const + { + unsigned int w = compositor_->GetCanvasWidth(); + LOG(TRACE) << "compositor_->GetCanvasWidth() = " << + compositor_->GetCanvasWidth(); + unsigned int h = compositor_->GetCanvasHeight(); + LOG(TRACE) << "compositor_->GetCanvasHeight() = " << + compositor_->GetCanvasHeight(); + + if ((w >= RAND_MAX) || (h >= RAND_MAX)) + LOG(WARNING) << "Canvas is too big : tools will not be randomly placed"; + + int x = rand() % w; + int y = rand() % h; + LOG(TRACE) << "random x = " << x << "random y = " << y; + + ScenePoint2D p = compositor_->GetPixelCenterCoordinates(x, y); + LOG(TRACE) << "--> p.GetX() = " << p.GetX() << " p.GetY() = " << p.GetY(); + + ScenePoint2D r = p.Apply(GetScene()->GetCanvasToSceneTransform()); + LOG(TRACE) << "--> r.GetX() = " << r.GetX() << " r.GetY() = " << r.GetY(); + return r; + } + + void RadiographyEditorApp::CreateRandomMeasureTool() + { + static bool srandCalled = false; + if (!srandCalled) + { + srand(42); + srandCalled = true; + } + + int i = rand() % 2; + LOG(TRACE) << "random i = " << i; + switch (i) + { + case 0: + // line measure + { + boost::shared_ptr<CreateLineMeasureCommand> cmd = + boost::make_shared<CreateLineMeasureCommand>( + boost::ref(IObserver::GetBroker()), + controller_, + GetRandomPointInScene()); + cmd->SetEnd(GetRandomPointInScene()); + controller_->PushCommand(cmd); + } + break; + case 1: + // angle measure + { + boost::shared_ptr<CreateAngleMeasureCommand> cmd = + boost::make_shared<CreateAngleMeasureCommand>( + boost::ref(IObserver::GetBroker()), + controller_, + GetRandomPointInScene()); + cmd->SetCenter(GetRandomPointInScene()); + cmd->SetSide2End(GetRandomPointInScene()); + controller_->PushCommand(cmd); + } + break; + } + } + + void RadiographyEditorApp::OnMouseMove(int x, int y, OrthancStone::KeyboardModifiers modifiers) + { + DisplayInfoText(); + if (activeTracker_.get() == NULL && (modifiers & OrthancStone::KeyboardModifiers_Alt)) + { + // The "left-ctrl" key is down, while no tracker is present + // Let's display the info text + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates(x, y)); + + DisplayFloatingCtrlInfoText(e); + } + else { + HideInfoText(); + //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)"; + if (activeTracker_.get() != NULL) + { + //LOG(TRACE) << "(activeTracker_.get() != NULL)"; + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates(x, y)); + + //LOG(TRACE) << "event.button.x = " << event.button.x << " " << + // "event.button.y = " << event.button.y; + LOG(TRACE) << "activeTracker_->PointerMove(e); " << + e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY(); + + activeTracker_->PointerMove(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + } + } + + void RadiographyEditorApp::OnKeyPressed(char keyChar, OrthancStone::KeyboardModifiers modifiers) + { + DisplayInfoText(); + + switch (keyChar) + { + case '\033': // escape + { + if (activeTracker_) + { + activeTracker_->Cancel(); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + };break; + case 't': + { + if (!activeTracker_) + SelectNextTool(); + else + { + LOG(WARNING) << "You cannot change the active tool when an interaction" + " is taking place"; + } + };break; + case 'm': + CreateRandomMeasureTool(); + break; + case 's': + controller_->FitContent(compositor_->GetCanvasWidth(), + compositor_->GetCanvasHeight()); + break; + case 'z': + LOG(TRACE) << "z has been pressed. modifier = " << modifiers; + if (modifiers & OrthancStone::KeyboardModifiers_Control) + { + if (controller_->CanUndo()) + { + LOG(TRACE) << "Undoing..."; + controller_->Undo(); + } + else + { + LOG(WARNING) << "Nothing to undo!!!"; + } + } + break; + + case 'y': + LOG(TRACE) << "y has been pressed. modifier = " << modifiers; + if (modifiers & OrthancStone::KeyboardModifiers_Control) + { + if (controller_->CanRedo()) + { + LOG(TRACE) << "Redoing..."; + controller_->Redo(); + } + else + { + LOG(WARNING) << "Nothing to redo!!!"; + } + } + break; + + case 'c': + TakeScreenshot( + "screenshot.png", + compositor_->GetCanvasWidth(), + compositor_->GetCanvasHeight()); + break; + + } + } + + void RadiographyEditorApp::OnMouseDown(int x, int y, OrthancStone::KeyboardModifiers modifiers, OrthancStone::MouseButton button) + { + DisplayInfoText(); + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates(x, y)); + // TODO: set modifiers in e + + if (activeTracker_) + { + activeTracker_->PointerDown(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + else + { + // we ATTEMPT to create a tracker if need be + activeTracker_ = CreateSuitableTracker(button, e); + } + } + + void RadiographyEditorApp::OnMouseUp(int x, int y, OrthancStone::KeyboardModifiers modifiers, OrthancStone::MouseButton button) + { + DisplayInfoText(); + if (activeTracker_) + { + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates(x, y)); + // TODO: set modifiers in e + + activeTracker_->PointerUp(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + } + + void RadiographyEditorApp::HandleApplicationEvent( + const SDL_Event & event) + { + DisplayInfoText(); + + if (event.type == SDL_MOUSEMOTION) + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + if (activeTracker_.get() == NULL && + SDL_SCANCODE_LALT < scancodeCount && + keyboardState[SDL_SCANCODE_LALT]) + { + // The "left-ctrl" key is down, while no tracker is present + // Let's display the info text + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates( + event.button.x, event.button.y)); + // TODO: set modifiers in e + + DisplayFloatingCtrlInfoText(e); + } + else + { + HideInfoText(); + //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)"; + if (activeTracker_.get() != NULL) + { + //LOG(TRACE) << "(activeTracker_.get() != NULL)"; + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates( + event.button.x, event.button.y)); + // TODO: set modifiers in e + + //LOG(TRACE) << "event.button.x = " << event.button.x << " " << + // "event.button.y = " << event.button.y; + LOG(TRACE) << "activeTracker_->PointerMove(e); " << + e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY(); + + activeTracker_->PointerMove(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + } + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + if (activeTracker_) + { + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates(event.button.x, event.button.y)); + // TODO: set modifiers in e + activeTracker_->PointerUp(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates( + event.button.x, event.button.y)); + // TODO: set modifiers in e + if (activeTracker_) + { + activeTracker_->PointerDown(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + else + { + // we ATTEMPT to create a tracker if need be +// activeTracker_ = CreateSuitableTracker(event, e); + } + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_ESCAPE: + if (activeTracker_) + { + activeTracker_->Cancel(); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + break; + + case SDLK_t: + if (!activeTracker_) + SelectNextTool(); + else + { + LOG(WARNING) << "You cannot change the active tool when an interaction" + " is taking place"; + } + break; + + case SDLK_m: + CreateRandomMeasureTool(); + break; + case SDLK_s: + controller_->FitContent(compositor_->GetCanvasWidth(), + compositor_->GetCanvasHeight()); + break; + + case SDLK_z: + LOG(TRACE) << "SDLK_z has been pressed. event.key.keysym.mod == " << event.key.keysym.mod; + if (event.key.keysym.mod & KMOD_CTRL) + { + if (controller_->CanUndo()) + { + LOG(TRACE) << "Undoing..."; + controller_->Undo(); + } + else + { + LOG(WARNING) << "Nothing to undo!!!"; + } + } + break; + + case SDLK_y: + LOG(TRACE) << "SDLK_y has been pressed. event.key.keysym.mod == " << event.key.keysym.mod; + if (event.key.keysym.mod & KMOD_CTRL) + { + if (controller_->CanRedo()) + { + LOG(TRACE) << "Redoing..."; + controller_->Redo(); + } + else + { + LOG(WARNING) << "Nothing to redo!!!"; + } + } + break; + + case SDLK_c: + TakeScreenshot( + "screenshot.png", + compositor_->GetCanvasWidth(), + compositor_->GetCanvasHeight()); + break; + + default: + break; + } + } + } + + + void RadiographyEditorApp::OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message) + { + DisplayInfoText(); + } + + boost::shared_ptr<IFlexiblePointerTracker> RadiographyEditorApp::CreateSuitableTracker( + OrthancStone::MouseButton button, + const PointerEvent & e) + { + using namespace Orthanc; + + switch (button) + { + case OrthancStone::MouseButton_Middle: + return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker + (controller_, e)); + + case OrthancStone::MouseButton_Right: + return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker + (controller_, e, compositor_->GetCanvasHeight())); + + case OrthancStone::MouseButton_Left: + { + //LOG(TRACE) << "CreateSuitableTracker: 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?) + boost::shared_ptr<IFlexiblePointerTracker> hitTestTracker = TrackerHitTest(e); + + if (hitTestTracker != NULL) + { + //LOG(TRACE) << "hitTestTracker != NULL"; + return hitTestTracker; + } + else + { + switch (currentTool_) + { + case GuiTool_Rotate: + //LOG(TRACE) << "Creating RotateSceneTracker"; + return boost::shared_ptr<IFlexiblePointerTracker>(new RotateSceneTracker( + controller_, e)); + case GuiTool_Pan: + return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker( + controller_, e)); + case GuiTool_Zoom: + return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker( + controller_, e, compositor_->GetCanvasHeight())); + //case GuiTool_AngleMeasure: + // return new AngleMeasureTracker(GetScene(), e); + //case GuiTool_CircleMeasure: + // return new CircleMeasureTracker(GetScene(), e); + //case GuiTool_EllipseMeasure: + // return new EllipseMeasureTracker(GetScene(), e); + case GuiTool_LineMeasure: + return boost::shared_ptr<IFlexiblePointerTracker>(new CreateLineMeasureTracker( + IObserver::GetBroker(), controller_, e)); + case GuiTool_AngleMeasure: + return boost::shared_ptr<IFlexiblePointerTracker>(new CreateAngleMeasureTracker( + IObserver::GetBroker(), controller_, e)); + case GuiTool_CircleMeasure: + LOG(ERROR) << "Not implemented yet!"; + return boost::shared_ptr<IFlexiblePointerTracker>(); + case GuiTool_EllipseMeasure: + LOG(ERROR) << "Not implemented yet!"; + return boost::shared_ptr<IFlexiblePointerTracker>(); + default: + throw OrthancException(ErrorCode_InternalError, "Wrong tool!"); + } + } + } + default: + return boost::shared_ptr<IFlexiblePointerTracker>(); + } + } + + + RadiographyEditorApp::RadiographyEditorApp(OrthancStone::IOracle& oracle, + IObservable& oracleObservable, + ICompositorFactory* compositorFactory) : + IObserver(oracleObservable.GetBroker()), + oracle_(oracle), + compositorFactory_(compositorFactory), + currentTool_(GuiTool_Rotate) + { + boost::shared_ptr<UndoStack> undoStack(new UndoStack); + controller_ = boost::shared_ptr<ViewportController>(new ViewportController(undoStack, IObserver::GetBroker())); + + controller_->RegisterObserverCallback( + new Callable<RadiographyEditorApp, ViewportController::SceneTransformChanged> + (*this, &RadiographyEditorApp::OnSceneTransformChanged)); + + TEXTURE_2x2_1_ZINDEX = 1; + TEXTURE_1x1_ZINDEX = 2; + TEXTURE_2x2_2_ZINDEX = 3; + LINESET_1_ZINDEX = 4; + LINESET_2_ZINDEX = 5; + FLOATING_INFOTEXT_LAYER_ZINDEX = 6; + FIXED_INFOTEXT_LAYER_ZINDEX = 7; + } + + void RadiographyEditorApp::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; + + GetScene()->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); + GetScene()->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); + GetScene()->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, 255, 0, 0); + + 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, 0, 255, 0); + + 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, 0, 0, 255); + + GetScene()->SetLayer(LINESET_1_ZINDEX, layer.release()); + } + + // Some text + { + std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer); + layer->SetText("Hello"); + GetScene()->SetLayer(LINESET_2_ZINDEX, layer.release()); + } + } + + + void RadiographyEditorApp::DisableTracker() + { + if (activeTracker_) + { + activeTracker_->Cancel(); + activeTracker_.reset(); + } + } + + void RadiographyEditorApp::TakeScreenshot(const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight) + { + CairoCompositor compositor(*GetScene(), canvasWidth, canvasHeight); + compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE_0, 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); + } + + + boost::shared_ptr<IFlexiblePointerTracker> RadiographyEditorApp::TrackerHitTest(const PointerEvent & e) + { + // std::vector<boost::shared_ptr<MeasureTool>> measureTools_; + return boost::shared_ptr<IFlexiblePointerTracker>(); + } + + + void RadiographyEditorApp::FitContent(unsigned int width, unsigned int height) + { + controller_->FitContent(width, height); + } + + void RadiographyEditorApp::UpdateSize() + { + if (dynamic_cast<OpenGLCompositor*>(compositor_.get()) != NULL) + { + dynamic_cast<OpenGLCompositor*>(compositor_.get())->UpdateSize(); + } + } + + void RadiographyEditorApp::Refresh() + { + compositor_.reset(compositorFactory_->GetCompositor(*GetScene())); + compositor_->Refresh(); + + // the following is paramount because the compositor holds a reference + // to the scene and we do not want this reference to become dangling + // TODO ???? compositor_.reset(NULL); + } + + void RadiographyEditorApp::SetInfoDisplayMessage( + std::string key, std::string value) + { + if (value == "") + infoTextMap_.erase(key); + else + infoTextMap_[key] = value; + DisplayInfoText(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Shared/RadiographyEditorApp.h Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,168 @@ +/** + * 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 "../../Framework/Messages/IObserver.h" +#include "../../Framework/Oracle/IOracle.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/Internals/CompositorHelper.h" +#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" +#include "../../Framework/Scene2DViewport/MeasureTool.h" +#include "../../Framework/Scene2DViewport/PredeclaredTypes.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" + +#include <SDL.h> + +#include <boost/make_shared.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + + +namespace OrthancStone +{ + class ICompositorFactory + { + public: + virtual OrthancStone::ICompositor* GetCompositor(const OrthancStone::Scene2D& scene) = 0; + }; + + class IInteractor + { + public: + virtual void OnMouseDown(int x, int y, OrthancStone::KeyboardModifiers modifiers, OrthancStone::MouseButton button) = 0; + virtual void OnMouseMove(int x, int y, OrthancStone::KeyboardModifiers modifiers) = 0; + virtual void OnMouseUp(int x, int y, OrthancStone::KeyboardModifiers modifiers, OrthancStone::MouseButton button) = 0; + virtual void OnKeyPressed(char keyChar, OrthancStone::KeyboardModifiers modifiers) = 0; + }; + + + + + 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 unsigned int FONT_SIZE_0 = 32; + static const unsigned int FONT_SIZE_1 = 24; + + class Scene2D; + + class RadiographyEditorApp : public IObserver + , public IInteractor, public boost::enable_shared_from_this<RadiographyEditorApp> + { + OrthancStone::IOracle& oracle_; + std::auto_ptr<ICompositorFactory> compositorFactory_; + std::auto_ptr<ICompositor> compositor_; + + public: + // 12 because. + RadiographyEditorApp(OrthancStone::IOracle& oracle, IObservable& oracleObservable, ICompositorFactory* compositorFactory); + + void PrepareScene(); + void FitContent(unsigned int width, unsigned int height); + void Refresh(); + void UpdateSize(); + void SetInfoDisplayMessage(std::string key, std::string value); + void DisableTracker(); + + virtual void OnMouseMove(int x, int y, OrthancStone::KeyboardModifiers modifiers); + virtual void OnKeyPressed(char keyChar, OrthancStone::KeyboardModifiers modifiers); + virtual void OnMouseDown(int x, int y, OrthancStone::KeyboardModifiers modifiers, OrthancStone::MouseButton button); + virtual void OnMouseUp(int x, int y, OrthancStone::KeyboardModifiers modifiers, OrthancStone::MouseButton button); + + boost::shared_ptr<Scene2D> GetScene(); + boost::shared_ptr<const Scene2D> GetScene() const; + + void HandleApplicationEvent(const SDL_Event& event); + + /** + This method is called when the scene transform changes. It allows to + recompute the visual elements whose content depend upon the scene transform + */ + void OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message); + + private: + void SelectNextTool(); + void CreateRandomMeasureTool(); + + /** + This returns a random point in the canvas part of the scene, but in + scene coordinates + */ + ScenePoint2D GetRandomPointInScene() const; + + boost::shared_ptr<IFlexiblePointerTracker> TrackerHitTest(const PointerEvent& e); + + boost::shared_ptr<IFlexiblePointerTracker> CreateSuitableTracker( + OrthancStone::MouseButton button, + const PointerEvent& e); + + 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(boost::shared_ptr<TrackerCommand> cmd); + void Undo(); + void Redo(); + + private: + void DisplayFloatingCtrlInfoText(const PointerEvent& e); + void DisplayInfoText(); + void HideInfoText(); + + private: + /** + WARNING: the measuring tools do store a reference to the scene, and it + paramount that the scene gets destroyed AFTER the measurement tools. + */ + boost::shared_ptr<ViewportController> controller_; + + std::map<std::string, std::string> infoTextMap_; + boost::shared_ptr<IFlexiblePointerTracker> activeTracker_; + + //static const int LAYER_POSITION = 150; + + int TEXTURE_2x2_1_ZINDEX; + int TEXTURE_1x1_ZINDEX; + int TEXTURE_2x2_2_ZINDEX; + int LINESET_1_ZINDEX; + int LINESET_2_ZINDEX; + int FLOATING_INFOTEXT_LAYER_ZINDEX; + int FIXED_INFOTEXT_LAYER_ZINDEX; + + GuiTool currentTool_; + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Shared/SharedBasicScene.cpp Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,274 @@ +/** + * 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 "SharedBasicScene.h" + +// From Stone +#include "../../Framework/Scene2D/Scene2D.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/PolylineSceneLayer.h" +#include "../../Framework/Scene2D/TextSceneLayer.h" + +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" + +#include "../../Framework/Scene2D/CairoCompositor.h" + +// From Orthanc framework +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Images/PngWriter.h> + +using namespace OrthancStone; + +const unsigned int BASIC_SCENE_FONT_SIZE = 32; +const int BASIC_SCENE_LAYER_POSITION = 150; + +void PrepareScene(Scene2D& scene) +{ + //Scene2D& scene(*controller->GetScene()); + // 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(12, 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 * 3.14); + scene.SetLayer(14, 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 * 3.14); + scene.SetLayer(13, 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, 255, 0, 0); + + 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, 0, 255, 0); + + 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, 0, 0, 255); + + // layer->SetColor(0,255, 255); + scene.SetLayer(50, layer.release()); + } + + // Some text + { + std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer); + layer->SetText("Hello"); + scene.SetLayer(100, layer.release()); + } +} + +#if ORTHANC_SANDBOXED == 0 +void TakeScreenshot(const std::string& target, + const OrthancStone::Scene2D& scene, + unsigned int canvasWidth, + unsigned int canvasHeight) +{ + using namespace OrthancStone; + // Take a screenshot, then save it as PNG file + CairoCompositor compositor(scene, canvasWidth, canvasHeight); + compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, BASIC_SCENE_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); +} +#endif + +void ShowCursorInfo(Scene2D& scene, const PointerEvent& pointerEvent) +{ + ScenePoint2D p = pointerEvent.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); + + char buf[64]; + sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY()); + + if (scene.HasLayer(BASIC_SCENE_LAYER_POSITION)) + { + TextSceneLayer& layer = + dynamic_cast<TextSceneLayer&>(scene.GetLayer(BASIC_SCENE_LAYER_POSITION)); + 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(BASIC_SCENE_LAYER_POSITION, layer.release()); + } +} + + + +bool BasicScene2DInteractor::OnMouseEvent(const GuiAdapterMouseEvent& event, const PointerEvent& pointerEvent) +{ + if (currentTracker_.get() != NULL) + { + switch (event.type) + { + case GUIADAPTER_EVENT_MOUSEUP: + { + currentTracker_->PointerUp(pointerEvent); + if (!currentTracker_->IsAlive()) + { + currentTracker_.reset(); + } + };break; + case GUIADAPTER_EVENT_MOUSEMOVE: + { + currentTracker_->PointerMove(pointerEvent); + };break; + default: + return false; + } + return true; + } + else if (event.type == GUIADAPTER_EVENT_MOUSEDOWN) + { + if (event.button == GUIADAPTER_MOUSEBUTTON_LEFT) + { + currentTracker_.reset(new RotateSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_MIDDLE) + { + currentTracker_.reset(new PanSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_RIGHT && compositor_.get() != NULL) + { + currentTracker_.reset(new ZoomSceneTracker(viewportController_, pointerEvent, compositor_->GetCanvasHeight())); + } + } + else if (event.type == GUIADAPTER_EVENT_MOUSEMOVE) + { + if (showCursorInfo_) + { + Scene2D& scene(viewportController_->GetScene()); + ShowCursorInfo(scene, pointerEvent); + } + return true; + } + return false; +} + +bool BasicScene2DInteractor::OnKeyboardEvent(const GuiAdapterKeyboardEvent& guiEvent) +{ + if (guiEvent.type == GUIADAPTER_EVENT_KEYDOWN) + { + switch (guiEvent.sym[0]) + { + case 's': + { + viewportController_->FitContent(compositor_->GetCanvasWidth(), compositor_->GetCanvasHeight()); + return true; + }; +#if ORTHANC_SANDBOXED == 0 + case 'c': + { + Scene2D& scene(viewportController_->GetScene()); + TakeScreenshot("screenshot.png", scene, compositor_->GetCanvasWidth(), compositor_->GetCanvasHeight()); + return true; + } +#endif + case 'd': + { + showCursorInfo_ = !showCursorInfo_; + if (!showCursorInfo_) + { + Scene2D& scene(viewportController_->GetScene()); + scene.DeleteLayer(BASIC_SCENE_LAYER_POSITION); + } + + return true; + } + } + } + return false; +} + +bool BasicScene2DInteractor::OnWheelEvent(const GuiAdapterWheelEvent& guiEvent) +{ + return false; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Shared/SharedBasicScene.h Wed Jul 17 09:53:51 2019 +0200 @@ -0,0 +1,55 @@ +/** + * 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 <boost/shared_ptr.hpp> +#include "../../Framework/Scene2DViewport/ViewportController.h" +#include "../../Framework/Scene2D/Scene2D.h" + +extern const unsigned int BASIC_SCENE_FONT_SIZE; +extern const int BASIC_SCENE_LAYER_POSITION; + +extern void PrepareScene(OrthancStone::Scene2D& scene); +extern void TakeScreenshot(const std::string& target, + const OrthancStone::Scene2D& scene, + unsigned int canvasWidth, + unsigned int canvasHeight); + + +#include "../../Applications/Generic/Scene2DInteractor.h" +#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" + + +class BasicScene2DInteractor : public OrthancStone::Scene2DInteractor +{ + boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> currentTracker_; + bool showCursorInfo_; +public: + BasicScene2DInteractor(boost::shared_ptr<OrthancStone::ViewportController> viewportController) : + Scene2DInteractor(viewportController), + showCursorInfo_(false) + {} + + virtual bool OnMouseEvent(const OrthancStone::GuiAdapterMouseEvent& event, const OrthancStone::PointerEvent& pointerEvent) override; + virtual bool OnKeyboardEvent(const OrthancStone::GuiAdapterKeyboardEvent& guiEvent) override; + virtual bool OnWheelEvent(const OrthancStone::GuiAdapterWheelEvent& guiEvent) override; +}; +
--- a/Samples/WebAssembly/CMakeLists.txt Wed Jul 17 09:39:51 2019 +0200 +++ b/Samples/WebAssembly/CMakeLists.txt Wed Jul 17 09:53:51 2019 +0200 @@ -79,6 +79,8 @@ if (ON) add_executable(BasicScene BasicScene.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.h + ${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.cpp ) target_link_libraries(BasicScene OrthancStone)
--- a/Samples/WebAssembly/NOTES.txt Wed Jul 17 09:39:51 2019 +0200 +++ b/Samples/WebAssembly/NOTES.txt Wed Jul 17 09:53:51 2019 +0200 @@ -63,3 +63,15 @@ $ make -C ~/Subversion/orthanc-webviewer/r -j4 $ ~/Subversion/orthanc/r/Orthanc ../ConfigurationLocalSJO.json + +Local AM +======== + +. ~/apps/emsdk/emsdk_env.sh +cd /mnt/c/o/ +mkdir -p build_stone_newsamples_wasm_wsl +mkdir -p build_install_stone_newsamples_wasm_wsl +cd build_stone_newsamples_wasm_wsl +cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=/mnt/c/o/orthanc/ -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON /mnt/c/o/orthanc-stone/Samples/WebAssembly -DCMAKE_INSTALL_PREFIX=/mnt/c/o/build_install_stone_newsamples_wasm_wsl +ninja +