# HG changeset patch # User Alain Mazy # Date 1557990674 -7200 # Node ID 3f13f7f1b55d5ba71dd5ef92e321e99b4c06f952 # Parent 9e3bb8b4f72662489a369fe9ce4abb94741e6c01# Parent 86930bc676c6f5640835526f6b1815206670efda merge default -> am-dev diff -r 9e3bb8b4f726 -r 3f13f7f1b55d .hgignore --- a/.hgignore Tue May 14 18:24:12 2019 +0200 +++ b/.hgignore Thu May 16 09:11:14 2019 +0200 @@ -1,21 +1,22 @@ -CMakeLists.txt.user -Platforms/Generic/ThirdPartyDownloads/ -Applications/build-* +syntax: glob +*~ +*.cpp.orig +*.h.orig +.vs/ +.vscode/ Applications/Qt/archive/ Applications/Samples/ThirdPartyDownloads/ +Applications/Samples/build-wasm/ +Applications/Samples/build-web/ +Applications/Samples/node_modules/ +Applications/Samples/rt-viewer-demo/ThirdPartyDownloads/ Applications/Samples/rt-viewer-demo/build-sdl-msvc15/ Applications/Samples/rt-viewer-demo/build-tsc-output/ Applications/Samples/rt-viewer-demo/build-wasm/ Applications/Samples/rt-viewer-demo/build-web/ -Applications/Samples/rt-viewer-demo/ThirdPartyDownloads/ -Applications/Samples/build-wasm/ -Applications/Samples/build-web/ -Applications/Samples/node_modules/ -Resources/CommandTool/protoc-tests/node_modules/ -Resources/CommandTool/protoc-tests/generated_js/ -Resources/CommandTool/protoc-tests/generated_ts/ -Resources/CommandTool/flatc-tests/basic/build/ -.vscode/ +Applications/build-* +CMakeLists.txt.user +Platforms/Generic/ThirdPartyDownloads/ Resources/CodeGeneration/.env Resources/CodeGeneration/.idea Resources/CodeGeneration/__pycache__ @@ -23,8 +24,13 @@ Resources/CodeGeneration/build_browser/ Resources/CodeGeneration/testCppHandler/build/ Resources/CodeGeneration/testCppHandler/build_msbuild/ -syntax: glob -Resources/CodeGeneration/testWasmIntegrated/build-wasm/ +Resources/CodeGeneration/testWasmIntegrated/build-final/ Resources/CodeGeneration/testWasmIntegrated/build-tsc/ -Resources/CodeGeneration/testWasmIntegrated/build-final/ +Resources/CodeGeneration/testWasmIntegrated/build-wasm/ +Resources/CommandTool/flatc-tests/basic/build/ +Resources/CommandTool/protoc-tests/generated_js/ +Resources/CommandTool/protoc-tests/generated_ts/ +Resources/CommandTool/protoc-tests/node_modules/ +Samples/Sdl/ThirdPartyDownloads/ +Samples/Sdl/CMakeLists.txt.orig diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Generic/NativeStoneApplicationRunner.cpp --- a/Applications/Generic/NativeStoneApplicationRunner.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Generic/NativeStoneApplicationRunner.cpp Thu May 16 09:11:14 2019 +0200 @@ -81,11 +81,13 @@ generic.add_options() ("help", "Display this help and exit") ("verbose", "Be verbose in logs") - ("orthanc", boost::program_options::value()->default_value("http://localhost:8042/"), + ("orthanc", boost::program_options::value()-> + default_value("http://localhost:8042/"), "URL to the Orthanc server") ("username", "Username for the Orthanc server") ("password", "Password for the Orthanc server") - ("https-verify", boost::program_options::value()->default_value(true), "Check HTTPS certificates") + ("https-verify", boost::program_options::value()-> + default_value(true), "Check HTTPS certificates") ; options.add(generic); @@ -102,13 +104,15 @@ try { - boost::program_options::store(boost::program_options::command_line_parser(argc, argv). - options(options).allow_unregistered().run(), parameters); + boost::program_options::store( + boost::program_options::command_line_parser(argc, argv). + options(options).allow_unregistered().run(), parameters); boost::program_options::notify(parameters); } catch (boost::program_options::error& e) { - LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what(); + LOG(ERROR) << + "Error while parsing the command-line arguments: " << e.what(); error = true; } @@ -119,13 +123,7 @@ if (error || parameters.count("help")) { - std::cout << std::endl - << "Usage: " << argv[0] << " [OPTION]..." - << std::endl - << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." - << std::endl << std::endl - << "Demonstration application of Orthanc Stone in native environment." - << std::endl; + std::cout << std::endl; std::cout << options << "\n"; return error ? -1 : 0; @@ -138,21 +136,24 @@ Orthanc::HttpClient::ConfigureSsl(false, ""); } + LOG(ERROR) << "???????? if (parameters.count(\"verbose\"))"; if (parameters.count("verbose")) { + LOG(ERROR) << "parameters.count(\"verbose\") != 0"; Orthanc::Logging::EnableInfoLevel(true); LOG(INFO) << "Verbose logs are enabled"; } + LOG(ERROR) << "???????? if (parameters.count(\"trace\"))"; if (parameters.count("trace")) { + LOG(ERROR) << "parameters.count(\"trace\") != 0"; Orthanc::Logging::EnableTraceLevel(true); VLOG(1) << "Trace logs are enabled"; } ParseCommandLineOptions(parameters); - bool success = true; try { @@ -169,17 +170,20 @@ if (parameters.count("username") && parameters.count("password")) { - webServiceParameters.SetCredentials(parameters["username"].as(), - parameters["password"].as()); + webServiceParameters.SetCredentials(parameters["username"]. + as(), + parameters["password"].as()); } - LOG(WARNING) << "URL to the Orthanc REST API: " << webServiceParameters.GetUrl(); + LOG(WARNING) << "URL to the Orthanc REST API: " << + webServiceParameters.GetUrl(); { OrthancPlugins::OrthancHttpConnection orthanc(webServiceParameters); if (!MessagingToolbox::CheckOrthancVersion(orthanc)) { - LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of Orthanc, please upgrade"; + LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of " + << "Orthanc, please upgrade"; throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); } } @@ -196,11 +200,15 @@ NativeStoneApplicationContext context(broker_); { - Oracle oracle(6); // use multiple threads to execute asynchronous tasks like download content + // use multiple threads to execute asynchronous tasks like + // download content + Oracle oracle(6); oracle.Start(); { - OracleWebService webService(broker_, oracle, webServiceParameters, context); + OracleWebService webService( + broker_, oracle, webServiceParameters, context); + context.SetWebService(webService); context.SetOrthancBaseUrl(webServiceParameters.GetUrl()); @@ -255,5 +263,4 @@ return (success ? 0 : -1); } - } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Samples/CMakeLists.txt --- a/Applications/Samples/CMakeLists.txt Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Samples/CMakeLists.txt Thu May 16 09:11:14 2019 +0200 @@ -216,7 +216,6 @@ ${SIMPLE_VIEWER_APPLICATION_SOURCES} ) target_link_libraries(OrthancStoneSimpleViewer OrthancStone) - endif() ##################################################################### diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Samples/SampleMainNative.cpp --- a/Applications/Samples/SampleMainNative.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Samples/SampleMainNative.cpp Thu May 16 09:11:14 2019 +0200 @@ -42,3 +42,4 @@ return qtAppRunner.Execute(argc, argv); #endif } + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Samples/SingleFrameApplication.h --- a/Applications/Samples/SingleFrameApplication.h Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Samples/SingleFrameApplication.h Thu May 16 09:11:14 2019 +0200 @@ -136,9 +136,9 @@ slice = 0; } - if (slice >= static_cast(source_->GetSliceCount())) + if (slice >= static_cast(source_->GetSlicesCount())) { - slice = static_cast(source_->GetSliceCount()) - 1; + slice = static_cast(source_->GetSlicesCount()) - 1; } if (slice != static_cast(slice_)) @@ -158,7 +158,7 @@ void SetSlice(size_t index) { if (source_ != NULL && - index < source_->GetSliceCount()) + index < source_->GetSlicesCount()) { slice_ = static_cast(index); @@ -191,7 +191,7 @@ // slice if (source_ == &message.GetOrigin()) { - SetSlice(source_->GetSliceCount() / 2); + SetSlice(source_->GetSlicesCount() / 2); } GetMainWidget().FitContent(); diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Samples/StoneSampleCommands_generated.hpp --- a/Applications/Samples/StoneSampleCommands_generated.hpp Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Samples/StoneSampleCommands_generated.hpp Thu May 16 09:11:14 2019 +0200 @@ -1,703 +1,703 @@ -/* - 1 2 3 4 5 6 7 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 - -Generated on 2019-03-18 12:07:42.696093 by stonegentool - -*/ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -//#define STONEGEN_NO_CPP11 1 - -#ifdef STONEGEN_NO_CPP11 -#define StoneSmartPtr std::auto_ptr -#else -#define StoneSmartPtr std::unique_ptr -#endif - -namespace StoneSampleCommands -{ - /** Throws in case of problem */ - inline void _StoneDeserializeValue(int32_t& destValue, const Json::Value& jsonValue) - { - destValue = jsonValue.asInt(); - } - - inline Json::Value _StoneSerializeValue(int32_t value) - { - Json::Value result(value); - return result; - } - - inline void _StoneDeserializeValue(Json::Value& destValue, const Json::Value& jsonValue) - { - destValue = jsonValue; - } - - inline Json::Value _StoneSerializeValue(Json::Value value) - { - return value; - } - - /** Throws in case of problem */ - inline void _StoneDeserializeValue(double& destValue, const Json::Value& jsonValue) - { - destValue = jsonValue.asDouble(); - } - - inline Json::Value _StoneSerializeValue(double value) - { - Json::Value result(value); - return result; - } - - /** Throws in case of problem */ - inline void _StoneDeserializeValue(bool& destValue, const Json::Value& jsonValue) - { - destValue = jsonValue.asBool(); - } - - inline Json::Value _StoneSerializeValue(bool value) - { - Json::Value result(value); - return result; - } - - /** Throws in case of problem */ - inline void _StoneDeserializeValue( - std::string& destValue - , const Json::Value& jsonValue) - { - destValue = jsonValue.asString(); - } - - inline Json::Value _StoneSerializeValue(const std::string& value) - { - // the following is better than - Json::Value result(value.data(),value.data()+value.size()); - return result; - } - - inline std::string MakeIndent(size_t indent) - { - char* txt = reinterpret_cast(malloc(indent+1)); // NO EXCEPTION BELOW!!!!!!!!!!!! - for(size_t i = 0; i < indent; ++i) - txt[i] = ' '; - txt[indent] = 0; - std::string retVal(txt); - free(txt); // NO EXCEPTION ABOVE !!!!!!!!!! - return retVal; - } - - // generic dumper - template - std::ostream& StoneDumpValue(std::ostream& out, const T& value, size_t indent) - { - out << MakeIndent(indent) << value; - return out; - } - - // string dumper - inline std::ostream& StoneDumpValue(std::ostream& out, const std::string& value, size_t indent) - { - out << MakeIndent(indent) << "\"" << value << "\""; - return out; - } - - /** Throws in case of problem */ - template - void _StoneDeserializeValue( - std::map& destValue, const Json::Value& jsonValue) - { - destValue.clear(); - for ( - Json::Value::const_iterator itr = jsonValue.begin(); - itr != jsonValue.end(); - itr++) - { - std::string key; - _StoneDeserializeValue(key, itr.key()); - - T innerDestValue; - _StoneDeserializeValue(innerDestValue, *itr); - - destValue[key] = innerDestValue; - } - } - - template - Json::Value _StoneSerializeValue(const std::map& value) - { - Json::Value result(Json::objectValue); - - for (typename std::map::const_iterator it = value.cbegin(); - it != value.cend(); ++it) - { - // it->first it->second - result[it->first] = _StoneSerializeValue(it->second); - } - return result; - } - - template - std::ostream& StoneDumpValue(std::ostream& out, const std::map& value, size_t indent) - { - out << MakeIndent(indent) << "{\n"; - for (typename std::map::const_iterator it = value.cbegin(); - it != value.cend(); ++it) - { - out << MakeIndent(indent+2) << "\"" << it->first << "\" : "; - StoneDumpValue(out, it->second, indent+2); - } - out << MakeIndent(indent) << "}\n"; - return out; - } - - /** Throws in case of problem */ - template - void _StoneDeserializeValue( - std::vector& destValue, const Json::Value& jsonValue) - { - destValue.clear(); - destValue.reserve(jsonValue.size()); - for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++) - { - T innerDestValue; - _StoneDeserializeValue(innerDestValue, jsonValue[i]); - destValue.push_back(innerDestValue); - } - } - - template - Json::Value _StoneSerializeValue(const std::vector& value) - { - Json::Value result(Json::arrayValue); - for (size_t i = 0; i < value.size(); ++i) - { - result.append(_StoneSerializeValue(value[i])); - } - return result; - } - - template - std::ostream& StoneDumpValue(std::ostream& out, const std::vector& value, size_t indent) - { - out << MakeIndent(indent) << "[\n"; - for (size_t i = 0; i < value.size(); ++i) - { - StoneDumpValue(out, value[i], indent+2); - } - out << MakeIndent(indent) << "]\n"; - return out; - } - - inline void StoneCheckSerializedValueTypeGeneric(const Json::Value& value) - { - if ((!value.isMember("type")) || (!value["type"].isString())) - { - std::stringstream ss; - ss << "Cannot deserialize value ('type' key invalid)"; - throw std::runtime_error(ss.str()); - } - } - - inline void StoneCheckSerializedValueType( - const Json::Value& value, std::string typeStr) - { - StoneCheckSerializedValueTypeGeneric(value); - - std::string actTypeStr = value["type"].asString(); - if (actTypeStr != typeStr) - { - std::stringstream ss; - ss << "Cannot deserialize type" << actTypeStr - << "into " << typeStr; - throw std::runtime_error(ss.str()); - } - } - - // end of generic methods - -// end of generic methods - - enum Tool { - Tool_LineMeasure, - Tool_CircleMeasure, - Tool_Crop, - Tool_Windowing, - Tool_Zoom, - Tool_Pan, - Tool_Move, - Tool_Rotate, - Tool_Resize, - Tool_Mask, - }; - - inline std::string ToString(const Tool& value) - { - if( value == Tool_LineMeasure) - { - return std::string("LineMeasure"); - } - if( value == Tool_CircleMeasure) - { - return std::string("CircleMeasure"); - } - if( value == Tool_Crop) - { - return std::string("Crop"); - } - if( value == Tool_Windowing) - { - return std::string("Windowing"); - } - if( value == Tool_Zoom) - { - return std::string("Zoom"); - } - if( value == Tool_Pan) - { - return std::string("Pan"); - } - if( value == Tool_Move) - { - return std::string("Move"); - } - if( value == Tool_Rotate) - { - return std::string("Rotate"); - } - if( value == Tool_Resize) - { - return std::string("Resize"); - } - if( value == Tool_Mask) - { - return std::string("Mask"); - } - std::stringstream ss; - ss << "Value \"" << value << "\" cannot be converted to Tool. Possible values are: " - << " LineMeasure = " << static_cast(Tool_LineMeasure) << ", " - << " CircleMeasure = " << static_cast(Tool_CircleMeasure) << ", " - << " Crop = " << static_cast(Tool_Crop) << ", " - << " Windowing = " << static_cast(Tool_Windowing) << ", " - << " Zoom = " << static_cast(Tool_Zoom) << ", " - << " Pan = " << static_cast(Tool_Pan) << ", " - << " Move = " << static_cast(Tool_Move) << ", " - << " Rotate = " << static_cast(Tool_Rotate) << ", " - << " Resize = " << static_cast(Tool_Resize) << ", " - << " Mask = " << static_cast(Tool_Mask) << ", " - << std::endl; - std::string msg = ss.str(); - throw std::runtime_error(msg); - } - - inline void FromString(Tool& value, std::string strValue) - { - if( strValue == std::string("LineMeasure") ) - { - value = Tool_LineMeasure; - return; - } - if( strValue == std::string("CircleMeasure") ) - { - value = Tool_CircleMeasure; - return; - } - if( strValue == std::string("Crop") ) - { - value = Tool_Crop; - return; - } - if( strValue == std::string("Windowing") ) - { - value = Tool_Windowing; - return; - } - if( strValue == std::string("Zoom") ) - { - value = Tool_Zoom; - return; - } - if( strValue == std::string("Pan") ) - { - value = Tool_Pan; - return; - } - if( strValue == std::string("Move") ) - { - value = Tool_Move; - return; - } - if( strValue == std::string("Rotate") ) - { - value = Tool_Rotate; - return; - } - if( strValue == std::string("Resize") ) - { - value = Tool_Resize; - return; - } - if( strValue == std::string("Mask") ) - { - value = Tool_Mask; - return; - } - - std::stringstream ss; - ss << "String \"" << strValue << "\" cannot be converted to Tool. Possible values are: LineMeasure CircleMeasure Crop Windowing Zoom Pan Move Rotate Resize Mask "; - std::string msg = ss.str(); - throw std::runtime_error(msg); - } - - - inline void _StoneDeserializeValue( - Tool& destValue, const Json::Value& jsonValue) - { - FromString(destValue, jsonValue.asString()); - } - - inline Json::Value _StoneSerializeValue(const Tool& value) - { - std::string strValue = ToString(value); - return Json::Value(strValue); - } - - inline std::ostream& StoneDumpValue(std::ostream& out, const Tool& value, size_t indent = 0) - { - if( value == Tool_LineMeasure) - { - out << MakeIndent(indent) << "LineMeasure" << std::endl; - } - if( value == Tool_CircleMeasure) - { - out << MakeIndent(indent) << "CircleMeasure" << std::endl; - } - if( value == Tool_Crop) - { - out << MakeIndent(indent) << "Crop" << std::endl; - } - if( value == Tool_Windowing) - { - out << MakeIndent(indent) << "Windowing" << std::endl; - } - if( value == Tool_Zoom) - { - out << MakeIndent(indent) << "Zoom" << std::endl; - } - if( value == Tool_Pan) - { - out << MakeIndent(indent) << "Pan" << std::endl; - } - if( value == Tool_Move) - { - out << MakeIndent(indent) << "Move" << std::endl; - } - if( value == Tool_Rotate) - { - out << MakeIndent(indent) << "Rotate" << std::endl; - } - if( value == Tool_Resize) - { - out << MakeIndent(indent) << "Resize" << std::endl; - } - if( value == Tool_Mask) - { - out << MakeIndent(indent) << "Mask" << std::endl; - } - return out; - } - - - enum ActionType { - ActionType_UndoCrop, - ActionType_Rotate, - ActionType_Invert, - }; - - inline std::string ToString(const ActionType& value) - { - if( value == ActionType_UndoCrop) - { - return std::string("UndoCrop"); - } - if( value == ActionType_Rotate) - { - return std::string("Rotate"); - } - if( value == ActionType_Invert) - { - return std::string("Invert"); - } - std::stringstream ss; - ss << "Value \"" << value << "\" cannot be converted to ActionType. Possible values are: " - << " UndoCrop = " << static_cast(ActionType_UndoCrop) << ", " - << " Rotate = " << static_cast(ActionType_Rotate) << ", " - << " Invert = " << static_cast(ActionType_Invert) << ", " - << std::endl; - std::string msg = ss.str(); - throw std::runtime_error(msg); - } - - inline void FromString(ActionType& value, std::string strValue) - { - if( strValue == std::string("UndoCrop") ) - { - value = ActionType_UndoCrop; - return; - } - if( strValue == std::string("Rotate") ) - { - value = ActionType_Rotate; - return; - } - if( strValue == std::string("Invert") ) - { - value = ActionType_Invert; - return; - } - - std::stringstream ss; - ss << "String \"" << strValue << "\" cannot be converted to ActionType. Possible values are: UndoCrop Rotate Invert "; - std::string msg = ss.str(); - throw std::runtime_error(msg); - } - - - inline void _StoneDeserializeValue( - ActionType& destValue, const Json::Value& jsonValue) - { - FromString(destValue, jsonValue.asString()); - } - - inline Json::Value _StoneSerializeValue(const ActionType& value) - { - std::string strValue = ToString(value); - return Json::Value(strValue); - } - - inline std::ostream& StoneDumpValue(std::ostream& out, const ActionType& value, size_t indent = 0) - { - if( value == ActionType_UndoCrop) - { - out << MakeIndent(indent) << "UndoCrop" << std::endl; - } - if( value == ActionType_Rotate) - { - out << MakeIndent(indent) << "Rotate" << std::endl; - } - if( value == ActionType_Invert) - { - out << MakeIndent(indent) << "Invert" << std::endl; - } - return out; - } - - - -#ifdef _MSC_VER -#pragma region SelectTool -#endif //_MSC_VER - - struct SelectTool - { - Tool tool; - - SelectTool(Tool tool = Tool()) - { - this->tool = tool; - } - }; - - inline void _StoneDeserializeValue(SelectTool& destValue, const Json::Value& value) - { - _StoneDeserializeValue(destValue.tool, value["tool"]); - } - - inline Json::Value _StoneSerializeValue(const SelectTool& value) - { - Json::Value result(Json::objectValue); - result["tool"] = _StoneSerializeValue(value.tool); - - return result; - } - - inline std::ostream& StoneDumpValue(std::ostream& out, const SelectTool& value, size_t indent = 0) - { - out << MakeIndent(indent) << "{\n"; - out << MakeIndent(indent) << "tool:\n"; - StoneDumpValue(out, value.tool,indent+2); - out << "\n"; - - out << MakeIndent(indent) << "}\n"; - return out; - } - - inline void StoneDeserialize(SelectTool& destValue, const Json::Value& value) - { - StoneCheckSerializedValueType(value, "StoneSampleCommands.SelectTool"); - _StoneDeserializeValue(destValue, value["value"]); - } - - inline Json::Value StoneSerializeToJson(const SelectTool& value) - { - Json::Value result(Json::objectValue); - result["type"] = "StoneSampleCommands.SelectTool"; - result["value"] = _StoneSerializeValue(value); - return result; - } - - inline std::string StoneSerialize(const SelectTool& value) - { - Json::Value resultJson = StoneSerializeToJson(value); - std::string resultStr = resultJson.toStyledString(); - return resultStr; - } - -#ifdef _MSC_VER -#pragma endregion SelectTool -#endif //_MSC_VER - -#ifdef _MSC_VER -#pragma region Action -#endif //_MSC_VER - - struct Action - { - ActionType type; - - Action(ActionType type = ActionType()) - { - this->type = type; - } - }; - - inline void _StoneDeserializeValue(Action& destValue, const Json::Value& value) - { - _StoneDeserializeValue(destValue.type, value["type"]); - } - - inline Json::Value _StoneSerializeValue(const Action& value) - { - Json::Value result(Json::objectValue); - result["type"] = _StoneSerializeValue(value.type); - - return result; - } - - inline std::ostream& StoneDumpValue(std::ostream& out, const Action& value, size_t indent = 0) - { - out << MakeIndent(indent) << "{\n"; - out << MakeIndent(indent) << "type:\n"; - StoneDumpValue(out, value.type,indent+2); - out << "\n"; - - out << MakeIndent(indent) << "}\n"; - return out; - } - - inline void StoneDeserialize(Action& destValue, const Json::Value& value) - { - StoneCheckSerializedValueType(value, "StoneSampleCommands.Action"); - _StoneDeserializeValue(destValue, value["value"]); - } - - inline Json::Value StoneSerializeToJson(const Action& value) - { - Json::Value result(Json::objectValue); - result["type"] = "StoneSampleCommands.Action"; - result["value"] = _StoneSerializeValue(value); - return result; - } - - inline std::string StoneSerialize(const Action& value) - { - Json::Value resultJson = StoneSerializeToJson(value); - std::string resultStr = resultJson.toStyledString(); - return resultStr; - } - -#ifdef _MSC_VER -#pragma endregion Action -#endif //_MSC_VER - -#ifdef _MSC_VER -#pragma region Dispatching code -#endif //_MSC_VER - - class IHandler - { - public: - virtual bool Handle(const SelectTool& value) = 0; - virtual bool Handle(const Action& value) = 0; - }; - - /** Service function for StoneDispatchToHandler */ - inline bool StoneDispatchJsonToHandler( - const Json::Value& jsonValue, IHandler* handler) - { - StoneCheckSerializedValueTypeGeneric(jsonValue); - std::string type = jsonValue["type"].asString(); - if (type == "") - { - // this should never ever happen - throw std::runtime_error("Caught empty type while dispatching"); - } - else if (type == "StoneSampleCommands.SelectTool") - { - SelectTool value; - _StoneDeserializeValue(value, jsonValue["value"]); - return handler->Handle(value); - } - else if (type == "StoneSampleCommands.Action") - { - Action value; - _StoneDeserializeValue(value, jsonValue["value"]); - return handler->Handle(value); - } - else - { - return false; - } - } - - /** Takes a serialized type and passes this to the handler */ - inline bool StoneDispatchToHandler(std::string strValue, IHandler* handler) - { - Json::Value readValue; - - Json::CharReaderBuilder builder; - Json::CharReader* reader = builder.newCharReader(); - - StoneSmartPtr ptr(reader); - - std::string errors; - - bool ok = reader->parse( - strValue.c_str(), - strValue.c_str() + strValue.size(), - &readValue, - &errors - ); - if (!ok) - { - std::stringstream ss; - ss << "Jsoncpp parsing error: " << errors; - throw std::runtime_error(ss.str()); - } - return StoneDispatchJsonToHandler(readValue, handler); - } - -#ifdef _MSC_VER -#pragma endregion Dispatching code -#endif //_MSC_VER +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 + +Generated on 2019-03-18 12:07:42.696093 by stonegentool + +*/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +//#define STONEGEN_NO_CPP11 1 + +#ifdef STONEGEN_NO_CPP11 +#define StoneSmartPtr std::auto_ptr +#else +#define StoneSmartPtr std::unique_ptr +#endif + +namespace StoneSampleCommands +{ + /** Throws in case of problem */ + inline void _StoneDeserializeValue(int32_t& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asInt(); + } + + inline Json::Value _StoneSerializeValue(int32_t value) + { + Json::Value result(value); + return result; + } + + inline void _StoneDeserializeValue(Json::Value& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue; + } + + inline Json::Value _StoneSerializeValue(Json::Value value) + { + return value; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue(double& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asDouble(); + } + + inline Json::Value _StoneSerializeValue(double value) + { + Json::Value result(value); + return result; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue(bool& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asBool(); + } + + inline Json::Value _StoneSerializeValue(bool value) + { + Json::Value result(value); + return result; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue( + std::string& destValue + , const Json::Value& jsonValue) + { + destValue = jsonValue.asString(); + } + + inline Json::Value _StoneSerializeValue(const std::string& value) + { + // the following is better than + Json::Value result(value.data(),value.data()+value.size()); + return result; + } + + inline std::string MakeIndent(size_t indent) + { + char* txt = reinterpret_cast(malloc(indent+1)); // NO EXCEPTION BELOW!!!!!!!!!!!! + for(size_t i = 0; i < indent; ++i) + txt[i] = ' '; + txt[indent] = 0; + std::string retVal(txt); + free(txt); // NO EXCEPTION ABOVE !!!!!!!!!! + return retVal; + } + + // generic dumper + template + std::ostream& StoneDumpValue(std::ostream& out, const T& value, size_t indent) + { + out << MakeIndent(indent) << value; + return out; + } + + // string dumper + inline std::ostream& StoneDumpValue(std::ostream& out, const std::string& value, size_t indent) + { + out << MakeIndent(indent) << "\"" << value << "\""; + return out; + } + + /** Throws in case of problem */ + template + void _StoneDeserializeValue( + std::map& destValue, const Json::Value& jsonValue) + { + destValue.clear(); + for ( + Json::Value::const_iterator itr = jsonValue.begin(); + itr != jsonValue.end(); + itr++) + { + std::string key; + _StoneDeserializeValue(key, itr.key()); + + T innerDestValue; + _StoneDeserializeValue(innerDestValue, *itr); + + destValue[key] = innerDestValue; + } + } + + template + Json::Value _StoneSerializeValue(const std::map& value) + { + Json::Value result(Json::objectValue); + + for (typename std::map::const_iterator it = value.cbegin(); + it != value.cend(); ++it) + { + // it->first it->second + result[it->first] = _StoneSerializeValue(it->second); + } + return result; + } + + template + std::ostream& StoneDumpValue(std::ostream& out, const std::map& value, size_t indent) + { + out << MakeIndent(indent) << "{\n"; + for (typename std::map::const_iterator it = value.cbegin(); + it != value.cend(); ++it) + { + out << MakeIndent(indent+2) << "\"" << it->first << "\" : "; + StoneDumpValue(out, it->second, indent+2); + } + out << MakeIndent(indent) << "}\n"; + return out; + } + + /** Throws in case of problem */ + template + void _StoneDeserializeValue( + std::vector& destValue, const Json::Value& jsonValue) + { + destValue.clear(); + destValue.reserve(jsonValue.size()); + for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++) + { + T innerDestValue; + _StoneDeserializeValue(innerDestValue, jsonValue[i]); + destValue.push_back(innerDestValue); + } + } + + template + Json::Value _StoneSerializeValue(const std::vector& value) + { + Json::Value result(Json::arrayValue); + for (size_t i = 0; i < value.size(); ++i) + { + result.append(_StoneSerializeValue(value[i])); + } + return result; + } + + template + std::ostream& StoneDumpValue(std::ostream& out, const std::vector& value, size_t indent) + { + out << MakeIndent(indent) << "[\n"; + for (size_t i = 0; i < value.size(); ++i) + { + StoneDumpValue(out, value[i], indent+2); + } + out << MakeIndent(indent) << "]\n"; + return out; + } + + inline void StoneCheckSerializedValueTypeGeneric(const Json::Value& value) + { + if ((!value.isMember("type")) || (!value["type"].isString())) + { + std::stringstream ss; + ss << "Cannot deserialize value ('type' key invalid)"; + throw std::runtime_error(ss.str()); + } + } + + inline void StoneCheckSerializedValueType( + const Json::Value& value, std::string typeStr) + { + StoneCheckSerializedValueTypeGeneric(value); + + std::string actTypeStr = value["type"].asString(); + if (actTypeStr != typeStr) + { + std::stringstream ss; + ss << "Cannot deserialize type" << actTypeStr + << "into " << typeStr; + throw std::runtime_error(ss.str()); + } + } + + // end of generic methods + +// end of generic methods + + enum Tool { + Tool_LineMeasure, + Tool_CircleMeasure, + Tool_Crop, + Tool_Windowing, + Tool_Zoom, + Tool_Pan, + Tool_Move, + Tool_Rotate, + Tool_Resize, + Tool_Mask, + }; + + inline std::string ToString(const Tool& value) + { + if( value == Tool_LineMeasure) + { + return std::string("LineMeasure"); + } + if( value == Tool_CircleMeasure) + { + return std::string("CircleMeasure"); + } + if( value == Tool_Crop) + { + return std::string("Crop"); + } + if( value == Tool_Windowing) + { + return std::string("Windowing"); + } + if( value == Tool_Zoom) + { + return std::string("Zoom"); + } + if( value == Tool_Pan) + { + return std::string("Pan"); + } + if( value == Tool_Move) + { + return std::string("Move"); + } + if( value == Tool_Rotate) + { + return std::string("Rotate"); + } + if( value == Tool_Resize) + { + return std::string("Resize"); + } + if( value == Tool_Mask) + { + return std::string("Mask"); + } + std::stringstream ss; + ss << "Value \"" << value << "\" cannot be converted to Tool. Possible values are: " + << " LineMeasure = " << static_cast(Tool_LineMeasure) << ", " + << " CircleMeasure = " << static_cast(Tool_CircleMeasure) << ", " + << " Crop = " << static_cast(Tool_Crop) << ", " + << " Windowing = " << static_cast(Tool_Windowing) << ", " + << " Zoom = " << static_cast(Tool_Zoom) << ", " + << " Pan = " << static_cast(Tool_Pan) << ", " + << " Move = " << static_cast(Tool_Move) << ", " + << " Rotate = " << static_cast(Tool_Rotate) << ", " + << " Resize = " << static_cast(Tool_Resize) << ", " + << " Mask = " << static_cast(Tool_Mask) << ", " + << std::endl; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + inline void FromString(Tool& value, std::string strValue) + { + if( strValue == std::string("LineMeasure") ) + { + value = Tool_LineMeasure; + return; + } + if( strValue == std::string("CircleMeasure") ) + { + value = Tool_CircleMeasure; + return; + } + if( strValue == std::string("Crop") ) + { + value = Tool_Crop; + return; + } + if( strValue == std::string("Windowing") ) + { + value = Tool_Windowing; + return; + } + if( strValue == std::string("Zoom") ) + { + value = Tool_Zoom; + return; + } + if( strValue == std::string("Pan") ) + { + value = Tool_Pan; + return; + } + if( strValue == std::string("Move") ) + { + value = Tool_Move; + return; + } + if( strValue == std::string("Rotate") ) + { + value = Tool_Rotate; + return; + } + if( strValue == std::string("Resize") ) + { + value = Tool_Resize; + return; + } + if( strValue == std::string("Mask") ) + { + value = Tool_Mask; + return; + } + + std::stringstream ss; + ss << "String \"" << strValue << "\" cannot be converted to Tool. Possible values are: LineMeasure CircleMeasure Crop Windowing Zoom Pan Move Rotate Resize Mask "; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + + inline void _StoneDeserializeValue( + Tool& destValue, const Json::Value& jsonValue) + { + FromString(destValue, jsonValue.asString()); + } + + inline Json::Value _StoneSerializeValue(const Tool& value) + { + std::string strValue = ToString(value); + return Json::Value(strValue); + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const Tool& value, size_t indent = 0) + { + if( value == Tool_LineMeasure) + { + out << MakeIndent(indent) << "LineMeasure" << std::endl; + } + if( value == Tool_CircleMeasure) + { + out << MakeIndent(indent) << "CircleMeasure" << std::endl; + } + if( value == Tool_Crop) + { + out << MakeIndent(indent) << "Crop" << std::endl; + } + if( value == Tool_Windowing) + { + out << MakeIndent(indent) << "Windowing" << std::endl; + } + if( value == Tool_Zoom) + { + out << MakeIndent(indent) << "Zoom" << std::endl; + } + if( value == Tool_Pan) + { + out << MakeIndent(indent) << "Pan" << std::endl; + } + if( value == Tool_Move) + { + out << MakeIndent(indent) << "Move" << std::endl; + } + if( value == Tool_Rotate) + { + out << MakeIndent(indent) << "Rotate" << std::endl; + } + if( value == Tool_Resize) + { + out << MakeIndent(indent) << "Resize" << std::endl; + } + if( value == Tool_Mask) + { + out << MakeIndent(indent) << "Mask" << std::endl; + } + return out; + } + + + enum ActionType { + ActionType_UndoCrop, + ActionType_Rotate, + ActionType_Invert, + }; + + inline std::string ToString(const ActionType& value) + { + if( value == ActionType_UndoCrop) + { + return std::string("UndoCrop"); + } + if( value == ActionType_Rotate) + { + return std::string("Rotate"); + } + if( value == ActionType_Invert) + { + return std::string("Invert"); + } + std::stringstream ss; + ss << "Value \"" << value << "\" cannot be converted to ActionType. Possible values are: " + << " UndoCrop = " << static_cast(ActionType_UndoCrop) << ", " + << " Rotate = " << static_cast(ActionType_Rotate) << ", " + << " Invert = " << static_cast(ActionType_Invert) << ", " + << std::endl; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + inline void FromString(ActionType& value, std::string strValue) + { + if( strValue == std::string("UndoCrop") ) + { + value = ActionType_UndoCrop; + return; + } + if( strValue == std::string("Rotate") ) + { + value = ActionType_Rotate; + return; + } + if( strValue == std::string("Invert") ) + { + value = ActionType_Invert; + return; + } + + std::stringstream ss; + ss << "String \"" << strValue << "\" cannot be converted to ActionType. Possible values are: UndoCrop Rotate Invert "; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + + inline void _StoneDeserializeValue( + ActionType& destValue, const Json::Value& jsonValue) + { + FromString(destValue, jsonValue.asString()); + } + + inline Json::Value _StoneSerializeValue(const ActionType& value) + { + std::string strValue = ToString(value); + return Json::Value(strValue); + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const ActionType& value, size_t indent = 0) + { + if( value == ActionType_UndoCrop) + { + out << MakeIndent(indent) << "UndoCrop" << std::endl; + } + if( value == ActionType_Rotate) + { + out << MakeIndent(indent) << "Rotate" << std::endl; + } + if( value == ActionType_Invert) + { + out << MakeIndent(indent) << "Invert" << std::endl; + } + return out; + } + + + +#ifdef _MSC_VER +#pragma region SelectTool +#endif //_MSC_VER + + struct SelectTool + { + Tool tool; + + SelectTool(Tool tool = Tool()) + { + this->tool = tool; + } + }; + + inline void _StoneDeserializeValue(SelectTool& destValue, const Json::Value& value) + { + _StoneDeserializeValue(destValue.tool, value["tool"]); + } + + inline Json::Value _StoneSerializeValue(const SelectTool& value) + { + Json::Value result(Json::objectValue); + result["tool"] = _StoneSerializeValue(value.tool); + + return result; + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const SelectTool& value, size_t indent = 0) + { + out << MakeIndent(indent) << "{\n"; + out << MakeIndent(indent) << "tool:\n"; + StoneDumpValue(out, value.tool,indent+2); + out << "\n"; + + out << MakeIndent(indent) << "}\n"; + return out; + } + + inline void StoneDeserialize(SelectTool& destValue, const Json::Value& value) + { + StoneCheckSerializedValueType(value, "StoneSampleCommands.SelectTool"); + _StoneDeserializeValue(destValue, value["value"]); + } + + inline Json::Value StoneSerializeToJson(const SelectTool& value) + { + Json::Value result(Json::objectValue); + result["type"] = "StoneSampleCommands.SelectTool"; + result["value"] = _StoneSerializeValue(value); + return result; + } + + inline std::string StoneSerialize(const SelectTool& value) + { + Json::Value resultJson = StoneSerializeToJson(value); + std::string resultStr = resultJson.toStyledString(); + return resultStr; + } + +#ifdef _MSC_VER +#pragma endregion SelectTool +#endif //_MSC_VER + +#ifdef _MSC_VER +#pragma region Action +#endif //_MSC_VER + + struct Action + { + ActionType type; + + Action(ActionType type = ActionType()) + { + this->type = type; + } + }; + + inline void _StoneDeserializeValue(Action& destValue, const Json::Value& value) + { + _StoneDeserializeValue(destValue.type, value["type"]); + } + + inline Json::Value _StoneSerializeValue(const Action& value) + { + Json::Value result(Json::objectValue); + result["type"] = _StoneSerializeValue(value.type); + + return result; + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const Action& value, size_t indent = 0) + { + out << MakeIndent(indent) << "{\n"; + out << MakeIndent(indent) << "type:\n"; + StoneDumpValue(out, value.type,indent+2); + out << "\n"; + + out << MakeIndent(indent) << "}\n"; + return out; + } + + inline void StoneDeserialize(Action& destValue, const Json::Value& value) + { + StoneCheckSerializedValueType(value, "StoneSampleCommands.Action"); + _StoneDeserializeValue(destValue, value["value"]); + } + + inline Json::Value StoneSerializeToJson(const Action& value) + { + Json::Value result(Json::objectValue); + result["type"] = "StoneSampleCommands.Action"; + result["value"] = _StoneSerializeValue(value); + return result; + } + + inline std::string StoneSerialize(const Action& value) + { + Json::Value resultJson = StoneSerializeToJson(value); + std::string resultStr = resultJson.toStyledString(); + return resultStr; + } + +#ifdef _MSC_VER +#pragma endregion Action +#endif //_MSC_VER + +#ifdef _MSC_VER +#pragma region Dispatching code +#endif //_MSC_VER + + class IHandler + { + public: + virtual bool Handle(const SelectTool& value) = 0; + virtual bool Handle(const Action& value) = 0; + }; + + /** Service function for StoneDispatchToHandler */ + inline bool StoneDispatchJsonToHandler( + const Json::Value& jsonValue, IHandler* handler) + { + StoneCheckSerializedValueTypeGeneric(jsonValue); + std::string type = jsonValue["type"].asString(); + if (type == "") + { + // this should never ever happen + throw std::runtime_error("Caught empty type while dispatching"); + } + else if (type == "StoneSampleCommands.SelectTool") + { + SelectTool value; + _StoneDeserializeValue(value, jsonValue["value"]); + return handler->Handle(value); + } + else if (type == "StoneSampleCommands.Action") + { + Action value; + _StoneDeserializeValue(value, jsonValue["value"]); + return handler->Handle(value); + } + else + { + return false; + } + } + + /** Takes a serialized type and passes this to the handler */ + inline bool StoneDispatchToHandler(std::string strValue, IHandler* handler) + { + Json::Value readValue; + + Json::CharReaderBuilder builder; + Json::CharReader* reader = builder.newCharReader(); + + StoneSmartPtr ptr(reader); + + std::string errors; + + bool ok = reader->parse( + strValue.c_str(), + strValue.c_str() + strValue.size(), + &readValue, + &errors + ); + if (!ok) + { + std::stringstream ss; + ss << "Jsoncpp parsing error: " << errors; + throw std::runtime_error(ss.str()); + } + return StoneDispatchJsonToHandler(readValue, handler); + } + +#ifdef _MSC_VER +#pragma endregion Dispatching code +#endif //_MSC_VER } \ No newline at end of file diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Samples/StoneSampleCommands_generated.ts --- a/Applications/Samples/StoneSampleCommands_generated.ts Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Samples/StoneSampleCommands_generated.ts Thu May 16 09:11:14 2019 +0200 @@ -1,333 +1,333 @@ -/* - 1 2 3 4 5 6 7 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 - -Generated on 2019-03-18 12:07:42.696093 by stonegentool - -*/ - -function StoneCheckSerializedValueType(value: any, typeStr: string) -{ - StoneCheckSerializedValueTypeGeneric(value); - - if (value['type'] != typeStr) - { - throw new Error( - `Cannot deserialize type ${value['type']} into ${typeStr}`); - } -} - -function isString(val: any) :boolean -{ - return ((typeof val === 'string') || (val instanceof String)); -} - -function StoneCheckSerializedValueTypeGeneric(value: any) -{ - // console.//log("+-------------------------------------------------+"); - // console.//log("| StoneCheckSerializedValueTypeGeneric |"); - // console.//log("+-------------------------------------------------+"); - // console.//log("value = "); - // console.//log(value); - if ( (!('type' in value)) || (!isString(value.type)) ) - { - throw new Error( - "Cannot deserialize value ('type' key invalid)"); - } -} - -// end of generic methods - -export enum Tool { - LineMeasure = "LineMeasure", - CircleMeasure = "CircleMeasure", - Crop = "Crop", - Windowing = "Windowing", - Zoom = "Zoom", - Pan = "Pan", - Move = "Move", - Rotate = "Rotate", - Resize = "Resize", - Mask = "Mask" -}; - -export function Tool_FromString(strValue:string) : Tool -{ - if( strValue == "LineMeasure" ) - { - return Tool.LineMeasure; - } - if( strValue == "CircleMeasure" ) - { - return Tool.CircleMeasure; - } - if( strValue == "Crop" ) - { - return Tool.Crop; - } - if( strValue == "Windowing" ) - { - return Tool.Windowing; - } - if( strValue == "Zoom" ) - { - return Tool.Zoom; - } - if( strValue == "Pan" ) - { - return Tool.Pan; - } - if( strValue == "Move" ) - { - return Tool.Move; - } - if( strValue == "Rotate" ) - { - return Tool.Rotate; - } - if( strValue == "Resize" ) - { - return Tool.Resize; - } - if( strValue == "Mask" ) - { - return Tool.Mask; - } - - let msg : string = `String ${strValue} cannot be converted to Tool. Possible values are: LineMeasure, CircleMeasure, Crop, Windowing, Zoom, Pan, Move, Rotate, Resize, Mask`; - throw new Error(msg); -} - -export function Tool_ToString(value:Tool) : string -{ - if( value == Tool.LineMeasure ) - { - return "LineMeasure"; - } - if( value == Tool.CircleMeasure ) - { - return "CircleMeasure"; - } - if( value == Tool.Crop ) - { - return "Crop"; - } - if( value == Tool.Windowing ) - { - return "Windowing"; - } - if( value == Tool.Zoom ) - { - return "Zoom"; - } - if( value == Tool.Pan ) - { - return "Pan"; - } - if( value == Tool.Move ) - { - return "Move"; - } - if( value == Tool.Rotate ) - { - return "Rotate"; - } - if( value == Tool.Resize ) - { - return "Resize"; - } - if( value == Tool.Mask ) - { - return "Mask"; - } - - let msg : string = `Value ${value} cannot be converted to Tool. Possible values are: `; - { - let _LineMeasure_enumValue : string = Tool.LineMeasure; // enums are strings in stonecodegen, so this will work. - let msg_LineMeasure : string = `LineMeasure (${_LineMeasure_enumValue}), `; - msg = msg + msg_LineMeasure; - } - { - let _CircleMeasure_enumValue : string = Tool.CircleMeasure; // enums are strings in stonecodegen, so this will work. - let msg_CircleMeasure : string = `CircleMeasure (${_CircleMeasure_enumValue}), `; - msg = msg + msg_CircleMeasure; - } - { - let _Crop_enumValue : string = Tool.Crop; // enums are strings in stonecodegen, so this will work. - let msg_Crop : string = `Crop (${_Crop_enumValue}), `; - msg = msg + msg_Crop; - } - { - let _Windowing_enumValue : string = Tool.Windowing; // enums are strings in stonecodegen, so this will work. - let msg_Windowing : string = `Windowing (${_Windowing_enumValue}), `; - msg = msg + msg_Windowing; - } - { - let _Zoom_enumValue : string = Tool.Zoom; // enums are strings in stonecodegen, so this will work. - let msg_Zoom : string = `Zoom (${_Zoom_enumValue}), `; - msg = msg + msg_Zoom; - } - { - let _Pan_enumValue : string = Tool.Pan; // enums are strings in stonecodegen, so this will work. - let msg_Pan : string = `Pan (${_Pan_enumValue}), `; - msg = msg + msg_Pan; - } - { - let _Move_enumValue : string = Tool.Move; // enums are strings in stonecodegen, so this will work. - let msg_Move : string = `Move (${_Move_enumValue}), `; - msg = msg + msg_Move; - } - { - let _Rotate_enumValue : string = Tool.Rotate; // enums are strings in stonecodegen, so this will work. - let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `; - msg = msg + msg_Rotate; - } - { - let _Resize_enumValue : string = Tool.Resize; // enums are strings in stonecodegen, so this will work. - let msg_Resize : string = `Resize (${_Resize_enumValue}), `; - msg = msg + msg_Resize; - } - { - let _Mask_enumValue : string = Tool.Mask; // enums are strings in stonecodegen, so this will work. - let msg_Mask : string = `Mask (${_Mask_enumValue})`; - msg = msg + msg_Mask; - } - throw new Error(msg); -} - -export enum ActionType { - UndoCrop = "UndoCrop", - Rotate = "Rotate", - Invert = "Invert" -}; - -export function ActionType_FromString(strValue:string) : ActionType -{ - if( strValue == "UndoCrop" ) - { - return ActionType.UndoCrop; - } - if( strValue == "Rotate" ) - { - return ActionType.Rotate; - } - if( strValue == "Invert" ) - { - return ActionType.Invert; - } - - let msg : string = `String ${strValue} cannot be converted to ActionType. Possible values are: UndoCrop, Rotate, Invert`; - throw new Error(msg); -} - -export function ActionType_ToString(value:ActionType) : string -{ - if( value == ActionType.UndoCrop ) - { - return "UndoCrop"; - } - if( value == ActionType.Rotate ) - { - return "Rotate"; - } - if( value == ActionType.Invert ) - { - return "Invert"; - } - - let msg : string = `Value ${value} cannot be converted to ActionType. Possible values are: `; - { - let _UndoCrop_enumValue : string = ActionType.UndoCrop; // enums are strings in stonecodegen, so this will work. - let msg_UndoCrop : string = `UndoCrop (${_UndoCrop_enumValue}), `; - msg = msg + msg_UndoCrop; - } - { - let _Rotate_enumValue : string = ActionType.Rotate; // enums are strings in stonecodegen, so this will work. - let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `; - msg = msg + msg_Rotate; - } - { - let _Invert_enumValue : string = ActionType.Invert; // enums are strings in stonecodegen, so this will work. - let msg_Invert : string = `Invert (${_Invert_enumValue})`; - msg = msg + msg_Invert; - } - throw new Error(msg); -} - - - -export class SelectTool { - tool:Tool; - - constructor() { - } - - public StoneSerialize(): string { - let container: object = {}; - container['type'] = 'StoneSampleCommands.SelectTool'; - container['value'] = this; - return JSON.stringify(container); - } - - public static StoneDeserialize(valueStr: string) : SelectTool - { - let value: any = JSON.parse(valueStr); - StoneCheckSerializedValueType(value, 'StoneSampleCommands.SelectTool'); - let result: SelectTool = value['value'] as SelectTool; - return result; - } -} -export class Action { - type:ActionType; - - constructor() { - } - - public StoneSerialize(): string { - let container: object = {}; - container['type'] = 'StoneSampleCommands.Action'; - container['value'] = this; - return JSON.stringify(container); - } - - public static StoneDeserialize(valueStr: string) : Action - { - let value: any = JSON.parse(valueStr); - StoneCheckSerializedValueType(value, 'StoneSampleCommands.Action'); - let result: Action = value['value'] as Action; - return result; - } -} - -export interface IHandler { -}; - -/** Service function for StoneDispatchToHandler */ -export function StoneDispatchJsonToHandler( - jsonValue: any, handler: IHandler): boolean -{ - StoneCheckSerializedValueTypeGeneric(jsonValue); - let type: string = jsonValue["type"]; - if (type == "") - { - // this should never ever happen - throw new Error("Caught empty type while dispatching"); - } - else - { - return false; - } -} - -/** Takes a serialized type and passes this to the handler */ -export function StoneDispatchToHandler( - strValue: string, handler: IHandler): boolean -{ - // console.//log("+------------------------------------------------+"); - // console.//log("| StoneDispatchToHandler |"); - // console.//log("+------------------------------------------------+"); - // console.//log("strValue = "); - // console.//log(strValue); - let jsonValue: any = JSON.parse(strValue) - return StoneDispatchJsonToHandler(jsonValue, handler); +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 + +Generated on 2019-03-18 12:07:42.696093 by stonegentool + +*/ + +function StoneCheckSerializedValueType(value: any, typeStr: string) +{ + StoneCheckSerializedValueTypeGeneric(value); + + if (value['type'] != typeStr) + { + throw new Error( + `Cannot deserialize type ${value['type']} into ${typeStr}`); + } +} + +function isString(val: any) :boolean +{ + return ((typeof val === 'string') || (val instanceof String)); +} + +function StoneCheckSerializedValueTypeGeneric(value: any) +{ + // console.//log("+-------------------------------------------------+"); + // console.//log("| StoneCheckSerializedValueTypeGeneric |"); + // console.//log("+-------------------------------------------------+"); + // console.//log("value = "); + // console.//log(value); + if ( (!('type' in value)) || (!isString(value.type)) ) + { + throw new Error( + "Cannot deserialize value ('type' key invalid)"); + } +} + +// end of generic methods + +export enum Tool { + LineMeasure = "LineMeasure", + CircleMeasure = "CircleMeasure", + Crop = "Crop", + Windowing = "Windowing", + Zoom = "Zoom", + Pan = "Pan", + Move = "Move", + Rotate = "Rotate", + Resize = "Resize", + Mask = "Mask" +}; + +export function Tool_FromString(strValue:string) : Tool +{ + if( strValue == "LineMeasure" ) + { + return Tool.LineMeasure; + } + if( strValue == "CircleMeasure" ) + { + return Tool.CircleMeasure; + } + if( strValue == "Crop" ) + { + return Tool.Crop; + } + if( strValue == "Windowing" ) + { + return Tool.Windowing; + } + if( strValue == "Zoom" ) + { + return Tool.Zoom; + } + if( strValue == "Pan" ) + { + return Tool.Pan; + } + if( strValue == "Move" ) + { + return Tool.Move; + } + if( strValue == "Rotate" ) + { + return Tool.Rotate; + } + if( strValue == "Resize" ) + { + return Tool.Resize; + } + if( strValue == "Mask" ) + { + return Tool.Mask; + } + + let msg : string = `String ${strValue} cannot be converted to Tool. Possible values are: LineMeasure, CircleMeasure, Crop, Windowing, Zoom, Pan, Move, Rotate, Resize, Mask`; + throw new Error(msg); +} + +export function Tool_ToString(value:Tool) : string +{ + if( value == Tool.LineMeasure ) + { + return "LineMeasure"; + } + if( value == Tool.CircleMeasure ) + { + return "CircleMeasure"; + } + if( value == Tool.Crop ) + { + return "Crop"; + } + if( value == Tool.Windowing ) + { + return "Windowing"; + } + if( value == Tool.Zoom ) + { + return "Zoom"; + } + if( value == Tool.Pan ) + { + return "Pan"; + } + if( value == Tool.Move ) + { + return "Move"; + } + if( value == Tool.Rotate ) + { + return "Rotate"; + } + if( value == Tool.Resize ) + { + return "Resize"; + } + if( value == Tool.Mask ) + { + return "Mask"; + } + + let msg : string = `Value ${value} cannot be converted to Tool. Possible values are: `; + { + let _LineMeasure_enumValue : string = Tool.LineMeasure; // enums are strings in stonecodegen, so this will work. + let msg_LineMeasure : string = `LineMeasure (${_LineMeasure_enumValue}), `; + msg = msg + msg_LineMeasure; + } + { + let _CircleMeasure_enumValue : string = Tool.CircleMeasure; // enums are strings in stonecodegen, so this will work. + let msg_CircleMeasure : string = `CircleMeasure (${_CircleMeasure_enumValue}), `; + msg = msg + msg_CircleMeasure; + } + { + let _Crop_enumValue : string = Tool.Crop; // enums are strings in stonecodegen, so this will work. + let msg_Crop : string = `Crop (${_Crop_enumValue}), `; + msg = msg + msg_Crop; + } + { + let _Windowing_enumValue : string = Tool.Windowing; // enums are strings in stonecodegen, so this will work. + let msg_Windowing : string = `Windowing (${_Windowing_enumValue}), `; + msg = msg + msg_Windowing; + } + { + let _Zoom_enumValue : string = Tool.Zoom; // enums are strings in stonecodegen, so this will work. + let msg_Zoom : string = `Zoom (${_Zoom_enumValue}), `; + msg = msg + msg_Zoom; + } + { + let _Pan_enumValue : string = Tool.Pan; // enums are strings in stonecodegen, so this will work. + let msg_Pan : string = `Pan (${_Pan_enumValue}), `; + msg = msg + msg_Pan; + } + { + let _Move_enumValue : string = Tool.Move; // enums are strings in stonecodegen, so this will work. + let msg_Move : string = `Move (${_Move_enumValue}), `; + msg = msg + msg_Move; + } + { + let _Rotate_enumValue : string = Tool.Rotate; // enums are strings in stonecodegen, so this will work. + let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `; + msg = msg + msg_Rotate; + } + { + let _Resize_enumValue : string = Tool.Resize; // enums are strings in stonecodegen, so this will work. + let msg_Resize : string = `Resize (${_Resize_enumValue}), `; + msg = msg + msg_Resize; + } + { + let _Mask_enumValue : string = Tool.Mask; // enums are strings in stonecodegen, so this will work. + let msg_Mask : string = `Mask (${_Mask_enumValue})`; + msg = msg + msg_Mask; + } + throw new Error(msg); +} + +export enum ActionType { + UndoCrop = "UndoCrop", + Rotate = "Rotate", + Invert = "Invert" +}; + +export function ActionType_FromString(strValue:string) : ActionType +{ + if( strValue == "UndoCrop" ) + { + return ActionType.UndoCrop; + } + if( strValue == "Rotate" ) + { + return ActionType.Rotate; + } + if( strValue == "Invert" ) + { + return ActionType.Invert; + } + + let msg : string = `String ${strValue} cannot be converted to ActionType. Possible values are: UndoCrop, Rotate, Invert`; + throw new Error(msg); +} + +export function ActionType_ToString(value:ActionType) : string +{ + if( value == ActionType.UndoCrop ) + { + return "UndoCrop"; + } + if( value == ActionType.Rotate ) + { + return "Rotate"; + } + if( value == ActionType.Invert ) + { + return "Invert"; + } + + let msg : string = `Value ${value} cannot be converted to ActionType. Possible values are: `; + { + let _UndoCrop_enumValue : string = ActionType.UndoCrop; // enums are strings in stonecodegen, so this will work. + let msg_UndoCrop : string = `UndoCrop (${_UndoCrop_enumValue}), `; + msg = msg + msg_UndoCrop; + } + { + let _Rotate_enumValue : string = ActionType.Rotate; // enums are strings in stonecodegen, so this will work. + let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `; + msg = msg + msg_Rotate; + } + { + let _Invert_enumValue : string = ActionType.Invert; // enums are strings in stonecodegen, so this will work. + let msg_Invert : string = `Invert (${_Invert_enumValue})`; + msg = msg + msg_Invert; + } + throw new Error(msg); +} + + + +export class SelectTool { + tool:Tool; + + constructor() { + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'StoneSampleCommands.SelectTool'; + container['value'] = this; + return JSON.stringify(container); + } + + public static StoneDeserialize(valueStr: string) : SelectTool + { + let value: any = JSON.parse(valueStr); + StoneCheckSerializedValueType(value, 'StoneSampleCommands.SelectTool'); + let result: SelectTool = value['value'] as SelectTool; + return result; + } +} +export class Action { + type:ActionType; + + constructor() { + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'StoneSampleCommands.Action'; + container['value'] = this; + return JSON.stringify(container); + } + + public static StoneDeserialize(valueStr: string) : Action + { + let value: any = JSON.parse(valueStr); + StoneCheckSerializedValueType(value, 'StoneSampleCommands.Action'); + let result: Action = value['value'] as Action; + return result; + } +} + +export interface IHandler { +}; + +/** Service function for StoneDispatchToHandler */ +export function StoneDispatchJsonToHandler( + jsonValue: any, handler: IHandler): boolean +{ + StoneCheckSerializedValueTypeGeneric(jsonValue); + let type: string = jsonValue["type"]; + if (type == "") + { + // this should never ever happen + throw new Error("Caught empty type while dispatching"); + } + else + { + return false; + } +} + +/** Takes a serialized type and passes this to the handler */ +export function StoneDispatchToHandler( + strValue: string, handler: IHandler): boolean +{ + // console.//log("+------------------------------------------------+"); + // console.//log("| StoneDispatchToHandler |"); + // console.//log("+------------------------------------------------+"); + // console.//log("strValue = "); + // console.//log(strValue); + let jsonValue: any = JSON.parse(strValue) + return StoneDispatchJsonToHandler(jsonValue, handler); } \ No newline at end of file diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Samples/build-web-ext.sh --- a/Applications/Samples/build-web-ext.sh Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Samples/build-web-ext.sh Thu May 16 09:11:14 2019 +0200 @@ -1,58 +1,58 @@ -#!/bin/bash - -set -e - -target=${1:-all} -# this script currently assumes that the wasm code has been built on its side and is availabie in build-wasm/ - -currentDir=$(pwd) - -scriptDirRel=$(dirname $0) -#echo $scriptDirRel -scriptDirAbs=$(realpath $scriptDirRel) -echo $scriptDirAbs - -samplesRootDir=scriptDirAbs - -outputDir=$samplesRootDir/build-web/ -mkdir -p $outputDir - -# files used by all single files samples -cp $samplesRootDir/Web/index.html $outputDir -cp $samplesRootDir/Web/samples-styles.css $outputDir - -# build simple-viewer-single-file (obsolete project) -if [[ $target == "all" || $target == "OrthancStoneSimpleViewerSingleFile" ]]; then - cp $samplesRootDir/Web/simple-viewer-single-file.html $outputDir - tsc --allowJs --project $samplesRootDir/Web/simple-viewer-single-file.tsconfig.json - cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.js $outputDir - cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.wasm $outputDir -fi - -# build single-frame -if [[ $target == "all" || $target == "OrthancStoneSingleFrame" ]]; then - cp $samplesRootDir/Web/single-frame.html $outputDir - tsc --allowJs --project $samplesRootDir/Web/single-frame.tsconfig.json - cp $currentDir/build-wasm/OrthancStoneSingleFrame.js $outputDir - cp $currentDir/build-wasm/OrthancStoneSingleFrame.wasm $outputDir -fi - -# build single-frame-editor -if [[ $target == "all" || $target == "OrthancStoneSingleFrameEditor" ]]; then - cp $samplesRootDir/Web/single-frame-editor.html $outputDir - tsc --allowJs --project $samplesRootDir/Web/single-frame-editor.tsconfig.json - cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.js $outputDir - cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.wasm $outputDir -fi - -# build simple-viewer project -if [[ $target == "all" || $target == "OrthancStoneSimpleViewer" ]]; then - mkdir -p $outputDir/simple-viewer/ - cp $samplesRootDir/SimpleViewer/Wasm/simple-viewer.html $outputDir/simple-viewer/ - cp $samplesRootDir/SimpleViewer/Wasm/styles.css $outputDir/simple-viewer/ - tsc --allowJs --project $samplesRootDir/SimpleViewer/Wasm/tsconfig-simple-viewer.json - cp $currentDir/build-wasm/OrthancStoneSimpleViewer.js $outputDir/simple-viewer/ - cp $currentDir/build-wasm/OrthancStoneSimpleViewer.wasm $outputDir/simple-viewer/ -fi - -cd $currentDir +#!/bin/bash + +set -e + +target=${1:-all} +# this script currently assumes that the wasm code has been built on its side and is availabie in build-wasm/ + +currentDir=$(pwd) + +scriptDirRel=$(dirname $0) +#echo $scriptDirRel +scriptDirAbs=$(realpath $scriptDirRel) +echo $scriptDirAbs + +samplesRootDir=scriptDirAbs + +outputDir=$samplesRootDir/build-web/ +mkdir -p $outputDir + +# files used by all single files samples +cp $samplesRootDir/Web/index.html $outputDir +cp $samplesRootDir/Web/samples-styles.css $outputDir + +# build simple-viewer-single-file (obsolete project) +if [[ $target == "all" || $target == "OrthancStoneSimpleViewerSingleFile" ]]; then + cp $samplesRootDir/Web/simple-viewer-single-file.html $outputDir + tsc --allowJs --project $samplesRootDir/Web/simple-viewer-single-file.tsconfig.json + cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.js $outputDir + cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.wasm $outputDir +fi + +# build single-frame +if [[ $target == "all" || $target == "OrthancStoneSingleFrame" ]]; then + cp $samplesRootDir/Web/single-frame.html $outputDir + tsc --allowJs --project $samplesRootDir/Web/single-frame.tsconfig.json + cp $currentDir/build-wasm/OrthancStoneSingleFrame.js $outputDir + cp $currentDir/build-wasm/OrthancStoneSingleFrame.wasm $outputDir +fi + +# build single-frame-editor +if [[ $target == "all" || $target == "OrthancStoneSingleFrameEditor" ]]; then + cp $samplesRootDir/Web/single-frame-editor.html $outputDir + tsc --allowJs --project $samplesRootDir/Web/single-frame-editor.tsconfig.json + cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.js $outputDir + cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.wasm $outputDir +fi + +# build simple-viewer project +if [[ $target == "all" || $target == "OrthancStoneSimpleViewer" ]]; then + mkdir -p $outputDir/simple-viewer/ + cp $samplesRootDir/SimpleViewer/Wasm/simple-viewer.html $outputDir/simple-viewer/ + cp $samplesRootDir/SimpleViewer/Wasm/styles.css $outputDir/simple-viewer/ + tsc --allowJs --project $samplesRootDir/SimpleViewer/Wasm/tsconfig-simple-viewer.json + cp $currentDir/build-wasm/OrthancStoneSimpleViewer.js $outputDir/simple-viewer/ + cp $currentDir/build-wasm/OrthancStoneSimpleViewer.wasm $outputDir/simple-viewer/ +fi + +cd $currentDir diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Samples/get-requirements-windows.ps1 --- a/Applications/Samples/get-requirements-windows.ps1 Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Samples/get-requirements-windows.ps1 Thu May 16 09:11:14 2019 +0200 @@ -1,50 +1,50 @@ - -if ($true) { - - Write-Error "This script is obsolete. Please work under WSL and run build-wasm.sh" - -} else { - - param( - [IO.DirectoryInfo] $EmsdkRootDir = "C:\Emscripten", - [bool] $Overwrite = $false - ) - - if (Test-Path -Path $EmsdkRootDir) { - if( $Override) { - Remove-Item -Path $EmsdkRootDir -Force -Recurse - } else { - throw "The `"$EmsdkRootDir`" folder may not exist! Use the Overwrite flag to bypass this check." - } - } - - # TODO: detect whether git is installed - # choco install -y git - - Write-Host "Will retrieve the Emscripten SDK to the `"$EmsdkRootDir`" folder" - - $EmsdkParentDir = split-path -Parent $EmsdkRootDir - $EmsdkRootName = split-path -Leaf $EmsdkRootDir - - Push-Location $EmsdkParentDir - - git clone https://github.com/juj/emsdk.git $EmsdkRootName - cd $EmsdkRootName - - git pull - - ./emsdk install latest - - ./emsdk activate latest - - echo "INFO: the ~/.emscripten file has been configured for this installation of Emscripten." - - Write-Host "emsdk is now installed in $EmsdkRootDir" - - Pop-Location - -} - - - - + +if ($true) { + + Write-Error "This script is obsolete. Please work under WSL and run build-wasm.sh" + +} else { + + param( + [IO.DirectoryInfo] $EmsdkRootDir = "C:\Emscripten", + [bool] $Overwrite = $false + ) + + if (Test-Path -Path $EmsdkRootDir) { + if( $Override) { + Remove-Item -Path $EmsdkRootDir -Force -Recurse + } else { + throw "The `"$EmsdkRootDir`" folder may not exist! Use the Overwrite flag to bypass this check." + } + } + + # TODO: detect whether git is installed + # choco install -y git + + Write-Host "Will retrieve the Emscripten SDK to the `"$EmsdkRootDir`" folder" + + $EmsdkParentDir = split-path -Parent $EmsdkRootDir + $EmsdkRootName = split-path -Leaf $EmsdkRootDir + + Push-Location $EmsdkParentDir + + git clone https://github.com/juj/emsdk.git $EmsdkRootName + cd $EmsdkRootName + + git pull + + ./emsdk install latest + + ./emsdk activate latest + + echo "INFO: the ~/.emscripten file has been configured for this installation of Emscripten." + + Write-Host "emsdk is now installed in $EmsdkRootDir" + + Pop-Location + +} + + + + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Samples/rt-viewer-demo/main.cpp --- a/Applications/Samples/rt-viewer-demo/main.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Samples/rt-viewer-demo/main.cpp Thu May 16 09:11:14 2019 +0200 @@ -546,6 +546,24 @@ void ParseParameters(const boost::program_options::variables_map& parameters) { + // Generic + { + if (parameters.count("verbose")) + { + Orthanc::Logging::EnableInfoLevel(true); + LOG(INFO) << "Verbose logs (info) are enabled"; + } + } + + { + if (parameters.count("trace")) + { + LOG(INFO) << "parameters.count(\"trace\") != 0"; + Orthanc::Logging::EnableTraceLevel(true); + VLOG(1) << "Trace logs (debug) are enabled"; + } + } + // CT series { @@ -570,12 +588,14 @@ if (parameters.count("dose-series") != 1) { LOG(ERROR) << "the RTDOSE series is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + throw Orthanc::OrthancException( + Orthanc::ErrorCode_ParameterOutOfRange); } doseSeries_ = parameters["ct"].as(); #endif LOG(ERROR) << "the RTSTRUCT instance is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + throw Orthanc::OrthancException( + Orthanc::ErrorCode_ParameterOutOfRange); } } @@ -592,26 +612,40 @@ if (parameters.count("struct-series") != 1) { LOG(ERROR) << "the RTSTRUCT series is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + throw Orthanc::OrthancException( + Orthanc::ErrorCode_ParameterOutOfRange); } - structSeries_ = parametersstruct - series"].as(); + structSeries_ = parameters["struct-series"].as(); #endif LOG(ERROR) << "the RTSTRUCT instance is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + throw Orthanc::OrthancException( + Orthanc::ErrorCode_ParameterOutOfRange); } } } - virtual void DeclareStartupOptions(boost::program_options::options_description& options) + virtual void DeclareStartupOptions( + boost::program_options::options_description& options) { - boost::program_options::options_description generic("RtViewerDemo options. Please note that some of these options are mutually exclusive"); + boost::program_options::options_description generic( + "RtViewerDemo options. Please note that some of these options " + "are mutually exclusive"); generic.add_options() - ("ct-series", boost::program_options::value(),"Orthanc ID of the CT series") - ("dose-instance", boost::program_options::value(), "Orthanc ID of the RTDOSE instance (incompatible with dose-series)") - ("dose-series", boost::program_options::value(), "NOT IMPLEMENTED YET. Orthanc ID of the RTDOSE series (incompatible with dose-instance)") - ("struct-instance", boost::program_options::value(), "Orthanc ID of the RTSTRUCT instance (incompatible with struct-series)") - ("struct-series", boost::program_options::value(), "NOT IMPLEMENTED YET. Orthanc ID of the RTSTRUCT (incompatible with struct-instance)") - ("smooth", boost::program_options::value()->default_value(true),"Enable bilinear image smoothing") + ("ct-series", boost::program_options::value(), + "Orthanc ID of the CT series") + ("dose-instance", boost::program_options::value(), + "Orthanc ID of the RTDOSE instance (incompatible with dose-series)") + ("dose-series", boost::program_options::value(), + "NOT IMPLEMENTED YET. Orthanc ID of the RTDOSE series (incompatible" + " with dose-instance)") + ("struct-instance", boost::program_options::value(), + "Orthanc ID of the RTSTRUCT instance (incompatible with struct-" + "series)") + ("struct-series", boost::program_options::value(), + "NOT IMPLEMENTED YET. Orthanc ID of the RTSTRUCT (incompatible with" + " struct-instance)") + ("smooth", boost::program_options::value()->default_value(true), + "Enable bilinear image smoothing") ; options.add(generic); @@ -637,8 +671,10 @@ ct_.reset(new OrthancStone::OrthancVolumeImage( IObserver::GetBroker(), context->GetOrthancApiClient(), false)); ct_->ScheduleLoadSeries(ctSeries_); - //ct_->ScheduleLoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // IBA - //ct_->ScheduleLoadSeries("03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0"); // 0522c0001 TCIA + //ct_->ScheduleLoadSeries( + // "a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // IBA + //ct_->ScheduleLoadSeries( + // "03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0"); // 0522c0001 TCIA } if (!doseSeries_.empty() || @@ -665,8 +701,10 @@ dose_->ScheduleLoadInstance(doseInstance_); } - //dose_->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1 - //dose_->ScheduleLoadInstance("269f26f4-0c83eeeb-2e67abbd-5467a40f-f1bec90c"); // 0522c0001 TCIA + //dose_->ScheduleLoadInstance( + //"830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1 + //dose_->ScheduleLoadInstance( + //"269f26f4-0c83eeeb-2e67abbd-5467a40f-f1bec90c"); //0522c0001 TCIA } if (!structInstance_.empty()) @@ -676,8 +714,10 @@ struct_->ScheduleLoadInstance(structInstance_); - //struct_->ScheduleLoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // IBA - //struct_->ScheduleLoadInstance("17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"); // 0522c0001 TCIA + //struct_->ScheduleLoadInstance( + //"54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // IBA + //struct_->ScheduleLoadInstance( + //"17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"); // 0522c0001 TCIA } mainWidget_ = new LayoutWidget("main-layout"); diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Sdl/SdlOpenGLWindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Sdl/SdlOpenGLWindow.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,122 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "SdlOpenGLWindow.h" + +#if ORTHANC_ENABLE_SDL == 1 + +#if !defined(ORTHANC_ENABLE_GLEW) +# error Macro ORTHANC_ENABLE_GLEW must be defined +#endif + +#if ORTHANC_ENABLE_GLEW == 1 +# include +#endif + +#include + +namespace OrthancStone +{ + SdlOpenGLWindow::SdlOpenGLWindow(const char* title, + unsigned int width, + unsigned int height, + bool allowDpiScaling) : + window_(title, width, height, true /* enable OpenGL */, allowDpiScaling) + { + context_ = SDL_GL_CreateContext(window_.GetObject()); + + if (context_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot initialize OpenGL"); + } + +#if ORTHANC_ENABLE_GLEW == 1 + // The initialization function of glew (i.e. "glewInit()") can + // only be called once an OpenGL is setup. + // https://stackoverflow.com/a/45033669/881731 + { + static boost::mutex mutex_; + static bool isGlewInitialized_ = false; + + boost::mutex::scoped_lock lock(mutex_); + + if (!isGlewInitialized_) + { + LOG(INFO) << "Initializing glew"; + + GLenum err = glewInit(); + if (GLEW_OK != err) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot initialize glew"); + } + + isGlewInitialized_ = true; + } + } +#endif + } + + + SdlOpenGLWindow::~SdlOpenGLWindow() + { + SDL_GL_DeleteContext(context_); + } + + + void SdlOpenGLWindow::MakeCurrent() + { + if (SDL_GL_MakeCurrent(window_.GetObject(), context_) != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot set current OpenGL context"); + } + + // This makes our buffer swap syncronized with the monitor's vertical refresh + SDL_GL_SetSwapInterval(1); + } + + + void SdlOpenGLWindow::SwapBuffer() + { + // Swap our buffer to display the current contents of buffer on screen + SDL_GL_SwapWindow(window_.GetObject()); + } + + + unsigned int SdlOpenGLWindow::GetCanvasWidth() const + { + int w = 0; + SDL_GL_GetDrawableSize(window_.GetObject(), &w, NULL); + return static_cast(w); + } + + + unsigned int SdlOpenGLWindow::GetCanvasHeight() const + { + int h = 0; + SDL_GL_GetDrawableSize(window_.GetObject(), NULL, &h); + return static_cast(h); + } +} + +#endif diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Sdl/SdlOpenGLWindow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Sdl/SdlOpenGLWindow.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,60 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if ORTHANC_ENABLE_SDL == 1 + +#include "../../Framework/OpenGL/IOpenGLContext.h" +#include "SdlWindow.h" + +namespace OrthancStone +{ + class SdlOpenGLWindow : public OpenGL::IOpenGLContext + { + private: + SdlWindow window_; + SDL_GLContext context_; + + public: + SdlOpenGLWindow(const char* title, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); + + ~SdlOpenGLWindow(); + + SdlWindow& GetWindow() + { + return window_; + } + + virtual void MakeCurrent(); + + virtual void SwapBuffer(); + + virtual unsigned int GetCanvasWidth() const; + + virtual unsigned int GetCanvasHeight() const; + }; +} + +#endif diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Sdl/SdlWindow.cpp --- a/Applications/Sdl/SdlWindow.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Sdl/SdlWindow.cpp Thu May 16 09:11:14 2019 +0200 @@ -26,6 +26,11 @@ #include #include +#ifdef WIN32 +#include // for SetProcessDpiAware +#endif +// WIN32 + #include namespace OrthancStone @@ -33,7 +38,8 @@ SdlWindow::SdlWindow(const char* title, unsigned int width, unsigned int height, - bool enableOpenGl) : + bool enableOpenGl, + bool allowDpiScaling) : maximized_(false) { // TODO Understand why, with SDL_WINDOW_OPENGL + MinGW32 + Release @@ -51,6 +57,33 @@ windowFlags = SDL_WINDOW_RESIZABLE; rendererFlags = SDL_RENDERER_SOFTWARE; } + +// TODO: probably required on MacOS X, too +#if defined(WIN32) && (_WIN32_WINNT >= 0x0600) + if (!allowDpiScaling) + { + // if we do NOT allow DPI scaling, it means an SDL pixel will be a real + // monitor pixel. This is needed for high-DPI applications + + // Enable high-DPI support on Windows + + // THE FOLLOWING HAS BEEN COMMENTED OUT BECAUSE IT WILL CRASH UNDER + // OLD WINDOWS VERSIONS + // ADD THIS AT THE TOP TO ENABLE IT: + // + //#pragma comment(lib, "Shcore.lib") THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness + //#include + //#include THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness + //#include THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness + // SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); + + // This is supported on Vista+ + SetProcessDPIAware(); + + windowFlags |= SDL_WINDOW_ALLOW_HIGHDPI; + } +#endif +// WIN32 window_ = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Sdl/SdlWindow.h --- a/Applications/Sdl/SdlWindow.h Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Sdl/SdlWindow.h Thu May 16 09:11:14 2019 +0200 @@ -40,7 +40,8 @@ SdlWindow(const char* title, unsigned int width, unsigned int height, - bool enableOpenGl); + bool enableOpenGl, + bool allowDpiScaling = true); ~SdlWindow(); diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Wasm/StartupParametersBuilder.cpp --- a/Applications/Wasm/StartupParametersBuilder.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Wasm/StartupParametersBuilder.cpp Thu May 16 09:11:14 2019 +0200 @@ -1,43 +1,71 @@ #include "StartupParametersBuilder.h" +#include +#include +#include "emscripten/html5.h" namespace OrthancStone { - void StartupParametersBuilder::Clear() { - startupParameters_.clear(); - } + void StartupParametersBuilder::Clear() + { + startupParameters_.clear(); + } + + void StartupParametersBuilder::SetStartupParameter( + const char* name, + const char* value) + { + startupParameters_.push_back(std::make_tuple(name, value)); + } - void StartupParametersBuilder::SetStartupParameter(const char* name, const char* value) { - startupParameters_.push_back(std::make_tuple(name, value)); + void StartupParametersBuilder::GetStartupParameters( + boost::program_options::variables_map& parameters, + const boost::program_options::options_description& options) + { + std::vector argvStrings(startupParameters_.size() + 1); + // argv mirrors pointers to the internal argvStrings buffers. + // ****************************************************** + // THIS IS HIGHLY DANGEROUS SO BEWARE!!!!!!!!!!!!!! + // ****************************************************** + std::vector argv(startupParameters_.size() + 1); + + int argCounter = 0; + argvStrings[argCounter] = "dummy.exe"; + argv[argCounter] = argvStrings[argCounter].c_str(); + + argCounter++; + + std::string cmdLine = ""; + for ( StartupParameters::const_iterator it = startupParameters_.begin(); + it != startupParameters_.end(); + it++) + { + std::stringstream argSs; + + argSs << "--" << std::get<0>(*it); + if(std::get<1>(*it).length() > 0) + argSs << "=" << std::get<1>(*it); + + argvStrings[argCounter] = argSs.str(); + cmdLine = cmdLine + " " + argvStrings[argCounter]; + std::cout << cmdLine << std::endl; + argv[argCounter] = argvStrings[argCounter].c_str(); + argCounter++; } - void StartupParametersBuilder::GetStartupParameters(boost::program_options::variables_map& parameters, const boost::program_options::options_description& options) { - - const char* argv[startupParameters_.size() + 1]; - int argCounter = 0; - argv[0] = "Toto.exe"; - argCounter++; - std::string cmdLine = ""; - for (StartupParameters::const_iterator it = startupParameters_.begin(); it != startupParameters_.end(); it++) { - char* arg = new char[128]; - snprintf(arg, 128, "--%s=%s", std::get<0>(*it).c_str(), std::get<1>(*it).c_str()); - argv[argCounter] = arg; - cmdLine = cmdLine + " --" + std::get<0>(*it) + "=" + std::get<1>(*it); - argCounter++; - } + std::cout << "simulated cmdLine = \"" << cmdLine.c_str() << "\"\n"; - printf("simulated cmdLine = %s\n", cmdLine.c_str()); - - try - { - boost::program_options::store(boost::program_options::command_line_parser(argCounter, argv). - options(options).allow_unregistered().run(), parameters); - boost::program_options::notify(parameters); - } - catch (boost::program_options::error& e) - { - printf("Error while parsing the command-line arguments: %s\n", e.what()); - } - + try + { + boost::program_options::store( + boost::program_options::command_line_parser(argCounter, argv.data()). + options(options).allow_unregistered().run(), parameters); + boost::program_options::notify(parameters); } -} \ No newline at end of file + catch (boost::program_options::error& e) + { + std::cerr << "Error while parsing the command-line arguments: " << + e.what() << std::endl; + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Applications/Wasm/StartupParametersBuilder.h --- a/Applications/Wasm/StartupParametersBuilder.h Tue May 14 18:24:12 2019 +0200 +++ b/Applications/Wasm/StartupParametersBuilder.h Thu May 16 09:11:14 2019 +0200 @@ -43,8 +43,12 @@ public: void Clear(); + // Please note that if a parameter is a flag-style one, the value that + // is passed should be an empty string void SetStartupParameter(const char* name, const char* value); - void GetStartupParameters(boost::program_options::variables_map& parameters_, const boost::program_options::options_description& options); + void GetStartupParameters( + boost::program_options::variables_map& parameters_, + const boost::program_options::options_description& options); }; } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/FontRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/FontRenderer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,185 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "FontRenderer.h" + +#include "../Toolbox/DynamicBitmap.h" + +#include + + +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H + + +// https://stackoverflow.com/questions/31161284/how-can-i-get-the-corresponding-error-string-from-an-ft-error-code +static std::string GetErrorMessage(FT_Error err) +{ +#undef __FTERRORS_H__ +#define FT_ERRORDEF( e, v, s ) case e: return s; +#define FT_ERROR_START_LIST switch (err) { +#define FT_ERROR_END_LIST } +#include FT_ERRORS_H + return "(Unknown error)"; +} + + +static void CheckError(FT_Error err) +{ + if (err != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Error in FreeType: " + GetErrorMessage(err)); + } +} + + +namespace OrthancStone +{ + class FontRenderer::PImpl : public boost::noncopyable + { + private: + std::string fontContent_; + FT_Library library_; + FT_Face face_; + + void Clear() + { + if (face_ != NULL) + { + FT_Done_Face(face_); + face_ = NULL; + } + + fontContent_.clear(); + } + + public: + PImpl() : + library_(NULL), + face_(NULL) + { + CheckError(FT_Init_FreeType(&library_)); + } + + + ~PImpl() + { + Clear(); + FT_Done_FreeType(library_); + } + + + void LoadFont(const std::string& fontContent, + unsigned int fontSize) + { + Clear(); + + // It is necessary to make a private copy of the font, as + // Freetype makes the assumption that the buffer containing the + // font is never deleted + fontContent_.assign(fontContent); + + const FT_Byte* data = reinterpret_cast(fontContent_.c_str()); + + CheckError(FT_New_Memory_Face(library_, data, fontContent_.size(), 0, &face_)); + CheckError(FT_Set_Char_Size(face_, // handle to face object + 0, // char_width in 1/64th of points + fontSize * 64, // char_height in 1/64th of points + 72, // horizontal device resolution + 72)); // vertical device resolution + + CheckError(FT_Select_Charmap(face_, FT_ENCODING_UNICODE)); + } + + + Glyph* Render(uint32_t unicode) + { + if (face_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "First call LoadFont()"); + } + else if (FT_Load_Char(face_, unicode, FT_LOAD_RENDER) != 0) + { + // This character is not available + return NULL; + } + else + { + if (face_->glyph->format != FT_GLYPH_FORMAT_BITMAP) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + //CheckError(FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1)); + } + + Orthanc::ImageAccessor bitmap; + bitmap.AssignReadOnly(Orthanc::PixelFormat_Grayscale8, + face_->glyph->bitmap.width, + face_->glyph->bitmap.rows, + face_->glyph->bitmap.pitch, + face_->glyph->bitmap.buffer); + + std::auto_ptr glyph( + new Glyph(bitmap.GetWidth(), + bitmap.GetHeight(), + face_->glyph->bitmap_left, + -face_->glyph->bitmap_top, // Positive for an upwards vertical distance + face_->glyph->advance.x >> 6, + face_->glyph->metrics.vertAdvance >> 6)); + + glyph->SetPayload(new DynamicBitmap(bitmap)); + + return glyph.release(); + } + } + }; + + + + FontRenderer::FontRenderer() : + pimpl_(new PImpl) + { + } + + + void FontRenderer::LoadFont(const std::string& fontContent, + unsigned int fontSize) + { + pimpl_->LoadFont(fontContent, fontSize); + } + + + void FontRenderer::LoadFont(Orthanc::EmbeddedResources::FileResourceId resource, + unsigned int fontSize) + { + std::string content; + Orthanc::EmbeddedResources::GetFileResource(content, resource); + LoadFont(content, fontSize); + } + + + Glyph* FontRenderer::Render(uint32_t unicode) + { + return pimpl_->Render(unicode); + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/FontRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/FontRenderer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,51 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "Glyph.h" + +#include + +#include +#include + + +namespace OrthancStone +{ + class FontRenderer : public boost::noncopyable + { + private: + class PImpl; + boost::shared_ptr pimpl_; + + public: + FontRenderer(); + + void LoadFont(const std::string& fontContent, + unsigned int fontSize); + + void LoadFont(Orthanc::EmbeddedResources::FileResourceId resource, + unsigned int fontSize); + + Glyph* Render(uint32_t unicode); + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/Glyph.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/Glyph.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,93 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "Glyph.h" + +#include + + +namespace OrthancStone +{ + Glyph::Glyph(const Glyph& other) : + width_(other.width_), + height_(other.height_), + offsetLeft_(other.offsetLeft_), + offsetTop_(other.offsetTop_), + advanceX_(other.advanceX_), + lineHeight_(other.lineHeight_) + { + } + + + Glyph::Glyph(unsigned int width, + unsigned int height, + int offsetLeft, + int offsetTop, + int advanceX, + unsigned int lineHeight) : + width_(width), + height_(height), + offsetLeft_(offsetLeft), + offsetTop_(offsetTop), + advanceX_(advanceX), + lineHeight_(lineHeight) + { + } + + + void Glyph::SetPayload(Orthanc::IDynamicObject* payload) // Takes ownership + { + if (payload == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + payload_.reset(payload); + } + } + + + const Orthanc::IDynamicObject& Glyph::GetPayload() const + { + if (payload_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *payload_; + } + } + + + Orthanc::IDynamicObject* Glyph::ReleasePayload() + { + if (payload_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return payload_.release(); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/Glyph.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/Glyph.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,95 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include + +#include + + +namespace OrthancStone +{ + class Glyph : public boost::noncopyable + { + private: + unsigned int width_; + unsigned int height_; + int offsetLeft_; + int offsetTop_; + int advanceX_; + unsigned int lineHeight_; + + std::auto_ptr payload_; + + public: + // WARNING: This does not copy the payload + Glyph(const Glyph& other); + + Glyph(unsigned int width, + unsigned int height, + int offsetLeft, + int offsetTop, + int advanceX, + unsigned int lineHeight); + + void SetPayload(Orthanc::IDynamicObject* payload); + + int GetOffsetLeft() const + { + return offsetLeft_; + } + + int GetOffsetTop() const + { + return offsetTop_; + } + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetAdvanceX() const + { + return advanceX_; + } + + unsigned int GetLineHeight() const + { + return lineHeight_; + } + + bool HasPayload() const + { + return payload_.get() != NULL; + } + + const Orthanc::IDynamicObject& GetPayload() const; + + Orthanc::IDynamicObject* ReleasePayload(); + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/GlyphAlphabet.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/GlyphAlphabet.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,170 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "GlyphAlphabet.h" + +#include +#include + + +namespace OrthancStone +{ + void GlyphAlphabet::Clear() + { + for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + + content_.clear(); + lineHeight_ = 0; + } + + + void GlyphAlphabet::Register(uint32_t unicode, + const Glyph& glyph, + Orthanc::IDynamicObject* payload) + { + std::auto_ptr protection(payload); + + // Don't add twice the same character + if (content_.find(unicode) == content_.end()) + { + std::auto_ptr raii(new Glyph(glyph)); + + if (payload != NULL) + { + raii->SetPayload(protection.release()); + } + + content_[unicode] = raii.release(); + + lineHeight_ = std::max(lineHeight_, glyph.GetLineHeight()); + } + } + + + void GlyphAlphabet::Register(FontRenderer& renderer, + uint32_t unicode) + { + std::auto_ptr glyph(renderer.Render(unicode)); + + if (glyph.get() != NULL) + { + Register(unicode, *glyph, glyph->ReleasePayload()); + } + } + + +#if ORTHANC_ENABLE_LOCALE == 1 + bool GlyphAlphabet::GetUnicodeFromCodepage(uint32_t& unicode, + unsigned int index, + Orthanc::Encoding encoding) + { + if (index > 255) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::string character; + character.resize(1); + character[0] = static_cast(index); + + std::string utf8 = Orthanc::Toolbox::ConvertToUtf8(character, encoding, false /* no code extensions */); + + if (utf8.empty()) + { + // This character is not available in this codepage + return false; + } + else + { + size_t length; + Orthanc::Toolbox::Utf8ToUnicodeCharacter(unicode, length, utf8, 0); + assert(length != 0); + return true; + } + } +#endif + + + void GlyphAlphabet::Apply(IGlyphVisitor& visitor) const + { + for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(it->second != NULL); + visitor.Visit(it->first, *it->second); + } + } + + + void GlyphAlphabet::Apply(ITextVisitor& visitor, + const std::string& utf8) const + { + size_t pos = 0; + int x = 0; + int y = 0; + + while (pos < utf8.size()) + { + if (utf8[pos] == '\r') + { + // Ignore carriage return + pos++; + } + else if (utf8[pos] == '\n') + { + // This is a newline character + x = 0; + y += static_cast(lineHeight_); + + pos++; + } + else + { + uint32_t unicode; + size_t length; + Orthanc::Toolbox::Utf8ToUnicodeCharacter(unicode, length, utf8, pos); + + Content::const_iterator glyph = content_.find(unicode); + + if (glyph != content_.end()) + { + assert(glyph->second != NULL); + const Orthanc::IDynamicObject* payload = + (glyph->second->HasPayload() ? &glyph->second->GetPayload() : NULL); + + visitor.Visit(unicode, + x + glyph->second->GetOffsetLeft(), + y + glyph->second->GetOffsetTop(), + glyph->second->GetWidth(), + glyph->second->GetHeight(), + payload); + x += glyph->second->GetAdvanceX(); + } + + assert(length != 0); + pos += length; + } + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/GlyphAlphabet.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/GlyphAlphabet.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,105 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "FontRenderer.h" + +#include + +#include + +namespace OrthancStone +{ + class GlyphAlphabet : public boost::noncopyable + { + public: + class ITextVisitor : public boost::noncopyable + { + public: + virtual ~ITextVisitor() + { + } + + virtual void Visit(uint32_t unicode, + int x, + int y, + unsigned int width, + unsigned int height, + const Orthanc::IDynamicObject* payload /* can be NULL */) = 0; + }; + + + class IGlyphVisitor : public boost::noncopyable + { + public: + virtual ~IGlyphVisitor() + { + } + + virtual void Visit(uint32_t unicode, + const Glyph& glyph) = 0; + }; + + + private: + typedef std::map Content; + + Content content_; + unsigned int lineHeight_; + + public: + GlyphAlphabet() : + lineHeight_(0) + { + } + + ~GlyphAlphabet() + { + Clear(); + } + + void Clear(); + + void Register(uint32_t unicode, + const Glyph& glyph, + Orthanc::IDynamicObject* payload); + + void Register(FontRenderer& renderer, + uint32_t unicode); + +#if ORTHANC_ENABLE_LOCALE == 1 + static bool GetUnicodeFromCodepage(uint32_t& unicode, + unsigned int index, + Orthanc::Encoding encoding); +#endif + + size_t GetSize() const + { + return content_.size(); + } + + void Apply(IGlyphVisitor& visitor) const; + + void Apply(ITextVisitor& visitor, + const std::string& utf8) const; + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/GlyphBitmapAlphabet.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/GlyphBitmapAlphabet.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,113 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "GlyphBitmapAlphabet.h" + +#include "TextBoundingBox.h" +#include "../Toolbox/DynamicBitmap.h" + +#include +#include + +namespace OrthancStone +{ + class GlyphBitmapAlphabet::RenderTextVisitor : public GlyphAlphabet::ITextVisitor + { + private: + Orthanc::ImageAccessor& target_; + const GlyphBitmapAlphabet& that_; + int offsetX_; + int offsetY_; + + public: + RenderTextVisitor(Orthanc::ImageAccessor& target, + const GlyphBitmapAlphabet& that, + int offsetX, + int offsetY) : + target_(target), + that_(that), + offsetX_(offsetX), + offsetY_(offsetY) + { + } + + virtual void Visit(uint32_t unicode, + int x, + int y, + unsigned int width, + unsigned int height, + const Orthanc::IDynamicObject* payload) + { + int left = x + offsetX_; + int top = y + offsetY_; + + assert(payload != NULL); + const DynamicBitmap& glyph = *dynamic_cast(payload); + + assert(left >= 0 && + top >= 0 && + static_cast(left) + width <= target_.GetWidth() && + static_cast(top) + height <= target_.GetHeight() && + width == glyph.GetBitmap().GetWidth() && + height == glyph.GetBitmap().GetHeight()); + + { + Orthanc::ImageAccessor region; + target_.GetRegion(region, left, top, width, height); + Orthanc::ImageProcessing::Copy(region, glyph.GetBitmap()); + } + } + }; + + +#if ORTHANC_ENABLE_LOCALE == 1 + void GlyphBitmapAlphabet::LoadCodepage(FontRenderer& renderer, + Orthanc::Encoding codepage) + { + for (unsigned int i = 0; i < 256; i++) + { + uint32_t unicode; + if (GlyphAlphabet::GetUnicodeFromCodepage(unicode, i, codepage)) + { + AddUnicodeCharacter(renderer, unicode); + } + } + } +#endif + + + Orthanc::ImageAccessor* GlyphBitmapAlphabet::RenderText(const std::string& utf8) const + { + TextBoundingBox box(alphabet_, utf8); + + std::auto_ptr bitmap( + new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, + box.GetWidth(), box.GetHeight(), + true /* force minimal pitch */)); + + Orthanc::ImageProcessing::Set(*bitmap, 0); + + RenderTextVisitor visitor(*bitmap, *this, -box.GetLeft(), -box.GetTop()); + alphabet_.Apply(visitor, utf8); + + return bitmap.release(); + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/GlyphBitmapAlphabet.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/GlyphBitmapAlphabet.h Thu May 16 09:11:14 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 . + **/ + + +#pragma once + +#include "GlyphAlphabet.h" + +#include + +namespace OrthancStone +{ + class GlyphBitmapAlphabet : public boost::noncopyable + { + private: + class RenderTextVisitor; + + GlyphAlphabet alphabet_; + + public: + const GlyphAlphabet& GetAlphabet() const + { + return alphabet_; + } + + void AddUnicodeCharacter(FontRenderer& renderer, + uint32_t unicode) + { + alphabet_.Register(renderer, unicode); + } + + +#if ORTHANC_ENABLE_LOCALE == 1 + void LoadCodepage(FontRenderer& renderer, + Orthanc::Encoding codepage); +#endif + + + Orthanc::ImageAccessor* RenderText(const std::string& utf8) const; + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/GlyphTextureAlphabet.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/GlyphTextureAlphabet.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,295 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "GlyphTextureAlphabet.h" + +#include "TextBoundingBox.h" +#include "../Toolbox/DynamicBitmap.h" + +#include +#include +#include + +#include + + +namespace OrthancStone +{ + class GlyphTextureAlphabet::GlyphSizeVisitor : public GlyphAlphabet::IGlyphVisitor + { + private: + unsigned int maxWidth_; + unsigned int maxHeight_; + + public: + GlyphSizeVisitor() : + maxWidth_(0), + maxHeight_(0) + { + } + + virtual void Visit(uint32_t unicode, + const Glyph& glyph) + { + maxWidth_ = std::max(maxWidth_, glyph.GetWidth()); + maxHeight_ = std::max(maxHeight_, glyph.GetHeight()); + } + + unsigned int GetMaxWidth() const + { + return maxWidth_; + } + + unsigned int GetMaxHeight() const + { + return maxHeight_; + } + }; + + + class GlyphTextureAlphabet::TextureGenerator : public GlyphAlphabet::IGlyphVisitor + { + private: + std::auto_ptr texture_; + + unsigned int countColumns_; + unsigned int countRows_; + GlyphAlphabet& targetAlphabet_; + unsigned int glyphMaxWidth_; + unsigned int glyphMaxHeight_; + unsigned int column_; + unsigned int row_; + + public: + TextureGenerator(GlyphAlphabet& targetAlphabet, + unsigned int countGlyphs, + unsigned int glyphMaxWidth, + unsigned int glyphMaxHeight) : + targetAlphabet_(targetAlphabet), + glyphMaxWidth_(glyphMaxWidth), + glyphMaxHeight_(glyphMaxHeight), + column_(0), + row_(0) + { + int c = boost::math::iround(sqrt(static_cast(countGlyphs))); + + if (c <= 0) + { + countColumns_ = 1; + } + else + { + countColumns_ = static_cast(c); + } + + countRows_ = countGlyphs / countColumns_; + if (countGlyphs % countColumns_ != 0) + { + countRows_++; + } + + texture_.reset(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, + countColumns_ * glyphMaxWidth_, + countRows_ * glyphMaxHeight_, + true /* force minimal pitch */)); + + Orthanc::ImageProcessing::Set(*texture_, 0, 0, 0, 0); + } + + + virtual void Visit(uint32_t unicode, + const Glyph& glyph) + { + if (!glyph.HasPayload()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + if (column_ >= countColumns_ || + row_ >= countRows_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + unsigned int x = column_ * glyphMaxWidth_; + unsigned int y = row_ * glyphMaxHeight_; + + const Orthanc::ImageAccessor& source = dynamic_cast(glyph.GetPayload()).GetBitmap(); + + if (source.GetFormat() != Orthanc::PixelFormat_Grayscale8) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + targetAlphabet_.Register(unicode, glyph, new TextureLocation(x, y)); + + Orthanc::ImageAccessor target; + texture_->GetRegion(target, x, y, source.GetWidth(), source.GetHeight()); + + //Orthanc::ImageProcessing::Copy(target, bitmap->GetBitmap()); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast(target.GetRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++) + { + // Premultiplied alpha + q[0] = 0; + q[1] = 0; + q[2] = 0; + q[3] = *p; + + p++; + q += 4; + } + } + + column_++; + if (column_ == countColumns_) + { + column_ = 0; + row_++; + } + } + + + Orthanc::ImageAccessor* ReleaseTexture() + { + return texture_.release(); + } + }; + + + class GlyphTextureAlphabet::RenderTextVisitor : public GlyphAlphabet::ITextVisitor + { + private: + Orthanc::ImageAccessor& target_; + const Orthanc::ImageAccessor& texture_; + int offsetX_; + int offsetY_; + + public: + RenderTextVisitor(Orthanc::ImageAccessor& target, + const GlyphTextureAlphabet& that, + int offsetX, + int offsetY) : + target_(target), + texture_(that.GetTexture()), + offsetX_(offsetX), + offsetY_(offsetY) + { + } + + virtual void Visit(uint32_t unicode, + int x, + int y, + unsigned int width, + unsigned int height, + const Orthanc::IDynamicObject* payload) + { + int left = x + offsetX_; + int top = y + offsetY_; + + assert(payload != NULL); + const TextureLocation& location = *dynamic_cast(payload); + + assert(left >= 0 && + top >= 0 && + static_cast(left) + width <= target_.GetWidth() && + static_cast(top) + height <= target_.GetHeight()); + + { + Orthanc::ImageAccessor to; + target_.GetRegion(to, left, top, width, height); + + Orthanc::ImageAccessor from; + texture_.GetRegion(from, location.GetX(), location.GetY(), width, height); + + Orthanc::ImageProcessing::Copy(to, from); + } + } + }; + + + GlyphTextureAlphabet::GlyphTextureAlphabet(const GlyphBitmapAlphabet& sourceAlphabet) : + textureWidth_(0), + textureHeight_(0) + { + GlyphSizeVisitor size; + sourceAlphabet.GetAlphabet().Apply(size); + + TextureGenerator generator(alphabet_, + sourceAlphabet.GetAlphabet().GetSize(), + size.GetMaxWidth(), + size.GetMaxHeight()); + sourceAlphabet.GetAlphabet().Apply(generator); + + texture_.reset(generator.ReleaseTexture()); + textureWidth_ = texture_->GetWidth(); + textureHeight_ = texture_->GetHeight(); + } + + + const Orthanc::ImageAccessor& GlyphTextureAlphabet::GetTexture() const + { + if (texture_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *texture_; + } + } + + + Orthanc::ImageAccessor* GlyphTextureAlphabet::ReleaseTexture() + { + if (texture_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return texture_.release(); + } + } + + + Orthanc::ImageAccessor* GlyphTextureAlphabet::RenderText(const std::string& utf8) + { + TextBoundingBox box(alphabet_, utf8); + + std::auto_ptr bitmap( + new Orthanc::Image(Orthanc::PixelFormat_RGBA32, + box.GetWidth(), box.GetHeight(), + true /* force minimal pitch */)); + + Orthanc::ImageProcessing::Set(*bitmap, 0, 0, 0, 0); + + RenderTextVisitor visitor(*bitmap, *this, -box.GetLeft(), -box.GetTop()); + alphabet_.Apply(visitor, utf8); + + return bitmap.release(); + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/GlyphTextureAlphabet.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/GlyphTextureAlphabet.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,92 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "GlyphBitmapAlphabet.h" + +#include + +namespace OrthancStone +{ + class GlyphTextureAlphabet : public boost::noncopyable + { + public: + class TextureLocation : public Orthanc::IDynamicObject + { + private: + unsigned int x_; + unsigned int y_; + + public: + TextureLocation(unsigned int x, + unsigned int y) : + x_(x), + y_(y) + { + } + + unsigned int GetX() const + { + return x_; + } + + unsigned int GetY() const + { + return y_; + } + }; + + private: + class GlyphSizeVisitor; + class TextureGenerator; + class RenderTextVisitor; + + GlyphAlphabet alphabet_; + std::auto_ptr texture_; + unsigned int textureWidth_; + unsigned int textureHeight_; + + public: + GlyphTextureAlphabet(const GlyphBitmapAlphabet& sourceAlphabet); + + const Orthanc::ImageAccessor& GetTexture() const; + + Orthanc::ImageAccessor* ReleaseTexture(); + + Orthanc::ImageAccessor* RenderText(const std::string& utf8); + + const GlyphAlphabet& GetAlphabet() const + { + return alphabet_; + } + + unsigned int GetTextureWidth() const + { + return textureWidth_; + } + + unsigned int GetTextureHeight() const + { + return textureHeight_; + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/OpenGLTextCoordinates.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/OpenGLTextCoordinates.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,117 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLTextCoordinates.h" + +#include + +namespace OrthancStone +{ + namespace OpenGL + { + void OpenGLTextCoordinates::Visit(uint32_t unicode, + int x, + int y, + unsigned int width, + unsigned int height, + const Orthanc::IDynamicObject* payload) + { + // Rendering coordinates + float rx1 = x - box_.GetLeft(); + float ry1 = y - box_.GetTop(); + float rx2 = rx1 + static_cast(width); + float ry2 = ry1 + static_cast(height); + + // Texture coordinates + assert(payload != NULL); + const GlyphTextureAlphabet::TextureLocation& location = + *dynamic_cast(payload); + + float tx1 = location.GetX() / textureWidth_; + float ty1 = location.GetY() / textureHeight_; + float tx2 = tx1 + (static_cast(width) / textureWidth_); + float ty2 = ty1 + (static_cast(height) / textureHeight_); + + const float rpos[6][2] = { + { rx1, ry1 }, + { rx1, ry2 }, + { rx2, ry1 }, + { rx2, ry1 }, + { rx1, ry2 }, + { rx2, ry2 } + }; + + const float tpos[6][2] = { + { tx1, ty1 }, + { tx1, ty2 }, + { tx2, ty1 }, + { tx2, ty1 }, + { tx1, ty2 }, + { tx2, ty2 } + }; + + for (unsigned int i = 0; i < 6; i++) + { + renderingCoords_.push_back(rpos[i][0]); + renderingCoords_.push_back(rpos[i][1]); + textureCoords_.push_back(tpos[i][0]); + textureCoords_.push_back(tpos[i][1]); + } + } + + + OpenGLTextCoordinates::OpenGLTextCoordinates(const GlyphTextureAlphabet& alphabet, + const std::string& utf8) : + box_(alphabet.GetAlphabet(), utf8), + textureWidth_(alphabet.GetTextureWidth()), + textureHeight_(alphabet.GetTextureHeight()) + { + if (textureWidth_ <= 0 || + textureHeight_ <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + width_ = static_cast(box_.GetWidth()); + height_ = static_cast(box_.GetHeight()); + + // Each character is made of two 2D triangles (= 2 * 3 * 2 = 12) + renderingCoords_.reserve(box_.GetCharactersCount() * 12); + textureCoords_.reserve(box_.GetCharactersCount() * 12); + + alphabet.GetAlphabet().Apply(*this, utf8); + } + + + const std::vector& OpenGLTextCoordinates::GetRenderingCoords() const + { + assert(renderingCoords_.size() == textureCoords_.size()); + return renderingCoords_; + } + + + const std::vector& OpenGLTextCoordinates::GetTextureCoords() const + { + assert(renderingCoords_.size() == textureCoords_.size()); + return textureCoords_; + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/OpenGLTextCoordinates.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/OpenGLTextCoordinates.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,76 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "GlyphTextureAlphabet.h" +#include "TextBoundingBox.h" + +#include + +namespace OrthancStone +{ + namespace OpenGL + { + class OpenGLTextCoordinates : protected GlyphAlphabet::ITextVisitor + { + private: + TextBoundingBox box_; + float width_; + float height_; + std::vector renderingCoords_; + std::vector textureCoords_; + float textureWidth_; + float textureHeight_; + + protected: + virtual void Visit(uint32_t unicode, + int x, + int y, + unsigned int width, + unsigned int height, + const Orthanc::IDynamicObject* payload); + + public: + OpenGLTextCoordinates(const GlyphTextureAlphabet& alphabet, + const std::string& utf8); + + unsigned int GetTextWidth() const + { + return box_.GetWidth(); + } + + unsigned int GetTextHeight() const + { + return box_.GetHeight(); + } + + bool IsEmpty() const + { + return renderingCoords_.empty(); + } + + const std::vector& GetRenderingCoords() const; + + const std::vector& GetTextureCoords() const; + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/TextBoundingBox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/TextBoundingBox.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,80 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "TextBoundingBox.h" + +namespace OrthancStone +{ + void TextBoundingBox::AddPoint(int x, + int y) + { + left_ = std::min(left_, x); + right_ = std::max(right_, x); + top_ = std::min(top_, y); + bottom_ = std::max(bottom_, y); + } + + + void TextBoundingBox::Clear() + { + left_ = 0; + top_ = 0; + right_ = 0; + bottom_ = 0; + countCharacters_ = 0; + } + + + void TextBoundingBox::Visit(uint32_t unicode, + int x, + int y, + unsigned int width, + unsigned int height, + const Orthanc::IDynamicObject* payload /* ignored */) + { + AddPoint(x, y); + AddPoint(x + static_cast(width), + y + static_cast(height)); + countCharacters_++; + } + + + TextBoundingBox::TextBoundingBox(const GlyphAlphabet& alphabet, + const std::string& utf8) + { + Clear(); + alphabet.Apply(*this, utf8); + } + + + unsigned int TextBoundingBox::GetWidth() const + { + assert(left_ <= right_); + return static_cast(right_ - left_ + 1); + } + + + unsigned int TextBoundingBox::GetHeight() const + { + assert(top_ <= bottom_); + return static_cast(bottom_ - top_ + 1); + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Fonts/TextBoundingBox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Fonts/TextBoundingBox.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,73 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "GlyphAlphabet.h" + +namespace OrthancStone +{ + class TextBoundingBox : protected GlyphAlphabet::ITextVisitor + { + private: + int left_; + int top_; + int right_; + int bottom_; + unsigned int countCharacters_; + + void AddPoint(int x, + int y); + + void Clear(); + + protected: + virtual void Visit(uint32_t unicode, + int x, + int y, + unsigned int width, + unsigned int height, + const Orthanc::IDynamicObject* payload /* ignored */); + + public: + TextBoundingBox(const GlyphAlphabet& alphabet, + const std::string& utf8); + + int GetLeft() const + { + return left_; + } + + int GetTop() const + { + return top_; + } + + unsigned int GetWidth() const; + + unsigned int GetHeight() const; + + unsigned int GetCharactersCount() const + { + return countCharacters_; + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Layers/DicomSeriesVolumeSlicer.cpp --- a/Framework/Layers/DicomSeriesVolumeSlicer.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Layers/DicomSeriesVolumeSlicer.cpp Thu May 16 09:11:14 2019 +0200 @@ -34,19 +34,19 @@ void DicomSeriesVolumeSlicer::OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message) { - if (message.GetOrigin().GetSliceCount() > 0) + if (message.GetOrigin().GetSlicesCount() > 0) { - EmitMessage(IVolumeSlicer::GeometryReadyMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); } else { - EmitMessage(IVolumeSlicer::GeometryErrorMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); } } void DicomSeriesVolumeSlicer::OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message) { - EmitMessage(IVolumeSlicer::GeometryErrorMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); } @@ -73,17 +73,17 @@ void DicomSeriesVolumeSlicer::OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message) { // first notify that the pixel data of the frame is ready (targeted to, i.e: an image cache) - EmitMessage(FrameReadyMessage(*this, message.GetImage(), + BroadcastMessage(FrameReadyMessage(*this, message.GetImage(), message.GetEffectiveQuality(), message.GetSlice())); // then notify that the layer is ready for rendering RendererFactory factory(message); - EmitMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, message.GetSlice().GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, message.GetSlice().GetGeometry())); } void DicomSeriesVolumeSlicer::OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message) { - EmitMessage(IVolumeSlicer::LayerErrorMessage(*this, message.GetSlice().GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, message.GetSlice().GetGeometry())); } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Layers/DicomSeriesVolumeSlicer.h --- a/Framework/Layers/DicomSeriesVolumeSlicer.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Layers/DicomSeriesVolumeSlicer.h Thu May 16 09:11:14 2019 +0200 @@ -38,8 +38,10 @@ { public: // TODO: Add "frame" and "instanceId" - class FrameReadyMessage : public OriginMessage + class FrameReadyMessage : public OriginMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: const Orthanc::ImageAccessor& frame_; SliceImageQuality imageQuality_; @@ -100,9 +102,9 @@ return quality_; } - size_t GetSliceCount() const + size_t GetSlicesCount() const { - return loader_.GetSliceCount(); + return loader_.GetSlicesCount(); } const Slice& GetSlice(size_t slice) const diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Layers/DicomStructureSetSlicer.cpp --- a/Framework/Layers/DicomStructureSetSlicer.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Layers/DicomStructureSetSlicer.cpp Thu May 16 09:11:14 2019 +0200 @@ -164,7 +164,7 @@ if (loader_.HasStructureSet()) { RendererFactory factory(loader_.GetStructureSet(), viewportPlane); - EmitMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, viewportPlane)); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, viewportPlane)); } } } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Layers/DicomStructureSetSlicer.h --- a/Framework/Layers/DicomStructureSetSlicer.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Layers/DicomStructureSetSlicer.h Thu May 16 09:11:14 2019 +0200 @@ -38,7 +38,7 @@ void OnStructureSetLoaded(const IVolumeLoader::ContentChangedMessage& message) { - EmitMessage(IVolumeSlicer::ContentChangedMessage(*this)); + BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); } public: diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Layers/IVolumeSlicer.h --- a/Framework/Layers/IVolumeSlicer.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Layers/IVolumeSlicer.h Thu May 16 09:11:14 2019 +0200 @@ -33,18 +33,20 @@ class IVolumeSlicer : public IObservable { public: - typedef OriginMessage GeometryReadyMessage; - typedef OriginMessage GeometryErrorMessage; - typedef OriginMessage ContentChangedMessage; + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeSlicer); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeSlicer); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeSlicer); - class SliceContentChangedMessage : public OriginMessage + class SliceContentChangedMessage : public OriginMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: const Slice& slice_; public: SliceContentChangedMessage(IVolumeSlicer& origin, - const Slice& slice) : + const Slice& slice) : OriginMessage(origin), slice_(slice) { @@ -57,8 +59,10 @@ }; - class LayerReadyMessage : public OriginMessage + class LayerReadyMessage : public OriginMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + public: class IRendererFactory : public boost::noncopyable { @@ -96,8 +100,10 @@ }; - class LayerErrorMessage : public OriginMessage + class LayerErrorMessage : public OriginMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: const CoordinateSystem3D& slice_; diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Layers/RenderStyle.cpp --- a/Framework/Layers/RenderStyle.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Layers/RenderStyle.cpp Thu May 16 09:11:14 2019 +0200 @@ -29,7 +29,7 @@ { visible_ = true; reverse_ = false; - windowing_ = ImageWindowing_Default; + windowing_ = ImageWindowing_Custom; alpha_ = 1; applyLut_ = false; lut_ = Orthanc::EmbeddedResources::COLORMAP_HOT; diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Messages/ICallable.h --- a/Framework/Messages/ICallable.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Messages/ICallable.h Thu May 16 09:11:14 2019 +0200 @@ -41,7 +41,8 @@ virtual void Apply(const IMessage& message) = 0; - virtual MessageType GetMessageType() const = 0; + virtual const MessageIdentifier& GetMessageIdentifier() = 0; + virtual IObserver* GetObserver() const = 0; }; @@ -58,8 +59,8 @@ private: typedef void (TObserver::* MemberFunction) (const TMessage&); - TObserver& observer_; - MemberFunction function_; + TObserver& observer_; + MemberFunction function_; public: Callable(TObserver& observer, @@ -79,9 +80,9 @@ ApplyInternal(dynamic_cast(message)); } - virtual MessageType GetMessageType() const + virtual const MessageIdentifier& GetMessageIdentifier() { - return static_cast(TMessage::Type); + return TMessage::GetStaticIdentifier(); } virtual IObserver* GetObserver() const @@ -115,11 +116,6 @@ lambda_(dynamic_cast(message)); } - virtual MessageType GetMessageType() const - { - return static_cast(TMessage::Type); - } - virtual IObserver* GetObserver() const { return &observer_; diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Messages/IMessage.h --- a/Framework/Messages/IMessage.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Messages/IMessage.h Thu May 16 09:11:14 2019 +0200 @@ -21,79 +21,79 @@ #pragma once -#include "../StoneEnumerations.h" +#include -#include +#include namespace OrthancStone { - // base message that are exchanged between IObservable and IObserver - class IMessage : public boost::noncopyable + class MessageIdentifier { private: - int messageType_; - - protected: - IMessage(const int& messageType) : - messageType_(messageType) + const char* file_; + int line_; + + public: + MessageIdentifier(const char* file, + int line) : + file_(file), + line_(line) + { + } + + MessageIdentifier() : + file_(NULL), + line_(0) { } + + bool operator< (const MessageIdentifier& other) const + { + if (file_ == NULL) + { + return false; + } + else if (line_ != other.line_) + { + return line_ < other.line_; + } + else + { + return strcmp(file_, other.file_) < 0; + } + } + }; + + /** + * Base messages that are exchanged between IObservable and + * IObserver. Messages are distinguished by the "__FILE__" and + * "__LINE__" macro, as in "Orthanc::SQLite::StatementId". + **/ + class IMessage : public boost::noncopyable + { public: virtual ~IMessage() { } - virtual int GetType() const - { - return messageType_; - } + virtual const MessageIdentifier& GetIdentifier() const = 0; }; - // base class to derive from to implement your own messages - // it handles the message type for you - template - class BaseMessage : public IMessage - { - public: - enum - { - Type = type - }; - - BaseMessage() : - IMessage(static_cast(Type)) - { - } - }; - - - // simple message implementation when no payload is needed - // sample usage: - // typedef NoPayloadMessage GeometryReadyMessage; - template - class NoPayloadMessage : public BaseMessage - { - public: - NoPayloadMessage() : - BaseMessage() - { - } - }; - - // simple message implementation when no payload is needed but the origin is required - // sample usage: - // typedef OriginMessage SliceGeometryErrorMessage; - template - class OriginMessage : public BaseMessage + /** + * Simple message implementation when no payload is needed but the + * origin is required. Sample usage: + * typedef OriginMessage SliceGeometryErrorMessage; + **/ + template + class OriginMessage : public IMessage { private: - const TOrigin& origin_; + const TOrigin& origin_; public: OriginMessage(const TOrigin& origin) : - BaseMessage(), origin_(origin) { } @@ -104,3 +104,36 @@ } }; } + + +#define ORTHANC_STONE_MESSAGE(FILE, LINE) \ + public: \ + static const ::OrthancStone::MessageIdentifier& GetStaticIdentifier() \ + { \ + static const ::OrthancStone::MessageIdentifier id(FILE, LINE); \ + return id; \ + } \ + \ + virtual const ::OrthancStone::MessageIdentifier& GetIdentifier() const \ + { \ + return GetStaticIdentifier(); \ + } + + +#define ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(FILE, LINE, NAME, ORIGIN) \ + class NAME : public ::OrthancStone::OriginMessage \ + { \ + ORTHANC_STONE_MESSAGE(FILE, LINE); \ + \ + NAME(const ORIGIN& origin) : \ + OriginMessage(origin) \ + { \ + } \ + }; + + +#define ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(FILE, LINE, NAME) \ + class NAME : public ::OrthancStone::IMessage \ + { \ + ORTHANC_STONE_MESSAGE(FILE, LINE); \ + }; diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Messages/IObservable.cpp --- a/Framework/Messages/IObservable.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Messages/IObservable.cpp Thu May 16 09:11:14 2019 +0200 @@ -59,9 +59,8 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } - MessageType messageType = callable->GetMessageType(); - - callables_[messageType].insert(callable); + const MessageIdentifier& id = callable->GetMessageIdentifier(); + callables_[id].insert(callable); } void IObservable::Unregister(IObserver *observer) @@ -84,9 +83,10 @@ } } - void IObservable::EmitMessage(const IMessage& message) + void IObservable::EmitMessageInternal(const IObserver* receiver, + const IMessage& message) { - Callables::const_iterator found = callables_.find(message.GetType()); + Callables::const_iterator found = callables_.find(message.GetIdentifier()); if (found != callables_.end()) { @@ -94,14 +94,33 @@ it = found->second.begin(); it != found->second.end(); ++it) { assert(*it != NULL); - if (broker_.IsActive(*(*it)->GetObserver())) + + const IObserver* observer = (*it)->GetObserver(); + if (broker_.IsActive(*observer)) { - (*it)->Apply(message); + if (receiver == NULL || // Are we broadcasting? + observer == receiver) // Not broadcasting, but this is the receiver + { + (*it)->Apply(message); + } } } } } + + void IObservable::BroadcastMessage(const IMessage& message) + { + EmitMessageInternal(NULL, message); + } + + + void IObservable::EmitMessage(const IObserver& observer, + const IMessage& message) + { + EmitMessageInternal(&observer, message); + } + void IObservable::RegisterForwarder(IMessageForwarder* forwarder) { diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Messages/IObservable.h --- a/Framework/Messages/IObservable.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Messages/IObservable.h Thu May 16 09:11:14 2019 +0200 @@ -35,13 +35,17 @@ class IObservable : public boost::noncopyable { private: - typedef std::map > Callables; - typedef std::set Forwarders; + typedef std::map > Callables; + + typedef std::set Forwarders; MessageBroker& broker_; Callables callables_; Forwarders forwarders_; + void EmitMessageInternal(const IObserver* receiver, + const IMessage& message); + public: IObservable(MessageBroker& broker) : broker_(broker) @@ -60,7 +64,10 @@ void Unregister(IObserver* observer); - void EmitMessage(const IMessage& message); + void BroadcastMessage(const IMessage& message); + + void EmitMessage(const IObserver& observer, + const IMessage& message); // Takes ownsership void RegisterForwarder(IMessageForwarder* forwarder); diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Messages/MessageForwarder.cpp --- a/Framework/Messages/MessageForwarder.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Messages/MessageForwarder.cpp Thu May 16 09:11:14 2019 +0200 @@ -28,7 +28,7 @@ void IMessageForwarder::ForwardMessageInternal(const IMessage& message) { - emitter_.EmitMessage(message); + emitter_.BroadcastMessage(message); } void IMessageForwarder::RegisterForwarderInEmitter() diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/OpenGL/IOpenGLContext.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/OpenGL/IOpenGLContext.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,47 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include + +namespace OrthancStone +{ + namespace OpenGL + { + class IOpenGLContext : public boost::noncopyable + { + public: + virtual ~IOpenGLContext() + { + } + + virtual void MakeCurrent() = 0; + + virtual void SwapBuffer() = 0; + + virtual unsigned int GetCanvasWidth() const = 0; + + virtual unsigned int GetCanvasHeight() const = 0; + }; + } +} + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/OpenGL/OpenGLIncludes.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/OpenGL/OpenGLIncludes.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,41 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_OPENGL) +# error The macro ORTHANC_ENABLE_OPENGL must be defined +#endif + +#if ORTHANC_ENABLE_OPENGL != 1 +# error Support for OpenGL is disabled +#endif + +#if defined(__APPLE__) +# include +# include +#elif defined(_WIN32) +// On Windows, use the compatibility headers provided by glew +# include +#else +# include +# include +#endif diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/OpenGL/OpenGLProgram.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/OpenGL/OpenGLProgram.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,103 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLProgram.h" + +#include "OpenGLShader.h" + +#include + + +namespace OrthancStone +{ + namespace OpenGL + { + OpenGLProgram::OpenGLProgram() + { + program_ = glCreateProgram(); + if (program_ == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot create an OpenGL program"); + } + } + + + OpenGLProgram::~OpenGLProgram() + { + assert(program_ != 0); + glDeleteProgram(program_); + } + + + void OpenGLProgram::Use() + { + glUseProgram(program_); + } + + + void OpenGLProgram::CompileShaders(const std::string& vertexCode, + const std::string& fragmentCode) + { + assert(program_ != 0); + + OpenGLShader vertexShader(GL_VERTEX_SHADER, vertexCode); + OpenGLShader fragmentShader(GL_FRAGMENT_SHADER, fragmentCode); + + glAttachShader(program_, vertexShader.Release()); + glAttachShader(program_, fragmentShader.Release()); + glLinkProgram(program_); + glValidateProgram(program_); + } + + + GLint OpenGLProgram::GetUniformLocation(const std::string& name) + { + GLint location = glGetUniformLocation(program_, name.c_str()); + + if (location == -1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, + "Inexistent uniform variable in shader: " + name); + } + else + { + return location; + } + } + + + GLint OpenGLProgram::GetAttributeLocation(const std::string& name) + { + GLint location = glGetAttribLocation(program_, name.c_str()); + + if (location == -1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, + "Inexistent attribute in shader: " + name); + } + else + { + return location; + } + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/OpenGL/OpenGLProgram.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/OpenGL/OpenGLProgram.h Thu May 16 09:11:14 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 . + **/ + + +#pragma once + +#include "OpenGLIncludes.h" + +#include +#include + +namespace OrthancStone +{ + namespace OpenGL + { + class OpenGLProgram : public boost::noncopyable + { + private: + GLuint program_; + + public: + // WARNING: A global OpenGL context must be active to create this object! + OpenGLProgram(); + + ~OpenGLProgram(); + + void Use(); + + // WARNING: A global OpenGL context must be active to run this method! + void CompileShaders(const std::string& vertexCode, + const std::string& fragmentCode); + + GLint GetUniformLocation(const std::string& name); + + GLint GetAttributeLocation(const std::string& name); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/OpenGL/OpenGLShader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/OpenGL/OpenGLShader.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,104 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLShader.h" + +#include + +namespace OrthancStone +{ + namespace OpenGL + { + static GLuint CompileShader(GLenum type, + const std::string& source) + { + // Create shader object + const GLchar* sourceString[1]; + GLint sourceStringLengths[1]; + + sourceString[0] = source.c_str(); + sourceStringLengths[0] = source.length(); + GLuint shader = glCreateShader(type); + + if (shader == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot create an OpenGL shader"); + } + else + { + // Assign and compile the source to the shader object + glShaderSource(shader, 1, sourceString, sourceStringLengths); + glCompileShader(shader); + + // Check if there were errors + int infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + + if (infoLen > 1) // Might be equal to 1, which amounts to no error + { + std::string infoLog; + infoLog.resize(infoLen + 1); + glGetShaderInfoLog(shader, infoLen, NULL, &infoLog[0]); + glDeleteShader(shader); + + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Error while creating an OpenGL shader: " + infoLog); + } + else + { + return shader; + } + } + } + + + OpenGLShader::OpenGLShader(GLenum type, + const std::string& source) + { + shader_ = CompileShader(type, source); + isValid_ = true; + } + + + OpenGLShader::~OpenGLShader() + { + if (isValid_) + { + glDeleteShader(shader_); + } + } + + + GLuint OpenGLShader::Release() + { + if (isValid_) + { + isValid_ = false; + return shader_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/OpenGL/OpenGLShader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/OpenGL/OpenGLShader.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,53 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "OpenGLIncludes.h" + +#include +#include + +namespace OrthancStone +{ + namespace OpenGL + { + class OpenGLShader : public boost::noncopyable + { + private: + bool isValid_; + GLuint shader_; + + public: + OpenGLShader(GLenum type, + const std::string& source); + + ~OpenGLShader(); + + bool IsValid() const + { + return isValid_; + } + + GLuint Release(); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/OpenGL/OpenGLTexture.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/OpenGL/OpenGLTexture.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,113 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLTexture.h" + +#include + +namespace OrthancStone +{ + namespace OpenGL + { + OpenGLTexture::OpenGLTexture() : + width_(0), + height_(0) + { + // Generate a texture object + glGenTextures(1, &texture_); + if (texture_ == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot create an OpenGL program"); + } + } + + + OpenGLTexture::~OpenGLTexture() + { + assert(texture_ != 0); + glDeleteTextures(1, &texture_); + } + + + void OpenGLTexture::Load(const Orthanc::ImageAccessor& image, + bool isLinearInterpolation) + { + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Disable byte-alignment restriction + + if (image.GetPitch() != image.GetBytesPerPixel() * image.GetWidth()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "Unsupported non-zero padding"); + } + + // Bind it + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_); + + GLenum sourceFormat, internalFormat; + + switch (image.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + sourceFormat = GL_RED; + internalFormat = GL_RED; + break; + + case Orthanc::PixelFormat_RGB24: + sourceFormat = GL_RGB; + internalFormat = GL_RGB; + break; + + case Orthanc::PixelFormat_RGBA32: + sourceFormat = GL_RGBA; + internalFormat = GL_RGBA; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "No support for this format in OpenGL textures: " + + std::string(EnumerationToString(image.GetFormat()))); + } + + width_ = image.GetWidth(); + height_ = image.GetHeight(); + + GLint interpolation = (isLinearInterpolation ? GL_LINEAR : GL_NEAREST); + + // Load the texture from the image buffer + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, image.GetWidth(), image.GetHeight(), + 0, sourceFormat, GL_UNSIGNED_BYTE, image.GetBuffer()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, interpolation); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, interpolation); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + + void OpenGLTexture::Bind(GLint location) + { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_); + glUniform1i(location, 0 /* texture unit */); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/OpenGL/OpenGLTexture.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/OpenGL/OpenGLTexture.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,63 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "OpenGLIncludes.h" + +#include + +#include + + +namespace OrthancStone +{ + namespace OpenGL + { + class OpenGLTexture : public boost::noncopyable + { + private: + GLuint texture_; + unsigned int width_; + unsigned int height_; + + public: + OpenGLTexture(); + + ~OpenGLTexture(); + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + void Load(const Orthanc::ImageAccessor& image, + bool isLinearInterpolation); + + void Bind(GLint location); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/OpenGL/WebAssemblyOpenGLContext.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/OpenGL/WebAssemblyOpenGLContext.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,179 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "WebAssemblyOpenGLContext.h" + +#if ORTHANC_ENABLE_WASM == 1 + +#include + +#include +#include + +namespace OrthancStone +{ + namespace OpenGL + { + class WebAssemblyOpenGLContext::PImpl + { + private: + std::string canvas_; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_; + unsigned int canvasWidth_; + unsigned int canvasHeight_; + + public: + PImpl(const std::string& canvas) : + canvas_(canvas) + { + // Context configuration + EmscriptenWebGLContextAttributes attr; + emscripten_webgl_init_context_attributes(&attr); + + context_ = emscripten_webgl_create_context(canvas.c_str(), &attr); + if (context_ < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot create an OpenGL context for canvas: " + canvas); + } + + UpdateSize(); + } + + ~PImpl() + { + emscripten_webgl_destroy_context(context_); + } + + const std::string& GetCanvasIdentifier() const + { + return canvas_; + } + + void MakeCurrent() + { + if (emscripten_webgl_make_context_current(context_) != EMSCRIPTEN_RESULT_SUCCESS) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot set the OpenGL context"); + } + } + + void SwapBuffer() + { + /** + * "Rendered WebGL content is implicitly presented (displayed to + * the user) on the canvas when the event handler that renders with + * WebGL returns back to the browser event loop." + * https://emscripten.org/docs/api_reference/html5.h.html#webgl-context + * + * Could call "emscripten_webgl_commit_frame()" if + * "explicitSwapControl" option were set to "true". + **/ + } + + unsigned int GetCanvasWidth() const + { + return canvasWidth_; + } + + unsigned int GetCanvasHeight() const + { + return canvasHeight_; + } + + void UpdateSize() + { + double w, h; + emscripten_get_element_css_size(canvas_.c_str(), &w, &h); + + /** + * Emscripten has the function emscripten_get_element_css_size() + * to query the width and height of a named HTML element. I'm + * calling this first to get the initial size of the canvas DOM + * element, and then call emscripten_set_canvas_size() to + * initialize the framebuffer size of the canvas to the same + * size as its DOM element. + * https://floooh.github.io/2017/02/22/emsc-html.html + **/ + + if (w <= 0 || + h <= 0) + { + canvasWidth_ = 0; + canvasHeight_ = 0; + } + else + { + canvasWidth_ = static_cast(boost::math::iround(w)); + canvasHeight_ = static_cast(boost::math::iround(h)); + } + + emscripten_set_canvas_element_size(canvas_.c_str(), canvasWidth_, canvasHeight_); + } + }; + + + WebAssemblyOpenGLContext::WebAssemblyOpenGLContext(const std::string& canvas) : + pimpl_(new PImpl(canvas)) + { + } + + void WebAssemblyOpenGLContext::MakeCurrent() + { + assert(pimpl_.get() != NULL); + pimpl_->MakeCurrent(); + } + + + void WebAssemblyOpenGLContext::SwapBuffer() + { + assert(pimpl_.get() != NULL); + pimpl_->SwapBuffer(); + } + + unsigned int WebAssemblyOpenGLContext::GetCanvasWidth() const + { + assert(pimpl_.get() != NULL); + return pimpl_->GetCanvasWidth(); + } + + unsigned int WebAssemblyOpenGLContext::GetCanvasHeight() const + { + assert(pimpl_.get() != NULL); + return pimpl_->GetCanvasHeight(); + } + + void WebAssemblyOpenGLContext::UpdateSize() + { + assert(pimpl_.get() != NULL); + pimpl_->UpdateSize(); + } + + const std::string& WebAssemblyOpenGLContext::GetCanvasIdentifier() const + { + assert(pimpl_.get() != NULL); + return pimpl_->GetCanvasIdentifier(); + } + } +} + +#endif diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/OpenGL/WebAssemblyOpenGLContext.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/OpenGL/WebAssemblyOpenGLContext.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,62 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_WASM) +# error Macro ORTHANC_ENABLE_WASM must be defined +#endif + +#if ORTHANC_ENABLE_WASM == 1 + +#include "IOpenGLContext.h" + +#include + +namespace OrthancStone +{ + namespace OpenGL + { + class WebAssemblyOpenGLContext : public OpenGL::IOpenGLContext + { + private: + class PImpl; + boost::shared_ptr pimpl_; + + public: + WebAssemblyOpenGLContext(const std::string& canvas); + + virtual void MakeCurrent(); + + virtual void SwapBuffer(); + + virtual unsigned int GetCanvasWidth() const; + + virtual unsigned int GetCanvasHeight() const; + + void UpdateSize(); + + const std::string& GetCanvasIdentifier() const; + }; + } +} + +#endif diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Radiography/RadiographyAlphaLayer.cpp --- a/Framework/Radiography/RadiographyAlphaLayer.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Radiography/RadiographyAlphaLayer.cpp Thu May 16 09:11:14 2019 +0200 @@ -46,7 +46,7 @@ SetSize(image->GetWidth(), image->GetHeight()); alpha_ = raii; - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyAlphaLayer::Render(Orthanc::ImageAccessor& buffer, diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Radiography/RadiographyDicomLayer.cpp --- a/Framework/Radiography/RadiographyDicomLayer.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.cpp Thu May 16 09:11:14 2019 +0200 @@ -47,6 +47,11 @@ } + RadiographyDicomLayer::RadiographyDicomLayer(MessageBroker& broker, const RadiographyScene& scene) : RadiographyLayer(broker, scene) + { + + } + void RadiographyDicomLayer::SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) { converter_.reset(new DicomFrameConverter); @@ -103,7 +108,13 @@ source_ = raii; ApplyConverter(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); + } + + + void RadiographyDicomLayer::SetDicomFrameConverter(DicomFrameConverter* converter) + { + converter_.reset(converter); } void RadiographyDicomLayer::Render(Orthanc::ImageAccessor& buffer, diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Radiography/RadiographyDicomLayer.h --- a/Framework/Radiography/RadiographyDicomLayer.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.h Thu May 16 09:11:14 2019 +0200 @@ -41,10 +41,7 @@ void ApplyConverter(); public: - RadiographyDicomLayer(MessageBroker& broker, const RadiographyScene& scene) - : RadiographyLayer(broker, scene) - { - } + RadiographyDicomLayer(MessageBroker& broker, const RadiographyScene& scene); void SetInstance(const std::string& instanceId, unsigned int frame) { @@ -69,7 +66,9 @@ const Orthanc::ImageAccessor* GetSourceImage() const {return source_.get();} // currently need this access to serialize scene in plain old data to send to a WASM worker const DicomFrameConverter& GetDicomFrameConverter() const {return *converter_;} // currently need this access to serialize scene in plain old data to send to a WASM worker - void SetDicomFrameConverter(DicomFrameConverter* converter) {converter_.reset(converter);} // Takes ownership + + // Takes ownership + void SetDicomFrameConverter(DicomFrameConverter* converter); virtual void Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Radiography/RadiographyLayer.cpp --- a/Framework/Radiography/RadiographyLayer.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Radiography/RadiographyLayer.cpp Thu May 16 09:11:14 2019 +0200 @@ -141,7 +141,7 @@ { prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode; - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyLayer::SetCrop(unsigned int x, @@ -163,7 +163,7 @@ geometry_.SetCrop(x, y, width, height); UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyLayer::SetGeometry(const Geometry& geometry) @@ -175,7 +175,7 @@ UpdateTransform(); } - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -203,7 +203,7 @@ geometry_.SetAngle(angle); UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyLayer::SetFlipVertical(bool flip) @@ -211,7 +211,7 @@ geometry_.SetFlipVertical(flip); UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyLayer::SetFlipHorizontal(bool flip) @@ -219,7 +219,7 @@ geometry_.SetFlipHorizontal(flip); UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyLayer::SetSize(unsigned int width, @@ -237,7 +237,7 @@ height_ = height; UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -315,7 +315,7 @@ { geometry_.SetPan(x, y); UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -324,7 +324,7 @@ { geometry_.SetPixelSpacing(x, y); UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Radiography/RadiographyLayer.h --- a/Framework/Radiography/RadiographyLayer.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Radiography/RadiographyLayer.h Thu May 16 09:11:14 2019 +0200 @@ -55,18 +55,7 @@ friend class RadiographyScene; public: - class LayerEditedMessage : - public OriginMessage - { - private: - - public: - LayerEditedMessage(const RadiographyLayer& origin) : - OriginMessage(origin) - { - } - }; - + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, LayerEditedMessage, RadiographyLayer); class Geometry { diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Radiography/RadiographyMaskLayer.cpp --- a/Framework/Radiography/RadiographyMaskLayer.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Radiography/RadiographyMaskLayer.cpp Thu May 16 09:11:14 2019 +0200 @@ -63,7 +63,7 @@ corners_.push_back(corner); invalidated_ = true; - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyMaskLayer::SetCorners(const std::vector& corners) @@ -71,7 +71,7 @@ corners_ = corners; invalidated_ = true; - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyMaskLayer::Render(Orthanc::ImageAccessor& buffer, diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Radiography/RadiographyScene.cpp --- a/Framework/Radiography/RadiographyScene.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.cpp Thu May 16 09:11:14 2019 +0200 @@ -140,8 +140,8 @@ raii->SetIndex(index); layers_[index] = raii.release(); - EmitMessage(GeometryChangedMessage(*this, *layer)); - EmitMessage(ContentChangedMessage(*this, *layer)); + BroadcastMessage(GeometryChangedMessage(*this, *layer)); + BroadcastMessage(ContentChangedMessage(*this, *layer)); layer->RegisterObserverCallback(new Callable(*this, &RadiographyScene::OnLayerEdited)); return *layer; @@ -149,7 +149,7 @@ void RadiographyScene::OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message) { - EmitMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin())); + BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin())); } RadiographyScene::RadiographyScene(MessageBroker& broker) : @@ -199,24 +199,37 @@ { LOG(INFO) << "Removing layer: " << layerIndex; - if (layerIndex > countLayers_) + Layers::iterator found = layers_.find(layerIndex); + + if (found == layers_.end()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - delete layers_[layerIndex]; - layers_.erase(layerIndex); - countLayers_--; - LOG(INFO) << "Removing layer, there are now : " << countLayers_ << " layers"; + else + { + assert(found->second != NULL); + delete found->second; + + layers_.erase(found); + countLayers_--; + + LOG(INFO) << "Removing layer, there are now : " << countLayers_ << " layers"; + } } const RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex) const { - if (layerIndex > countLayers_) + Layers::const_iterator found = layers_.find(layerIndex); + + if (found == layers_.end()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - - return *(layers_.at(layerIndex)); + else + { + assert(found->second != NULL); + return *found->second; + } } bool RadiographyScene::GetWindowing(float& center, @@ -253,7 +266,7 @@ windowingCenter_ = center; windowingWidth_ = width; - EmitMessage(RadiographyScene::WindowingChangedMessage(*this)); + BroadcastMessage(RadiographyScene::WindowingChangedMessage(*this)); } @@ -434,7 +447,7 @@ windowingWidth_ = w; } - EmitMessage(GeometryChangedMessage(*this, *(layer->second))); + BroadcastMessage(GeometryChangedMessage(*this, *(layer->second))); } } @@ -461,7 +474,7 @@ reader->ReadFromMemory(content); dynamic_cast(layer->second)->SetSourceImage(reader.release()); - EmitMessage(ContentChangedMessage(*this, *(layer->second))); + BroadcastMessage(ContentChangedMessage(*this, *(layer->second))); } } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Radiography/RadiographyScene.h --- a/Framework/Radiography/RadiographyScene.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.h Thu May 16 09:11:14 2019 +0200 @@ -37,9 +37,10 @@ public IObservable { public: - class GeometryChangedMessage : - public OriginMessage + class GeometryChangedMessage : public OriginMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: RadiographyLayer& layer_; @@ -57,9 +58,10 @@ } }; - class ContentChangedMessage : - public OriginMessage + class ContentChangedMessage : public OriginMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: RadiographyLayer& layer_; @@ -77,9 +79,10 @@ } }; - class LayerEditedMessage : - public OriginMessage + class LayerEditedMessage : public OriginMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: const RadiographyLayer& layer_; @@ -95,20 +98,12 @@ { return layer_; } - }; - class WindowingChangedMessage : - public OriginMessage - { - public: - WindowingChangedMessage(const RadiographyScene& origin) : - OriginMessage(origin) - { - } - }; + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, WindowingChangedMessage, RadiographyScene); + class LayerAccessor : public boost::noncopyable { private: diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Radiography/RadiographySceneReader.cpp --- a/Framework/Radiography/RadiographySceneReader.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Radiography/RadiographySceneReader.cpp Thu May 16 09:11:14 2019 +0200 @@ -21,6 +21,8 @@ #include "RadiographySceneReader.h" +#include + #include #include #include diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/CairoCompositor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/CairoCompositor.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,177 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "CairoCompositor.h" + +#include "Internals/CairoColorTextureRenderer.h" +#include "Internals/CairoFloatTextureRenderer.h" +#include "Internals/CairoInfoPanelRenderer.h" +#include "Internals/CairoPolylineRenderer.h" +#include "Internals/CairoTextRenderer.h" + +#include + +namespace OrthancStone +{ + cairo_t* CairoCompositor::GetCairoContext() + { + if (context_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return context_->GetObject(); + } + } + + + Internals::CompositorHelper::ILayerRenderer* CairoCompositor::Create(const ISceneLayer& layer) + { + switch (layer.GetType()) + { + case ISceneLayer::Type_Polyline: + return new Internals::CairoPolylineRenderer(*this, layer); + + case ISceneLayer::Type_InfoPanel: + return new Internals::CairoInfoPanelRenderer(*this, layer); + + case ISceneLayer::Type_ColorTexture: + return new Internals::CairoColorTextureRenderer(*this, layer); + + case ISceneLayer::Type_FloatTexture: + return new Internals::CairoFloatTextureRenderer(*this, layer); + + case ISceneLayer::Type_Text: + { + const TextSceneLayer& l = dynamic_cast(layer); + + Fonts::const_iterator found = fonts_.find(l.GetFontIndex()); + if (found == fonts_.end()) + { + return NULL; + } + else + { + assert(found->second != NULL); + return new Internals::CairoTextRenderer(*this, *found->second, l); + } + } + + default: + return NULL; + } + } + + + CairoCompositor::CairoCompositor(const Scene2D& scene, + unsigned int canvasWidth, + unsigned int canvasHeight) : + helper_(scene, *this) + { + canvas_.SetSize(canvasWidth, canvasHeight, false); + } + + + CairoCompositor::~CairoCompositor() + { + for (Fonts::iterator it = fonts_.begin(); it != fonts_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + } + + + void CairoCompositor::SetFont(size_t index, + GlyphBitmapAlphabet* dict) // Takes ownership + { + if (dict == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + std::auto_ptr protection(dict); + + Fonts::iterator found = fonts_.find(index); + + if (found == fonts_.end()) + { + fonts_[index] = protection.release(); + } + else + { + assert(found->second != NULL); + delete found->second; + + found->second = protection.release(); + } + } + } + + +#if ORTHANC_ENABLE_LOCALE == 1 + void CairoCompositor::SetFont(size_t index, + Orthanc::EmbeddedResources::FileResourceId resource, + unsigned int fontSize, + Orthanc::Encoding codepage) + { + FontRenderer renderer; + renderer.LoadFont(resource, fontSize); + + std::auto_ptr alphabet(new GlyphBitmapAlphabet); + alphabet->LoadCodepage(renderer, codepage); + + SetFont(index, alphabet.release()); + } +#endif + + + void CairoCompositor::Refresh() + { + context_.reset(new CairoContext(canvas_)); + + // https://www.cairographics.org/FAQ/#clear_a_surface + cairo_set_source_rgba(context_->GetObject(), 0, 0, 0, 255); + cairo_paint(context_->GetObject()); + + helper_.Refresh(canvas_.GetWidth(), canvas_.GetHeight()); + context_.reset(); + } + + + Orthanc::ImageAccessor* CairoCompositor::RenderText(size_t fontIndex, + const std::string& utf8) const + { + Fonts::const_iterator found = fonts_.find(fontIndex); + + if (found == fonts_.end()) + { + return NULL; + } + else + { + assert(found->second != NULL); + return found->second->RenderText(utf8); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/CairoCompositor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/CairoCompositor.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,86 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Fonts/GlyphBitmapAlphabet.h" +#include "../Viewport/CairoContext.h" +#include "Internals/CompositorHelper.h" +#include "Internals/ICairoContextProvider.h" + +namespace OrthancStone +{ + class CairoCompositor : + private Internals::CompositorHelper::IRendererFactory, + private Internals::ICairoContextProvider + { + private: + typedef std::map Fonts; + + Internals::CompositorHelper helper_; + CairoSurface canvas_; + Fonts fonts_; + + // Only valid during a call to "Refresh()" + std::auto_ptr context_; + + virtual cairo_t* GetCairoContext(); + + virtual unsigned int GetCairoWidth() + { + return canvas_.GetWidth(); + } + + virtual unsigned int GetCairoHeight() + { + return canvas_.GetHeight(); + } + + virtual Internals::CompositorHelper::ILayerRenderer* Create(const ISceneLayer& layer); + + public: + CairoCompositor(const Scene2D& scene, + unsigned int canvasWidth, + unsigned int canvasHeight); + + ~CairoCompositor(); + + const CairoSurface& GetCanvas() const + { + return canvas_; + } + + void SetFont(size_t index, + GlyphBitmapAlphabet* dict); // Takes ownership + +#if ORTHANC_ENABLE_LOCALE == 1 + void SetFont(size_t index, + Orthanc::EmbeddedResources::FileResourceId resource, + unsigned int fontSize, + Orthanc::Encoding codepage); +#endif + + void Refresh(); + + Orthanc::ImageAccessor* RenderText(size_t fontIndex, + const std::string& utf8) const; + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/ColorSceneLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/ColorSceneLayer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,100 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ISceneLayer.h" +#include + +#include + +namespace OrthancStone +{ + class ColorSceneLayer : public ISceneLayer + { + private: + uint8_t red_; + uint8_t green_; + uint8_t blue_; + uint64_t revision_; + + protected: + void BumpRevision() + { + // this is *not* thread-safe!!! => (SJO) no problem, Stone assumes mono-threading + revision_++; + } + + public: + ColorSceneLayer() : + red_(255), + green_(255), + blue_(255), + revision_(0) + { + } + + virtual uint64_t GetRevision() const ORTHANC_OVERRIDE + { + return revision_; + } + + void SetColor(uint8_t red, + uint8_t green, + uint8_t blue) + { + red_ = red; + green_ = green; + blue_ = blue; + BumpRevision(); + } + + uint8_t GetRed() const + { + return red_; + } + + uint8_t GetGreen() const + { + return green_; + } + + uint8_t GetBlue() const + { + return blue_; + } + + float GetRedAsFloat() const + { + return static_cast(red_) / 255.0f; + } + + float GetGreenAsFloat() const + { + return static_cast(green_) / 255.0f; + } + + float GetBlueAsFloat() const + { + return static_cast(blue_) / 255.0f; + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/ColorTextureSceneLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/ColorTextureSceneLayer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,49 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "ColorTextureSceneLayer.h" + +#include +#include + + +namespace OrthancStone +{ + ColorTextureSceneLayer::ColorTextureSceneLayer(const Orthanc::ImageAccessor& texture) + { + if (texture.GetFormat() != Orthanc::PixelFormat_Grayscale8 && + texture.GetFormat() != Orthanc::PixelFormat_RGBA32 && + texture.GetFormat() != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + SetTexture(Orthanc::Image::Clone(texture)); + } + + + ISceneLayer* ColorTextureSceneLayer::Clone() const + { + std::auto_ptr cloned(new ColorTextureSceneLayer(GetTexture())); + cloned->CopyParameters(*this); + return cloned.release(); + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/ColorTextureSceneLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/ColorTextureSceneLayer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,40 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "TextureBaseSceneLayer.h" + +namespace OrthancStone +{ + class ColorTextureSceneLayer : public TextureBaseSceneLayer + { + public: + ColorTextureSceneLayer(const Orthanc::ImageAccessor& texture); + + virtual ISceneLayer* Clone() const; + + virtual Type GetType() const + { + return Type_ColorTexture; + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/FloatTextureSceneLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/FloatTextureSceneLayer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,122 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "FloatTextureSceneLayer.h" + +#include +#include +#include + +namespace OrthancStone +{ + FloatTextureSceneLayer::FloatTextureSceneLayer(const Orthanc::ImageAccessor& texture) + { + { + std::auto_ptr t( + new Orthanc::Image(Orthanc::PixelFormat_Float32, + texture.GetWidth(), + texture.GetHeight(), + false)); + + Orthanc::ImageProcessing::Convert(*t, texture); + SetTexture(t.release()); + } + + SetCustomWindowing(128, 256); + } + + + void FloatTextureSceneLayer::SetWindowing(ImageWindowing windowing) + { + if (windowing_ != windowing) + { + if (windowing == ImageWindowing_Custom) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + windowing_ = windowing; + IncrementRevision(); + } + } + } + + + void FloatTextureSceneLayer::SetCustomWindowing(float customCenter, + float customWidth) + { + if (customWidth <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + windowing_ = ImageWindowing_Custom; + customCenter_ = customCenter; + customWidth_ = customWidth; + IncrementRevision(); + } + } + + + void FloatTextureSceneLayer::GetWindowing(float& targetCenter, + float& targetWidth) const + { + ::OrthancStone::ComputeWindowing(targetCenter, targetWidth, + windowing_, customCenter_, customWidth_); + } + + + void FloatTextureSceneLayer::FitRange() + { + float minValue, maxValue; + Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, GetTexture()); + + float width; + + assert(minValue <= maxValue); + if (LinearAlgebra::IsCloseToZero(maxValue - minValue)) + { + width = 1; + } + else + { + width = maxValue - minValue; + } + + SetCustomWindowing((minValue + maxValue) / 2.0f, width); + } + + + ISceneLayer* FloatTextureSceneLayer::Clone() const + { + std::auto_ptr cloned + (new FloatTextureSceneLayer(GetTexture())); + + cloned->CopyParameters(*this); + cloned->windowing_ = windowing_; + cloned->customCenter_ = customCenter_; + cloned->customWidth_ = customWidth_; + + return cloned.release(); + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/FloatTextureSceneLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/FloatTextureSceneLayer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,61 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "TextureBaseSceneLayer.h" + +namespace OrthancStone +{ + class FloatTextureSceneLayer : public TextureBaseSceneLayer + { + private: + ImageWindowing windowing_; + float customCenter_; + float customWidth_; + + public: + // The pixel format must be "Float32" + FloatTextureSceneLayer(const Orthanc::ImageAccessor& texture); + + void SetWindowing(ImageWindowing windowing); + + void SetCustomWindowing(float customCenter, + float customWidth); + + void GetWindowing(float& targetCenter, + float& targetWidth) const; + + ImageWindowing GetWindowingType() const + { + return windowing_; + } + + void FitRange(); + + virtual ISceneLayer* Clone() const; + + virtual Type GetType() const + { + return Type_FloatTexture; + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/IPointerTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/IPointerTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,46 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "PointerEvent.h" + +namespace OrthancStone +{ + class IPointerTracker : public boost::noncopyable + { + public: + virtual ~IPointerTracker() + { + } + + /** + This method will be repeatedly called during user interaction + */ + virtual void Update(const PointerEvent& event) = 0; + + /** + This method will be called when the tracker should commit its result + before being destroyed. + */ + virtual void Release() = 0; + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/ISceneLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/ISceneLayer.h Thu May 16 09:11:14 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 . + **/ + + +#pragma once + +#include "../Toolbox/Extent2D.h" + +#include +#include + +namespace OrthancStone +{ + class ISceneLayer : public boost::noncopyable + { + public: + enum Type + { + Type_InfoPanel, + Type_ColorTexture, + Type_Polyline, + Type_Text, + Type_FloatTexture + }; + + virtual ~ISceneLayer() + { + } + + virtual ISceneLayer* Clone() const = 0; + + virtual Type GetType() const = 0; + + virtual bool GetBoundingBox(Extent2D& target) const = 0; + + virtual uint64_t GetRevision() const = 0; + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/InfoPanelSceneLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/InfoPanelSceneLayer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,105 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "InfoPanelSceneLayer.h" + +#include +#include + +namespace OrthancStone +{ + InfoPanelSceneLayer::InfoPanelSceneLayer(const Orthanc::ImageAccessor& texture, + BitmapAnchor anchor, + bool isLinearInterpolation) : + texture_(Orthanc::Image::Clone(texture)), + anchor_(anchor), + isLinearInterpolation_(isLinearInterpolation) + { + if (texture_->GetFormat() != Orthanc::PixelFormat_RGBA32 && + texture_->GetFormat() != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + } + + + void InfoPanelSceneLayer::ComputeAnchorLocation(int& x, + int& y, + BitmapAnchor anchor, + unsigned int textureWidth, + unsigned int textureHeight, + unsigned int canvasWidth, + unsigned int canvasHeight) + { + int tw = static_cast(textureWidth); + int th = static_cast(textureHeight); + int cw = static_cast(canvasWidth); + int ch = static_cast(canvasHeight); + + switch (anchor) + { + case BitmapAnchor_TopLeft: + case BitmapAnchor_CenterLeft: + case BitmapAnchor_BottomLeft: + x = 0; + break; + + case BitmapAnchor_TopCenter: + case BitmapAnchor_Center: + case BitmapAnchor_BottomCenter: + x = (cw - tw) / 2; + break; + + case BitmapAnchor_TopRight: + case BitmapAnchor_CenterRight: + case BitmapAnchor_BottomRight: + x = cw - tw; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + switch (anchor) + { + case BitmapAnchor_TopLeft: + case BitmapAnchor_TopCenter: + case BitmapAnchor_TopRight: + y = 0; + break; + + case BitmapAnchor_CenterLeft: + case BitmapAnchor_Center: + case BitmapAnchor_CenterRight: + y = (ch - th) / 2; + break; + + case BitmapAnchor_BottomLeft: + case BitmapAnchor_BottomCenter: + case BitmapAnchor_BottomRight: + y = ch - th; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/InfoPanelSceneLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/InfoPanelSceneLayer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,88 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ISceneLayer.h" +#include "../StoneEnumerations.h" + +#include + +#include + +namespace OrthancStone +{ + class InfoPanelSceneLayer : public ISceneLayer + { + private: + std::auto_ptr texture_; + BitmapAnchor anchor_; + bool isLinearInterpolation_; + + public: + InfoPanelSceneLayer(const Orthanc::ImageAccessor& texture, + BitmapAnchor anchor, + bool isLinearInterpolation); + + virtual ISceneLayer* Clone() const + { + return new InfoPanelSceneLayer(*texture_, anchor_, isLinearInterpolation_); + } + + const Orthanc::ImageAccessor& GetTexture() const + { + return *texture_; + } + + BitmapAnchor GetAnchor() const + { + return anchor_; + } + + bool IsLinearInterpolation() const + { + return isLinearInterpolation_; + } + + virtual Type GetType() const + { + return Type_InfoPanel; + } + + virtual bool GetBoundingBox(Extent2D& target) const + { + return false; + } + + virtual uint64_t GetRevision() const + { + return 0; + } + + static void ComputeAnchorLocation(int& x, + int& y, + BitmapAnchor anchor, + unsigned int textureWidth, + unsigned int textureHeight, + unsigned int canvasWidth, + unsigned int canvasHeight); + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CairoBaseRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoBaseRenderer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,63 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ICairoContextProvider.h" +#include "CompositorHelper.h" + +namespace OrthancStone +{ + namespace Internals + { + class CairoBaseRenderer : public CompositorHelper::ILayerRenderer + { + private: + ICairoContextProvider& target_; + std::auto_ptr layer_; + + protected: + template + const T& GetLayer() const + { + return dynamic_cast(*layer_); + } + + cairo_t* GetCairoContext() const + { + return target_.GetCairoContext(); + } + + public: + CairoBaseRenderer(ICairoContextProvider& target, + const ISceneLayer& layer) : + target_(target) + { + Update(layer); + } + + virtual void Update(const ISceneLayer& layer) + { + layer_.reset(layer.Clone()); + } + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,79 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "CairoColorTextureRenderer.h" + +#include "../ColorTextureSceneLayer.h" + +namespace OrthancStone +{ + namespace Internals + { + CairoColorTextureRenderer::CairoColorTextureRenderer(ICairoContextProvider& target, + const ISceneLayer& layer) : + target_(target) + { + Update(layer); + } + + + void CairoColorTextureRenderer::Update(const ISceneLayer& layer) + { + const ColorTextureSceneLayer& l = dynamic_cast(layer); + + texture_.Copy(l.GetTexture(), true); + textureTransform_ = l.GetTransform(); + isLinearInterpolation_ = l.IsLinearInterpolation(); + } + + + void CairoColorTextureRenderer::Render(const AffineTransform2D& transform) + { + cairo_t* cr = target_.GetCairoContext(); + + AffineTransform2D t = + AffineTransform2D::Combine(transform, textureTransform_); + Matrix h = t.GetHomogeneousMatrix(); + + cairo_save(cr); + + cairo_matrix_t m; + cairo_matrix_init(&m, h(0, 0), h(1, 0), h(0, 1), h(1, 1), h(0, 2), h(1, 2)); + cairo_transform(cr, &m); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cr, texture_.GetObject(), 0, 0); + + if (isLinearInterpolation_) + { + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR); + } + else + { + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); + } + + cairo_paint(cr); + + cairo_restore(cr); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CairoColorTextureRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoColorTextureRenderer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,49 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../Viewport/CairoSurface.h" +#include "CompositorHelper.h" +#include "ICairoContextProvider.h" + +namespace OrthancStone +{ + namespace Internals + { + class CairoColorTextureRenderer : public CompositorHelper::ILayerRenderer + { + private: + ICairoContextProvider& target_; + CairoSurface texture_; + AffineTransform2D textureTransform_; + bool isLinearInterpolation_; + + public: + CairoColorTextureRenderer(ICairoContextProvider& target, + const ISceneLayer& layer); + + virtual void Update(const ISceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Thu May 16 09:11:14 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 . + **/ + + +#include "CairoFloatTextureRenderer.h" + +#include "../FloatTextureSceneLayer.h" + +namespace OrthancStone +{ + namespace Internals + { + void CairoFloatTextureRenderer::Update(const ISceneLayer& layer) + { + const FloatTextureSceneLayer& l = dynamic_cast(layer); + + textureTransform_ = l.GetTransform(); + isLinearInterpolation_ = l.IsLinearInterpolation(); + + float windowCenter, windowWidth; + l.GetWindowing(windowCenter, windowWidth); + + const float a = windowCenter - windowWidth; + const float slope = 256.0f / (2.0f * windowWidth); + + const Orthanc::ImageAccessor& source = l.GetTexture(); + const unsigned int width = source.GetWidth(); + const unsigned int height = source.GetHeight(); + texture_.SetSize(width, height, false); + + Orthanc::ImageAccessor target; + texture_.GetWriteableAccessor(target); + + assert(source.GetFormat() == Orthanc::PixelFormat_Float32 && + target.GetFormat() == Orthanc::PixelFormat_BGRA32 && + sizeof(float) == 4); + + for (unsigned int y = 0; y < height; y++) + { + const float* p = reinterpret_cast(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast(target.GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + float v = (*p - a) * slope; + if (v <= 0) + { + v = 0; + } + else if (v >= 255) + { + v = 255; + } + + uint8_t vv = static_cast(v); + + q[0] = vv; + q[1] = vv; + q[2] = vv; + + p++; + q += 4; + } + } + } + + + void CairoFloatTextureRenderer::Render(const AffineTransform2D& transform) + { + cairo_t* cr = target_.GetCairoContext(); + + AffineTransform2D t = + AffineTransform2D::Combine(transform, textureTransform_); + Matrix h = t.GetHomogeneousMatrix(); + + cairo_save(cr); + + cairo_matrix_t m; + cairo_matrix_init(&m, h(0, 0), h(1, 0), h(0, 1), h(1, 1), h(0, 2), h(1, 2)); + cairo_transform(cr, &m); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cr, texture_.GetObject(), 0, 0); + + if (isLinearInterpolation_) + { + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR); + } + else + { + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); + } + + cairo_paint(cr); + + cairo_restore(cr); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CairoFloatTextureRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,53 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../Viewport/CairoSurface.h" +#include "CompositorHelper.h" +#include "ICairoContextProvider.h" + +namespace OrthancStone +{ + namespace Internals + { + class CairoFloatTextureRenderer : public CompositorHelper::ILayerRenderer + { + private: + ICairoContextProvider& target_; + CairoSurface texture_; + AffineTransform2D textureTransform_; + bool isLinearInterpolation_; + + public: + CairoFloatTextureRenderer(ICairoContextProvider& target, + const ISceneLayer& layer) : + target_(target) + { + Update(layer); + } + + virtual void Update(const ISceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,73 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "CairoInfoPanelRenderer.h" + +#include "../InfoPanelSceneLayer.h" + +namespace OrthancStone +{ + namespace Internals + { + void CairoInfoPanelRenderer::Update(const ISceneLayer& layer) + { + const InfoPanelSceneLayer& l = dynamic_cast(layer); + + texture_.Copy(l.GetTexture(), true); + anchor_ = l.GetAnchor(); + isLinearInterpolation_ = l.IsLinearInterpolation(); + } + + + void CairoInfoPanelRenderer::Render(const AffineTransform2D& transform) + { + int dx, dy; + InfoPanelSceneLayer::ComputeAnchorLocation( + dx, dy, anchor_, texture_.GetWidth(), texture_.GetHeight(), + target_.GetCairoWidth(), target_.GetCairoHeight()); + + cairo_t* cr = target_.GetCairoContext(); + + cairo_save(cr); + + cairo_matrix_t t; + cairo_matrix_init_identity(&t); + cairo_matrix_translate(&t, dx, dy); + cairo_transform(cr, &t); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cr, texture_.GetObject(), 0, 0); + + if (isLinearInterpolation_) + { + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR); + } + else + { + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); + } + + cairo_paint(cr); + + cairo_restore(cr); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CairoInfoPanelRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoInfoPanelRenderer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,53 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../Viewport/CairoSurface.h" +#include "CompositorHelper.h" +#include "ICairoContextProvider.h" + +namespace OrthancStone +{ + namespace Internals + { + class CairoInfoPanelRenderer : public CompositorHelper::ILayerRenderer + { + private: + ICairoContextProvider& target_; + CairoSurface texture_; + BitmapAnchor anchor_; + bool isLinearInterpolation_; + + public: + CairoInfoPanelRenderer(ICairoContextProvider& target, + const ISceneLayer& layer) : + target_(target) + { + Update(layer); + } + + virtual void Update(const ISceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CairoPolylineRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,70 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "CairoPolylineRenderer.h" + +#include "../PolylineSceneLayer.h" + +namespace OrthancStone +{ + namespace Internals + { + void CairoPolylineRenderer::Render(const AffineTransform2D& transform) + { + const PolylineSceneLayer& layer = GetLayer(); + + cairo_t* cr = GetCairoContext(); + + cairo_set_source_rgb(cr, layer.GetRedAsFloat(), layer.GetGreenAsFloat(), layer.GetBlueAsFloat()); + cairo_set_line_width(cr, layer.GetThickness()); + + for (size_t i = 0; i < layer.GetChainsCount(); i++) + { + const PolylineSceneLayer::Chain& chain = layer.GetChain(i); + + if (!chain.empty()) + { + for (size_t j = 0; j < chain.size(); j++) + { + ScenePoint2D p = chain[j].Apply(transform); + + if (j == 0) + { + cairo_move_to(cr, p.GetX(), p.GetY()); + } + else + { + cairo_line_to(cr, p.GetX(), p.GetY()); + } + } + + if (layer.IsClosedChain(i)) + { + ScenePoint2D p = chain[0].Apply(transform); + cairo_line_to(cr, p.GetX(), p.GetY()); + } + } + } + + cairo_stroke(cr); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CairoPolylineRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoPolylineRenderer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,42 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "CairoBaseRenderer.h" + +namespace OrthancStone +{ + namespace Internals + { + class CairoPolylineRenderer : public CairoBaseRenderer + { + public: + CairoPolylineRenderer(ICairoContextProvider& target, + const ISceneLayer& layer) : + CairoBaseRenderer(target, layer) + { + } + + virtual void Render(const AffineTransform2D& transform); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CairoTextRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoTextRenderer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,114 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "CairoTextRenderer.h" + +#include + +namespace OrthancStone +{ + namespace Internals + { + CairoTextRenderer::CairoTextRenderer(ICairoContextProvider& target, + const GlyphBitmapAlphabet& alphabet, + const TextSceneLayer& layer) : + CairoBaseRenderer(target, layer) + { + std::auto_ptr source(alphabet.RenderText(layer.GetText())); + + if (source.get() != NULL) + { + text_.SetSize(source->GetWidth(), source->GetHeight(), true); + + Orthanc::ImageAccessor target; + text_.GetWriteableAccessor(target); + + if (source->GetFormat() != Orthanc::PixelFormat_Grayscale8 || + target.GetFormat() != Orthanc::PixelFormat_BGRA32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + const unsigned int width = source->GetWidth(); + const unsigned int red = layer.GetRed(); + const unsigned int green = layer.GetGreen(); + const unsigned int blue = layer.GetBlue(); + + for (unsigned int y = 0; y < source->GetHeight(); y++) + { + const uint8_t* p = reinterpret_cast(source->GetConstRow(y)); + uint8_t* q = reinterpret_cast(target.GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + unsigned int alpha = *p; + + // Premultiplied alpha + q[0] = static_cast((blue * alpha) / 255); + q[1] = static_cast((green * alpha) / 255); + q[2] = static_cast((red * alpha) / 255); + q[3] = *p; + + p++; + q += 4; + } + } + + cairo_surface_mark_dirty(text_.GetObject()); + } + } + + + void CairoTextRenderer::Render(const AffineTransform2D& transform) + { + if (text_.GetWidth() != 0 && + text_.GetHeight() != 0) + { + const TextSceneLayer& layer = GetLayer(); + + cairo_t* cr = GetCairoContext(); + cairo_set_source_rgb(cr, layer.GetRedAsFloat(), layer.GetGreenAsFloat(), layer.GetBlueAsFloat()); + + double dx, dy; // In pixels + ComputeAnchorTranslation(dx, dy, layer.GetAnchor(), text_.GetWidth(), + text_.GetHeight(), layer.GetBorder()); + + double x = layer.GetX(); + double y = layer.GetY(); + transform.Apply(x, y); + + cairo_save(cr); + + cairo_matrix_t t; + cairo_matrix_init_identity(&t); + cairo_matrix_translate(&t, x + dx, y + dy); + cairo_transform(cr, &t); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cr, text_.GetObject(), 0, 0); + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR); + cairo_paint(cr); + + cairo_restore(cr); + } + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CairoTextRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoTextRenderer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,46 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../Fonts/GlyphBitmapAlphabet.h" +#include "../../Viewport/CairoSurface.h" +#include "../TextSceneLayer.h" +#include "CairoBaseRenderer.h" + +namespace OrthancStone +{ + namespace Internals + { + class CairoTextRenderer : public CairoBaseRenderer + { + private: + CairoSurface text_; + + public: + CairoTextRenderer(ICairoContextProvider& target, + const GlyphBitmapAlphabet& alphabet, + const TextSceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CompositorHelper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CompositorHelper.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,156 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "CompositorHelper.h" + +#include + +namespace OrthancStone +{ + namespace Internals + { + class CompositorHelper::Item : public boost::noncopyable + { + private: + std::auto_ptr renderer_; + const ISceneLayer& layer_; + uint64_t layerIdentifier_; + uint64_t lastRevision_; + + public: + Item(ILayerRenderer* renderer, // Takes ownership + const ISceneLayer& layer, + uint64_t layerIdentifier) : + renderer_(renderer), + layer_(layer), + layerIdentifier_(layerIdentifier), + lastRevision_(layer.GetRevision()) + { + if (renderer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + ILayerRenderer& GetRenderer() const + { + assert(renderer_.get() != NULL); + return *renderer_; + } + + const ISceneLayer& GetLayer() const + { + return layer_; + } + + uint64_t GetLayerIdentifier() const + { + return layerIdentifier_; + } + + uint64_t GetLastRevision() const + { + return lastRevision_; + } + + void UpdateRenderer() + { + assert(renderer_.get() != NULL); + renderer_->Update(layer_); + lastRevision_ = layer_.GetRevision(); + } + }; + + + void CompositorHelper::Visit(const ISceneLayer& layer, + uint64_t layerIdentifier, + int depth) + { + // "Visit()" is only applied to layers existing in the scene + assert(scene_.HasLayer(depth)); + + Content::iterator found = content_.find(depth); + + assert(found == content_.end() || + found->second != NULL); + + if (found == content_.end() || + found->second->GetLayerIdentifier() != layerIdentifier) + { + // This is the first time this layer is rendered, or the layer + // is not the same as before + if (found != content_.end()) + { + delete found->second; + content_.erase(found); + } + + std::auto_ptr renderer(factory_.Create(layer)); + + if (renderer.get() != NULL) + { + renderer->Render(sceneTransform_); + content_[depth] = new Item(renderer.release(), layer, layerIdentifier); + } + } + else + { + // This layer has already been rendered + assert(found->second->GetLastRevision() <= layer.GetRevision()); + + if (found->second->GetLastRevision() < layer.GetRevision()) + { + found->second->UpdateRenderer(); + } + + found->second->GetRenderer().Render(sceneTransform_); + } + + // Check invariants + assert(content_.find(depth) == content_.end() || + (content_[depth]->GetLayerIdentifier() == layerIdentifier && + content_[depth]->GetLastRevision() == layer.GetRevision())); + } + + + CompositorHelper::~CompositorHelper() + { + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + } + + + void CompositorHelper::Refresh(unsigned int canvasWidth, + unsigned int canvasHeight) + { + // Bring coordinate (0,0) to the center of the canvas + AffineTransform2D offset = AffineTransform2D::CreateOffset( + static_cast(canvasWidth) / 2.0, + static_cast(canvasHeight) / 2.0); + + sceneTransform_ = AffineTransform2D::Combine(offset, scene_.GetSceneToCanvasTransform()); + scene_.Apply(*this); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/CompositorHelper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CompositorHelper.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,85 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Scene2D.h" + +namespace OrthancStone +{ + namespace Internals + { + class CompositorHelper : protected Scene2D::IVisitor + { + public: + class ILayerRenderer : public boost::noncopyable + { + public: + virtual ~ILayerRenderer() + { + } + + virtual void Render(const AffineTransform2D& transform) = 0; + + // "Update()" is only called if the type of the layer has not changed + virtual void Update(const ISceneLayer& layer) = 0; + }; + + class IRendererFactory : public boost::noncopyable + { + public: + virtual ~IRendererFactory() + { + } + + virtual ILayerRenderer* Create(const ISceneLayer& layer) = 0; + }; + + private: + class Item; + + typedef std::map Content; + + const Scene2D& scene_; + IRendererFactory& factory_; + Content content_; + AffineTransform2D sceneTransform_; + + protected: + virtual void Visit(const ISceneLayer& layer, + uint64_t layerIdentifier, + int depth); + + public: + CompositorHelper(const Scene2D& scene, + IRendererFactory& factory) : + scene_(scene), + factory_(factory) + { + } + + ~CompositorHelper(); + + void Refresh(unsigned int canvasWidth, + unsigned int canvasHeight); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/FixedPointAligner.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/FixedPointAligner.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,48 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "FixedPointAligner.h" + +namespace OrthancStone +{ + namespace Internals + { + FixedPointAligner::FixedPointAligner(Scene2D& scene, + const ScenePoint2D& p) : + scene_(scene), + canvas_(p) + { + pivot_ = canvas_.Apply(scene_.GetCanvasToSceneTransform()); + } + + + void FixedPointAligner::Apply() + { + ScenePoint2D p = canvas_.Apply(scene_.GetCanvasToSceneTransform()); + + scene_.SetSceneToCanvasTransform( + AffineTransform2D::Combine( + scene_.GetSceneToCanvasTransform(), + AffineTransform2D::CreateOffset(p.GetX() - pivot_.GetX(), + p.GetY() - pivot_.GetY()))); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/FixedPointAligner.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/FixedPointAligner.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,47 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Scene2D.h" +#include "../ScenePoint2D.h" + +namespace OrthancStone +{ + namespace Internals + { + // During a mouse event that modifies the view of a scene, keeps + // one point (the pivot) at the same position on the canvas + class FixedPointAligner : public boost::noncopyable + { + private: + Scene2D& scene_; + ScenePoint2D pivot_; + ScenePoint2D canvas_; + + public: + FixedPointAligner(Scene2D& scene, + const ScenePoint2D& p); + + void Apply(); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/ICairoContextProvider.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/ICairoContextProvider.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,46 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include +#include + +namespace OrthancStone +{ + namespace Internals + { + class ICairoContextProvider : public boost::noncopyable + { + public: + virtual ~ICairoContextProvider() + { + } + + virtual cairo_t* GetCairoContext() = 0; + + virtual unsigned int GetCairoWidth() = 0; + + virtual unsigned int GetCairoHeight() = 0; + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,51 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLAdvancedPolylineRenderer.h" + +#include + + +namespace OrthancStone +{ + namespace Internals + { + void OpenGLAdvancedPolylineRenderer::LoadLayer(const PolylineSceneLayer& layer) + { + data_.reset(new OpenGLLinesProgram::Data(context_, layer)); + + if (data_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + + OpenGLAdvancedPolylineRenderer::OpenGLAdvancedPolylineRenderer(OpenGL::IOpenGLContext& context, + OpenGLLinesProgram& program, + const PolylineSceneLayer& layer) : + context_(context), + program_(program) + { + LoadLayer(layer); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,57 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "CompositorHelper.h" +#include "OpenGLLinesProgram.h" + + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLAdvancedPolylineRenderer : public CompositorHelper::ILayerRenderer + { + private: + OpenGL::IOpenGLContext& context_; + OpenGLLinesProgram& program_; + std::auto_ptr data_; + + void LoadLayer(const PolylineSceneLayer& layer); + + public: + OpenGLAdvancedPolylineRenderer(OpenGL::IOpenGLContext& context, + OpenGLLinesProgram& program, + const PolylineSceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform) + { + program_.Apply(*data_, transform, true, true); + } + + virtual void Update(const ISceneLayer& layer) + { + LoadLayer(dynamic_cast(layer)); + } + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,90 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLBasicPolylineRenderer.h" + +#include "../../OpenGL/OpenGLIncludes.h" + +namespace OrthancStone +{ + namespace Internals + { + OpenGLBasicPolylineRenderer::OpenGLBasicPolylineRenderer(OpenGL::IOpenGLContext& context, + const PolylineSceneLayer& layer) : + context_(context) + { + layer_.Copy(layer); + } + + + void OpenGLBasicPolylineRenderer::Render(const AffineTransform2D& transform) + { + AffineTransform2D t = AffineTransform2D::Combine( + AffineTransform2D::CreateOpenGLClipspace(context_.GetCanvasWidth(), context_.GetCanvasHeight()), + transform); + + glUseProgram(0); + glColor3ub(layer_.GetRed(), layer_.GetGreen(), layer_.GetBlue()); + + glBegin(GL_LINES); + + for (size_t i = 0; i < layer_.GetChainsCount(); i++) + { + const PolylineSceneLayer::Chain& chain = layer_.GetChain(i); + + if (chain.size() > 1) + { + ScenePoint2D previous = chain[0].Apply(t); + + for (size_t j = 1; j < chain.size(); j++) + { + ScenePoint2D p = chain[j].Apply(t); + + glVertex2f(static_cast(previous.GetX()), + static_cast(previous.GetY())); + glVertex2f(static_cast(p.GetX()), + static_cast(p.GetY())); + + previous = p; + } + + if (layer_.IsClosedChain(i)) + { + ScenePoint2D p = chain[0].Apply(t); + + glVertex2f(static_cast(previous.GetX()), + static_cast(previous.GetY())); + glVertex2f(static_cast(p.GetX()), + static_cast(p.GetY())); + } + } + } + + glEnd(); + } + + + void OpenGLBasicPolylineRenderer::Update(const ISceneLayer& layer) + { + layer_.Copy(dynamic_cast(layer)); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,47 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../OpenGL/IOpenGLContext.h" +#include "../PolylineSceneLayer.h" +#include "CompositorHelper.h" + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLBasicPolylineRenderer : public CompositorHelper::ILayerRenderer + { + private: + OpenGL::IOpenGLContext& context_; + PolylineSceneLayer layer_; + + public: + OpenGLBasicPolylineRenderer(OpenGL::IOpenGLContext& context, + const PolylineSceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform); + + virtual void Update(const ISceneLayer& layer); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLColorTextureProgram.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLColorTextureProgram.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,64 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLColorTextureProgram.h" +#include "OpenGLShaderVersionDirective.h" + +static const char* FRAGMENT_SHADER = + ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE + "uniform sampler2D u_texture; \n" + "varying vec2 v_texcoord; \n" + "void main() \n" + "{ \n" + " gl_FragColor = texture2D(u_texture, v_texcoord); \n" + "}"; + + +namespace OrthancStone +{ + namespace Internals + { + OpenGLColorTextureProgram::OpenGLColorTextureProgram(OpenGL::IOpenGLContext& context) : + program_(context, FRAGMENT_SHADER) + { + } + + + void OpenGLColorTextureProgram::Apply(OpenGL::OpenGLTexture& texture, + const AffineTransform2D& transform, + bool useAlpha) + { + OpenGLTextureProgram::Execution execution(program_, texture, transform); + + if (useAlpha) + { + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + execution.DrawTriangles(); + glDisable(GL_BLEND); + } + else + { + execution.DrawTriangles(); + } + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLColorTextureProgram.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLColorTextureProgram.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,43 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "OpenGLTextureProgram.h" + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLColorTextureProgram : public boost::noncopyable + { + private: + OpenGLTextureProgram program_; + + public: + OpenGLColorTextureProgram(OpenGL::IOpenGLContext& context); + + void Apply(OpenGL::OpenGLTexture& texture, + const AffineTransform2D& transform, + bool useAlpha); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLColorTextureRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLColorTextureRenderer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,62 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLColorTextureRenderer.h" + +namespace OrthancStone +{ + namespace Internals + { + void OpenGLColorTextureRenderer::LoadTexture(const ColorTextureSceneLayer& layer) + { + context_.MakeCurrent(); + texture_.reset(new OpenGL::OpenGLTexture); + texture_->Load(layer.GetTexture(), layer.IsLinearInterpolation()); + layerTransform_ = layer.GetTransform(); + } + + + OpenGLColorTextureRenderer::OpenGLColorTextureRenderer(OpenGL::IOpenGLContext& context, + OpenGLColorTextureProgram& program, + const ColorTextureSceneLayer& layer) : + context_(context), + program_(program) + { + LoadTexture(layer); + } + + + void OpenGLColorTextureRenderer::Render(const AffineTransform2D& transform) + { + if (texture_.get() != NULL) + { + program_.Apply(*texture_, AffineTransform2D::Combine(transform, layerTransform_), true); + } + } + + + void OpenGLColorTextureRenderer::Update(const ISceneLayer& layer) + { + // Should never happen (no revisions in color textures) + LoadTexture(dynamic_cast(layer)); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLColorTextureRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLColorTextureRenderer.h Thu May 16 09:11:14 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 . + **/ + + +#pragma once + +#include "OpenGLColorTextureProgram.h" +#include "CompositorHelper.h" +#include "../ColorTextureSceneLayer.h" + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLColorTextureRenderer : public CompositorHelper::ILayerRenderer + { + private: + OpenGL::IOpenGLContext& context_; + OpenGLColorTextureProgram& program_; + std::auto_ptr texture_; + AffineTransform2D layerTransform_; + + void LoadTexture(const ColorTextureSceneLayer& layer); + + public: + OpenGLColorTextureRenderer(OpenGL::IOpenGLContext& context, + OpenGLColorTextureProgram& program, + const ColorTextureSceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform); + + virtual void Update(const ISceneLayer& layer); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLFloatTextureProgram.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,148 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLFloatTextureProgram.h" +#include "OpenGLShaderVersionDirective.h" + +#include +#include +#include + + +static const char* FRAGMENT_SHADER = + ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE + "uniform float u_offset; \n" + "uniform float u_slope; \n" + "uniform float u_windowCenter; \n" + "uniform float u_windowWidth; \n" + "uniform sampler2D u_texture; \n" + "varying vec2 v_texcoord; \n" + "void main() \n" + "{ \n" + " vec4 t = texture2D(u_texture, v_texcoord); \n" + " float v = (t.r * 256.0 + t.g) * 256.0; \n" + " v = v * u_slope + u_offset; \n" // (*) + " float a = u_windowCenter - u_windowWidth; \n" + " float dy = 1.0 / (2.0 * u_windowWidth); \n" + " if (v <= a) \n" + " v = 0.0; \n" + " else \n" + " { \n" + " v = (v - a) * dy; \n" + " if (v >= 1.0) \n" + " v = 1.0; \n" + " } \n" + " gl_FragColor = vec4(v, v, v, 1); \n" + "}"; + + +namespace OrthancStone +{ + namespace Internals + { + OpenGLFloatTextureProgram::Data::Data(const Orthanc::ImageAccessor& texture, + bool isLinearInterpolation) + { + if (texture.GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + float minValue, maxValue; + Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, texture); + + offset_ = minValue; + + if (LinearAlgebra::IsCloseToZero(maxValue - minValue)) + { + slope_ = 1; + } + else + { + slope_ = (maxValue - minValue) / 65536.0f; + assert(!LinearAlgebra::IsCloseToZero(slope_)); + } + + const unsigned int width = texture.GetWidth(); + const unsigned int height = texture.GetHeight(); + + Orthanc::Image converted(Orthanc::PixelFormat_RGB24, width, height, true); + + for (unsigned int y = 0; y < height; y++) + { + const float *p = reinterpret_cast(texture.GetConstRow(y)); + uint8_t *q = reinterpret_cast(converted.GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + /** + * At (*), the floating-point "value" is reconstructed as + * "value = texture * slope + offset". + * <=> texture = (value - offset) / slope + **/ + + float texture = (*p - offset_) / slope_; + if (texture < 0) + { + texture = 0; + } + else if (texture >= 65535.0f) + { + texture = 65535.0f; + } + + uint16_t t = static_cast(texture); + + q[0] = t / 256; // red + q[1] = t % 256; // green + q[2] = 0; // blue is unused + + p++; + q += 3; + } + } + + texture_.Load(converted, isLinearInterpolation); + } + + + OpenGLFloatTextureProgram::OpenGLFloatTextureProgram(OpenGL::IOpenGLContext& context) : + program_(context, FRAGMENT_SHADER) + { + } + + + void OpenGLFloatTextureProgram::Apply(Data& data, + const AffineTransform2D& transform, + float windowCenter, + float windowWidth) + { + OpenGLTextureProgram::Execution execution(program_, data.GetTexture(), transform); + + glUniform1f(execution.GetUniformLocation("u_slope"), data.GetSlope()); + glUniform1f(execution.GetUniformLocation("u_offset"), data.GetOffset()); + glUniform1f(execution.GetUniformLocation("u_windowCenter"), windowCenter); + glUniform1f(execution.GetUniformLocation("u_windowWidth"), windowWidth); + + execution.DrawTriangles(); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLFloatTextureProgram.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,72 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "OpenGLTextureProgram.h" + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLFloatTextureProgram : public boost::noncopyable + { + public: + class Data : public boost::noncopyable + { + private: + OpenGL::OpenGLTexture texture_; + float offset_; + float slope_; + + public: + Data(const Orthanc::ImageAccessor& texture, + bool isLinearInterpolation); + + float GetOffset() const + { + return offset_; + } + + float GetSlope() const + { + return slope_; + } + + OpenGL::OpenGLTexture& GetTexture() + { + return texture_; + } + }; + + private: + OpenGLTextureProgram program_; + + public: + OpenGLFloatTextureProgram(OpenGL::IOpenGLContext& context); + + void Apply(Data& data, + const AffineTransform2D& transform, + float windowCenter, + float windowWidth); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,67 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLFloatTextureRenderer.h" + +namespace OrthancStone +{ + namespace Internals + { + void OpenGLFloatTextureRenderer::UpdateInternal(const FloatTextureSceneLayer& layer, + bool loadTexture) + { + if (loadTexture) + { + context_.MakeCurrent(); + texture_.reset(new OpenGLFloatTextureProgram::Data(layer.GetTexture(), layer.IsLinearInterpolation())); + } + + layerTransform_ = layer.GetTransform(); + layer.GetWindowing(windowCenter_, windowWidth_); + } + + + OpenGLFloatTextureRenderer::OpenGLFloatTextureRenderer(OpenGL::IOpenGLContext& context, + OpenGLFloatTextureProgram& program, + const FloatTextureSceneLayer& layer) : + context_(context), + program_(program) + { + UpdateInternal(layer, true); + } + + + void OpenGLFloatTextureRenderer::Render(const AffineTransform2D& transform) + { + if (texture_.get() != NULL) + { + program_.Apply(*texture_, AffineTransform2D::Combine(transform, layerTransform_), + windowCenter_, windowWidth_); + } + } + + + void OpenGLFloatTextureRenderer::Update(const ISceneLayer& layer) + { + UpdateInternal(dynamic_cast(layer), false); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.h Thu May 16 09:11:14 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 . + **/ + + +#pragma once + +#include "CompositorHelper.h" +#include "OpenGLFloatTextureProgram.h" +#include "../FloatTextureSceneLayer.h" + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLFloatTextureRenderer : public CompositorHelper::ILayerRenderer + { + private: + OpenGL::IOpenGLContext& context_; + OpenGLFloatTextureProgram& program_; + std::auto_ptr texture_; + AffineTransform2D layerTransform_; + float windowCenter_; + float windowWidth_; + + void UpdateInternal(const FloatTextureSceneLayer& layer, + bool loadTexture); + + public: + OpenGLFloatTextureRenderer(OpenGL::IOpenGLContext& context, + OpenGLFloatTextureProgram& program, + const FloatTextureSceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform); + + virtual void Update(const ISceneLayer& layer); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,62 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLInfoPanelRenderer.h" + +namespace OrthancStone +{ + namespace Internals + { + void OpenGLInfoPanelRenderer::LoadTexture(const InfoPanelSceneLayer& layer) + { + context_.MakeCurrent(); + texture_.reset(new OpenGL::OpenGLTexture); + texture_->Load(layer.GetTexture(), layer.IsLinearInterpolation()); + anchor_ = layer.GetAnchor(); + } + + + OpenGLInfoPanelRenderer::OpenGLInfoPanelRenderer(OpenGL::IOpenGLContext& context, + OpenGLColorTextureProgram& program, + const InfoPanelSceneLayer& layer) : + context_(context), + program_(program) + { + LoadTexture(layer); + } + + + void OpenGLInfoPanelRenderer::Render(const AffineTransform2D& transform) + { + if (texture_.get() != NULL) + { + int dx, dy; + InfoPanelSceneLayer::ComputeAnchorLocation( + dx, dy, anchor_, texture_->GetWidth(), texture_->GetHeight(), + context_.GetCanvasWidth(), context_.GetCanvasHeight()); + + // The position of this type of layer is layer: Ignore the + // "transform" coming from the scene + program_.Apply(*texture_, AffineTransform2D::CreateOffset(dx, dy), true); + } + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.h Thu May 16 09:11:14 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 . + **/ + + +#pragma once + +#include "CompositorHelper.h" +#include "OpenGLColorTextureProgram.h" +#include "../InfoPanelSceneLayer.h" + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLInfoPanelRenderer : public CompositorHelper::ILayerRenderer + { + private: + OpenGL::IOpenGLContext& context_; + OpenGLColorTextureProgram& program_; + std::auto_ptr texture_; + BitmapAnchor anchor_; + + void LoadTexture(const InfoPanelSceneLayer& layer); + + public: + OpenGLInfoPanelRenderer(OpenGL::IOpenGLContext& context, + OpenGLColorTextureProgram& program, + const InfoPanelSceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform); + + virtual void Update(const ISceneLayer& layer) + { + LoadTexture(dynamic_cast(layer)); + } + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLLinesProgram.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLLinesProgram.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,462 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLLinesProgram.h" +#include "OpenGLShaderVersionDirective.h" + +#include + + +static const unsigned int COMPONENTS_POSITION = 3; +static const unsigned int COMPONENTS_MITER = 2; + + +static const char* VERTEX_SHADER = + ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE + "attribute vec2 a_miter_direction; \n" + "attribute vec4 a_position; \n" + "uniform float u_thickness; \n" + "uniform mat4 u_matrix; \n" + "varying float v_distance; \n" + "void main() \n" + "{ \n" + " v_distance = a_position.z; \n" + " gl_Position = u_matrix * vec4(a_position.xy + a_position.z * a_miter_direction * u_thickness, 0, 1); \n" + "}"; + + +static const char* FRAGMENT_SHADER = + ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE + "uniform bool u_antialiasing; \n" + "uniform float u_antialiasing_start; \n" + "uniform vec3 u_color; \n" + "varying float v_distance; \n" // Distance of the point to the segment + "void main() \n" + "{ \n" + " float d = abs(v_distance); \n" + " if (!u_antialiasing || \n" + " d <= u_antialiasing_start) \n" + " gl_FragColor = vec4(u_color, 1); \n" + " else if (d >= 1.0) \n" + " gl_FragColor = vec4(0, 0, 0, 0); \n" + " else \n" + " { \n" + " float alpha = 1.0 - smoothstep(u_antialiasing_start, 1.0, d); \n" + " gl_FragColor = vec4(u_color * alpha, alpha); \n" + " } \n" + "}"; + + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLLinesProgram::Data::Segment + { + private: + bool isEmpty_; + double x1_; + double y1_; + double x2_; + double y2_; + double miterX1_; + double miterY1_; + double miterX2_; + double miterY2_; + + Vector lineAbove_; // In homogeneous coordinates (size = 3) + Vector lineBelow_; + + public: + Segment(const PolylineSceneLayer::Chain& chain, + size_t index1, + size_t index2) : + isEmpty_(false) + { + if (index1 >= chain.size() || + index2 >= chain.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + const ScenePoint2D& p = chain[index1]; + const ScenePoint2D& q = chain[index2]; + + x1_ = p.GetX(); + y1_ = p.GetY(); + x2_ = q.GetX(); + y2_ = q.GetY(); + + const double dx = x2_ - x1_; + const double dy = y2_ - y1_; + const double norm = sqrt(dx * dx + dy * dy); + + if (LinearAlgebra::IsCloseToZero(norm)) + { + isEmpty_ = true; + } + else + { + isEmpty_ = false; + const double normalX = -dy / norm; + const double normalY = dx / norm; + + miterX1_ = normalX; + miterY1_ = normalY; + miterX2_ = normalX; + miterY2_ = normalY; + + Vector a = LinearAlgebra::CreateVector(x1_ + normalX, y1_ + normalY, 1); + Vector b = LinearAlgebra::CreateVector(x2_ + normalX, y2_ + normalY, 1); + LinearAlgebra::CrossProduct(lineAbove_, a, b); + + a = LinearAlgebra::CreateVector(x1_ - normalX, y1_ - normalY, 1); + b = LinearAlgebra::CreateVector(x2_ - normalX, y2_ - normalY, 1); + LinearAlgebra::CrossProduct(lineBelow_, a, b); + } + } + } + + bool IsEmpty() const + { + return isEmpty_; + } + + static double ComputeSignedArea(double x1, + double y1, + double x2, + double y2, + double x3, + double y3) + { + // This computes the signed area of a 2D triangle. This + // formula is e.g. used in the sorting algorithm of Graham's + // scan to compute the convex hull. + // https://en.wikipedia.org/wiki/Graham_scan + return (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1); + } + + static void CreateMiter(Segment& left, + Segment& right) + { + if (!left.IsEmpty() && + !right.IsEmpty()) + { + Vector above, below; + LinearAlgebra::CrossProduct(above, left.lineAbove_, right.lineAbove_); + LinearAlgebra::CrossProduct(below, left.lineBelow_, right.lineBelow_); + + if (!LinearAlgebra::IsCloseToZero(above[2]) && + !LinearAlgebra::IsCloseToZero(below[2])) + { + // Back to inhomogeneous 2D coordinates + above /= above[2]; + below /= below[2]; + + // Check whether "above" and "below" intersection points + // are on the half-plane defined by the endpoints of the + // two segments. This is an indicator of whether the angle + // is too acute. + double s1 = ComputeSignedArea(left.x1_, left.y1_, + above[0], above[1], + right.x2_, right.y2_); + double s2 = ComputeSignedArea(left.x1_, left.y1_, + below[0], below[1], + right.x2_, right.y2_); + + // The two signed areas must have the same sign + if (s1 * s2 >= 0) + { + left.miterX2_ = above[0] - left.x2_; + left.miterY2_ = above[1] - left.y2_; + + right.miterX1_ = left.miterX2_; + right.miterY1_ = left.miterY2_; + } + } + } + } + + void AddTriangles(std::vector& coords, + std::vector& miterDirections) + { + if (isEmpty_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + // First triangle + coords.push_back(x1_); + coords.push_back(y1_); + coords.push_back(1); + coords.push_back(x2_); + coords.push_back(y2_); + coords.push_back(-1); + coords.push_back(x2_); + coords.push_back(y2_); + coords.push_back(1); + + miterDirections.push_back(miterX1_); + miterDirections.push_back(miterY1_); + miterDirections.push_back(miterX2_); + miterDirections.push_back(miterY2_); + miterDirections.push_back(miterX2_); + miterDirections.push_back(miterY2_); + + // Second triangle + coords.push_back(x1_); + coords.push_back(y1_); + coords.push_back(1); + coords.push_back(x1_); + coords.push_back(y1_); + coords.push_back(-1); + coords.push_back(x2_); + coords.push_back(y2_); + coords.push_back(-1); + + miterDirections.push_back(miterX1_); + miterDirections.push_back(miterY1_); + miterDirections.push_back(miterX1_); + miterDirections.push_back(miterY1_); + miterDirections.push_back(miterX2_); + miterDirections.push_back(miterY2_); + } + }; + + + OpenGLLinesProgram::Data::Data(OpenGL::IOpenGLContext& context, + const PolylineSceneLayer& layer) : + context_(context), + verticesCount_(0), + thickness_(layer.GetThickness()), + red_(layer.GetRedAsFloat()), + green_(layer.GetGreenAsFloat()), + blue_(layer.GetBlueAsFloat()) + { + // High-level reference: + // https://mattdesl.svbtle.com/drawing-lines-is-hard + // https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader + + size_t countVertices = 0; + for (size_t i = 0; i < layer.GetChainsCount(); i++) + { + size_t countSegments = layer.GetChain(i).size() - 1; + + if (layer.IsClosedChain(i)) + { + countSegments++; + } + + // Each segment is made of 2 triangles. One triangle is + // defined by 3 points in 2D => 6 vertices per segment. + countVertices += countSegments * 2 * 3; + } + + std::vector coords, miterDirections; + coords.reserve(countVertices * COMPONENTS_POSITION); + miterDirections.reserve(countVertices * COMPONENTS_MITER); + + for (size_t i = 0; i < layer.GetChainsCount(); i++) + { + const PolylineSceneLayer::Chain& chain = layer.GetChain(i); + + if (chain.size() > 1) + { + std::vector segments; + for (size_t j = 1; j < chain.size(); j++) + { + segments.push_back(Segment(chain, j - 1, j)); + } + + if (layer.IsClosedChain(i)) + { + segments.push_back(Segment(chain, chain.size() - 1, 0)); + } + + // Try and create nice miters + for (size_t j = 1; j < segments.size(); j++) + { + Segment::CreateMiter(segments[j - 1], segments[j]); + } + + if (layer.IsClosedChain(i)) + { + Segment::CreateMiter(segments.back(), segments.front()); + } + + for (size_t j = 0; j < segments.size(); j++) + { + if (!segments[j].IsEmpty()) + { + segments[j].AddTriangles(coords, miterDirections); + } + } + } + } + + if (!coords.empty()) + { + verticesCount_ = coords.size() / COMPONENTS_POSITION; + + context_.MakeCurrent(); + glGenBuffers(2, buffers_); + + glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * coords.size(), &coords[0], GL_STATIC_DRAW); + + glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * miterDirections.size(), &miterDirections[0], GL_STATIC_DRAW); + } + } + + + OpenGLLinesProgram::Data::~Data() + { + if (!IsEmpty()) + { + context_.MakeCurrent(); + glDeleteBuffers(2, buffers_); + } + } + + + GLuint OpenGLLinesProgram::Data::GetVerticesBuffer() const + { + if (IsEmpty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return buffers_[0]; + } + } + + + GLuint OpenGLLinesProgram::Data::GetMiterDirectionsBuffer() const + { + if (IsEmpty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return buffers_[1]; + } + } + + + OpenGLLinesProgram::OpenGLLinesProgram(OpenGL::IOpenGLContext& context) : + context_(context) + { + + context_.MakeCurrent(); + + program_.reset(new OpenGL::OpenGLProgram); + program_->CompileShaders(VERTEX_SHADER, FRAGMENT_SHADER); + } + + + void OpenGLLinesProgram::Apply(const Data& data, + const AffineTransform2D& transform, + bool antialiasing, + bool scaleIndependantThickness) + { + if (!data.IsEmpty()) + { + context_.MakeCurrent(); + program_->Use(); + + GLint locationPosition = program_->GetAttributeLocation("a_position"); + GLint locationMiterDirection = program_->GetAttributeLocation("a_miter_direction"); + + float m[16]; + transform.ConvertToOpenGLMatrix(m, context_.GetCanvasWidth(), context_.GetCanvasHeight()); + + glUniformMatrix4fv(program_->GetUniformLocation("u_matrix"), 1, GL_FALSE, m); + glUniform3f(program_->GetUniformLocation("u_color"), + data.GetRed(), data.GetGreen(), data.GetBlue()); + + glBindBuffer(GL_ARRAY_BUFFER, data.GetVerticesBuffer()); + glEnableVertexAttribArray(locationPosition); + glVertexAttribPointer(locationPosition, COMPONENTS_POSITION, GL_FLOAT, GL_FALSE, 0, 0); + + glBindBuffer(GL_ARRAY_BUFFER, data.GetMiterDirectionsBuffer()); + glEnableVertexAttribArray(locationMiterDirection); + glVertexAttribPointer(locationMiterDirection, COMPONENTS_MITER, GL_FLOAT, GL_FALSE, 0, 0); + + glUniform1i(program_->GetUniformLocation("u_antialiasing"), (antialiasing ? 1 : 0)); + + const double zoom = transform.ComputeZoom(); + const double thickness = data.GetThickness() / 2.0; + const double aliasingBorder = 2.0; // Border for antialiasing ramp, in pixels + assert(aliasingBorder > 0); // Prevent division by zero with "t1" + + if (scaleIndependantThickness) + { + if (antialiasing) + { + double t1 = std::max(thickness, aliasingBorder); + double t0 = std::max(0.0, thickness - aliasingBorder); + + glUniform1f(program_->GetUniformLocation("u_thickness"), t1 / zoom); + glUniform1f(program_->GetUniformLocation("u_antialiasing_start"), t0 / t1); + } + else + { + glUniform1f(program_->GetUniformLocation("u_thickness"), thickness / zoom); + } + } + else + { + if (antialiasing) + { + double t1 = std::max(thickness, aliasingBorder / zoom); + double t0 = std::max(0.0, thickness - aliasingBorder / zoom); + + glUniform1f(program_->GetUniformLocation("u_thickness"), t1); + glUniform1f(program_->GetUniformLocation("u_antialiasing_start"), t0 / t1); + } + else + { + glUniform1f(program_->GetUniformLocation("u_thickness"), thickness); + } + } + + if (antialiasing) + { + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDrawArrays(GL_TRIANGLES, 0, data.GetVerticesCount()); + glDisable(GL_BLEND); + } + else + { + glDrawArrays(GL_TRIANGLES, 0, data.GetVerticesCount()); + } + + glDisableVertexAttribArray(locationPosition); + glDisableVertexAttribArray(locationMiterDirection); + } + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLLinesProgram.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLLinesProgram.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,103 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../OpenGL/IOpenGLContext.h" +#include "../../OpenGL/OpenGLProgram.h" +#include "../../Toolbox/AffineTransform2D.h" +#include "../PolylineSceneLayer.h" + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLLinesProgram : public boost::noncopyable + { + public: + class Data : public boost::noncopyable + { + private: + class Segment; + + OpenGL::IOpenGLContext& context_; + GLuint buffers_[2]; + size_t verticesCount_; + float thickness_; + float red_; + float green_; + float blue_; + + public: + Data(OpenGL::IOpenGLContext& context, + const PolylineSceneLayer& layer); + + ~Data(); + + bool IsEmpty() const + { + return verticesCount_ == 0; + } + + const size_t GetVerticesCount() const + { + return verticesCount_; + } + + GLuint GetVerticesBuffer() const; + + GLuint GetMiterDirectionsBuffer() const; + + float GetThickness() const + { + return thickness_; + } + + float GetRed() const + { + return red_; + } + + float GetGreen() const + { + return green_; + } + + float GetBlue() const + { + return blue_; + } + }; + + private: + OpenGL::IOpenGLContext& context_; + std::auto_ptr program_; + + public: + OpenGLLinesProgram(OpenGL::IOpenGLContext& context); + + void Apply(const Data& data, + const AffineTransform2D& transform, + bool antialiasing, + bool scaleIndependantThickness); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLShaderVersionDirective.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLShaderVersionDirective.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,8 @@ +#pragma once + +#if ORTHANC_ENABLE_WASM == 1 +// https://emscripten.org/docs/optimizing/Optimizing-WebGL.html +# define ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE "precision mediump float;\n" +#else +# define ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE "#version 110\n" +#endif diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLTextProgram.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLTextProgram.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,193 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLTextProgram.h" +#include "OpenGLShaderVersionDirective.h" + +#include "../../Fonts/OpenGLTextCoordinates.h" + +#include + + +static const unsigned int COMPONENTS = 2; + +static const char* VERTEX_SHADER = + ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE + "attribute vec2 a_texcoord; \n" + "attribute vec4 a_position; \n" + "uniform mat4 u_matrix; \n" + "varying vec2 v_texcoord; \n" + "void main() \n" + "{ \n" + " gl_Position = u_matrix * a_position; \n" + " v_texcoord = a_texcoord; \n" + "}"; + +static const char* FRAGMENT_SHADER = + ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE + "uniform sampler2D u_texture; \n" + "uniform vec3 u_color; \n" + "varying vec2 v_texcoord; \n" + "void main() \n" + "{ \n" + " vec4 v = texture2D(u_texture, v_texcoord); \n" + " gl_FragColor = vec4(u_color * v.w, v.w); \n" // Premultiplied alpha + "}"; + + +namespace OrthancStone +{ + namespace Internals + { + OpenGLTextProgram::OpenGLTextProgram(OpenGL::IOpenGLContext& context) : + context_(context) + { + + context_.MakeCurrent(); + + program_.reset(new OpenGL::OpenGLProgram); + program_->CompileShaders(VERTEX_SHADER, FRAGMENT_SHADER); + + positionLocation_ = program_->GetAttributeLocation("a_position"); + textureLocation_ = program_->GetAttributeLocation("a_texcoord"); + } + + + OpenGLTextProgram::Data::Data(OpenGL::IOpenGLContext& context, + const GlyphTextureAlphabet& alphabet, + const TextSceneLayer& layer) : + context_(context), + red_(layer.GetRedAsFloat()), + green_(layer.GetGreenAsFloat()), + blue_(layer.GetBlueAsFloat()), + x_(layer.GetX()), + y_(layer.GetY()), + border_(layer.GetBorder()), + anchor_(layer.GetAnchor()) + { + OpenGL::OpenGLTextCoordinates coordinates(alphabet, layer.GetText()); + textWidth_ = coordinates.GetTextWidth(); + textHeight_ = coordinates.GetTextHeight(); + + if (coordinates.IsEmpty()) + { + coordinatesCount_ = 0; + } + else + { + coordinatesCount_ = coordinates.GetRenderingCoords().size(); + + context_.MakeCurrent(); + glGenBuffers(2, buffers_); + + glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * coordinatesCount_, + &coordinates.GetRenderingCoords() [0], GL_STATIC_DRAW); + + glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * coordinatesCount_, + &coordinates.GetTextureCoords() [0], GL_STATIC_DRAW); + } + } + + + OpenGLTextProgram::Data::~Data() + { + if (!IsEmpty()) + { + context_.MakeCurrent(); + glDeleteBuffers(2, buffers_); + } + } + + + GLuint OpenGLTextProgram::Data::GetSceneLocationsBuffer() const + { + if (IsEmpty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return buffers_[0]; + } + } + + + GLuint OpenGLTextProgram::Data::GetTextureLocationsBuffer() const + { + if (IsEmpty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return buffers_[1]; + } + } + + + void OpenGLTextProgram::Apply(OpenGL::OpenGLTexture& fontTexture, + const Data& data, + const AffineTransform2D& transform) + { + if (!data.IsEmpty()) + { + context_.MakeCurrent(); + program_->Use(); + + double dx, dy; // In pixels + ComputeAnchorTranslation(dx, dy, data.GetAnchor(), + data.GetTextWidth(), data.GetTextHeight(), data.GetBorder()); + + double x = data.GetX(); + double y = data.GetY(); + transform.Apply(x, y); + + const AffineTransform2D t = AffineTransform2D::CreateOffset(x + dx, y + dy); + + float m[16]; + t.ConvertToOpenGLMatrix(m, context_.GetCanvasWidth(), context_.GetCanvasHeight()); + + fontTexture.Bind(program_->GetUniformLocation("u_texture")); + glUniformMatrix4fv(program_->GetUniformLocation("u_matrix"), 1, GL_FALSE, m); + glUniform3f(program_->GetUniformLocation("u_color"), + data.GetRed(), data.GetGreen(), data.GetBlue()); + + glBindBuffer(GL_ARRAY_BUFFER, data.GetSceneLocationsBuffer()); + glEnableVertexAttribArray(positionLocation_); + glVertexAttribPointer(positionLocation_, COMPONENTS, GL_FLOAT, GL_FALSE, 0, 0); + + glBindBuffer(GL_ARRAY_BUFFER, data.GetTextureLocationsBuffer()); + glEnableVertexAttribArray(textureLocation_); + glVertexAttribPointer(textureLocation_, COMPONENTS, GL_FLOAT, GL_FALSE, 0, 0); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDrawArrays(GL_TRIANGLES, 0, data.GetCoordinatesCount() / COMPONENTS); + glDisable(GL_BLEND); + + glDisableVertexAttribArray(positionLocation_); + glDisableVertexAttribArray(textureLocation_); + } + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLTextProgram.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLTextProgram.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,135 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../Fonts/GlyphTextureAlphabet.h" +#include "../../OpenGL/IOpenGLContext.h" +#include "../../OpenGL/OpenGLProgram.h" +#include "../../OpenGL/OpenGLTexture.h" +#include "../../Toolbox/AffineTransform2D.h" +#include "../TextSceneLayer.h" + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLTextProgram : public boost::noncopyable + { + public: + class Data : public boost::noncopyable + { + private: + OpenGL::IOpenGLContext& context_; + size_t coordinatesCount_; + GLuint buffers_[2]; + float red_; + float green_; + float blue_; + double x_; + double y_; + double border_; + unsigned int textWidth_; + unsigned int textHeight_; + BitmapAnchor anchor_; + + public: + Data(OpenGL::IOpenGLContext& context, + const GlyphTextureAlphabet& alphabet, + const TextSceneLayer& layer); + + ~Data(); + + bool IsEmpty() const + { + return coordinatesCount_ == 0; + } + + size_t GetCoordinatesCount() const + { + return coordinatesCount_; + } + + GLuint GetSceneLocationsBuffer() const; + + GLuint GetTextureLocationsBuffer() const; + + float GetRed() const + { + return red_; + } + + float GetGreen() const + { + return green_; + } + + float GetBlue() const + { + return blue_; + } + + double GetX() const + { + return x_; + } + + double GetY() const + { + return y_; + } + + double GetBorder() const + { + return border_; + } + + unsigned int GetTextWidth() const + { + return textWidth_; + } + + unsigned int GetTextHeight() const + { + return textHeight_; + } + + BitmapAnchor GetAnchor() const + { + return anchor_; + } + }; + + private: + OpenGL::IOpenGLContext& context_; + std::auto_ptr program_; + GLint positionLocation_; + GLint textureLocation_; + + public: + OpenGLTextProgram(OpenGL::IOpenGLContext& context); + + void Apply(OpenGL::OpenGLTexture& fontTexture, + const Data& data, + const AffineTransform2D& transform); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLTextRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLTextRenderer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,62 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLTextRenderer.h" + +namespace OrthancStone +{ + namespace Internals + { + void OpenGLTextRenderer::LoadLayer(const TextSceneLayer& layer) + { + data_.reset(new OpenGLTextProgram::Data(context_, alphabet_, layer)); + } + + + OpenGLTextRenderer::OpenGLTextRenderer(OpenGL::IOpenGLContext& context, + OpenGLTextProgram& program, + const GlyphTextureAlphabet& alphabet, + OpenGL::OpenGLTexture& texture, + const TextSceneLayer& layer) : + context_(context), + program_(program), + alphabet_(alphabet), + texture_(texture) + { + LoadLayer(layer); + } + + + void OpenGLTextRenderer::Render(const AffineTransform2D& transform) + { + if (data_.get() != NULL) + { + program_.Apply(texture_, *data_, transform); + } + } + + + void OpenGLTextRenderer::Update(const ISceneLayer& layer) + { + LoadLayer(dynamic_cast(layer)); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLTextRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLTextRenderer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,54 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "CompositorHelper.h" +#include "OpenGLTextProgram.h" + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLTextRenderer : public CompositorHelper::ILayerRenderer + { + private: + OpenGL::IOpenGLContext& context_; + OpenGLTextProgram& program_; + const GlyphTextureAlphabet& alphabet_; + OpenGL::OpenGLTexture& texture_; + std::auto_ptr data_; + + void LoadLayer(const TextSceneLayer& layer); + + public: + OpenGLTextRenderer(OpenGL::IOpenGLContext& context, + OpenGLTextProgram& program, + const GlyphTextureAlphabet& alphabet, + OpenGL::OpenGLTexture& texture, + const TextSceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform); + + virtual void Update(const ISceneLayer& layer); + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLTextureProgram.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLTextureProgram.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,122 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLTextureProgram.h" +#include "OpenGLShaderVersionDirective.h" + +static const unsigned int COMPONENTS = 2; +static const unsigned int COUNT = 6; // 2 triangles in 2D + +static const char* VERTEX_SHADER = + ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE + "attribute vec2 a_texcoord; \n" + "attribute vec4 a_position; \n" + "uniform mat4 u_matrix; \n" + "varying vec2 v_texcoord; \n" + "void main() \n" + "{ \n" + " gl_Position = u_matrix * a_position; \n" + " v_texcoord = a_texcoord; \n" + "}"; + + +namespace OrthancStone +{ + namespace Internals + { + void OpenGLTextureProgram::InitializeExecution(OpenGL::OpenGLTexture& texture, + const AffineTransform2D& transform) + { + context_.MakeCurrent(); + program_->Use(); + + AffineTransform2D scale = AffineTransform2D::CreateScaling + (texture.GetWidth(), texture.GetHeight()); + + AffineTransform2D t = AffineTransform2D::Combine(transform, scale); + + float m[16]; + t.ConvertToOpenGLMatrix(m, context_.GetCanvasWidth(), context_.GetCanvasHeight()); + + texture.Bind(program_->GetUniformLocation("u_texture")); + glUniformMatrix4fv(program_->GetUniformLocation("u_matrix"), 1, GL_FALSE, m); + + glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]); + glEnableVertexAttribArray(positionLocation_); + glVertexAttribPointer(positionLocation_, COMPONENTS, GL_FLOAT, GL_FALSE, 0, 0); + + glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]); + glEnableVertexAttribArray(textureLocation_); + glVertexAttribPointer(textureLocation_, COMPONENTS, GL_FLOAT, GL_FALSE, 0, 0); + } + + + void OpenGLTextureProgram::FinalizeExecution() + { + glDisableVertexAttribArray(positionLocation_); + glDisableVertexAttribArray(textureLocation_); + } + + + OpenGLTextureProgram::OpenGLTextureProgram(OpenGL::IOpenGLContext& context, + const char* fragmentShader) : + context_(context) + { + static const float POSITIONS[COMPONENTS * COUNT] = { + 0, 0, + 0, 1, + 1, 0, + 1, 0, + 0, 1, + 1, 1 + }; + + context_.MakeCurrent(); + + program_.reset(new OpenGL::OpenGLProgram); + program_->CompileShaders(VERTEX_SHADER, fragmentShader); + + positionLocation_ = program_->GetAttributeLocation("a_position"); + textureLocation_ = program_->GetAttributeLocation("a_texcoord"); + + glGenBuffers(2, buffers_); + + glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * COMPONENTS * COUNT, POSITIONS, GL_STATIC_DRAW); + + glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * COMPONENTS * COUNT, POSITIONS, GL_STATIC_DRAW); + } + + + OpenGLTextureProgram::~OpenGLTextureProgram() + { + context_.MakeCurrent(); + glDeleteBuffers(2, buffers_); + } + + + void OpenGLTextureProgram::Execution::DrawTriangles() + { + glDrawArrays(GL_TRIANGLES, 0, COUNT); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Internals/OpenGLTextureProgram.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLTextureProgram.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,81 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../OpenGL/IOpenGLContext.h" +#include "../../OpenGL/OpenGLProgram.h" +#include "../../OpenGL/OpenGLTexture.h" +#include "../../Toolbox/AffineTransform2D.h" + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLTextureProgram : public boost::noncopyable + { + private: + OpenGL::IOpenGLContext& context_; + std::auto_ptr program_; + GLint positionLocation_; + GLint textureLocation_; + GLuint buffers_[2]; + + void InitializeExecution(OpenGL::OpenGLTexture& texture, + const AffineTransform2D& transform); + + void FinalizeExecution(); + + public: + OpenGLTextureProgram(OpenGL::IOpenGLContext& context, + const char* fragmentShader); + + ~OpenGLTextureProgram(); + + class Execution : public boost::noncopyable + { + private: + OpenGLTextureProgram& that_; + + public: + Execution(OpenGLTextureProgram& that, + OpenGL::OpenGLTexture& texture, + const AffineTransform2D& transform) : + that_(that) + { + that_.InitializeExecution(texture, transform); + } + + ~Execution() + { + that_.FinalizeExecution(); + } + + void DrawTriangles(); + + GLint GetUniformLocation(const std::string& name) + { + return that_.program_->GetUniformLocation(name); + } + }; + }; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/OpenGLCompositor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/OpenGLCompositor.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,213 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OpenGLCompositor.h" + +#include "Internals/OpenGLAdvancedPolylineRenderer.h" +#include "Internals/OpenGLBasicPolylineRenderer.h" +#include "Internals/OpenGLColorTextureRenderer.h" +#include "Internals/OpenGLFloatTextureRenderer.h" +#include "Internals/OpenGLInfoPanelRenderer.h" +#include "Internals/OpenGLTextRenderer.h" + +namespace OrthancStone +{ + class OpenGLCompositor::Font : public boost::noncopyable + { + private: + std::auto_ptr alphabet_; + std::auto_ptr texture_; + + public: + Font(const GlyphBitmapAlphabet& dict) + { + alphabet_.reset(new GlyphTextureAlphabet(dict)); + texture_.reset(new OpenGL::OpenGLTexture); + + std::auto_ptr bitmap(alphabet_->ReleaseTexture()); + texture_->Load(*bitmap, true /* enable linear interpolation */); + } + + OpenGL::OpenGLTexture& GetTexture() const + { + assert(texture_.get() != NULL); + return *texture_; + } + + const GlyphTextureAlphabet& GetAlphabet() const + { + assert(alphabet_.get() != NULL); + return *alphabet_; + } + }; + + + const OpenGLCompositor::Font* OpenGLCompositor::GetFont(size_t fontIndex) const + { + Fonts::const_iterator found = fonts_.find(fontIndex); + + if (found == fonts_.end()) + { + return NULL; // Unknown font, nothing should be drawn + } + else + { + assert(found->second != NULL); + return found->second; + } + } + + + Internals::CompositorHelper::ILayerRenderer* OpenGLCompositor::Create(const ISceneLayer& layer) + { + switch (layer.GetType()) + { + case ISceneLayer::Type_InfoPanel: + return new Internals::OpenGLInfoPanelRenderer + (context_, colorTextureProgram_, dynamic_cast(layer)); + + case ISceneLayer::Type_ColorTexture: + return new Internals::OpenGLColorTextureRenderer + (context_, colorTextureProgram_, dynamic_cast(layer)); + + case ISceneLayer::Type_FloatTexture: + return new Internals::OpenGLFloatTextureRenderer + (context_, floatTextureProgram_, dynamic_cast(layer)); + + case ISceneLayer::Type_Polyline: + return new Internals::OpenGLAdvancedPolylineRenderer + (context_, linesProgram_, dynamic_cast(layer)); + //return new Internals::OpenGLBasicPolylineRenderer(context_, dynamic_cast(layer)); + + case ISceneLayer::Type_Text: + { + const TextSceneLayer& l = dynamic_cast(layer); + const Font* font = GetFont(l.GetFontIndex()); + if (font == NULL) + { + return NULL; + } + else + { + return new Internals::OpenGLTextRenderer + (context_, textProgram_, font->GetAlphabet(), font->GetTexture(), l); + } + } + + default: + return NULL; + } + } + + + OpenGLCompositor::OpenGLCompositor(OpenGL::IOpenGLContext& context, + const Scene2D& scene) : + context_(context), + helper_(scene, *this), + colorTextureProgram_(context), + floatTextureProgram_(context), + linesProgram_(context), + textProgram_(context), + canvasWidth_(0), + canvasHeight_(0) + { + UpdateSize(); + } + + + OpenGLCompositor::~OpenGLCompositor() + { + for (Fonts::iterator it = fonts_.begin(); it != fonts_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + } + + + void OpenGLCompositor::UpdateSize() + { + canvasWidth_ = context_.GetCanvasWidth(); + canvasHeight_ = context_.GetCanvasHeight(); + + context_.MakeCurrent(); + glViewport(0, 0, canvasWidth_, canvasHeight_); + } + + + void OpenGLCompositor::Refresh() + { + context_.MakeCurrent(); + + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + helper_.Refresh(canvasWidth_, canvasHeight_); + + context_.SwapBuffer(); + } + + + void OpenGLCompositor::SetFont(size_t index, + const GlyphBitmapAlphabet& dict) + { + std::auto_ptr font(new Font(dict)); + + Fonts::iterator found = fonts_.find(index); + + if (found == fonts_.end()) + { + fonts_[index] = font.release(); + } + else + { + assert(found->second != NULL); + delete found->second; + + found->second = font.release(); + } + } + + +#if ORTHANC_ENABLE_LOCALE == 1 + void OpenGLCompositor::SetFont(size_t index, + Orthanc::EmbeddedResources::FileResourceId resource, + unsigned int fontSize, + Orthanc::Encoding codepage) + { + FontRenderer renderer; + renderer.LoadFont(resource, fontSize); + + GlyphBitmapAlphabet dict; + dict.LoadCodepage(renderer, codepage); + + SetFont(index, dict); + } +#endif + + + ScenePoint2D OpenGLCompositor::GetPixelCenterCoordinates(int x, int y) const + { + return ScenePoint2D( + static_cast(x) + 0.5 - static_cast(canvasWidth_) / 2.0, + static_cast(y) + 0.5 - static_cast(canvasHeight_) / 2.0); + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/OpenGLCompositor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/OpenGLCompositor.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,85 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "Internals/CompositorHelper.h" +#include "Internals/OpenGLColorTextureProgram.h" +#include "Internals/OpenGLFloatTextureProgram.h" +#include "Internals/OpenGLLinesProgram.h" +#include "Internals/OpenGLTextProgram.h" + +namespace OrthancStone +{ + class OpenGLCompositor : private Internals::CompositorHelper::IRendererFactory + { + private: + class Font; + + typedef std::map Fonts; + + OpenGL::IOpenGLContext& context_; + Fonts fonts_; + Internals::CompositorHelper helper_; + Internals::OpenGLColorTextureProgram colorTextureProgram_; + Internals::OpenGLFloatTextureProgram floatTextureProgram_; + Internals::OpenGLLinesProgram linesProgram_; + Internals::OpenGLTextProgram textProgram_; + unsigned int canvasWidth_; + unsigned int canvasHeight_; + + const Font* GetFont(size_t fontIndex) const; + + virtual Internals::CompositorHelper::ILayerRenderer* Create(const ISceneLayer& layer); + + public: + OpenGLCompositor(OpenGL::IOpenGLContext& context, + const Scene2D& scene); + + ~OpenGLCompositor(); + + void UpdateSize(); + + void Refresh(); + + void SetFont(size_t index, + const GlyphBitmapAlphabet& dict); + +#if ORTHANC_ENABLE_LOCALE == 1 + void SetFont(size_t index, + Orthanc::EmbeddedResources::FileResourceId resource, + unsigned int fontSize, + Orthanc::Encoding codepage); +#endif + + unsigned int GetCanvasWidth() const + { + return canvasWidth_; + } + + unsigned int GetCanvasHeight() const + { + return canvasHeight_; + } + + ScenePoint2D GetPixelCenterCoordinates(int x, int y) const; + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/PanSceneTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/PanSceneTracker.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,46 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "PanSceneTracker.h" + +namespace OrthancStone +{ + PanSceneTracker::PanSceneTracker(Scene2D& scene, + const PointerEvent& event) : + scene_(scene), + originalSceneToCanvas_(scene_.GetSceneToCanvasTransform()), + originalCanvasToScene_(scene_.GetCanvasToSceneTransform()) + { + pivot_ = event.GetMainPosition().Apply(originalCanvasToScene_); + } + + + void PanSceneTracker::Update(const PointerEvent& event) + { + ScenePoint2D p = event.GetMainPosition().Apply(originalCanvasToScene_); + + scene_.SetSceneToCanvasTransform( + AffineTransform2D::Combine( + originalSceneToCanvas_, + AffineTransform2D::CreateOffset(p.GetX() - pivot_.GetX(), + p.GetY() - pivot_.GetY()))); + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/PanSceneTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/PanSceneTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,47 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "IPointerTracker.h" +#include "Scene2D.h" + +namespace OrthancStone +{ + class PanSceneTracker : public IPointerTracker + { + private: + Scene2D& scene_; + ScenePoint2D pivot_; + AffineTransform2D originalSceneToCanvas_; + AffineTransform2D originalCanvasToScene_; + + public: + PanSceneTracker(Scene2D& scene, + const PointerEvent& event); + + virtual void Update(const PointerEvent& event); + + virtual void Release() + { + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/PointerEvent.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/PointerEvent.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,60 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "PointerEvent.h" + +#include + +namespace OrthancStone +{ + PointerEvent::PointerEvent() : + hasAltModifier_(false), + hasControlModifier_(false), + hasShiftModifier_(false) + { + } + + + ScenePoint2D PointerEvent::GetMainPosition() const + { + if (positions_.empty()) + { + return ScenePoint2D(0, 0); + } + else + { + return positions_[0]; + } + } + + + ScenePoint2D PointerEvent::GetPosition(size_t index) const + { + if (index < positions_.size()) + { + return positions_[index]; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/PointerEvent.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/PointerEvent.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,92 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ScenePoint2D.h" + +#include +#include + +namespace OrthancStone +{ + class PointerEvent : public boost::noncopyable + { + private: + std::vector positions_; + bool hasAltModifier_; + bool hasControlModifier_; + bool hasShiftModifier_; + + public: + PointerEvent(); + + ScenePoint2D GetMainPosition() const; + + void AddPosition(const ScenePoint2D& p) + { + positions_.push_back(p); + } + + void AddPosition(double x, + double y) + { + positions_.push_back(ScenePoint2D(x, y)); + } + + size_t GetPositionsCount() const + { + return positions_.size(); + } + + ScenePoint2D GetPosition(size_t index) const; + + void SetAltModifier(bool value) + { + hasAltModifier_ = value; + } + + bool HasAltModifier() const + { + return hasAltModifier_; + } + + void SetControlModifier(bool value) + { + hasControlModifier_ = value; + } + + bool HasControlModifier() const + { + return hasControlModifier_; + } + + void SetShiftModifier(bool value) + { + hasShiftModifier_ = value; + } + + bool HasShiftModifier() const + { + return hasShiftModifier_; + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/PolylineSceneLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/PolylineSceneLayer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,127 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "PolylineSceneLayer.h" + +#include + +namespace OrthancStone +{ + ISceneLayer* PolylineSceneLayer::Clone() const + { + std::auto_ptr cloned(new PolylineSceneLayer); + cloned->Copy(*this); + return cloned.release(); + } + + + void PolylineSceneLayer::SetThickness(double thickness) + { + if (thickness <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + thickness_ = thickness; + BumpRevision(); + } + } + + + void PolylineSceneLayer::Copy(const PolylineSceneLayer& from) + { + SetColor(from.GetRed(), from.GetGreen(), from.GetBlue()); + chains_ = from.chains_; + closed_ = from.closed_; + thickness_ = from.thickness_; + BumpRevision(); + } + + + void PolylineSceneLayer::Reserve(size_t countChains) + { + chains_.reserve(countChains); + closed_.reserve(countChains); + } + + + void PolylineSceneLayer::AddChain(const Chain& chain, + bool isClosed) + { + if (!chain.empty()) + { + chains_.push_back(chain); + closed_.push_back(isClosed); + BumpRevision(); + } + } + + + void PolylineSceneLayer::ClearAllChains() + { + chains_.clear(); + closed_.clear(); + BumpRevision(); + } + + const PolylineSceneLayer::Chain& PolylineSceneLayer::GetChain(size_t i) const + { + if (i < chains_.size()) + { + return chains_[i]; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + bool PolylineSceneLayer::IsClosedChain(size_t i) const + { + if (i < closed_.size()) + { + return closed_[i]; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + bool PolylineSceneLayer::GetBoundingBox(Extent2D& target) const + { + target.Reset(); + + for (size_t i = 0; i < chains_.size(); i++) + { + for (size_t j = 0; j < chains_[i].size(); j++) + { + const ScenePoint2D& p = chains_[i][j]; + target.AddPoint(p.GetX(), p.GetY()); + } + } + + return true; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/PolylineSceneLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/PolylineSceneLayer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,82 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ColorSceneLayer.h" +#include "ScenePoint2D.h" + +#include + +namespace OrthancStone +{ + class PolylineSceneLayer : public ColorSceneLayer + { + public: + typedef std::vector Chain; + + private: + std::vector chains_; + std::vector closed_; + double thickness_; + + public: + PolylineSceneLayer() : + thickness_(1.0) + { + } + + virtual ISceneLayer* Clone() const; + + void SetThickness(double thickness); + + double GetThickness() const + { + return thickness_; + } + + void Copy(const PolylineSceneLayer& from); + + void Reserve(size_t countChains); + + void AddChain(const Chain& chain, + bool isClosed); + + void ClearAllChains(); + + size_t GetChainsCount() const + { + return chains_.size(); + } + + const Chain& GetChain(size_t i) const; + + bool IsClosedChain(size_t i) const; + + virtual Type GetType() const + { + return Type_Polyline; + } + + virtual bool GetBoundingBox(Extent2D& target) const; + + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/RotateSceneTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/RotateSceneTracker.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,62 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "RotateSceneTracker.h" + +namespace OrthancStone +{ + RotateSceneTracker::RotateSceneTracker(Scene2D& scene, + const PointerEvent& event) : + scene_(scene), + click_(event.GetMainPosition()), + aligner_(scene, click_), + isFirst_(true), + originalSceneToCanvas_(scene.GetSceneToCanvasTransform()) + { + } + + + void RotateSceneTracker::Update(const PointerEvent& event) + { + ScenePoint2D p = event.GetMainPosition(); + double dx = p.GetX() - click_.GetX(); + double dy = p.GetY() - click_.GetY(); + + if (std::abs(dx) > 5.0 || + std::abs(dy) > 5.0) + { + double a = atan2(dy, dx); + + if (isFirst_) + { + referenceAngle_ = a; + isFirst_ = false; + } + + scene_.SetSceneToCanvasTransform( + AffineTransform2D::Combine( + AffineTransform2D::CreateRotation(a - referenceAngle_), + originalSceneToCanvas_)); + + aligner_.Apply(); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/RotateSceneTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/RotateSceneTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,49 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "IPointerTracker.h" +#include "Internals/FixedPointAligner.h" + +namespace OrthancStone +{ + class RotateSceneTracker : public IPointerTracker + { + private: + Scene2D& scene_; + ScenePoint2D click_; + Internals::FixedPointAligner aligner_; + double referenceAngle_; + bool isFirst_; + AffineTransform2D originalSceneToCanvas_; + + public: + RotateSceneTracker(Scene2D& scene, + const PointerEvent& event); + + virtual void Update(const PointerEvent& event); + + virtual void Release() + { + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Scene2D.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Scene2D.cpp Thu May 16 09:11:14 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 . + **/ + + +#include "Scene2D.h" + +#include + + +namespace OrthancStone +{ + class Scene2D::Item + { + private: + std::auto_ptr layer_; + uint64_t identifier_; + + public: + Item(ISceneLayer* layer, + uint64_t identifier) : + layer_(layer), + identifier_(identifier) + { + if (layer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + ISceneLayer& GetLayer() const + { + if (layer_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *layer_; + } + } + + ISceneLayer* ReleaseLayer() + { + if (layer_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return layer_.release(); + } + } + + uint64_t GetIdentifier() const + { + return identifier_; + } + }; + + + Scene2D::Scene2D(const Scene2D& other) + : IObservable(other.GetBroker()) + , sceneToCanvas_(other.sceneToCanvas_) + , canvasToScene_(other.canvasToScene_) + , layerCounter_(0) + { + for (Content::const_iterator it = other.content_.begin(); + it != other.content_.end(); ++it) + { + content_[it->first] = new Item(it->second->GetLayer().Clone(), layerCounter_++); + } + } + + + Scene2D::~Scene2D() + { + for (Content::iterator it = content_.begin(); + it != content_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + } + + + void Scene2D::SetLayer(int depth, + ISceneLayer* layer) // Takes ownership + { + LOG(INFO) << "SetLayer(" << depth << ", " << + reinterpret_cast(layer) << ")"; + std::auto_ptr item(new Item(layer, layerCounter_++)); + + if (layer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + Content::iterator found = content_.find(depth); + + if (found == content_.end()) + { + content_[depth] = item.release(); + } + else + { + assert(found->second != NULL); + delete found->second; + found->second = item.release(); + } + } + + + void Scene2D::DeleteLayer(int depth) + { + + Content::iterator found = content_.find(depth); + + if (found != content_.end()) + { + LOG(INFO) << "DeleteLayer --found-- (" << depth << ")"; + assert(found->second != NULL); + delete found->second; + content_.erase(found); + } + } + + + bool Scene2D::HasLayer(int depth) const + { + return (content_.find(depth) != content_.end()); + } + + + ISceneLayer& Scene2D::GetLayer(int depth) const + { + Content::const_iterator found = content_.find(depth); + + if (found == content_.end()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + assert(found->second != NULL); + return found->second->GetLayer(); + } + } + + + int Scene2D::GetMinDepth() const + { + if (content_.size() == 0) + return 0; + else + return content_.begin()->first; + } + + + int Scene2D::GetMaxDepth() const + { + if (content_.size() == 0) + return 0; + else + return content_.rbegin()->first; + } + + ISceneLayer* Scene2D::ReleaseLayer(int depth) + { + Content::iterator found = content_.find(depth); + + if (found == content_.end()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + assert(found->second != NULL); + + std::auto_ptr layer(found->second->ReleaseLayer()); + assert(layer.get() != NULL); + + content_.erase(found); + + return layer.release(); + } + } + + + void Scene2D::Apply(IVisitor& visitor) const + { + for (Content::const_iterator it = content_.begin(); + it != content_.end(); ++it) + { + assert(it->second != NULL); + visitor.Visit(it->second->GetLayer(), it->second->GetIdentifier(), it->first); + } + } + + + void Scene2D::SetSceneToCanvasTransform(const AffineTransform2D& transform) + { + // Make sure the transform is invertible before making any change + AffineTransform2D inverse = AffineTransform2D::Invert(transform); + + sceneToCanvas_ = transform; + canvasToScene_ = inverse; + BroadcastMessage(SceneTransformChanged(*this)); + } + + void Scene2D::FitContent(unsigned int canvasWidth, + unsigned int canvasHeight) + { + Extent2D extent; + + for (Content::const_iterator it = content_.begin(); + it != content_.end(); ++it) + { + assert(it->second != NULL); + + Extent2D tmp; + if (it->second->GetLayer().GetBoundingBox(tmp)) + { + extent.Union(tmp); + } + } + + if (!extent.IsEmpty()) + { + double zoomX = static_cast(canvasWidth) / extent.GetWidth(); + double zoomY = static_cast(canvasHeight) / extent.GetHeight(); + + double zoom = std::min(zoomX, zoomY); + if (LinearAlgebra::IsCloseToZero(zoom)) + { + zoom = 1; + } + + double panX = extent.GetCenterX(); + double panY = extent.GetCenterY(); + + // Bring the center of the scene to (0,0) + AffineTransform2D t1 = AffineTransform2D::CreateOffset(-panX, -panY); + + // Scale the scene + AffineTransform2D t2 = AffineTransform2D::CreateScaling(zoom, zoom); + + SetSceneToCanvasTransform(AffineTransform2D::Combine(t2, t1)); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/Scene2D.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Scene2D.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,121 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ISceneLayer.h" +#include "../Toolbox/AffineTransform2D.h" +#include +#include + +#include + +namespace OrthancStone +{ + class Scene2D : public IObservable + { + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SceneTransformChanged, Scene2D); + + class IVisitor : public boost::noncopyable + { + public: + virtual ~IVisitor() + { + } + + virtual void Visit(const ISceneLayer& layer, + uint64_t layerIdentifier, + int depth) = 0; + }; + + private: + class Item; + + typedef std::map Content; + + Content content_; + AffineTransform2D sceneToCanvas_; + AffineTransform2D canvasToScene_; + uint64_t layerCounter_; + + Scene2D(const Scene2D& other); + + public: + Scene2D(MessageBroker& broker) + : IObservable(broker) + , layerCounter_(0) + { + } + + ~Scene2D(); + + Scene2D* Clone() const + { + return new Scene2D(*this); + } + + void SetLayer(int depth, + ISceneLayer* layer); // Takes ownership + + /** + Removes the layer at specified depth and deletes the underlying object + */ + void DeleteLayer(int depth); + + bool HasLayer(int depth) const; + + ISceneLayer& GetLayer(int depth) const; + + /** + Returns the minimum depth among all layers or 0 if there are no layers + */ + int GetMinDepth() const; + + /** + Returns the minimum depth among all layers or 0 if there are no layers + */ + int GetMaxDepth() const; + + /** + Removes the layer at specified depth and transfers the object + ownership to the caller + */ + ISceneLayer* ReleaseLayer(int depth); + + void Apply(IVisitor& visitor) const; + + const AffineTransform2D& GetSceneToCanvasTransform() const + { + return sceneToCanvas_; + } + + const AffineTransform2D& GetCanvasToSceneTransform() const + { + return canvasToScene_; + } + + void SetSceneToCanvasTransform(const AffineTransform2D& transform); + + void FitContent(unsigned int canvasWidth, + unsigned int canvasHeight); + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/ScenePoint2D.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/ScenePoint2D.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,67 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Toolbox/AffineTransform2D.h" + + +namespace OrthancStone +{ + class ScenePoint2D + { + private: + double x_; + double y_; + + public: + ScenePoint2D() : + x_(0), + y_(0) + { + } + + ScenePoint2D(double x, + double y) : + x_(x), + y_(y) + { + } + + double GetX() const + { + return x_; + } + + double GetY() const + { + return y_; + } + + ScenePoint2D Apply(const AffineTransform2D& t) const + { + double x = x_; + double y = y_; + t.Apply(x, y); + return ScenePoint2D(x, y); + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/TextSceneLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/TextSceneLayer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,81 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "TextSceneLayer.h" + +namespace OrthancStone +{ + TextSceneLayer::TextSceneLayer() : + x_(0), + y_(0), + fontIndex_(0), + anchor_(BitmapAnchor_Center), + border_(0), + revision_(0) + { + } + + + ISceneLayer* TextSceneLayer::Clone() const + { + std::auto_ptr cloned(new TextSceneLayer); + cloned->SetColor(GetRed(), GetGreen(), GetBlue()); + cloned->x_ = x_; + cloned->y_ = y_; + cloned->utf8_ = utf8_; + cloned->fontIndex_ = fontIndex_; + cloned->anchor_ = anchor_; + cloned->border_ = border_; + return cloned.release(); + } + + void TextSceneLayer::SetPosition(double x, + double y) + { + x_ = x; + y_ = y; + revision_ ++; + } + + void TextSceneLayer::SetText(const std::string& utf8) + { + utf8_ = utf8; + revision_ ++; + } + + void TextSceneLayer::SetFontIndex(size_t fontIndex) + { + fontIndex_ = fontIndex; + revision_ ++; + } + + void TextSceneLayer::SetAnchor(BitmapAnchor anchor) + { + anchor_ = anchor; + revision_ ++; + } + + void TextSceneLayer::SetBorder(unsigned int border) + { + border_ = border; + revision_ ++; + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/TextSceneLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/TextSceneLayer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,104 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ColorSceneLayer.h" +#include "../StoneEnumerations.h" + +#include +#include + +namespace OrthancStone +{ + class TextSceneLayer : public ColorSceneLayer + { + private: + double x_; + double y_; + std::string utf8_; + size_t fontIndex_; + BitmapAnchor anchor_; + unsigned int border_; + uint64_t revision_; + + public: + TextSceneLayer(); + + virtual ISceneLayer* Clone() const; + + void SetPosition(double x, + double y); + + void SetText(const std::string& utf8); + + void SetFontIndex(size_t fontIndex); + + void SetAnchor(BitmapAnchor anchor); + + void SetBorder(unsigned int border); + + double GetX() const + { + return x_; + } + + double GetY() const + { + return y_; + } + + unsigned int GetBorder() const + { + return border_; + } + + const std::string& GetText() const + { + return utf8_; + } + + size_t GetFontIndex() const + { + return fontIndex_; + } + + BitmapAnchor GetAnchor() const + { + return anchor_; + } + + virtual Type GetType() const + { + return Type_Text; + } + + virtual bool GetBoundingBox(Extent2D& target) const + { + return false; + } + + virtual uint64_t GetRevision() const + { + return revision_; + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/TextureBaseSceneLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/TextureBaseSceneLayer.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,170 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "TextureBaseSceneLayer.h" + +#include + +namespace OrthancStone +{ + void TextureBaseSceneLayer::SetTexture(Orthanc::ImageAccessor* texture) + { + if (texture == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + texture_.reset(texture); + IncrementRevision(); + } + } + + + void TextureBaseSceneLayer::CopyParameters(const TextureBaseSceneLayer& other) + { + originX_ = other.originX_; + originY_ = other.originY_; + pixelSpacingX_ = other.pixelSpacingX_; + pixelSpacingY_ = other.pixelSpacingY_; + angle_ = other.angle_; + isLinearInterpolation_ = other.isLinearInterpolation_; + } + + + TextureBaseSceneLayer::TextureBaseSceneLayer() : + originX_(0), + originY_(0), + pixelSpacingX_(1), + pixelSpacingY_(1), + angle_(0), + isLinearInterpolation_(false), + revision_(0) + { + if (pixelSpacingX_ <= 0 || + pixelSpacingY_ <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + void TextureBaseSceneLayer::SetOrigin(double x, + double y) + { + originX_ = x; + originY_ = y; + IncrementRevision(); + } + + + void TextureBaseSceneLayer::SetPixelSpacing(double sx, + double sy) + { + if (sx <= 0 || + sy <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + pixelSpacingX_ = sx; + pixelSpacingY_ = sy; + IncrementRevision(); + } + } + + + void TextureBaseSceneLayer::SetAngle(double angle) + { + angle_ = angle; + IncrementRevision(); + } + + + void TextureBaseSceneLayer::SetLinearInterpolation(bool isLinearInterpolation) + { + isLinearInterpolation_ = isLinearInterpolation; + IncrementRevision(); + } + + + const Orthanc::ImageAccessor& TextureBaseSceneLayer::GetTexture() const + { + if (!HasTexture()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *texture_; + } + } + + + AffineTransform2D TextureBaseSceneLayer::GetTransform() const + { + return AffineTransform2D::Combine( + AffineTransform2D::CreateOffset(originX_, originY_), + AffineTransform2D::CreateRotation(angle_), + AffineTransform2D::CreateScaling(pixelSpacingX_, pixelSpacingY_), + AffineTransform2D::CreateOffset(-0.5, -0.5)); + } + + + bool TextureBaseSceneLayer::GetBoundingBox(Extent2D& target) const + { + if (texture_.get() == NULL) + { + return false; + } + else + { + const AffineTransform2D t = GetTransform(); + + target.Reset(); + + double x, y; + + x = 0; + y = 0; + t.Apply(x, y); + target.AddPoint(x, y); + + x = static_cast(texture_->GetWidth()); + y = 0; + t.Apply(x, y); + target.AddPoint(x, y); + + x = 0; + y = static_cast(texture_->GetHeight()); + t.Apply(x, y); + target.AddPoint(x, y); + + x = static_cast(texture_->GetWidth()); + y = static_cast(texture_->GetHeight()); + t.Apply(x, y); + target.AddPoint(x, y); + + return true; + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/TextureBaseSceneLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/TextureBaseSceneLayer.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,114 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ISceneLayer.h" +#include "../Toolbox/AffineTransform2D.h" + +#include + +namespace OrthancStone +{ + class TextureBaseSceneLayer : public ISceneLayer + { + private: + std::auto_ptr texture_; + double originX_; + double originY_; + double pixelSpacingX_; + double pixelSpacingY_; + double angle_; + bool isLinearInterpolation_; + uint64_t revision_; + + protected: + void SetTexture(Orthanc::ImageAccessor* texture); + + void IncrementRevision() + { + revision_++; + } + + void CopyParameters(const TextureBaseSceneLayer& other); + + public: + TextureBaseSceneLayer(); + + // Center of the top-left pixel + void SetOrigin(double x, + double y); + + void SetPixelSpacing(double sx, + double sy); + + // In radians + void SetAngle(double angle); + + void SetLinearInterpolation(bool isLinearInterpolation); + + double GetOriginX() const + { + return originX_; + } + + double GetOriginY() const + { + return originY_; + } + + double GetPixelSpacingX() const + { + return pixelSpacingX_; + } + + double GetPixelSpacingY() const + { + return pixelSpacingY_; + } + + double GetAngle() const + { + return angle_; + } + + bool IsLinearInterpolation() const + { + return isLinearInterpolation_; + } + + bool HasTexture() const + { + return (texture_.get() != NULL); + } + + const Orthanc::ImageAccessor& GetTexture() const; + + AffineTransform2D GetTransform() const; + + virtual bool GetBoundingBox(Extent2D& target) const; + + virtual uint64_t GetRevision() const + { + return revision_; + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/ZoomSceneTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/ZoomSceneTracker.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,81 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "ZoomSceneTracker.h" + +namespace OrthancStone +{ + ZoomSceneTracker::ZoomSceneTracker(Scene2D& scene, + const PointerEvent& event, + unsigned int canvasHeight) : + scene_(scene), + clickY_(event.GetMainPosition().GetY()), + aligner_(scene, event.GetMainPosition()), + originalSceneToCanvas_(scene.GetSceneToCanvasTransform()) + { + if (canvasHeight <= 3) + { + active_ = false; + } + else + { + normalization_ = 1.0 / static_cast(canvasHeight - 1); + active_ = true; + } + } + + + void ZoomSceneTracker::Update(const PointerEvent& event) + { + static const double MIN_ZOOM = -4; + static const double MAX_ZOOM = 4; + + if (active_) + { + double y = event.GetMainPosition().GetY(); + double dy = static_cast(y - clickY_) * normalization_; // In the range [-1,1] + double z; + + // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM] + if (dy < -1.0) + { + z = MIN_ZOOM; + } + else if (dy > 1.0) + { + z = MAX_ZOOM; + } + else + { + z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0; + } + + double zoom = pow(2.0, z); + + scene_.SetSceneToCanvasTransform( + AffineTransform2D::Combine( + AffineTransform2D::CreateScaling(zoom, zoom), + originalSceneToCanvas_)); + + aligner_.Apply(); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Scene2D/ZoomSceneTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/ZoomSceneTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,50 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "IPointerTracker.h" +#include "Internals/FixedPointAligner.h" + +namespace OrthancStone +{ + class ZoomSceneTracker : public IPointerTracker + { + private: + Scene2D& scene_; + double clickY_; + bool active_; + double normalization_; + Internals::FixedPointAligner aligner_; + AffineTransform2D originalSceneToCanvas_; + + public: + ZoomSceneTracker(Scene2D& scene, + const PointerEvent& event, + unsigned int canvasHeight); + + virtual void Update(const PointerEvent& event); + + virtual void Release() + { + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/SmartLoader.cpp --- a/Framework/SmartLoader.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/SmartLoader.cpp Thu May 16 09:11:14 2019 +0200 @@ -95,7 +95,7 @@ LOG(WARNING) << "ScheduleLayerCreation for CachedSlice (image is loaded): " << slice_->GetOrthancInstanceId(); RendererFactory factory(*this); - EmitMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice_->GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice_->GetGeometry())); } else { @@ -174,7 +174,7 @@ if (cachedSlice != NULL) { - EmitMessage(IVolumeSlicer::GeometryReadyMessage(*cachedSlice)); + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*cachedSlice)); } } @@ -242,7 +242,7 @@ cachedSlices_[sliceKeyId] = boost::shared_ptr(cachedSlice); // re-emit original Layer message to observers - EmitMessage(message); + BroadcastMessage(message); } @@ -264,7 +264,7 @@ cachedSlices_[sliceKeyId] = cachedSlice; // re-emit original Layer message to observers - EmitMessage(message); + BroadcastMessage(message); } @@ -286,6 +286,6 @@ } // re-emit original Layer message to observers - EmitMessage(message); + BroadcastMessage(message); } } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/StoneEnumerations.cpp --- a/Framework/StoneEnumerations.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/StoneEnumerations.cpp Thu May 16 09:11:14 2019 +0200 @@ -27,20 +27,18 @@ namespace OrthancStone { - bool StringToSopClassUid(SopClassUid& result, - const std::string& source) + SopClassUid StringToSopClassUid(const std::string& source) { std::string s = Orthanc::Toolbox::StripSpaces(source); if (s == "1.2.840.10008.5.1.4.1.1.481.2") { - result = SopClassUid_RTDose; - return true; + return SopClassUid_RTDose; } else { - //LOG(INFO) << "Unknown SOP class UID: " << source; - return false; + //LOG(INFO) << "Other SOP class UID: " << source; + return SopClassUid_Other; } } @@ -48,14 +46,14 @@ void ComputeWindowing(float& targetCenter, float& targetWidth, ImageWindowing windowing, - float defaultCenter, - float defaultWidth) + float customCenter, + float customWidth) { switch (windowing) { - case ImageWindowing_Default: - targetCenter = defaultCenter; - targetWidth = defaultWidth; + case ImageWindowing_Custom: + targetCenter = customCenter; + targetWidth = customWidth; break; case ImageWindowing_Bone: diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/StoneEnumerations.h --- a/Framework/StoneEnumerations.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/StoneEnumerations.h Thu May 16 09:11:14 2019 +0200 @@ -34,7 +34,6 @@ enum ImageWindowing { - ImageWindowing_Default, ImageWindowing_Bone, ImageWindowing_Lung, ImageWindowing_Custom @@ -99,6 +98,7 @@ enum SopClassUid { + SopClassUid_Other, SopClassUid_RTDose }; @@ -115,67 +115,6 @@ BitmapAnchor_TopRight }; - enum MessageType - { - MessageType_Widget_GeometryChanged, - MessageType_Widget_ContentChanged, - - MessageType_VolumeSlicer_GeometryReady, // instance tags have been loaded - MessageType_VolumeSlicer_GeometryError, - MessageType_VolumeSlicer_ContentChanged, - MessageType_VolumeSlicer_SliceChanged, - MessageType_VolumeSlicer_LayerReady, // layer is ready to be rendered - MessageType_VolumeSlicer_LayerError, - - MessageType_DicomSeriesVolumeSlicer_FrameReady, // pixels data of the frame have been loaded - - MessageType_SliceViewerWidget_DisplayedSlice, // The displayed slice has changed - - MessageType_SliceLoader_GeometryReady, - MessageType_SliceLoader_GeometryError, - MessageType_SliceLoader_ImageReady, - MessageType_SliceLoader_ImageError, - - MessageType_VolumeLoader_GeometryReady, - MessageType_VolumeLoader_GeometryError, - MessageType_VolumeLoader_ContentChanged, // Content of several slices in the loader has changed - - MessageType_SlicedVolume_GeometryReady, - MessageType_SlicedVolume_GeometryError, - MessageType_SlicedVolume_VolumeReady, - MessageType_SlicedVolume_ContentChanged, - MessageType_SlicedVolume_SliceContentChanged, - - MessageType_HttpRequestSuccess, - MessageType_HttpRequestError, - - MessageType_OrthancApi_InternalGetJsonResponseReady, - MessageType_OrthancApi_InternalGetJsonResponseError, - - MessageType_OrthancApi_GenericGetJson_Ready, - MessageType_OrthancApi_GenericGetBinary_Ready, - MessageType_OrthancApi_GenericHttpError_Ready, - MessageType_OrthancApi_GenericEmptyResponse_Ready, - - MessageType_RadiographyScene_GeometryChanged, - MessageType_RadiographyScene_ContentChanged, - MessageType_RadiographyScene_LayerEdited, - MessageType_RadiographyScene_WindowingChanged, - - MessageType_RadiographyLayer_Edited, - - MessageType_ViewportChanged, - - MessageType_Timeout, - - // used in unit tests only - MessageType_Test1, - MessageType_Test2, - - MessageType_CustomMessage // Custom messages ids ust be greater than this (this one must remain in last position) - }; - - enum ControlPointType { ControlPoint_TopLeftCorner = 0, @@ -193,14 +132,13 @@ }; - bool StringToSopClassUid(SopClassUid& result, - const std::string& source); + SopClassUid StringToSopClassUid(const std::string& source); void ComputeWindowing(float& targetCenter, float& targetWidth, ImageWindowing windowing, - float defaultCenter, - float defaultWidth); + float customCenter, + float customWidth); void ComputeAnchorTranslation(double& deltaX /* out */, double& deltaY /* out */, diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/StoneInitialization.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/StoneInitialization.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,61 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "StoneInitialization.h" + +#include + +#if !defined(ORTHANC_ENABLE_SDL) +# error Macro ORTHANC_ENABLE_SDL must be defined +#endif + +#if ORTHANC_ENABLE_SDL == 1 +# include "../Applications/Sdl/SdlWindow.h" +#endif + +namespace OrthancStone +{ +#if ORTHANC_ENABLE_LOGGING_PLUGIN == 1 + void StoneInitialize(OrthancPluginContext* context) +#else + void StoneInitialize() +#endif + { +#if ORTHANC_ENABLE_LOGGING_PLUGIN == 1 + Orthanc::Logging::Initialize(context); +#else + Orthanc::Logging::Initialize(); +#endif + +#if ORTHANC_ENABLE_SDL == 1 + OrthancStone::SdlWindow::GlobalInitialize(); +#endif + } + + void StoneFinalize() + { +#if ORTHANC_ENABLE_SDL == 1 + OrthancStone::SdlWindow::GlobalFinalize(); +#endif + + Orthanc::Logging::Finalize(); + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/StoneInitialization.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/StoneInitialization.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,35 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include + +namespace OrthancStone +{ +#if ORTHANC_ENABLE_LOGGING_PLUGIN == 1 + void StoneInitialize(OrthancPluginContext* context); +#else + void StoneInitialize(); +#endif + + void StoneFinalize(); +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/AffineTransform2D.cpp --- a/Framework/Toolbox/AffineTransform2D.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/AffineTransform2D.cpp Thu May 16 09:11:14 2019 +0200 @@ -90,6 +90,76 @@ } + void AffineTransform2D::ConvertToOpenGLMatrix(float target[16], + unsigned int canvasWidth, + unsigned int canvasHeight) const + { + const AffineTransform2D t = AffineTransform2D::Combine( + CreateOpenGLClipspace(canvasWidth, canvasHeight), *this); + + const Matrix source = t.GetHomogeneousMatrix(); + + if (source.size1() != 3 || + source.size2() != 3) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + // "z" must be in the [-1,1] range, otherwise the texture does not show up + float z = 0; + + // Embed the 3x3 affine transform of the 2D plane into a 4x4 + // matrix (3D) for OpenGL. The matrix must be transposed. + + target[0] = static_cast(source(0, 0)); + target[1] = static_cast(source(1, 0)); + target[2] = 0; + target[3] = static_cast(source(2, 0)); + target[4] = static_cast(source(0, 1)); + target[5] = static_cast(source(1, 1)); + target[6] = 0; + target[7] = static_cast(source(2, 1)); + target[8] = 0; + target[9] = 0; + target[10] = -1; + target[11] = 0; + target[12] = static_cast(source(0, 2)); + target[13] = static_cast(source(1, 2)); + target[14] = -z; + target[15] = static_cast(source(2, 2)); + } + + + double AffineTransform2D::ComputeZoom() const + { + // Compute the length of the (0,0)-(1,1) diagonal (whose + // length is sqrt(2)) instead of the (0,0)-(1,0) unit segment, + // in order to cope with possible anisotropic zooming + + double x1 = 0; + double y1 = 0; + Apply(x1, y1); + + double x2 = 1; + double y2 = 1; + Apply(x2, y2); + + double dx = x2 - x1; + double dy = y2 - y1; + + double zoom = sqrt(dx * dx + dy * dy) / sqrt(2.0); + + if (LinearAlgebra::IsCloseToZero(zoom)) + { + return 1; // Default value if transform is ill-conditioned + } + else + { + return zoom; + } + } + + AffineTransform2D AffineTransform2D::Invert(const AffineTransform2D& a) { AffineTransform2D t; @@ -163,4 +233,17 @@ return t; } + + + AffineTransform2D AffineTransform2D::CreateOpenGLClipspace(unsigned int canvasWidth, + unsigned int canvasHeight) + { + AffineTransform2D t; + t.matrix_(0, 0) = 2.0 / static_cast(canvasWidth); + t.matrix_(0, 2) = -1.0; + t.matrix_(1, 1) = -2.0 / static_cast(canvasHeight); + t.matrix_(1, 2) = 1.0; + + return t; + } } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/AffineTransform2D.h --- a/Framework/Toolbox/AffineTransform2D.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/AffineTransform2D.h Thu May 16 09:11:14 2019 +0200 @@ -56,6 +56,12 @@ const Orthanc::ImageAccessor& source, ImageInterpolation interpolation, bool clear) const; + + void ConvertToOpenGLMatrix(float target[16], + unsigned int canvasWidth, + unsigned int canvasHeight) const; + + double ComputeZoom() const; static AffineTransform2D Invert(const AffineTransform2D& a); @@ -78,5 +84,8 @@ double sy); static AffineTransform2D CreateRotation(double angle); + + static AffineTransform2D CreateOpenGLClipspace(unsigned int canvasWidth, + unsigned int canvasHeight); }; } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/BaseWebService.h --- a/Framework/Toolbox/BaseWebService.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/BaseWebService.h Thu May 16 09:11:14 2019 +0200 @@ -81,7 +81,7 @@ class BaseWebServicePayload; bool cacheEnabled_; - std::map> cache_; // TODO: this is currently an infinite cache ! + std::map > cache_; // TODO: this is currently an infinite cache ! public: diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/CoordinateSystem3D.cpp --- a/Framework/Toolbox/CoordinateSystem3D.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.cpp Thu May 16 09:11:14 2019 +0200 @@ -187,4 +187,24 @@ { return GeometryToolbox::IntersectPlaneAndLine(p, normal_, d_, origin, direction); } + + + bool CoordinateSystem3D::GetDistance(double& distance, + const CoordinateSystem3D& a, + const CoordinateSystem3D& b) + { + bool opposite; // Ignored + + if (OrthancStone::GeometryToolbox::IsParallelOrOpposite( + opposite, a.GetNormal(), b.GetNormal())) + { + distance = std::abs(a.ProjectAlongNormal(a.GetOrigin()) - + a.ProjectAlongNormal(b.GetOrigin())); + return true; + } + else + { + return false; + } + } } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/CoordinateSystem3D.h --- a/Framework/Toolbox/CoordinateSystem3D.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.h Thu May 16 09:11:14 2019 +0200 @@ -102,5 +102,10 @@ bool IntersectLine(Vector& p, const Vector& origin, const Vector& direction) const; + + // Returns "false" is the two planes are not parallel + static bool GetDistance(double& distance, + const CoordinateSystem3D& a, + const CoordinateSystem3D& b); }; } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/DynamicBitmap.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/DynamicBitmap.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,37 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "DynamicBitmap.h" + +#include +#include + +namespace OrthancStone +{ + DynamicBitmap::DynamicBitmap(const Orthanc::ImageAccessor& bitmap) : + bitmap_(Orthanc::Image::Clone(bitmap)) + { + if (bitmap_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/DynamicBitmap.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/DynamicBitmap.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,44 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include + +#include + +namespace OrthancStone +{ + class DynamicBitmap : public Orthanc::IDynamicObject + { + private: + std::auto_ptr bitmap_; + + public: + DynamicBitmap(const Orthanc::ImageAccessor& bitmap); + + const Orthanc::ImageAccessor& GetBitmap() const + { + return *bitmap_; + } + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/IDelayedCallExecutor.h --- a/Framework/Toolbox/IDelayedCallExecutor.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/IDelayedCallExecutor.h Thu May 16 09:11:14 2019 +0200 @@ -39,8 +39,7 @@ MessageBroker& broker_; public: - - typedef NoPayloadMessage TimeoutMessage; + ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(__FILE__, __LINE__, TimeoutMessage); IDelayedCallExecutor(MessageBroker& broker) : broker_(broker) diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/IWebService.h --- a/Framework/Toolbox/IWebService.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/IWebService.h Thu May 16 09:11:14 2019 +0200 @@ -45,8 +45,10 @@ public: typedef std::map HttpHeaders; - class HttpRequestSuccessMessage : public BaseMessage + class HttpRequestSuccessMessage : public IMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: const std::string& uri_; const void* answer_; @@ -97,8 +99,10 @@ }; - class HttpRequestErrorMessage : public BaseMessage + class HttpRequestErrorMessage : public IMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: const std::string& uri_; const Orthanc::IDynamicObject* payload_; diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/LinearAlgebra.h --- a/Framework/Toolbox/LinearAlgebra.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/LinearAlgebra.h Thu May 16 09:11:14 2019 +0200 @@ -137,7 +137,7 @@ double y, double threshold) { - return fabs(x - y) < threshold; + return fabs(x - y) <= threshold; } inline bool IsNear(double x, diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/OrthancApiClient.h --- a/Framework/Toolbox/OrthancApiClient.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/OrthancApiClient.h Thu May 16 09:11:14 2019 +0200 @@ -35,9 +35,10 @@ public IObserver { public: - class JsonResponseReadyMessage : - public BaseMessage + class JsonResponseReadyMessage : public IMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: const std::string& uri_; const Json::Value& json_; @@ -72,9 +73,10 @@ }; - class BinaryResponseReadyMessage : - public BaseMessage + class BinaryResponseReadyMessage : public IMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: const std::string& uri_; const void* answer_; @@ -117,9 +119,10 @@ }; - class EmptyResponseReadyMessage : - public BaseMessage + class EmptyResponseReadyMessage : public IMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: const std::string& uri_; const Orthanc::IDynamicObject* payload_; diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/OrthancSlicesLoader.cpp --- a/Framework/Toolbox/OrthancSlicesLoader.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.cpp Thu May 16 09:11:14 2019 +0200 @@ -177,7 +177,7 @@ { OrthancSlicesLoader::SliceImageReadyMessage msg (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality()); - EmitMessage(msg); + BroadcastMessage(msg); } @@ -185,43 +185,31 @@ { OrthancSlicesLoader::SliceImageErrorMessage msg (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality()); - EmitMessage(msg); + BroadcastMessage(msg); } void OrthancSlicesLoader::SortAndFinalizeSlices() { - bool ok = false; - - if (slices_.GetSliceCount() > 0) - { - Vector normal; - if (slices_.SelectNormal(normal)) - { - slices_.FilterNormal(normal); - slices_.SetNormal(normal); - slices_.Sort(); - ok = true; - } - } + bool ok = slices_.Sort(); state_ = State_GeometryReady; if (ok) { - LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)"; - EmitMessage(SliceGeometryReadyMessage(*this)); + LOG(INFO) << "Loaded a series with " << slices_.GetSlicesCount() << " slice(s)"; + BroadcastMessage(SliceGeometryReadyMessage(*this)); } else { LOG(ERROR) << "This series is empty"; - EmitMessage(SliceGeometryErrorMessage(*this)); + BroadcastMessage(SliceGeometryErrorMessage(*this)); } } void OrthancSlicesLoader::OnGeometryError(const IWebService::HttpRequestErrorMessage& message) { - EmitMessage(SliceGeometryErrorMessage(*this)); + BroadcastMessage(SliceGeometryErrorMessage(*this)); state_ = State_Error; } @@ -256,7 +244,8 @@ std::auto_ptr slice(new Slice); if (slice->ParseOrthancFrame(dicom, instances[i], frame)) { - slices_.AddSlice(slice.release()); + CoordinateSystem3D geometry = slice->GetGeometry(); + slices_.AddSlice(geometry, slice.release()); } else { @@ -291,12 +280,13 @@ std::auto_ptr slice(new Slice); if (slice->ParseOrthancFrame(dicom, instanceId, frame)) { - slices_.AddSlice(slice.release()); + CoordinateSystem3D geometry = slice->GetGeometry(); + slices_.AddSlice(geometry, slice.release()); } else { LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId; - EmitMessage(SliceGeometryErrorMessage(*this)); + BroadcastMessage(SliceGeometryErrorMessage(*this)); return; } } @@ -322,13 +312,16 @@ if (slice->ParseOrthancFrame(dicom, instanceId, frame)) { LOG(INFO) << "Loaded instance geometry " << instanceId; - slices_.AddSlice(slice.release()); - EmitMessage(SliceGeometryReadyMessage(*this)); + + CoordinateSystem3D geometry = slice->GetGeometry(); + slices_.AddSlice(geometry, slice.release()); + + BroadcastMessage(SliceGeometryReadyMessage(*this)); } else { LOG(WARNING) << "Skipping invalid instance " << instanceId; - EmitMessage(SliceGeometryErrorMessage(*this)); + BroadcastMessage(SliceGeometryErrorMessage(*this)); } } @@ -717,14 +710,14 @@ } - size_t OrthancSlicesLoader::GetSliceCount() const + size_t OrthancSlicesLoader::GetSlicesCount() const { if (state_ != State_GeometryReady) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - return slices_.GetSliceCount(); + return slices_.GetSlicesCount(); } @@ -734,8 +727,8 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - - return slices_.GetSlice(index); + + return dynamic_cast(slices_.GetSlicePayload(index)); } @@ -746,8 +739,10 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - - return slices_.LookupSlice(index, plane); + + double distance; + return (slices_.LookupClosestSlice(index, distance, plane) && + distance <= GetSlice(index).GetThickness() / 2.0); } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/OrthancSlicesLoader.h --- a/Framework/Toolbox/OrthancSlicesLoader.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.h Thu May 16 09:11:14 2019 +0200 @@ -26,6 +26,7 @@ #include "IWebService.h" #include "OrthancApiClient.h" #include "SlicesSorter.h" +#include "Slice.h" #include @@ -35,13 +36,14 @@ class OrthancSlicesLoader : public IObservable, public IObserver { public: - - typedef OriginMessage SliceGeometryReadyMessage; - typedef OriginMessage SliceGeometryErrorMessage; + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryReadyMessage, OrthancSlicesLoader); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryErrorMessage, OrthancSlicesLoader); - class SliceImageReadyMessage : - public OriginMessage + + class SliceImageReadyMessage : public OriginMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: unsigned int sliceIndex_; const Slice& slice_; @@ -84,9 +86,10 @@ }; - class SliceImageErrorMessage : - public OriginMessage + class SliceImageErrorMessage : public OriginMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: const Slice& slice_; unsigned int sliceIndex_; @@ -193,7 +196,7 @@ bool IsGeometryReady() const; - size_t GetSliceCount() const; + size_t GetSlicesCount() const; const Slice& GetSlice(size_t index) const; diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/Slice.cpp --- a/Framework/Toolbox/Slice.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/Slice.cpp Thu May 16 09:11:14 2019 +0200 @@ -194,20 +194,16 @@ geometry_ = CoordinateSystem3D(position, orientation); bool ok = true; - SopClassUid tmp; - if (StringToSopClassUid(tmp, sopClassUid_)) + switch (StringToSopClassUid(sopClassUid_)) { - switch (tmp) - { - case SopClassUid_RTDose: - type_ = Type_OrthancRawFrame; - ok = ComputeRTDoseGeometry(dataset, frame); - break; + case SopClassUid_RTDose: + type_ = Type_OrthancRawFrame; + ok = ComputeRTDoseGeometry(dataset, frame); + break; - default: - break; - } + default: + break; } if (!ok) diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/Slice.h --- a/Framework/Toolbox/Slice.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/Slice.h Thu May 16 09:11:14 2019 +0200 @@ -25,10 +25,13 @@ #include "DicomFrameConverter.h" #include +#include namespace OrthancStone { - class Slice : public boost::noncopyable + // TODO - Remove this class + class Slice : + public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */ { private: enum Type diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/SlicesSorter.cpp --- a/Framework/Toolbox/SlicesSorter.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/SlicesSorter.cpp Thu May 16 09:11:14 2019 +0200 @@ -30,25 +30,23 @@ class SlicesSorter::SliceWithDepth : public boost::noncopyable { private: - std::auto_ptr slice_; - double depth_; + CoordinateSystem3D geometry_; + double depth_; + + std::auto_ptr payload_; public: - SliceWithDepth(Slice* slice) : - slice_(slice), - depth_(0) + SliceWithDepth(const CoordinateSystem3D& geometry, + Orthanc::IDynamicObject* payload) : + geometry_(geometry), + depth_(0), + payload_(payload) { - if (slice == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } } void SetNormal(const Vector& normal) { - assert(slice_.get() != NULL); - depth_ = boost::numeric::ublas::inner_prod - (slice_->GetGeometry().GetOrigin(), normal); + depth_ = boost::numeric::ublas::inner_prod(geometry_.GetOrigin(), normal); } double GetDepth() const @@ -56,10 +54,26 @@ return depth_; } - const Slice& GetSlice() const + const CoordinateSystem3D& GetGeometry() const + { + return geometry_; + } + + bool HasPayload() const { - assert(slice_.get() != NULL); - return *slice_; + return (payload_.get() != NULL); + } + + const Orthanc::IDynamicObject& GetPayload() const + { + if (HasPayload()) + { + return *payload_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } } }; @@ -84,21 +98,42 @@ } - void SlicesSorter::AddSlice(Slice* slice) + void SlicesSorter::AddSlice(const CoordinateSystem3D& slice, + Orthanc::IDynamicObject* payload) { - slices_.push_back(new SliceWithDepth(slice)); + slices_.push_back(new SliceWithDepth(slice, payload)); } - const Slice& SlicesSorter::GetSlice(size_t i) const + const SlicesSorter::SliceWithDepth& SlicesSorter::GetSlice(size_t i) const { if (i >= slices_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } + else + { + assert(slices_[i] != NULL); + return *slices_[i]; + } + } - assert(slices_[i] != NULL); - return slices_[i]->GetSlice(); + + const CoordinateSystem3D& SlicesSorter::GetSliceGeometry(size_t i) const + { + return GetSlice(i).GetGeometry(); + } + + + bool SlicesSorter::HasSlicePayload(size_t i) const + { + return GetSlice(i).HasPayload(); + } + + + const Orthanc::IDynamicObject& SlicesSorter::GetSlicePayload(size_t i) const + { + return GetSlice(i).GetPayload(); } @@ -113,7 +148,7 @@ } - void SlicesSorter::Sort() + void SlicesSorter::SortInternal() { if (!hasNormal_) { @@ -131,7 +166,7 @@ for (size_t i = 0; i < slices_.size(); i++) { - if (GeometryToolbox::IsParallel(normal, slices_[i]->GetSlice().GetGeometry().GetNormal())) + if (GeometryToolbox::IsParallel(normal, slices_[i]->GetGeometry().GetNormal())) { // This slice is compatible with the selected normal slices_[pos] = slices_[i]; @@ -155,7 +190,7 @@ bool found = false; - for (size_t i = 0; !found && i < GetSliceCount(); i++) + for (size_t i = 0; !found && i < GetSlicesCount(); i++) { const Vector& normal = GetSlice(i).GetGeometry().GetNormal(); @@ -190,8 +225,8 @@ for (size_t i = 0; !found && i < normalCandidates.size(); i++) { unsigned int count = normalCount[i]; - if (count == GetSliceCount() || - count + 1 == GetSliceCount()) + if (count == GetSlicesCount() || + count + 1 == GetSlicesCount()) { normal = normalCandidates[i]; found = true; @@ -202,21 +237,90 @@ } - bool SlicesSorter::LookupSlice(size_t& index, - const CoordinateSystem3D& slice) const + bool SlicesSorter::Sort() { - // TODO Turn this linear-time lookup into a log-time lookup, - // keeping track of whether the slices are sorted along the normal - - for (size_t i = 0; i < slices_.size(); i++) + if (GetSlicesCount() > 0) { - if (slices_[i]->GetSlice().ContainsPlane(slice)) + Vector normal; + if (SelectNormal(normal)) { - index = i; + FilterNormal(normal); + SetNormal(normal); + SortInternal(); return true; } } return false; } + + + bool SlicesSorter::LookupClosestSlice(size_t& index, + double& distance, + const CoordinateSystem3D& slice) const + { + // TODO Turn this linear-time lookup into a log-time lookup, + // keeping track of whether the slices are sorted along the normal + + bool found = false; + + distance = std::numeric_limits::infinity(); + + for (size_t i = 0; i < slices_.size(); i++) + { + assert(slices_[i] != NULL); + + double tmp; + if (CoordinateSystem3D::GetDistance(tmp, slices_[i]->GetGeometry(), slice)) + { + if (!found || + tmp < distance) + { + index = i; + distance = tmp; + found = true; + } + } + } + + return found; + } + + + double SlicesSorter::ComputeSpacingBetweenSlices() const + { + if (GetSlicesCount() <= 1) + { + // This is a volume that is empty or that contains one single + // slice: Choose a dummy z-dimension for voxels + return 1.0; + } + + const OrthancStone::CoordinateSystem3D& reference = GetSliceGeometry(0); + + double referencePosition = reference.ProjectAlongNormal(reference.GetOrigin()); + + double p = reference.ProjectAlongNormal(GetSliceGeometry(1).GetOrigin()); + double spacingZ = p - referencePosition; + + if (spacingZ <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "Please call the Sort() method before"); + } + + for (size_t i = 1; i < GetSlicesCount(); i++) + { + OrthancStone::Vector p = reference.GetOrigin() + spacingZ * static_cast(i) * reference.GetNormal(); + double d = boost::numeric::ublas::norm_2(p - GetSliceGeometry(i).GetOrigin()); + + if (!OrthancStone::LinearAlgebra::IsNear(d, 0, 0.001 /* tolerance expressed in mm */)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, + "The origins of the slices of a volume image are not regularly spaced"); + } + } + + return spacingZ; + } } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Toolbox/SlicesSorter.h --- a/Framework/Toolbox/SlicesSorter.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Toolbox/SlicesSorter.h Thu May 16 09:11:14 2019 +0200 @@ -21,10 +21,13 @@ #pragma once -#include "Slice.h" +#include "CoordinateSystem3D.h" + +#include namespace OrthancStone { + // TODO - Rename this as "PlanesSorter" class SlicesSorter : public boost::noncopyable { private: @@ -36,6 +39,16 @@ Slices slices_; bool hasNormal_; + const SliceWithDepth& GetSlice(size_t i) const; + + void SetNormal(const Vector& normal); + + void SortInternal(); + + void FilterNormal(const Vector& normal); + + bool SelectNormal(Vector& normal) const; + public: SlicesSorter() : hasNormal_(false) { @@ -48,24 +61,35 @@ slices_.reserve(count); } - void AddSlice(Slice* slice); // Takes ownership + void AddSlice(const CoordinateSystem3D& plane) + { + AddSlice(plane, NULL); + } - size_t GetSliceCount() const + void AddSlice(const CoordinateSystem3D& plane, + Orthanc::IDynamicObject* payload); // Takes ownership + + size_t GetSlicesCount() const { return slices_.size(); } - const Slice& GetSlice(size_t i) const; + const CoordinateSystem3D& GetSliceGeometry(size_t i) const; - void SetNormal(const Vector& normal); + bool HasSlicePayload(size_t i) const; - void Sort(); + const Orthanc::IDynamicObject& GetSlicePayload(size_t i) const; - void FilterNormal(const Vector& normal); + // WARNING - Apply the sorting algorithm can reduce the number of + // slices. This is notably the case if all the slices are not + // parallel to the reference normal that will be selected. + bool Sort(); - bool SelectNormal(Vector& normal) const; + bool LookupClosestSlice(size_t& index, + double& distance, + const CoordinateSystem3D& slice) const; - bool LookupSlice(size_t& index, - const CoordinateSystem3D& slice) const; + // WARNING - The slices must have been sorted before calling this method + double ComputeSpacingBetweenSlices() const; }; } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Viewport/IMouseTracker.h --- a/Framework/Viewport/IMouseTracker.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Viewport/IMouseTracker.h Thu May 16 09:11:14 2019 +0200 @@ -62,7 +62,5 @@ virtual void MouseMove(int x, int y, const std::vector& displayTouches) = 0; - - virtual bool IsTouchTracker() const {return false;} }; } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Viewport/IViewport.h --- a/Framework/Viewport/IViewport.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Viewport/IViewport.h Thu May 16 09:11:14 2019 +0200 @@ -35,7 +35,7 @@ class IViewport : public IObservable { public: - typedef OriginMessage ViewportChangedMessage; + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ViewportChangedMessage, IViewport); IViewport(MessageBroker& broker) : IObservable(broker) @@ -89,7 +89,7 @@ // TODO Why should this be virtual? virtual void NotifyContentChanged() { - EmitMessage(ViewportChangedMessage(*this)); + BroadcastMessage(ViewportChangedMessage(*this)); } }; } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Volumes/ISlicedVolume.h --- a/Framework/Volumes/ISlicedVolume.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Volumes/ISlicedVolume.h Thu May 16 09:11:14 2019 +0200 @@ -29,14 +29,16 @@ class ISlicedVolume : public IObservable { public: - typedef OriginMessage ContentChangedMessage; - typedef OriginMessage GeometryErrorMessage; - typedef OriginMessage GeometryReadyMessage; - typedef OriginMessage VolumeReadyMessage; + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, ISlicedVolume); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, ISlicedVolume); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, ISlicedVolume); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, VolumeReadyMessage, ISlicedVolume); - class SliceContentChangedMessage : - public OriginMessage + + class SliceContentChangedMessage : public OriginMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: size_t sliceIndex_; const Slice& slice_; diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Volumes/IVolumeLoader.h --- a/Framework/Volumes/IVolumeLoader.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Volumes/IVolumeLoader.h Thu May 16 09:11:14 2019 +0200 @@ -28,9 +28,9 @@ class IVolumeLoader : public IObservable { public: - typedef OriginMessage GeometryReadyMessage; - typedef OriginMessage GeometryErrorMessage; - typedef OriginMessage ContentChangedMessage; + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeLoader); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeLoader); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeLoader); IVolumeLoader(MessageBroker& broker) : IObservable(broker) diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Volumes/ImageBuffer3D.cpp --- a/Framework/Volumes/ImageBuffer3D.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Volumes/ImageBuffer3D.cpp Thu May 16 09:11:14 2019 +0200 @@ -118,8 +118,9 @@ { LinearAlgebra::AssignVector(voxelDimensions_, 1, 1, 1); - LOG(INFO) << "Created an image of " - << (GetEstimatedMemorySize() / (1024ll * 1024ll)) << "MB"; + LOG(INFO) << "Created a 3D image of size " << width << "x" << height + << "x" << depth << " in " << Orthanc::EnumerationToString(format) + << " (" << (GetEstimatedMemorySize() / (1024ll * 1024ll)) << "MB)"; } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Volumes/StructureSetLoader.cpp --- a/Framework/Volumes/StructureSetLoader.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Volumes/StructureSetLoader.cpp Thu May 16 09:11:14 2019 +0200 @@ -44,7 +44,7 @@ MessagingToolbox::ConvertDataset(slice, dataset); structureSet_->AddReferencedSlice(slice); - EmitMessage(ContentChangedMessage(*this)); + BroadcastMessage(ContentChangedMessage(*this)); } @@ -63,7 +63,7 @@ new Callable(*this, &StructureSetLoader::OnLookupCompleted)); } - EmitMessage(GeometryReadyMessage(*this)); + BroadcastMessage(GeometryReadyMessage(*this)); } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Volumes/VolumeReslicer.cpp --- a/Framework/Volumes/VolumeReslicer.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Volumes/VolumeReslicer.cpp Thu May 16 09:11:14 2019 +0200 @@ -655,8 +655,7 @@ float rescaleSlope, float rescaleIntercept) { - if (windowing == ImageWindowing_Custom || - windowing == ImageWindowing_Default) + if (windowing == ImageWindowing_Custom) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Widgets/SliceViewerWidget.cpp --- a/Framework/Widgets/SliceViewerWidget.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Widgets/SliceViewerWidget.cpp Thu May 16 09:11:14 2019 +0200 @@ -527,7 +527,7 @@ InvalidateAllLayers(); // TODO Removing this line avoid loading twice the image in WASM } - EmitMessage(DisplayedSliceMessage(*this, displayedSlice)); + BroadcastMessage(DisplayedSliceMessage(*this, displayedSlice)); } @@ -541,7 +541,7 @@ changedLayers_[i] = true; //layers_[i]->ScheduleLayerCreation(plane_); } - EmitMessage(GeometryChangedMessage(*this)); + BroadcastMessage(GeometryChangedMessage(*this)); } @@ -579,7 +579,7 @@ InvalidateLayer(index); } - EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); } @@ -594,7 +594,7 @@ } } - EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); } @@ -607,7 +607,7 @@ UpdateLayer(index, message.CreateRenderer(), message.GetSlice()); } - EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); } @@ -621,7 +621,7 @@ // TODO //UpdateLayer(index, new SliceOutlineRenderer(slice), slice); - EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); } } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/Widgets/SliceViewerWidget.h --- a/Framework/Widgets/SliceViewerWidget.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/Widgets/SliceViewerWidget.h Thu May 16 09:11:14 2019 +0200 @@ -36,12 +36,15 @@ public IObservable { public: - typedef OriginMessage GeometryChangedMessage; - typedef OriginMessage ContentChangedMessage; + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryChangedMessage, SliceViewerWidget); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, SliceViewerWidget); + // TODO - Use this message in ReferenceLineSource - class DisplayedSliceMessage : public OriginMessage + class DisplayedSliceMessage : public OriginMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + private: const Slice& slice_; diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Framework/dev.h --- a/Framework/dev.h Tue May 14 18:24:12 2019 +0200 +++ b/Framework/dev.h Thu May 16 09:11:14 2019 +0200 @@ -110,25 +110,25 @@ { assert(&message.GetOrigin() == &loader_); - if (loader_.GetSliceCount() == 0) + if (loader_.GetSlicesCount() == 0) { LOG(ERROR) << "Empty volume image"; - EmitMessage(ISlicedVolume::GeometryErrorMessage(*this)); + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); return; } - for (size_t i = 1; i < loader_.GetSliceCount(); i++) + for (size_t i = 1; i < loader_.GetSlicesCount(); i++) { if (!IsCompatible(loader_.GetSlice(0), loader_.GetSlice(i))) { - EmitMessage(ISlicedVolume::GeometryErrorMessage(*this)); + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); return; } } double spacingZ; - if (loader_.GetSliceCount() > 1) + if (loader_.GetSlicesCount() > 1) { spacingZ = GetDistance(loader_.GetSlice(0), loader_.GetSlice(1)); } @@ -139,13 +139,13 @@ spacingZ = 1; } - for (size_t i = 1; i < loader_.GetSliceCount(); i++) + for (size_t i = 1; i < loader_.GetSlicesCount(); i++) { if (!LinearAlgebra::IsNear(spacingZ, GetDistance(loader_.GetSlice(i - 1), loader_.GetSlice(i)), 0.001 /* this is expressed in mm */)) { LOG(ERROR) << "The distance between successive slices is not constant in a volume image"; - EmitMessage(ISlicedVolume::GeometryErrorMessage(*this)); + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); return; } } @@ -154,16 +154,16 @@ unsigned int height = loader_.GetSlice(0).GetHeight(); Orthanc::PixelFormat format = loader_.GetSlice(0).GetConverter().GetExpectedPixelFormat(); LOG(INFO) << "Creating a volume image of size " << width << "x" << height - << "x" << loader_.GetSliceCount() << " in " << Orthanc::EnumerationToString(format); + << "x" << loader_.GetSlicesCount() << " in " << Orthanc::EnumerationToString(format); - image_.reset(new ImageBuffer3D(format, width, height, static_cast(loader_.GetSliceCount()), computeRange_)); + image_.reset(new ImageBuffer3D(format, width, height, static_cast(loader_.GetSlicesCount()), computeRange_)); image_->SetAxialGeometry(loader_.GetSlice(0).GetGeometry()); image_->SetVoxelDimensions(loader_.GetSlice(0).GetPixelSpacingX(), loader_.GetSlice(0).GetPixelSpacingY(), spacingZ); image_->Clear(); - downloadStack_.reset(new DownloadStack(static_cast(loader_.GetSliceCount()))); - pendingSlices_ = loader_.GetSliceCount(); + downloadStack_.reset(new DownloadStack(static_cast(loader_.GetSlicesCount()))); + pendingSlices_ = loader_.GetSlicesCount(); for (unsigned int i = 0; i < 4; i++) // Limit to 4 simultaneous downloads { @@ -172,7 +172,7 @@ // TODO Check the DicomFrameConverter are constant - EmitMessage(ISlicedVolume::GeometryReadyMessage(*this)); + BroadcastMessage(ISlicedVolume::GeometryReadyMessage(*this)); } @@ -181,7 +181,7 @@ assert(&message.GetOrigin() == &loader_); LOG(ERROR) << "Unable to download a volume image"; - EmitMessage(ISlicedVolume::GeometryErrorMessage(*this)); + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); } @@ -194,12 +194,12 @@ Orthanc::ImageProcessing::Copy(writer.GetAccessor(), message.GetImage()); } - EmitMessage(ISlicedVolume::SliceContentChangedMessage + BroadcastMessage(ISlicedVolume::SliceContentChangedMessage (*this, message.GetSliceIndex(), message.GetSlice())); if (pendingSlices_ == 1) { - EmitMessage(ISlicedVolume::VolumeReadyMessage(*this)); + BroadcastMessage(ISlicedVolume::VolumeReadyMessage(*this)); pendingSlices_ = 0; } else if (pendingSlices_ > 1) @@ -263,9 +263,9 @@ loader_.ScheduleLoadFrame(instanceId, frame); } - virtual size_t GetSliceCount() const + virtual size_t GetSlicesCount() const { - return loader_.GetSliceCount(); + return loader_.GetSlicesCount(); } virtual const Slice& GetSlice(size_t index) const @@ -317,7 +317,7 @@ { double thickness; - size_t n = volume.GetSliceCount(); + size_t n = volume.GetSlicesCount(); if (n > 1) { const Slice& a = volume.GetSlice(0); @@ -349,7 +349,7 @@ width_ = axial.GetWidth(); height_ = axial.GetHeight(); - depth_ = volume.GetSliceCount(); + depth_ = volume.GetSlicesCount(); pixelSpacingX_ = axial.GetPixelSpacingX(); pixelSpacingY_ = axial.GetPixelSpacingY(); @@ -364,7 +364,7 @@ double axialThickness = ComputeAxialThickness(volume); width_ = axial.GetWidth(); - height_ = static_cast(volume.GetSliceCount()); + height_ = static_cast(volume.GetSlicesCount()); depth_ = axial.GetHeight(); pixelSpacingX_ = axial.GetPixelSpacingX(); @@ -372,7 +372,7 @@ sliceThickness_ = axial.GetPixelSpacingY(); Vector origin = axial.GetGeometry().GetOrigin(); - origin += (static_cast(volume.GetSliceCount() - 1) * + origin += (static_cast(volume.GetSlicesCount() - 1) * axialThickness * axial.GetGeometry().GetNormal()); reference_ = CoordinateSystem3D(origin, @@ -386,7 +386,7 @@ double axialThickness = ComputeAxialThickness(volume); width_ = axial.GetHeight(); - height_ = static_cast(volume.GetSliceCount()); + height_ = static_cast(volume.GetSlicesCount()); depth_ = axial.GetWidth(); pixelSpacingX_ = axial.GetPixelSpacingY(); @@ -394,7 +394,7 @@ sliceThickness_ = axial.GetPixelSpacingX(); Vector origin = axial.GetGeometry().GetOrigin(); - origin += (static_cast(volume.GetSliceCount() - 1) * + origin += (static_cast(volume.GetSlicesCount() - 1) * axialThickness * axial.GetGeometry().GetNormal()); reference_ = CoordinateSystem3D(origin, @@ -406,7 +406,7 @@ VolumeImageGeometry(const OrthancVolumeImage& volume, VolumeProjection projection) { - if (volume.GetSliceCount() == 0) + if (volume.GetSlicesCount() == 0) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } @@ -432,7 +432,7 @@ } } - size_t GetSliceCount() const + size_t GetSlicesCount() const { return depth_; } @@ -540,21 +540,21 @@ coronalGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Coronal)); sagittalGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Sagittal)); - EmitMessage(IVolumeSlicer::GeometryReadyMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); } void OnGeometryError(const ISlicedVolume::GeometryErrorMessage& message) { assert(&message.GetOrigin() == &volume_); - EmitMessage(IVolumeSlicer::GeometryErrorMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); } void OnContentChanged(const ISlicedVolume::ContentChangedMessage& message) { assert(&message.GetOrigin() == &volume_); - EmitMessage(IVolumeSlicer::ContentChangedMessage(*this)); + BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); } void OnSliceContentChanged(const ISlicedVolume::SliceContentChangedMessage& message) @@ -564,7 +564,7 @@ //IVolumeSlicer::OnSliceContentChange(slice); // TODO Improve this? - EmitMessage(IVolumeSlicer::ContentChangedMessage(*this)); + BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); } const VolumeImageGeometry& GetProjectionGeometry(VolumeProjection projection) @@ -697,14 +697,14 @@ RendererFactory factory(*frame, *slice, isFullQuality); - EmitMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice->GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice->GetGeometry())); return; } } // Error CoordinateSystem3D slice; - EmitMessage(IVolumeSlicer::LayerErrorMessage(*this, slice)); + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, slice)); } }; @@ -728,7 +728,7 @@ dynamic_cast(message.GetOrigin()); slices_.reset(new VolumeImageGeometry(image, projection_)); - SetSlice(slices_->GetSliceCount() / 2); + SetSlice(slices_->GetSlicesCount() / 2); widget_.FitContent(); } @@ -817,7 +817,7 @@ return slices_.get() != NULL; } - size_t GetSliceCount() const + size_t GetSlicesCount() const { if (slices_.get() == NULL) { @@ -825,7 +825,7 @@ } else { - return slices_->GetSliceCount(); + return slices_->GetSlicesCount(); } } @@ -840,9 +840,9 @@ slice = 0; } - if (slice >= static_cast(slices_->GetSliceCount())) + if (slice >= static_cast(slices_->GetSlicesCount())) { - slice = static_cast(slices_->GetSliceCount()) - 1; + slice = static_cast(slices_->GetSlicesCount()) - 1; } if (slice != static_cast(slice_)) @@ -906,7 +906,7 @@ IVolumeSlicer(broker), otherPlane_(otherPlane) { - EmitMessage(IVolumeSlicer::GeometryReadyMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); } virtual bool GetExtent(std::vector& points, @@ -929,7 +929,7 @@ viewportSlice.GetOrigin(), viewportSlice.GetNormal())) { // The two slice are parallel, don't try and display the intersection - EmitMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); } else { @@ -945,12 +945,12 @@ extent.GetX2(), extent.GetY2())) { RendererFactory factory(x1, y1, x2, y2, slice); - EmitMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, reference.GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, reference.GetGeometry())); } else { // Error: Parallel slices - EmitMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); } } } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Platforms/Generic/DelayedCallCommand.cpp --- a/Platforms/Generic/DelayedCallCommand.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Platforms/Generic/DelayedCallCommand.cpp Thu May 16 09:11:14 2019 +0200 @@ -36,7 +36,7 @@ callback_(callback), payload_(payload), context_(context), - expirationTimePoint_(boost::chrono::system_clock::now() + boost::chrono::milliseconds(timeoutInMs)), + expirationTimePoint_(boost::posix_time::microsec_clock::local_time() + boost::posix_time::milliseconds(timeoutInMs)), timeoutInMs_(timeoutInMs) { } @@ -44,9 +44,9 @@ void DelayedCallCommand::Execute() { - while (boost::chrono::system_clock::now() < expirationTimePoint_) + while (boost::posix_time::microsec_clock::local_time() < expirationTimePoint_) { - boost::this_thread::sleep_for(boost::chrono::milliseconds(1)); + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); } } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Platforms/Generic/DelayedCallCommand.h --- a/Platforms/Generic/DelayedCallCommand.h Tue May 14 18:24:12 2019 +0200 +++ b/Platforms/Generic/DelayedCallCommand.h Thu May 16 09:11:14 2019 +0200 @@ -27,7 +27,8 @@ #include "../../Framework/Messages/IObservable.h" #include "../../Framework/Messages/ICallable.h" #include "../../Applications/Generic/NativeStoneApplicationContext.h" -#include + +#include namespace OrthancStone { @@ -37,7 +38,7 @@ std::auto_ptr > callback_; std::auto_ptr payload_; NativeStoneApplicationContext& context_; - boost::chrono::system_clock::time_point expirationTimePoint_; + boost::posix_time::ptime expirationTimePoint_; unsigned int timeoutInMs_; public: diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Platforms/Wasm/Defaults.cpp --- a/Platforms/Wasm/Defaults.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Platforms/Wasm/Defaults.cpp Thu May 16 09:11:14 2019 +0200 @@ -5,9 +5,12 @@ #include #include "Framework/Widgets/TestCairoWidget.h" #include +#include +#include +#include + #include -#include "Applications/Wasm/StartupParametersBuilder.h" -#include "Platforms/Wasm/WasmPlatformApplicationAdapter.h" + static unsigned int width_ = 0; static unsigned int height_ = 0; @@ -88,6 +91,10 @@ printf("StartWasmApplication\n"); + Orthanc::Logging::SetErrorWarnInfoTraceLoggingFunctions( + stone_console_error, stone_console_warning, + stone_console_info, stone_console_trace); + // recreate a command line from uri arguments and parse it boost::program_options::variables_map parameters; boost::program_options::options_description options; @@ -106,6 +113,16 @@ printf("StartWasmApplication - completed\n"); } + bool EMSCRIPTEN_KEEPALIVE WasmIsTraceLevelEnabled() + { + return Orthanc::Logging::IsTraceLevelEnabled(); + } + + bool EMSCRIPTEN_KEEPALIVE WasmIsInfoLevelEnabled() + { + return Orthanc::Logging::IsInfoLevelEnabled(); + } + void EMSCRIPTEN_KEEPALIVE WasmDoAnimation() { for (auto viewport : viewports_) { diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Platforms/Wasm/Defaults.h --- a/Platforms/Wasm/Defaults.h Tue May 14 18:24:12 2019 +0200 +++ b/Platforms/Wasm/Defaults.h Thu May 16 09:11:14 2019 +0200 @@ -18,7 +18,11 @@ extern void ScheduleWebViewportRedrawFromCpp(ViewportHandle cppViewportHandle); extern void UpdateStoneApplicationStatusFromCppWithString(const char* statusUpdateMessage); extern void UpdateStoneApplicationStatusFromCppWithSerializedMessage(const char* statusUpdateMessage); - + extern void stone_console_error(const char*); + extern void stone_console_warning(const char*); + extern void stone_console_info(const char*); + extern void stone_console_trace(const char*); + // C++ methods accessible from JS extern void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle cppViewportHandle); extern void EMSCRIPTEN_KEEPALIVE SetStartupParameter(const char* keyc, const char* value); diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Platforms/Wasm/default-library.js --- a/Platforms/Wasm/default-library.js Tue May 14 18:24:12 2019 +0200 +++ b/Platforms/Wasm/default-library.js Thu May 16 09:11:14 2019 +0200 @@ -1,21 +1,49 @@ // this file contains the JS method you want to expose to C++ code mergeInto(LibraryManager.library, { + ScheduleWebViewportRedrawFromCpp: function(cppViewportHandle) { window.ScheduleWebViewportRedraw(cppViewportHandle); }, + CreateWasmViewportFromCpp: function(htmlCanvasId) { return window.CreateWasmViewport(htmlCanvasId); }, + // each time the StoneApplication updates its status, it may signal it // through this method. i.e, to change the status of a button in the web interface UpdateStoneApplicationStatusFromCppWithString: function(statusUpdateMessage) { var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage); window.UpdateWebApplicationWithString(statusUpdateMessage_); }, + // same, but with a serialized message UpdateStoneApplicationStatusFromCppWithSerializedMessage: function(statusUpdateMessage) { var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage); window.UpdateWebApplicationWithSerializedMessage(statusUpdateMessage_); + }, + + // These functions are called from C++ (through an extern declaration) + // and call the standard logger that, here, routes to the console. + + stone_console_error : function(message) { + var text = UTF8ToString(message); + window.errorFromCpp(text); + }, + + stone_console_warning : function(message) { + var text = UTF8ToString(message); + window.warningFromCpp(text); + }, + + stone_console_info: function(message) { + var text = UTF8ToString(message); + window.infoFromCpp(text); + }, + + stone_console_trace : function(message) { + var text = UTF8ToString(message); + window.debugFromCpp(text); } + }); diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Platforms/Wasm/logger.ts --- a/Platforms/Wasm/logger.ts Tue May 14 18:24:12 2019 +0200 +++ b/Platforms/Wasm/logger.ts Thu May 16 09:11:14 2019 +0200 @@ -1,90 +1,111 @@ -export enum LogSource { - Cpp, - Typescript -} - -export class StandardConsoleLogger { - public showSource: boolean = true; - - public debug(...args: any[]): void { - this._debug(LogSource.Typescript, ...args); - } - - public info(...args: any[]): void { - this._info(LogSource.Typescript, ...args); - } - - public infoFromCpp(message: string): void { - this._info(LogSource.Cpp, message); - } - - public warning(...args: any[]): void { - this._warning(LogSource.Typescript, ...args); - } - - public error(...args: any[]): void { - this._error(LogSource.Typescript, ...args); - } - - public errorFromCpp(message: string): void { - this._error(LogSource.Cpp, message); - } - - public _debug(source: LogSource, ...args: any[]): void { - var output = this.getOutput(source, args); - console.debug(...output); - } - - private _info(source: LogSource, ...args: any[]): void { - var output = this.getOutput(source, args); - console.info(...output); - } - - public _warning(source: LogSource, ...args: any[]): void { - var output = this.getOutput(source, args); - console.warn(...output); - } - - public _error(source: LogSource, ...args: any[]): void { - var output = this.getOutput(source, args); - console.error(...output); - } - - - private getOutput(source: LogSource, args: any[]): any[] { - var prefix = this.getPrefix(); - var prefixAndSource = []; - - if (prefix != null) { - prefixAndSource = [prefix]; - } - - if (this.showSource) { - if (source == LogSource.Typescript) { - prefixAndSource = [...prefixAndSource, "TS "]; - } else if (source == LogSource.Cpp) { - prefixAndSource = [...prefixAndSource, "C++"]; - } - } - - if (prefixAndSource.length > 0) { - prefixAndSource = [...prefixAndSource, "|"]; - } - - return [...prefixAndSource, ...args]; - } - - protected getPrefix(): string { - return null; - } -} - -export class TimeConsoleLogger extends StandardConsoleLogger { - protected getPrefix(): string { - let now = new Date(); - let timeString = now.getHours().toString().padStart(2, "0") + ":" + now.getMinutes().toString().padStart(2, "0") + ":" + now.getSeconds().toString().padStart(2, "0") + "." + now.getMilliseconds().toString().padStart(3, "0"); - return timeString; - } -} - -export var defaultLogger: StandardConsoleLogger = new TimeConsoleLogger(); +export enum LogSource { + Cpp, + Typescript +} + +export class StandardConsoleLogger { + public showSource: boolean = true; + + public debug(...args: any[]): void { + this._debug(LogSource.Typescript, ...args); + } + + public debugFromCpp(...args: any[]): void { + this._debug(LogSource.Cpp, ...args); + } + + public info(...args: any[]): void { + this._info(LogSource.Typescript, ...args); + } + + public infoFromCpp(message: string): void { + this._info(LogSource.Cpp, message); + } + + public warning(...args: any[]): void { + this._warning(LogSource.Typescript, ...args); + } + + public warningFromCpp(message: string): void { + this._warning(LogSource.Cpp, message); + } + + public error(...args: any[]): void { + this._error(LogSource.Typescript, ...args); + } + + public errorFromCpp(message: string): void { + this._error(LogSource.Cpp, message); + } + + public _debug(source: LogSource, ...args: any[]): void { + if (( window).IsTraceLevelEnabled) + { + if (( window).IsTraceLevelEnabled()) + { + var output = this.getOutput(source, args); + console.debug(...output); + } + } + } + + private _info(source: LogSource, ...args: any[]): void { + if (( window).IsInfoLevelEnabled) + { + if (( window).IsInfoLevelEnabled()) + { + var output = this.getOutput(source, args); + console.info(...output); + } + } + } + + public _warning(source: LogSource, ...args: any[]): void { + var output = this.getOutput(source, args); + console.warn(...output); + } + + public _error(source: LogSource, ...args: any[]): void { + var output = this.getOutput(source, args); + console.error(...output); + } + + + private getOutput(source: LogSource, args: any[]): any[] { + var prefix = this.getPrefix(); + var prefixAndSource = []; + + if (prefix != null) { + prefixAndSource = [prefix]; + } + + if (this.showSource) { + if (source == LogSource.Typescript) { + prefixAndSource = [...prefixAndSource, "TS "]; + } else if (source == LogSource.Cpp) { + prefixAndSource = [...prefixAndSource, "C++"]; + } + } + + if (prefixAndSource.length > 0) { + prefixAndSource = [...prefixAndSource, "|"]; + } + + return [...prefixAndSource, ...args]; + } + + protected getPrefix(): string { + return null; + } +} + +export class TimeConsoleLogger extends StandardConsoleLogger { + protected getPrefix(): string { + let now = new Date(); + let timeString = now.getHours().toString().padStart(2, "0") + ":" + now.getMinutes().toString().padStart(2, "0") + ":" + now.getSeconds().toString().padStart(2, "0") + "." + now.getMilliseconds().toString().padStart(3, "0"); + return timeString; + } +} + +export var defaultLogger: StandardConsoleLogger = new TimeConsoleLogger(); + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Platforms/Wasm/stone-framework-loader.ts --- a/Platforms/Wasm/stone-framework-loader.ts Tue May 14 18:24:12 2019 +0200 +++ b/Platforms/Wasm/stone-framework-loader.ts Thu May 16 09:11:14 2019 +0200 @@ -59,6 +59,11 @@ { Logger.defaultLogger.debug('Initializing WebAssembly Module'); + ( window).errorFromCpp = function(text:any) { Logger.defaultLogger.errorFromCpp(text); }; + ( window).warningFromCpp = function(text:any) { Logger.defaultLogger.warningFromCpp(text); }; + ( window).infoFromCpp = function(text:any) { Logger.defaultLogger.infoFromCpp(text); }; + ( window).debugFromCpp = function(text:any) { Logger.defaultLogger.debugFromCpp(text); }; + // ( window). ( window).StoneFrameworkModule = { preRun: [ @@ -76,12 +81,6 @@ callback(); } ], - print: function(text : string) { - Logger.defaultLogger.infoFromCpp(text); - }, - printErr: function(text : string) { - Logger.defaultLogger.errorFromCpp(text); - }, totalDependencies: 0 }; diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Platforms/Wasm/wasm-application-runner.ts --- a/Platforms/Wasm/wasm-application-runner.ts Tue May 14 18:24:12 2019 +0200 +++ b/Platforms/Wasm/wasm-application-runner.ts Thu May 16 09:11:14 2019 +0200 @@ -27,7 +27,8 @@ WasmDoAnimation(); } - setTimeout(DoAnimationThread, 100); // Update the viewport content every 100ms if need be + // Update the viewport content every 100ms if need be + setTimeout(DoAnimationThread, 100); } function GetUriParameters(): Map { @@ -42,10 +43,12 @@ var tmp = tokens[i].split('='); if (tmp.length == 2) { result[tmp[0]] = decodeURIComponent(tmp[1]); + } else if(tmp.length == 1) { + // if there is no '=', we treat ot afterwards as a flag-style param + result[tmp[0]] = ""; } } - - return result; + return result; } else { return new Map(); @@ -65,6 +68,8 @@ for (let key in parameters) { if (parameters.hasOwnProperty(key)) { + Logger.defaultLogger.debug( + `About to call SetStartupParameter("${key}","${parameters[key]}")`); SetStartupParameter(key, parameters[key]); } } @@ -92,6 +97,8 @@ CreateCppViewport = ( window).StoneFrameworkModule.cwrap('CreateCppViewport', 'number', []); ReleaseCppViewport = ( window).StoneFrameworkModule.cwrap('ReleaseCppViewport', null, ['number']); StartWasmApplication = ( window).StoneFrameworkModule.cwrap('StartWasmApplication', null, ['string']); + ( window).IsTraceLevelEnabled = ( window).StoneFrameworkModule.cwrap('WasmIsTraceLevelEnabled', 'boolean', null); + ( window).IsInfoLevelEnabled = ( window).StoneFrameworkModule.cwrap('WasmIsInfoLevelEnabled', 'boolean', null); ( window).WasmWebService_NotifyCachedSuccess = ( window).StoneFrameworkModule.cwrap('WasmWebService_NotifyCachedSuccess', null, ['number']); ( window).WasmWebService_NotifySuccess = ( window).StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, ['number', 'string', 'array', 'number', 'number']); diff -r 9e3bb8b4f726 -r 3f13f7f1b55d README.md --- a/README.md Tue May 14 18:24:12 2019 +0200 +++ b/README.md Thu May 16 09:11:14 2019 +0200 @@ -236,9 +236,22 @@ url="https://doi.org/10.1007/s10278-018-0082-y" } -Various notes to be deleted +Various notes to be sorted --------------------------- -class BaseCommand : public ICommand +How to build the newest (2019-04-29) SDL samples under Windows, *inside* a +folder that is sibling to the orthanc-stone folder: + +``` +cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl +``` -RadiographySceneCommand -GenericNoArgCommand +And under Ubuntu (note the /mnt/c/osi/dev/orthanc folder): +``` +cmake -G "Ninja" -DENABLE_OPENGL=ON -DSTATIC_BUILD=OFF -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="/mnt/c/osi/dev/orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl +``` + +TODO trackers: +- text overlay 50% --> ColorTextureLayer 50% +- angle tracker: draw arcs + + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Resources/CMake/FreetypeConfiguration.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/FreetypeConfiguration.cmake Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,87 @@ +if (STATIC_BUILD OR NOT USE_SYSTEM_FREETYPE) + set(FREETYPE_SOURCES_DIR ${CMAKE_BINARY_DIR}/freetype-2.9.1) + set(FREETYPE_URL "http://orthanc.osimis.io/ThirdPartyDownloads/freetype-2.9.1.tar.gz") + set(FREETYPE_MD5 "3adb0e35d3c100c456357345ccfa8056") + + DownloadPackage(${FREETYPE_MD5} ${FREETYPE_URL} "${FREETYPE_SOURCES_DIR}") + + include_directories(BEFORE + ${FREETYPE_SOURCES_DIR}/include/ + ) + + add_definitions( + -DFT2_BUILD_LIBRARY + -DFT_CONFIG_OPTION_NO_ASSEMBLER + ) + + set(FREETYPE_SOURCES + ${FREETYPE_SOURCES_DIR}/src/autofit/autofit.c + ${FREETYPE_SOURCES_DIR}/src/base/ftbase.c + ${FREETYPE_SOURCES_DIR}/src/base/ftbbox.c + ${FREETYPE_SOURCES_DIR}/src/base/ftbdf.c + ${FREETYPE_SOURCES_DIR}/src/base/ftbitmap.c + ${FREETYPE_SOURCES_DIR}/src/base/ftcid.c + ${FREETYPE_SOURCES_DIR}/src/base/ftfstype.c + ${FREETYPE_SOURCES_DIR}/src/base/ftgasp.c + ${FREETYPE_SOURCES_DIR}/src/base/ftglyph.c + ${FREETYPE_SOURCES_DIR}/src/base/ftgxval.c + ${FREETYPE_SOURCES_DIR}/src/base/ftinit.c + ${FREETYPE_SOURCES_DIR}/src/base/ftmm.c + ${FREETYPE_SOURCES_DIR}/src/base/ftotval.c + ${FREETYPE_SOURCES_DIR}/src/base/ftpatent.c + ${FREETYPE_SOURCES_DIR}/src/base/ftpfr.c + ${FREETYPE_SOURCES_DIR}/src/base/ftstroke.c + ${FREETYPE_SOURCES_DIR}/src/base/ftsynth.c + ${FREETYPE_SOURCES_DIR}/src/base/ftsystem.c + ${FREETYPE_SOURCES_DIR}/src/base/fttype1.c + ${FREETYPE_SOURCES_DIR}/src/base/ftwinfnt.c + ${FREETYPE_SOURCES_DIR}/src/bdf/bdf.c + ${FREETYPE_SOURCES_DIR}/src/bzip2/ftbzip2.c + ${FREETYPE_SOURCES_DIR}/src/cache/ftcache.c + ${FREETYPE_SOURCES_DIR}/src/cff/cff.c + ${FREETYPE_SOURCES_DIR}/src/cid/type1cid.c + ${FREETYPE_SOURCES_DIR}/src/gzip/ftgzip.c + ${FREETYPE_SOURCES_DIR}/src/lzw/ftlzw.c + ${FREETYPE_SOURCES_DIR}/src/pcf/pcf.c + ${FREETYPE_SOURCES_DIR}/src/pfr/pfr.c + ${FREETYPE_SOURCES_DIR}/src/psaux/psaux.c + ${FREETYPE_SOURCES_DIR}/src/pshinter/pshinter.c + ${FREETYPE_SOURCES_DIR}/src/psnames/psnames.c + ${FREETYPE_SOURCES_DIR}/src/raster/raster.c + ${FREETYPE_SOURCES_DIR}/src/sfnt/sfnt.c + ${FREETYPE_SOURCES_DIR}/src/smooth/smooth.c + ${FREETYPE_SOURCES_DIR}/src/truetype/truetype.c + ${FREETYPE_SOURCES_DIR}/src/type1/type1.c + ${FREETYPE_SOURCES_DIR}/src/type42/type42.c + ${FREETYPE_SOURCES_DIR}/src/winfonts/winfnt.c + ) + + if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND FREETYPE_SOURCES + ${FREETYPE_SOURCES_DIR}/builds/windows/ftdebug.c + ) + endif() + + foreach(header + ${FREETYPE_SOURCES_DIR}/include/freetype/config/ftconfig.h + ${FREETYPE_SOURCES_DIR}/include/freetype/config/ftoption.h + ) + + set_source_files_properties( + ${FREETYPE_SOURCES} + PROPERTIES OBJECT_DEPENDS ${header} + ) + endforeach() + + source_group(ThirdParty\\Freetype REGULAR_EXPRESSION ${FREETYPE_SOURCES_DIR}/.*) + +else() + include(FindFreetype) + + if (NOT FREETYPE_FOUND) + message(FATAL_ERROR "Please install the libfreetype6-dev package") + endif() + + include_directories(${FREETYPE_INCLUDE_DIRS}) + link_libraries(${FREETYPE_LIBRARIES}) +endif() diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Resources/CMake/GlewConfiguration.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/GlewConfiguration.cmake Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,44 @@ +# 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 . + + +if (STATIC_BUILD OR NOT USE_SYSTEM_GLEW) + SET(GLEW_SOURCES_DIR ${CMAKE_BINARY_DIR}/glew-2.1.0) + SET(GLEW_URL "http://orthanc.osimis.io/ThirdPartyDownloads/glew-2.1.0.tgz") + SET(GLEW_MD5 "b2ab12331033ddfaa50dc39345343980") + DownloadPackage(${GLEW_MD5} ${GLEW_URL} "${GLEW_SOURCES_DIR}") + + set(GLEW_SOURCES + ${GLEW_SOURCES_DIR}/src/glew.c + ) + + include_directories(${GLEW_SOURCES_DIR}/include) + + add_definitions( + -DGLEW_STATIC=1 + ) + +else() + include(FindGLEW) + if (NOT GLEW_FOUND) + message(FATAL_ERROR "Please install the libglew-dev package") + endif() + + include_directories(${GLEW_INCLUDE_DIRS}) + link_libraries(${GLEW_LIBRARIES}) +endif() diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Tue May 14 18:24:12 2019 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Thu May 16 09:11:14 2019 +0200 @@ -56,6 +56,27 @@ message(FATAL_ERROR "Cannot enable SSL in sandboxed environments") endif() endif() + +if (ENABLE_WASM) + if (NOT ORTHANC_SANDBOXED) + message(FATAL_ERROR "WebAssembly target must me configured as sandboxed") + endif() + + if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + message(FATAL_ERROR "WebAssembly target requires the emscripten compiler") + endif() + + add_definitions(-DORTHANC_ENABLE_WASM=1) +else() + if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR + CMAKE_SYSTEM_NAME STREQUAL "PNaCl" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl64") + message(FATAL_ERROR "Trying to use a Web compiler for a native build") + endif() + + add_definitions(-DORTHANC_ENABLE_WASM=0) +endif() ##################################################################### @@ -67,6 +88,7 @@ include(FindPkgConfig) include(${CMAKE_CURRENT_LIST_DIR}/BoostExtendedConfiguration.cmake) include(${CMAKE_CURRENT_LIST_DIR}/CairoConfiguration.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/FreetypeConfiguration.cmake) include(${CMAKE_CURRENT_LIST_DIR}/PixmanConfiguration.cmake) @@ -87,21 +109,62 @@ elseif(ENABLE_SDL) message("SDL is enabled") include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake) - add_definitions(-DORTHANC_ENABLE_NATIVE=1) - add_definitions(-DORTHANC_ENABLE_QT=0) - add_definitions(-DORTHANC_ENABLE_SDL=1) + add_definitions( + -DORTHANC_ENABLE_NATIVE=1 + -DORTHANC_ENABLE_QT=0 + -DORTHANC_ENABLE_SDL=1 + ) elseif(ENABLE_QT) message("QT is enabled") include(${CMAKE_CURRENT_LIST_DIR}/QtConfiguration.cmake) - add_definitions(-DORTHANC_ENABLE_NATIVE=1) - add_definitions(-DORTHANC_ENABLE_QT=1) - add_definitions(-DORTHANC_ENABLE_SDL=0) + add_definitions( + -DORTHANC_ENABLE_NATIVE=1 + -DORTHANC_ENABLE_QT=1 + -DORTHANC_ENABLE_SDL=0 + ) else() message("SDL and QT are both disabled") unset(USE_SYSTEM_SDL CACHE) - add_definitions(-DORTHANC_ENABLE_SDL=0) - add_definitions(-DORTHANC_ENABLE_QT=0) - add_definitions(-DORTHANC_ENABLE_NATIVE=0) + add_definitions( + -DORTHANC_ENABLE_SDL=0 + -DORTHANC_ENABLE_QT=0 + -DORTHANC_ENABLE_NATIVE=0 + ) +endif() + + +if (ENABLE_OPENGL AND CMAKE_SYSTEM_NAME STREQUAL "Windows") + include(${CMAKE_CURRENT_LIST_DIR}/GlewConfiguration.cmake) + add_definitions( + -DORTHANC_ENABLE_GLEW=1 + ) +else() + add_definitions( + -DORTHANC_ENABLE_GLEW=0 + ) +endif() + + +if (ENABLE_OPENGL) + if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + # If including "FindOpenGL.cmake" using Emscripten (targeting + # WebAssembly), the "OPENGL_LIBRARIES" value incorrectly includes + # the "nul" library, which leads to warning message in Emscripten: + # 'shared:WARNING: emcc: cannot find library "nul"'. + include(FindOpenGL) + if (NOT OPENGL_FOUND) + message(FATAL_ERROR "Cannot find OpenGL on your system") + endif() + + link_libraries(${OPENGL_LIBRARIES}) + endif() + + add_definitions( + -DGL_GLEXT_PROTOTYPES=1 + -DORTHANC_ENABLE_OPENGL=1 + ) +else() + add_definitions(-DORTHANC_ENABLE_OPENGL=0) endif() @@ -124,6 +187,8 @@ add_definitions(-DCHECK_OBSERVERS_MESSAGES) endif() + + ##################################################################### ## Embed the colormaps into the binaries ##################################################################### @@ -200,10 +265,11 @@ ) if (ENABLE_SDL) list(APPEND APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlStoneApplicationRunner.cpp + ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlCairoSurface.cpp ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlEngine.cpp - ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlCairoSurface.cpp + ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOpenGLWindow.cpp ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOrthancSurface.cpp + ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlStoneApplicationRunner.cpp ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlWindow.cpp ) endif() @@ -243,6 +309,32 @@ #${ORTHANC_STONE_ROOT}/Framework/Layers/SeriesFrameRendererFactory.cpp #${ORTHANC_STONE_ROOT}/Framework/Layers/SingleFrameRendererFactory.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/CairoCompositor.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ColorTextureSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/FloatTextureSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/InfoPanelSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoTextRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CompositorHelper.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/FixedPointAligner.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PanSceneTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PointerEvent.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PolylineSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/RotateSceneTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Scene2D.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextureBaseSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ZoomSceneTracker.cpp + + ${ORTHANC_STONE_ROOT}/Framework/Fonts/FontRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Fonts/Glyph.cpp + ${ORTHANC_STONE_ROOT}/Framework/Fonts/GlyphAlphabet.cpp + ${ORTHANC_STONE_ROOT}/Framework/Fonts/GlyphBitmapAlphabet.cpp + ${ORTHANC_STONE_ROOT}/Framework/Fonts/GlyphTextureAlphabet.cpp + ${ORTHANC_STONE_ROOT}/Framework/Fonts/TextBoundingBox.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/CircleMeasureTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/ColorFrameRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/DicomSeriesVolumeSlicer.cpp @@ -273,12 +365,14 @@ ${ORTHANC_STONE_ROOT}/Framework/SmartLoader.cpp ${ORTHANC_STONE_ROOT}/Framework/StoneEnumerations.cpp ${ORTHANC_STONE_ROOT}/Framework/StoneException.h + ${ORTHANC_STONE_ROOT}/Framework/StoneInitialization.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/AffineTransform2D.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/BaseWebService.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/CoordinateSystem3D.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomFrameConverter.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomStructureSet.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DownloadStack.cpp + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DynamicBitmap.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/Extent2D.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/FiniteProjectiveCamera.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp @@ -320,6 +414,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Widgets/WidgetBase.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/WorldSceneWidget.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/ZoomMouseTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/dev.h ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h @@ -343,14 +438,45 @@ # Mandatory components ${CAIRO_SOURCES} + ${FREETYPE_SOURCES} ${PIXMAN_SOURCES} # Optional components ${SDL_SOURCES} ${QT_SOURCES} ${BOOST_EXTENDED_SOURCES} + ${GLEW_SOURCES} ) + +if (ENABLE_OPENGL) + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Fonts/OpenGLTextCoordinates.cpp + ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLProgram.cpp + ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLShader.cpp + ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLTexture.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/OpenGLCompositor.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureProgram.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLLinesProgram.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextProgram.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextureProgram.cpp + ) + + if (ENABLE_WASM) + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/OpenGL/WebAssemblyOpenGLContext.cpp + ) + endif() +endif() + + include_directories(${ORTHANC_STONE_ROOT}) diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Resources/CMake/OrthancStoneParameters.cmake --- a/Resources/CMake/OrthancStoneParameters.cmake Tue May 14 18:24:12 2019 +0200 +++ b/Resources/CMake/OrthancStoneParameters.cmake Thu May 16 09:11:14 2019 +0200 @@ -27,9 +27,10 @@ set(ENABLE_DCMTK OFF) set(ENABLE_GOOGLE_TEST ON) +set(ENABLE_JPEG ON) +set(ENABLE_OPENSSL_ENGINES ON) +set(ENABLE_PNG ON) set(ENABLE_SQLITE OFF) -set(ENABLE_JPEG ON) -set(ENABLE_PNG ON) set(ENABLE_ZLIB ON) set(HAS_EMBEDDED_RESOURCES ON) @@ -40,6 +41,8 @@ # Advanced parameters to fine-tune linking against system libraries set(USE_SYSTEM_CAIRO ON CACHE BOOL "Use the system version of Cairo") +set(USE_SYSTEM_FREETYPE ON CACHE BOOL "Use the system version of Freetype") +set(USE_SYSTEM_GLEW ON CACHE BOOL "Use the system version of glew (for Windows only)") set(USE_SYSTEM_PIXMAN ON CACHE BOOL "Use the system version of Pixman") set(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2") @@ -49,3 +52,5 @@ ## the Stone of Orthanc ##################################################################### +set(ENABLE_OPENGL ON CACHE INTERNAL "Enable support of OpenGL") +set(ENABLE_WASM OFF CACHE INTERNAL "Enable support of WebAssembly") diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Resources/CMake/SdlConfiguration.cmake --- a/Resources/CMake/SdlConfiguration.cmake Tue May 14 18:24:12 2019 +0200 +++ b/Resources/CMake/SdlConfiguration.cmake Thu May 16 09:11:14 2019 +0200 @@ -21,7 +21,6 @@ SET(SDL_SOURCES_DIR ${CMAKE_BINARY_DIR}/SDL2-2.0.4) SET(SDL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/SDL2-2.0.4.tar.gz") SET(SDL_MD5 "44fc4a023349933e7f5d7a582f7b886e") - DownloadPackage(${SDL_MD5} ${SDL_URL} "${SDL_SOURCES_DIR}") include_directories(${SDL_SOURCES_DIR}/include) diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Resources/CodeGeneration/stonegentool.py --- a/Resources/CodeGeneration/stonegentool.py Tue May 14 18:24:12 2019 +0200 +++ b/Resources/CodeGeneration/stonegentool.py Thu May 16 09:11:14 2019 +0200 @@ -186,6 +186,7 @@ RegisterTemplateFunction(template,NeedsCppConstruction) RegisterTemplateFunction(template, DefaultValueToTs) RegisterTemplateFunction(template, DefaultValueToCpp) + RegisterTemplateFunction(template, sorted) return template def MakeTemplateFromFile(templateFileName): @@ -533,6 +534,11 @@ lineNumber = schemaText.count("\n",0,i) + 1 raise RuntimeError("Error at line " + str(lineNumber) + " in the schema: colons must be followed by a space or a newline!") schema = yaml.load(schemaText) + print("*******************************************") + print("*******************************************") + print(schema["struct EventBase"]) + print("*******************************************") + print("*******************************************") return schema def GetTemplatingDictFromSchemaFilename(fn): diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Resources/CodeGeneration/template.in.h.j2 --- a/Resources/CodeGeneration/template.in.h.j2 Tue May 14 18:24:12 2019 +0200 +++ b/Resources/CodeGeneration/template.in.h.j2 Thu May 16 09:11:14 2019 +0200 @@ -394,9 +394,9 @@ struct {{struct['name']}} { -{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} {{CanonToCpp(struct['fields'][key]['type'])}} {{key}}; +{% if struct %}{% if struct['fields'] %}{% for key in sorted(struct['fields']) %} {{CanonToCpp(struct['fields'][key]['type'])}} {{key}}; {% endfor %}{% endif %}{% endif %} - {{struct['name']}}({% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}{{CanonToCpp(struct['fields'][key]['type'])}} {{key}} = {% if struct['fields'][key]['defaultValue'] %}{{DefaultValueToCpp(rootName,enums,struct['fields'][key])}} {%else%} {{CanonToCpp(struct['fields'][key]['type'])}}() {%endif%} {{ ", " if not loop.last }}{% endfor %}{% endif %}{% endif %}) + {{struct['name']}}({% if struct %}{% if struct['fields'] %}{% for key in sorted(struct['fields']) %}{{CanonToCpp(struct['fields'][key]['type'])}} {{key}} = {% if struct['fields'][key]['defaultValue'] %}{{DefaultValueToCpp(rootName,enums,struct['fields'][key])}} {%else%} {{CanonToCpp(struct['fields'][key]['type'])}}() {%endif%} {{ ", " if not loop.last }}{% endfor %}{% endif %}{% endif %}) { {% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} this->{{key}} = {{key}}; {% endfor %}{% endif %}{% endif %} } diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Resources/CodeGeneration/testCppHandler/main.cpp --- a/Resources/CodeGeneration/testCppHandler/main.cpp Tue May 14 18:24:12 2019 +0200 +++ b/Resources/CodeGeneration/testCppHandler/main.cpp Thu May 16 09:11:14 2019 +0200 @@ -1,139 +1,139 @@ -#include -#include -#include -#include -using namespace std; -namespace fs = std::filesystem; - -#include -using namespace boost::program_options; - -#include "VsolMessages_generated.hpp" - -/** -Transforms `str` by replacing occurrences of `oldStr` with `newStr`, using -plain text (*not* regular expressions.) -*/ -static inline void ReplaceInString( - string& str, - const std::string& oldStr, - const std::string& newStr) -{ - std::string::size_type pos = 0u; - while ((pos = str.find(oldStr, pos)) != std::string::npos) { - str.replace(pos, oldStr.length(), newStr); - pos += newStr.length(); - } -} - -string SlurpFile(const string& fileName) -{ - ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate); - - ifstream::pos_type fileSize = ifs.tellg(); - ifs.seekg(0, ios::beg); - - vector bytes(fileSize); - ifs.read(bytes.data(), fileSize); - - return string(bytes.data(), fileSize); -} - -class MyHandler : public VsolMessages::IHandler -{ -public: - virtual bool Handle(const VsolMessages::A& value) override - { - VsolMessages::StoneDumpValue(cout, value); - return true; - } - virtual bool Handle(const VsolMessages::B& value) override - { - VsolMessages::StoneDumpValue(cout, value); - return true; - } - virtual bool Handle(const VsolMessages::C& value) override - { - VsolMessages::StoneDumpValue(cout, value); - return true; - } - virtual bool Handle(const VsolMessages::Message1& value) override - { - VsolMessages::StoneDumpValue(cout, value); - return true; - } - virtual bool Handle(const VsolMessages::Message2& value) override - { - VsolMessages::StoneDumpValue(cout, value); - return true; - } -}; - -template -void ProcessPath(T filePath) -{ - cout << "+--------------------------------------------+\n"; - cout << "| Processing: " << filePath.path().string() << "\n"; - cout << "+--------------------------------------------+\n"; - MyHandler handler; - auto contents = SlurpFile(filePath.path().string()); - VsolMessages::StoneDispatchToHandler(contents, &handler); -} - -int main(int argc, char** argv) -{ - try - { - - options_description desc("Allowed options"); - desc.add_options() - // First parameter describes option name/short name - // The second is parameter to option - // The third is description - ("help,h", "print usage message") - ("pattern,p", value(), "pattern for input") - ; - - variables_map vm; - store(parse_command_line(argc, argv, desc), vm); - - if (vm.count("help")) - { - cout << desc << "\n"; - return 0; - } - - notify(vm); - - string pattern = vm["pattern"].as(); - - // tranform globbing pattern into regex - // we should deal with -, ., *... - string regexPatternStr = pattern; - cout << "Pattern is: " << regexPatternStr << endl; - ReplaceInString(regexPatternStr, "\\", "\\\\"); - ReplaceInString(regexPatternStr, "-", "\\-"); - ReplaceInString(regexPatternStr, ".", "\\."); - ReplaceInString(regexPatternStr, "*", ".*"); - ReplaceInString(regexPatternStr, "?", "."); - cout << "Corresponding regex is: " << regexPatternStr << endl; - - regex regexPattern(regexPatternStr); - - for (auto& p : fs::directory_iterator(".")) - { - auto fileName = p.path().filename().string(); - if (regex_match(fileName, regexPattern)) - { - ProcessPath(p); - } - } - return 0; - - - } - catch (exception& e) - { - cerr << e.what() << "\n"; - } +#include +#include +#include +#include +using namespace std; +namespace fs = std::filesystem; + +#include +using namespace boost::program_options; + +#include "VsolMessages_generated.hpp" + +/** +Transforms `str` by replacing occurrences of `oldStr` with `newStr`, using +plain text (*not* regular expressions.) +*/ +static inline void ReplaceInString( + string& str, + const std::string& oldStr, + const std::string& newStr) +{ + std::string::size_type pos = 0u; + while ((pos = str.find(oldStr, pos)) != std::string::npos) { + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } +} + +string SlurpFile(const string& fileName) +{ + ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate); + + ifstream::pos_type fileSize = ifs.tellg(); + ifs.seekg(0, ios::beg); + + vector bytes(fileSize); + ifs.read(bytes.data(), fileSize); + + return string(bytes.data(), fileSize); +} + +class MyHandler : public VsolMessages::IHandler +{ +public: + virtual bool Handle(const VsolMessages::A& value) override + { + VsolMessages::StoneDumpValue(cout, value); + return true; + } + virtual bool Handle(const VsolMessages::B& value) override + { + VsolMessages::StoneDumpValue(cout, value); + return true; + } + virtual bool Handle(const VsolMessages::C& value) override + { + VsolMessages::StoneDumpValue(cout, value); + return true; + } + virtual bool Handle(const VsolMessages::Message1& value) override + { + VsolMessages::StoneDumpValue(cout, value); + return true; + } + virtual bool Handle(const VsolMessages::Message2& value) override + { + VsolMessages::StoneDumpValue(cout, value); + return true; + } +}; + +template +void ProcessPath(T filePath) +{ + cout << "+--------------------------------------------+\n"; + cout << "| Processing: " << filePath.path().string() << "\n"; + cout << "+--------------------------------------------+\n"; + MyHandler handler; + auto contents = SlurpFile(filePath.path().string()); + VsolMessages::StoneDispatchToHandler(contents, &handler); +} + +int main(int argc, char** argv) +{ + try + { + + options_description desc("Allowed options"); + desc.add_options() + // First parameter describes option name/short name + // The second is parameter to option + // The third is description + ("help,h", "print usage message") + ("pattern,p", value(), "pattern for input") + ; + + variables_map vm; + store(parse_command_line(argc, argv, desc), vm); + + if (vm.count("help")) + { + cout << desc << "\n"; + return 0; + } + + notify(vm); + + string pattern = vm["pattern"].as(); + + // tranform globbing pattern into regex + // we should deal with -, ., *... + string regexPatternStr = pattern; + cout << "Pattern is: " << regexPatternStr << endl; + ReplaceInString(regexPatternStr, "\\", "\\\\"); + ReplaceInString(regexPatternStr, "-", "\\-"); + ReplaceInString(regexPatternStr, ".", "\\."); + ReplaceInString(regexPatternStr, "*", ".*"); + ReplaceInString(regexPatternStr, "?", "."); + cout << "Corresponding regex is: " << regexPatternStr << endl; + + regex regexPattern(regexPatternStr); + + for (auto& p : fs::directory_iterator(".")) + { + auto fileName = p.path().filename().string(); + if (regex_match(fileName, regexPattern)) + { + ProcessPath(p); + } + } + return 0; + + + } + catch (exception& e) + { + cerr << e.what() << "\n"; + } } \ No newline at end of file diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Resources/Orthanc/DownloadOrthancFramework.cmake --- a/Resources/Orthanc/DownloadOrthancFramework.cmake Tue May 14 18:24:12 2019 +0200 +++ b/Resources/Orthanc/DownloadOrthancFramework.cmake Thu May 16 09:11:14 2019 +0200 @@ -95,6 +95,16 @@ set(ORTHANC_FRAMEWORK_MD5 "4429d8d9dea4ff6648df80ec3c64d79e") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.1") set(ORTHANC_FRAMEWORK_MD5 "099671538865e5da96208b37494d6718") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.2") + set(ORTHANC_FRAMEWORK_MD5 "8867050f3e9a1ce6157c1ea7a9433b1b") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.3") + set(ORTHANC_FRAMEWORK_MD5 "bf2f5ed1adb8b0fc5f10d278e68e1dfe") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.4") + set(ORTHANC_FRAMEWORK_MD5 "404baef5d4c43e7c5d9410edda8ef5a5") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.5") + set(ORTHANC_FRAMEWORK_MD5 "cfc437e0687ae4bd725fd93dc1f08bc4") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.6") + set(ORTHANC_FRAMEWORK_MD5 "3c29de1e289b5472342947168f0105c0") endif() endif() endif() diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Resources/Orthanc/LinuxStandardBaseToolchain.cmake --- a/Resources/Orthanc/LinuxStandardBaseToolchain.cmake Tue May 14 18:24:12 2019 +0200 +++ b/Resources/Orthanc/LinuxStandardBaseToolchain.cmake Thu May 16 09:11:14 2019 +0200 @@ -1,4 +1,4 @@ -# LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON +# LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DBOOST_LOCALE_BACKEND=icu INCLUDE(CMakeForceCompiler) diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/AngleMeasureTool.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/AngleMeasureTool.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,261 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "AngleMeasureTool.h" +#include "MeasureToolsToolbox.h" + +#include + +#include + +extern void TrackerSample_SetInfoDisplayMessage(std::string key, std::string value); + +namespace OrthancStone +{ + AngleMeasureTool::~AngleMeasureTool() + { + // this measuring tool is a RABI for the corresponding visual layers + // stored in the 2D scene + Disable(); + RemoveFromScene(); + } + + void AngleMeasureTool::RemoveFromScene() + { + if (layersCreated) + { + assert(GetScene().HasLayer(polylineZIndex_)); + assert(GetScene().HasLayer(textZIndex_)); + GetScene().DeleteLayer(polylineZIndex_); + GetScene().DeleteLayer(textZIndex_); + } + } + + void AngleMeasureTool::SetSide1End(ScenePoint2D pt) + { + side1End_ = pt; + RefreshScene(); + } + + void AngleMeasureTool::SetSide2End(ScenePoint2D pt) + { + side2End_ = pt; + RefreshScene(); + } + + void AngleMeasureTool::SetCenter(ScenePoint2D pt) + { + center_ = pt; + RefreshScene(); + } + + PolylineSceneLayer* AngleMeasureTool::GetPolylineLayer() + { + assert(GetScene().HasLayer(polylineZIndex_)); + ISceneLayer* layer = &(GetScene().GetLayer(polylineZIndex_)); + PolylineSceneLayer* concreteLayer = dynamic_cast(layer); + assert(concreteLayer != NULL); + return concreteLayer; + } + + TextSceneLayer* AngleMeasureTool::GetTextLayer() + { + assert(GetScene().HasLayer(textZIndex_)); + ISceneLayer* layer = &(GetScene().GetLayer(textZIndex_)); + TextSceneLayer* concreteLayer = dynamic_cast(layer); + assert(concreteLayer != NULL); + return concreteLayer; + } + + + void AngleMeasureTool::RefreshScene() + { + if (IsEnabled()) + { + // get the scaling factor + const double pixelToScene = + GetScene().GetCanvasToSceneTransform().ComputeZoom(); + + if (!layersCreated) + { + // Create the layers if need be + + assert(textZIndex_ == -1); + { + polylineZIndex_ = GetScene().GetMaxDepth() + 100; + //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_; + std::auto_ptr layer(new PolylineSceneLayer()); + GetScene().SetLayer(polylineZIndex_, layer.release()); + } + { + textZIndex_ = GetScene().GetMaxDepth() + 100; + //LOG(INFO) << "set textZIndex_ to: " << textZIndex_; + std::auto_ptr layer(new TextSceneLayer()); + GetScene().SetLayer(textZIndex_, layer.release()); + } + layersCreated = true; + } + else + { + assert(GetScene().HasLayer(polylineZIndex_)); + assert(GetScene().HasLayer(textZIndex_)); + } + { + // Fill the polyline layer with the measurement line + + PolylineSceneLayer* polylineLayer = GetPolylineLayer(); + polylineLayer->ClearAllChains(); + polylineLayer->SetColor(0, 223, 21); + + // sides + { + { + PolylineSceneLayer::Chain chain; + chain.push_back(side1End_); + chain.push_back(center_); + polylineLayer->AddChain(chain, false); + } + { + PolylineSceneLayer::Chain chain; + chain.push_back(side2End_); + chain.push_back(center_); + polylineLayer->AddChain(chain, false); + } + } + + // handles + { + //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength) + + { + PolylineSceneLayer::Chain chain; + AddSquare(chain, GetScene(), side1End_, 10.0* pixelToScene); //TODO: take DPI into account + polylineLayer->AddChain(chain, true); + } + + { + PolylineSceneLayer::Chain chain; + AddSquare(chain, GetScene(), side2End_, 10.0* pixelToScene); //TODO: take DPI into account + polylineLayer->AddChain(chain, true); + } + } + + // arc + { + PolylineSceneLayer::Chain chain; + + const double ARC_RADIUS_CANVAS_COORD = 30.0; + AddShortestArc(chain, GetScene(), side1End_, center_, side2End_, + ARC_RADIUS_CANVAS_COORD*pixelToScene); + polylineLayer->AddChain(chain, false); + } + } + { + // Set the text layer + + double p1cAngle = atan2( + side1End_.GetY() - center_.GetY(), + side1End_.GetX() - center_.GetX()); + + TrackerSample_SetInfoDisplayMessage("center_.GetX()", + boost::lexical_cast(center_.GetX())); + + TrackerSample_SetInfoDisplayMessage("center_.GetY()", + boost::lexical_cast(center_.GetY())); + + TrackerSample_SetInfoDisplayMessage("side1End_.GetX()", + boost::lexical_cast(side1End_.GetX())); + + TrackerSample_SetInfoDisplayMessage("side1End_.GetY()", + boost::lexical_cast(side1End_.GetY())); + + TrackerSample_SetInfoDisplayMessage("side2End_.GetX()", + boost::lexical_cast(side2End_.GetX())); + + TrackerSample_SetInfoDisplayMessage("side2End_.GetY()", + boost::lexical_cast(side2End_.GetY())); + + TrackerSample_SetInfoDisplayMessage("p1cAngle (deg)", + boost::lexical_cast(RadiansToDegrees(p1cAngle))); + + double p2cAngle = atan2( + side2End_.GetY() - center_.GetY(), + side2End_.GetX() - center_.GetX()); + + double delta = NormalizeAngle(p2cAngle - p1cAngle); + TrackerSample_SetInfoDisplayMessage("delta (deg)", + boost::lexical_cast(RadiansToDegrees(delta))); + + double theta = p1cAngle + delta/2; + + TrackerSample_SetInfoDisplayMessage("theta (deg)", + boost::lexical_cast(RadiansToDegrees(theta))); + + TrackerSample_SetInfoDisplayMessage("p2cAngle (deg)", + boost::lexical_cast(RadiansToDegrees(p2cAngle))); + + const double TEXT_CENTER_DISTANCE_CANVAS_COORD = 90; + + double offsetX = TEXT_CENTER_DISTANCE_CANVAS_COORD * cos(theta); + TrackerSample_SetInfoDisplayMessage("offsetX (pix)", + boost::lexical_cast(offsetX)); + + double offsetY = TEXT_CENTER_DISTANCE_CANVAS_COORD * sin(theta); + TrackerSample_SetInfoDisplayMessage("offsetY (pix)", + boost::lexical_cast(offsetY)); + + double pointX = center_.GetX() + offsetX * pixelToScene; + double pointY = center_.GetY() + offsetY * pixelToScene; + TrackerSample_SetInfoDisplayMessage("pointX", + boost::lexical_cast(pointX)); + + TrackerSample_SetInfoDisplayMessage("pointY", + boost::lexical_cast(pointY)); + + TextSceneLayer* textLayer = GetTextLayer(); + + char buf[64]; + double angleDeg = RadiansToDegrees(delta); + + TrackerSample_SetInfoDisplayMessage("angleDeg", + boost::lexical_cast(angleDeg)); + + sprintf(buf, "%0.02f deg", angleDeg); + textLayer->SetText(buf); + textLayer->SetColor(0, 223, 21); + + ScenePoint2D textAnchor; + //GetPositionOnBisectingLine( + // textAnchor, side1End_, center_, side2End_, 40.0*pixelToScene); + textLayer->SetPosition(pointX, pointY); + } + } + else + { + if (layersCreated) + { + RemoveFromScene(); + layersCreated = false; + } + } + } + + +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/AngleMeasureTool.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/AngleMeasureTool.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,74 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include "MeasureTools.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace OrthancStone +{ + class AngleMeasureTool : public MeasureTool + { + public: + AngleMeasureTool(MessageBroker& broker, Scene2D& scene) + : MeasureTool(broker, scene) + , layersCreated(false) + , polylineZIndex_(-1) + , textZIndex_(-1) + { + + } + + ~AngleMeasureTool(); + + void SetSide1End(ScenePoint2D start); + void SetCenter(ScenePoint2D start); + void SetSide2End(ScenePoint2D start); + + private: + PolylineSceneLayer* GetPolylineLayer(); + TextSceneLayer* GetTextLayer(); + virtual void RefreshScene() ORTHANC_OVERRIDE; + void RemoveFromScene(); + + private: + ScenePoint2D side1End_; + ScenePoint2D side2End_; + ScenePoint2D center_; + bool layersCreated; + int polylineZIndex_; + int textZIndex_; + }; + + typedef boost::shared_ptr AngleMeasureToolPtr; +} + + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/CreateAngleMeasureTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/CreateAngleMeasureTracker.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,128 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "CreateAngleMeasureTracker.h" +#include + +using namespace Orthanc; + +namespace OrthancStone +{ + CreateAngleMeasureTracker::CreateAngleMeasureTracker( + MessageBroker& broker, + Scene2D& scene, + std::vector& undoStack, + std::vector& measureTools, + const PointerEvent& e) + : CreateMeasureTracker(scene, undoStack, measureTools) + , state_(CreatingSide1) + { + command_.reset( + new CreateAngleMeasureCommand( + broker, + scene, + measureTools, + e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()))); + } + + CreateAngleMeasureTracker::~CreateAngleMeasureTracker() + { + } + + void CreateAngleMeasureTracker::PointerMove(const PointerEvent& event) + { + if (!active_) + { + throw OrthancException(ErrorCode_InternalError, + "Internal error: wrong state in CreateAngleMeasureTracker::" + "PointerMove: active_ == false"); + } + + ScenePoint2D scenePos = event.GetMainPosition().Apply( + scene_.GetCanvasToSceneTransform()); + + switch (state_) + { + case CreatingSide1: + GetCommand()->SetCenter(scenePos); + break; + case CreatingSide2: + GetCommand()->SetSide2End(scenePos); + break; + default: + throw OrthancException(ErrorCode_InternalError, + "Wrong state in CreateAngleMeasureTracker::" + "PointerMove: state_ invalid"); + } + //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << " " << + // "scenePos.GetY() = " << scenePos.GetY(); + } + + void CreateAngleMeasureTracker::PointerUp(const PointerEvent& e) + { + // TODO: the current app does not prevent multiple PointerDown AND + // PointerUp to be sent to the tracker. + // Unless we augment the PointerEvent structure with the button index, + // we cannot really tell if this pointer up event matches the initial + // pointer down event. Let's make it simple for now. + + switch (state_) + { + case CreatingSide1: + state_ = CreatingSide2; + break; + case CreatingSide2: + throw OrthancException(ErrorCode_InternalError, + "Wrong state in CreateAngleMeasureTracker::" + "PointerUp: state_ == CreatingSide2 ; this should not happen"); + break; + default: + throw OrthancException(ErrorCode_InternalError, + "Wrong state in CreateAngleMeasureTracker::" + "PointerMove: state_ invalid"); + } + } + + void CreateAngleMeasureTracker::PointerDown(const PointerEvent& e) + { + switch (state_) + { + case CreatingSide1: + throw OrthancException(ErrorCode_InternalError, + "Wrong state in CreateAngleMeasureTracker::" + "PointerDown: state_ == CreatingSide1 ; this should not happen"); + break; + case CreatingSide2: + // we are done + active_ = false; + break; + default: + throw OrthancException(ErrorCode_InternalError, + "Wrong state in CreateAngleMeasureTracker::" + "PointerMove: state_ invalid"); + } + } + + CreateAngleMeasureCommandPtr CreateAngleMeasureTracker::GetCommand() + { + return boost::dynamic_pointer_cast(command_); + } + +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/CreateAngleMeasureTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/CreateAngleMeasureTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,65 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include "MeasureTrackers.h" +#include "MeasureCommands.h" + +#include + +namespace OrthancStone +{ + class CreateAngleMeasureTracker : public CreateMeasureTracker + { + public: + /** + When you create this tracker, you need to supply it with the undo stack + where it will store the commands that perform the actual measure tool + creation and modification. + In turn, a container for these commands to store the actual measuring + must be supplied, too + */ + CreateAngleMeasureTracker( + MessageBroker& broker, + Scene2D& scene, + std::vector& undoStack, + std::vector& measureTools, + const PointerEvent& e); + + ~CreateAngleMeasureTracker(); + + virtual void PointerMove(const PointerEvent& e) ORTHANC_OVERRIDE; + virtual void PointerUp(const PointerEvent& e) ORTHANC_OVERRIDE; + virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE; + + private: + CreateAngleMeasureCommandPtr GetCommand(); + + enum State + { + CreatingSide1, + CreatingSide2, + Finished // just for debug + }; + State state_; + + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/CreateCircleMeasureTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/CreateCircleMeasureTracker.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,23 @@ +/** + * 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 . + **/ + +namespace OrthancStone +{ +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/CreateCircleMeasureTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/CreateCircleMeasureTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,25 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +namespace OrthancStone +{ +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/CreateLineMeasureTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/CreateLineMeasureTracker.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,91 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "CreateLineMeasureTracker.h" +#include + +using namespace Orthanc; + +namespace OrthancStone +{ + CreateLineMeasureTracker::CreateLineMeasureTracker( + MessageBroker& broker, + Scene2D& scene, + std::vector& undoStack, + std::vector& measureTools, + const PointerEvent& e) + : CreateMeasureTracker(scene, undoStack, measureTools) + { + command_.reset( + new CreateLineMeasureCommand( + broker, + scene, + measureTools, + e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()))); + } + + CreateLineMeasureTracker::~CreateLineMeasureTracker() + { + + } + + void CreateLineMeasureTracker::PointerMove(const PointerEvent& event) + { + if (!active_) + { + throw OrthancException(ErrorCode_InternalError, + "Internal error: wrong state in CreateLineMeasureTracker::" + "PointerMove: active_ == false"); + } + + ScenePoint2D scenePos = event.GetMainPosition().Apply( + scene_.GetCanvasToSceneTransform()); + + //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << " " << + // "scenePos.GetY() = " << scenePos.GetY(); + + CreateLineMeasureTracker* concreteThis = + dynamic_cast(this); + assert(concreteThis != NULL); + GetCommand()->SetEnd(scenePos); + } + + void CreateLineMeasureTracker::PointerUp(const PointerEvent& e) + { + // TODO: the current app does not prevent multiple PointerDown AND + // PointerUp to be sent to the tracker. + // Unless we augment the PointerEvent structure with the button index, + // we cannot really tell if this pointer up event matches the initial + // pointer down event. Let's make it simple for now. + active_ = false; + } + + void CreateLineMeasureTracker::PointerDown(const PointerEvent& e) + { + LOG(WARNING) << "Additional touches (fingers, pen, mouse buttons...) " + "are ignored when the line measure creation tracker is active"; + } + + CreateLineMeasureCommandPtr CreateLineMeasureTracker::GetCommand() + { + return boost::dynamic_pointer_cast(command_); + } + +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/CreateLineMeasureTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/CreateLineMeasureTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,53 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include "MeasureTrackers.h" + +namespace OrthancStone +{ + class CreateLineMeasureTracker : public CreateMeasureTracker + { + public: + /** + When you create this tracker, you need to supply it with the undo stack + where it will store the commands that perform the actual measure tool + creation and modification. + In turn, a container for these commands to store the actual measuring + must be supplied, too + */ + CreateLineMeasureTracker( + MessageBroker& broker, + Scene2D& scene, + std::vector& undoStack, + std::vector& measureTools, + const PointerEvent& e); + + ~CreateLineMeasureTracker(); + + virtual void PointerMove(const PointerEvent& e) ORTHANC_OVERRIDE; + virtual void PointerUp(const PointerEvent& e) ORTHANC_OVERRIDE; + virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE; + + private: + CreateLineMeasureCommandPtr GetCommand(); + }; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/CreateMeasureTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/CreateMeasureTracker.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,20 @@ +/** + * 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 . + **/ + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/CreateMeasureTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/CreateMeasureTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,22 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/CreateSimpleTrackerAdapter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/CreateSimpleTrackerAdapter.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,77 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "IFlexiblePointerTracker.h" +#include + + +namespace OrthancStone +{ + namespace + { + class SimpleTrackerAdapter : public IFlexiblePointerTracker + { + public: + SimpleTrackerAdapter(PointerTrackerPtr wrappedTracker) + : wrappedTracker_(wrappedTracker) + , active_(true) + { + } + + virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE + { + if(active_) + wrappedTracker_->Update(event); + }; + virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE + { + if (wrappedTracker_) + { + wrappedTracker_->Release(); + wrappedTracker_ = NULL; + } + active_ = false; + } + virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE + { + // nothing to do atm + } + virtual bool IsActive() const ORTHANC_OVERRIDE + { + return active_; + } + + virtual void Cancel() ORTHANC_OVERRIDE + { + wrappedTracker_ = NULL; + active_ = false; + } + + private: + PointerTrackerPtr wrappedTracker_; + bool active_; + }; + } + + FlexiblePointerTrackerPtr CreateSimpleTrackerAdapter(PointerTrackerPtr t) + { + return FlexiblePointerTrackerPtr(new SimpleTrackerAdapter(t)); + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/EditAngleMeasureTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/EditAngleMeasureTracker.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,23 @@ +/** + * 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 . + **/ + +namespace OrthancStone +{ +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/EditAngleMeasureTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/EditAngleMeasureTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,25 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +namespace OrthancStone +{ +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/EditCircleMeasureTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/EditCircleMeasureTracker.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,23 @@ +/** + * 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 . + **/ + +namespace OrthancStone +{ +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/EditCircleMeasureTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/EditCircleMeasureTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,25 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +namespace OrthancStone +{ +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/EditLineMeasureTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/EditLineMeasureTracker.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,23 @@ +/** + * 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 . + **/ + +namespace OrthancStone +{ +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/EditLineMeasureTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/EditLineMeasureTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,25 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +namespace OrthancStone +{ +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/IFlexiblePointerTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/IFlexiblePointerTracker.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,84 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include + +namespace OrthancStone +{ + class IPointerTracker; + typedef boost::shared_ptr PointerTrackerPtr; + + /** + This interface represents a flexible mouse tracker that can respond to + several events and is not automatically deleted upon mouse up or when touch + interaction is suspended : for instance, a stateful tracker with a two-step + interaction like: click & drag --> mouse up --> drag --> mouse click + (for instance, for an angle measuring tracker or an ellipse tracker) + */ + class IFlexiblePointerTracker : public boost::noncopyable + { + public: + virtual ~IFlexiblePointerTracker() {} + + /** + This method will be repeatedly called during user interaction + */ + virtual void PointerMove(const PointerEvent& event) = 0; + + /** + This method will be called when a touch/pointer is removed (mouse up, + pen lift, finger removed...) + */ + virtual void PointerUp(const PointerEvent& event) = 0; + + /** + This method will be called when a touch/pointer is added (mouse down, + pen or finger press) + */ + virtual void PointerDown(const PointerEvent& event) = 0; + + /** + This method will be repeatedly called by the tracker owner (for instance, + the application) to check whether the tracker must keep on receiving + interaction or if its job is done and it should be deleted. + */ + virtual bool IsActive() const = 0; + + /** + This will be called if the tracker needs to be dismissed without committing + its changes to the underlying model. If the model has been modified during + tracker lifetime, it must be restored to its initial value + */ + virtual void Cancel() = 0; + }; + + typedef boost::shared_ptr FlexiblePointerTrackerPtr; + + /** + This factory adopts the supplied simple tracker and creates a flexible + tracker wrapper around it. + */ + FlexiblePointerTrackerPtr CreateSimpleTrackerAdapter(PointerTrackerPtr); +} + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/LineMeasureTool.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/LineMeasureTool.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,200 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "LineMeasureTool.h" +#include "MeasureToolsToolbox.h" + +#include + + +namespace OrthancStone +{ + LineMeasureTool::~LineMeasureTool() + { + // this measuring tool is a RABI for the corresponding visual layers + // stored in the 2D scene + Disable(); + RemoveFromScene(); + } + + void LineMeasureTool::RemoveFromScene() + { + if (layersCreated) + { + assert(GetScene().HasLayer(polylineZIndex_)); + assert(GetScene().HasLayer(textZIndex_)); + GetScene().DeleteLayer(polylineZIndex_); + GetScene().DeleteLayer(textZIndex_); + } + } + + + void LineMeasureTool::SetStart(ScenePoint2D start) + { + start_ = start; + RefreshScene(); + } + + void LineMeasureTool::SetEnd(ScenePoint2D end) + { + end_ = end; + RefreshScene(); + } + + void LineMeasureTool::Set(ScenePoint2D start, ScenePoint2D end) + { + start_ = start; + end_ = end; + RefreshScene(); + } + + PolylineSceneLayer* LineMeasureTool::GetPolylineLayer() + { + assert(GetScene().HasLayer(polylineZIndex_)); + ISceneLayer* layer = &(GetScene().GetLayer(polylineZIndex_)); + PolylineSceneLayer* concreteLayer = dynamic_cast(layer); + assert(concreteLayer != NULL); + return concreteLayer; + } + + TextSceneLayer* LineMeasureTool::GetTextLayer() + { + assert(GetScene().HasLayer(textZIndex_)); + ISceneLayer* layer = &(GetScene().GetLayer(textZIndex_)); + TextSceneLayer* concreteLayer = dynamic_cast(layer); + assert(concreteLayer != NULL); + return concreteLayer; + } + + void LineMeasureTool::RefreshScene() + { + if (IsEnabled()) + { + if (!layersCreated) + { + // Create the layers if need be + + assert(textZIndex_ == -1); + { + polylineZIndex_ = GetScene().GetMaxDepth() + 100; + //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_; + std::auto_ptr layer(new PolylineSceneLayer()); + GetScene().SetLayer(polylineZIndex_, layer.release()); + } + { + textZIndex_ = GetScene().GetMaxDepth() + 100; + //LOG(INFO) << "set textZIndex_ to: " << textZIndex_; + std::auto_ptr layer(new TextSceneLayer()); + GetScene().SetLayer(textZIndex_, layer.release()); + } + layersCreated = true; + } + else + { + assert(GetScene().HasLayer(polylineZIndex_)); + assert(GetScene().HasLayer(textZIndex_)); + } + { + // Fill the polyline layer with the measurement line + + PolylineSceneLayer* polylineLayer = GetPolylineLayer(); + polylineLayer->ClearAllChains(); + polylineLayer->SetColor(0, 223, 21); + + { + PolylineSceneLayer::Chain chain; + chain.push_back(start_); + chain.push_back(end_); + polylineLayer->AddChain(chain, false); + } + + // handles + { + //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength) + + { + PolylineSceneLayer::Chain chain; + AddSquare(chain, GetScene(), start_, 10.0); //TODO: take DPI into account + polylineLayer->AddChain(chain, true); + } + + { + PolylineSceneLayer::Chain chain; + AddSquare(chain, GetScene(), end_, 10.0); //TODO: take DPI into account + polylineLayer->AddChain(chain, true); + } + + //ScenePoint2D startC = start_.Apply(GetScene().GetSceneToCanvasTransform()); + //double squareSize = 10.0; + //double startHandleLX = startC.GetX() - squareSize/2; + //double startHandleTY = startC.GetY() - squareSize / 2; + //double startHandleRX = startC.GetX() + squareSize / 2; + //double startHandleBY = startC.GetY() + squareSize / 2; + //ScenePoint2D startLTC(startHandleLX, startHandleTY); + //ScenePoint2D startRTC(startHandleRX, startHandleTY); + //ScenePoint2D startRBC(startHandleRX, startHandleBY); + //ScenePoint2D startLBC(startHandleLX, startHandleBY); + + //ScenePoint2D startLT = startLTC.Apply(GetScene().GetCanvasToSceneTransform()); + //ScenePoint2D startRT = startRTC.Apply(GetScene().GetCanvasToSceneTransform()); + //ScenePoint2D startRB = startRBC.Apply(GetScene().GetCanvasToSceneTransform()); + //ScenePoint2D startLB = startLBC.Apply(GetScene().GetCanvasToSceneTransform()); + + //PolylineSceneLayer::Chain chain; + //chain.push_back(startLT); + //chain.push_back(startRT); + //chain.push_back(startRB); + //chain.push_back(startLB); + //polylineLayer->AddChain(chain, true); + } + + } + { + // Set the text layer proporeties + + TextSceneLayer* textLayer = GetTextLayer(); + double deltaX = end_.GetX() - start_.GetX(); + double deltaY = end_.GetY() - start_.GetY(); + double squareDist = deltaX * deltaX + deltaY * deltaY; + double dist = sqrt(squareDist); + char buf[64]; + sprintf(buf, "%0.02f units", dist); + textLayer->SetText(buf); + textLayer->SetColor(0, 223, 21); + + // TODO: for now we simply position the text overlay at the middle + // of the measuring segment + double midX = 0.5*(end_.GetX() + start_.GetX()); + double midY = 0.5*(end_.GetY() + start_.GetY()); + textLayer->SetPosition(midX, midY); + } + } + else + { + if (layersCreated) + { + RemoveFromScene(); + layersCreated = false; + } + } + } + + +} \ No newline at end of file diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/LineMeasureTool.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/LineMeasureTool.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,72 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include "MeasureTools.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace OrthancStone +{ + class LineMeasureTool : public MeasureTool + { + public: + LineMeasureTool(MessageBroker& broker, Scene2D& scene) + : MeasureTool(broker, scene) + , layersCreated(false) + , polylineZIndex_(-1) + , textZIndex_(-1) + { + + } + + ~LineMeasureTool(); + + void SetStart(ScenePoint2D start); + void SetEnd(ScenePoint2D end); + void Set(ScenePoint2D start, ScenePoint2D end); + + private: + PolylineSceneLayer* GetPolylineLayer(); + TextSceneLayer* GetTextLayer(); + virtual void RefreshScene() ORTHANC_OVERRIDE; + void RemoveFromScene(); + + private: + ScenePoint2D start_; + ScenePoint2D end_; + bool layersCreated; + int polylineZIndex_; + int textZIndex_; + }; + + typedef boost::shared_ptr LineMeasureToolPtr; +} + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/MeasureCommands.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureCommands.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,93 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "MeasureCommands.h" + +namespace OrthancStone +{ + void CreateMeasureCommand::Undo() + { + // simply disable the measure tool upon undo + GetMeasureTool()->Disable(); + } + + void CreateMeasureCommand::Redo() + { + GetMeasureTool()->Enable(); + } + + CreateMeasureCommand::CreateMeasureCommand( + Scene2D& scene, MeasureToolList& measureTools) + : TrackerCommand(scene) + , measureTools_(measureTools) + { + + } + + CreateMeasureCommand::~CreateMeasureCommand() + { + // deleting the command should not change the model state + // we thus leave it as is + } + + CreateLineMeasureCommand::CreateLineMeasureCommand( + MessageBroker& broker, + Scene2D& scene, + MeasureToolList& measureTools, + ScenePoint2D point) + : CreateMeasureCommand(scene, measureTools) + , measureTool_(new LineMeasureTool(broker,scene)) + { + measureTools_.push_back(measureTool_); + measureTool_->Set(point, point); + } + + void CreateLineMeasureCommand::SetEnd(ScenePoint2D scenePos) + { + measureTool_->SetEnd(scenePos); + } + + CreateAngleMeasureCommand::CreateAngleMeasureCommand( + MessageBroker& broker, + Scene2D& scene, + MeasureToolList& measureTools, + ScenePoint2D point) + : CreateMeasureCommand(scene, measureTools) + , measureTool_(new AngleMeasureTool(broker,scene)) + { + measureTools_.push_back(measureTool_); + measureTool_->SetSide1End(point); + measureTool_->SetCenter(point); + measureTool_->SetSide2End(point); + } + + /** This method sets center*/ + void CreateAngleMeasureCommand::SetCenter(ScenePoint2D scenePos) + { + measureTool_->SetCenter(scenePos); + } + + /** This method sets end of side 2*/ + void CreateAngleMeasureCommand::SetSide2End(ScenePoint2D scenePos) + { + measureTool_->SetSide2End(scenePos); + } + +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/MeasureCommands.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureCommands.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,118 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ +#pragma once + +#include +#include + +// to be moved into Stone +#include "MeasureTools.h" +#include "LineMeasureTool.h" +#include "AngleMeasureTool.h" + +namespace OrthancStone +{ + //class LineMeasureTool; + //typedef boost::shared_ptr LineMeasureToolPtr; + //class AngleMeasureTool; + //typedef boost::shared_ptr AngleMeasureToolPtr; + + class TrackerCommand + { + public: + TrackerCommand(Scene2D& scene) : scene_(scene) + { + + } + virtual void Undo() = 0; + virtual void Redo() = 0; + Scene2D& GetScene() + { + return scene_; + } + + protected: + Scene2D& scene_; + private: + TrackerCommand(const TrackerCommand&); + TrackerCommand& operator=(const TrackerCommand&); + }; + + typedef boost::shared_ptr TrackerCommandPtr; + + class CreateMeasureCommand : public TrackerCommand + { + public: + CreateMeasureCommand(Scene2D& scene, MeasureToolList& measureTools); + ~CreateMeasureCommand(); + virtual void Undo() ORTHANC_OVERRIDE; + virtual void Redo() ORTHANC_OVERRIDE; + protected: + MeasureToolList& measureTools_; + private: + /** Must be implemented by the subclasses that create the actual tool */ + virtual MeasureToolPtr GetMeasureTool() = 0; + }; + + typedef boost::shared_ptr CreateMeasureCommandPtr; + + class CreateLineMeasureCommand : public CreateMeasureCommand + { + public: + CreateLineMeasureCommand( + MessageBroker& broker, Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point); + + // the starting position is set in the ctor + void SetEnd(ScenePoint2D scenePos); + + private: + virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE + { + return measureTool_; + } + LineMeasureToolPtr measureTool_; + }; + + typedef boost::shared_ptr CreateLineMeasureCommandPtr; + + class CreateAngleMeasureCommand : public CreateMeasureCommand + { + public: + /** Ctor sets end of side 1*/ + CreateAngleMeasureCommand( + MessageBroker& broker, Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point); + + /** This method sets center*/ + void SetCenter(ScenePoint2D scenePos); + + /** This method sets end of side 2*/ + void SetSide2End(ScenePoint2D scenePos); + + private: + virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE + { + return measureTool_; + } + AngleMeasureToolPtr measureTool_; + }; + + typedef boost::shared_ptr CreateAngleMeasureCommandPtr; +} + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/MeasureTools.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureTools.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,75 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "MeasureTools.h" + +#include + +#include + +namespace OrthancStone +{ + + MeasureTool::~MeasureTool() + { + + } + + void MeasureTool::Enable() + { + enabled_ = true; + RefreshScene(); + } + + void MeasureTool::Disable() + { + enabled_ = false; + RefreshScene(); + } + + bool MeasureTool::IsEnabled() const + { + return enabled_; + } + + OrthancStone::Scene2D& MeasureTool::GetScene() + { + return scene_; + } + + MeasureTool::MeasureTool(MessageBroker& broker, Scene2D& scene) + : IObserver(broker) + , scene_(scene) + , enabled_(true) + { + scene_.RegisterObserverCallback( + new Callable + (*this, &MeasureTool::OnSceneTransformChanged)); + } + + void MeasureTool::OnSceneTransformChanged( + const Scene2D::SceneTransformChanged& message) + { + RefreshScene(); + } + + +} + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/MeasureTools.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureTools.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,89 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace OrthancStone +{ + class MeasureTool : public IObserver + { + public: + virtual ~MeasureTool(); + + /** + Enabled tools are rendered in the scene. + */ + void Enable(); + + /** + Disabled tools are not rendered in the scene. This is useful to be able + to use them as their own memento in command stacks (when a measure tool + creation command has been undone, the measure remains alive in the + command object but is disabled so that it can be redone later on easily) + */ + void Disable(); + + /** + 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 Scene2D::SceneTransformChanged& message); + + protected: + MeasureTool(MessageBroker& broker, Scene2D& scene); + + /** + This is the meat of the tool: this method must [create (if needed) and] + update the layers and their data according to the measure tool kind and + current state. This is repeatedly called during user interaction + */ + virtual void RefreshScene() = 0; + + Scene2D& GetScene(); + + /** + enabled_ is not accessible by subclasses because there is a state machine + that we do not wanna mess with + */ + bool IsEnabled() const; + + private: + Scene2D& scene_; + bool enabled_; + }; + + typedef boost::shared_ptr MeasureToolPtr; + typedef std::vector MeasureToolList; +} + + +extern void TrackerSample_SetInfoDisplayMessage( + std::string key, std::string value); diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/MeasureToolsToolbox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureToolsToolbox.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,278 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "MeasureToolsToolbox.h" + +#include + +namespace +{ + double g_pi = boost::math::constants::pi(); +} + +namespace OrthancStone +{ + double RadiansToDegrees(double angleRad) + { + static const double factor = 180.0 / g_pi; + return angleRad * factor; + } + + void AddSquare(PolylineSceneLayer::Chain& chain, + const Scene2D& scene, + const ScenePoint2D& centerS, + const double& sideLength) + { + chain.clear(); + chain.reserve(4); + ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform()); + //TODO: take DPI into account + double handleLX = centerC.GetX() - sideLength / 2; + double handleTY = centerC.GetY() - sideLength / 2; + double handleRX = centerC.GetX() + sideLength / 2; + double handleBY = centerC.GetY() + sideLength / 2; + ScenePoint2D LTC(handleLX, handleTY); + ScenePoint2D RTC(handleRX, handleTY); + ScenePoint2D RBC(handleRX, handleBY); + ScenePoint2D LBC(handleLX, handleBY); + + ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform()); + ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform()); + ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform()); + ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform()); + + chain.push_back(startLT); + chain.push_back(startRT); + chain.push_back(startRB); + chain.push_back(startLB); + } +#if 0 + void AddArc( + PolylineSceneLayer::Chain& chain + , const Scene2D& scene + , const ScenePoint2D& p1 + , const ScenePoint2D& c + , const ScenePoint2D& p2 + , const double& radiusS + , const bool clockwise + , const int subdivisionsCount) + { + double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX()); + double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX()); + AddArc( + chain, scene, c, radiusS, p1cAngle, p2cAngle, + clockwise, subdivisionsCount); + } +#endif + + void AddShortestArc( + PolylineSceneLayer::Chain& chain + , const Scene2D& scene + , const ScenePoint2D& p1 + , const ScenePoint2D& c + , const ScenePoint2D& p2 + , const double& radiusS + , const int subdivisionsCount) + { + double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX()); + double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX()); + AddShortestArc( + chain, scene, c, radiusS, p1cAngle, p2cAngle, subdivisionsCount); + } + + void GetPositionOnBisectingLine( + ScenePoint2D& result + , const ScenePoint2D& p1 + , const ScenePoint2D& c + , const ScenePoint2D& p2 + , const double d) + { + // TODO: fix correct half-plane + double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX()); + double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX()); + double angle = 0.5*(p1cAngle + p2cAngle); + double unitVectorX = cos(angle); + double unitVectorY = sin(angle); + double posX = c.GetX() + d * unitVectorX; + double posY = c.GetX() + d * unitVectorY; + result = ScenePoint2D(posX, posY); + } + + + void AddShortestArc( + PolylineSceneLayer::Chain& chain + , const Scene2D& scene + , const ScenePoint2D& centerS + , const double& radiusS + , const double startAngleRad + , const double endAngleRad + , const int subdivisionsCount) + { + // this gives a signed difference between angle which + // is the smallest difference (in magnitude) between + // the angles + double delta = NormalizeAngle(endAngleRad-startAngleRad); + + chain.clear(); + chain.reserve(subdivisionsCount + 1); + + double angleIncr = delta/static_cast(subdivisionsCount); + + double theta = startAngleRad; + for (int i = 0; i < subdivisionsCount + 1; ++i) + { + double offsetX = radiusS * cos(theta); + double offsetY = radiusS * sin(theta); + double pointX = centerS.GetX() + offsetX; + double pointY = centerS.GetY() + offsetY; + chain.push_back(ScenePoint2D(pointX, pointY)); + theta += angleIncr; + } + } + +#if 0 + void AddArc( + PolylineSceneLayer::Chain& chain + , const Scene2D& scene + , const ScenePoint2D& centerS + , const double& radiusS + , const double startAngleRad + , const double endAngleRad + , const bool clockwise + , const int subdivisionsCount) + { + double startAngleRadN = NormalizeAngle(startAngleRad); + double endAngleRadN = NormalizeAngle(endAngleRad); + + double angle1Rad = std::min(startAngleRadN, endAngleRadN); + double angle2Rad = std::max(startAngleRadN, endAngleRadN); + + // now we are sure angle1Rad < angle2Rad + // this means that if we draw from 1 to 2, it will be clockwise ( + // increasing angles). + // let's fix this: + if (!clockwise) + { + angle2Rad -= 2 * g_pi; + // now we are sure angle2Rad < angle1Rad (since they were normalized) + // and, thus, going from 1 to 2 means the angle values will DECREASE, + // which is the definition of anticlockwise + } + + chain.clear(); + chain.reserve(subdivisionsCount + 1); + + double angleIncr = (angle2Rad - angle1Rad) + / static_cast(subdivisionsCount); + + double theta = angle1Rad; + for (int i = 0; i < subdivisionsCount + 1; ++i) + { + double offsetX = radiusS * cos(theta); + double offsetY = radiusS * sin(theta); + double pointX = centerS.GetX() + offsetX; + double pointY = centerS.GetY() + offsetY; + chain.push_back(ScenePoint2D(pointX, pointY)); + theta += angleIncr; + } + } +#endif + + void AddCircle(PolylineSceneLayer::Chain& chain, + const Scene2D& scene, + const ScenePoint2D& centerS, + const double& radiusS, + const int numSubdivisions) + { + //ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform()); + //TODO: take DPI into account + + // TODO: automatically compute the number for segments for smooth + // display based on the radius in pixels. + + chain.clear(); + chain.reserve(numSubdivisions); + + double angleIncr = (2.0 * g_pi) + / static_cast(numSubdivisions); + + double theta = 0; + for (int i = 0; i < numSubdivisions; ++i) + { + double offsetX = radiusS * cos(theta); + double offsetY = radiusS * sin(theta); + double pointX = centerS.GetX() + offsetX; + double pointY = centerS.GetY() + offsetY; + chain.push_back(ScenePoint2D(pointX, pointY)); + theta += angleIncr; + } + } + + double NormalizeAngle(double angle) + { + double retAngle = angle; + while (retAngle < -1.0*g_pi) + retAngle += 2 * g_pi; + while (retAngle >= g_pi) + retAngle -= 2 * g_pi; + return retAngle; + } + + double MeasureAngle(const ScenePoint2D& p1, const ScenePoint2D& c, const ScenePoint2D& p2) + { + double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX()); + double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX()); + double delta = p2cAngle - p1cAngle; + return NormalizeAngle(delta); + } + + +#if 0 + void AddEllipse(PolylineSceneLayer::Chain& chain, + const Scene2D& scene, + const ScenePoint2D& centerS, + const double& halfHAxis, + const double& halfVAxis) + { + chain.clear(); + chain.reserve(4); + ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform()); + //TODO: take DPI into account + double handleLX = centerC.GetX() - sideLength / 2; + double handleTY = centerC.GetY() - sideLength / 2; + double handleRX = centerC.GetX() + sideLength / 2; + double handleBY = centerC.GetY() + sideLength / 2; + ScenePoint2D LTC(handleLX, handleTY); + ScenePoint2D RTC(handleRX, handleTY); + ScenePoint2D RBC(handleRX, handleBY); + ScenePoint2D LBC(handleLX, handleBY); + + ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform()); + ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform()); + ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform()); + ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform()); + + chain.push_back(startLT); + chain.push_back(startRT); + chain.push_back(startRB); + chain.push_back(startLB); +} +#endif +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/MeasureToolsToolbox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureToolsToolbox.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,173 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include +#include + +namespace OrthancStone +{ + + /** + This function will create a square around the center point supplied in + scene coordinates, with a side length given in canvas coordinates. The + square sides are parallel to the canvas boundaries. + */ + void AddSquare(PolylineSceneLayer::Chain& chain, + const Scene2D& scene, + const ScenePoint2D& centerS, + const double& sideLength); + + + /** + Creates an arc centered on c that goes + - from a point r1: + - so that r1 belongs to the p1,c line + - so that the distance from c to r1 equals radius + - to a point r2: + - so that r2 belongs to the p2,c line + - so that the distance from c to r2 equals radius + - that follows the shortest among the two possible paths + + Warning: the existing chain content will be wiped out. + */ + void AddShortestArc( + PolylineSceneLayer::Chain& chain + , const Scene2D& scene + , const ScenePoint2D& p1 + , const ScenePoint2D& c + , const ScenePoint2D& p2 + , const double& radiusS + , const int subdivisionsCount = 63); + + /** + Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from + start angle to end angle, by following the shortest arc. + + Warning: the existing chain content will be wiped out. + */ + void AddShortestArc( + PolylineSceneLayer::Chain& chain + , const Scene2D& scene + , const ScenePoint2D& centerS + , const double& radiusS + , const double startAngleRad + , const double endAngleRad + , const int subdivisionsCount = 63); + +#if 0 + /** + Creates an arc centered on c that goes + - from a point r1: + - so that r1 belongs to the p1,c line + - so that the distance from c to r1 equals radius + - to a point r2: + - so that r2 belongs to the p2,c line + - so that the distance from c to r2 equals radius + + if clockwise is true, the arc is drawn from r1 to r2 with increasing + angle values. Otherwise, the angle values decrease. + + Warning: the existing chain content will be wiped out. + */ + + void AddArc( + PolylineSceneLayer::Chain& chain + , const Scene2D& scene + , const ScenePoint2D& p1 + , const ScenePoint2D& c + , const ScenePoint2D& p2 + , const double& radiusS + , const bool clockwise + , const int subdivisionsCount = 63); + + /** + Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from + start angle to end angle with the supplied radius. + + if clockwise is true, the arc is drawn from start to end by increasing the + angle values. + + Otherwise, the angle value decreases from start to end. + + Warning: the existing chain content will be wiped out. + */ + void AddArc( + PolylineSceneLayer::Chain& chain + , const Scene2D& scene + , const ScenePoint2D& centerS + , const double& radiusS + , const double startAngleRad + , const double endAngleRad + , const bool clockwise + , const int subdivisionsCount = 63); +#endif + /** + Creates a circle (closed curve) with "numSubdivisions" + (N points) + + Warning: the existing chain content will be wiped out. + */ + void AddCircle(PolylineSceneLayer::Chain& chain, + const Scene2D& scene, + const ScenePoint2D& centerS, + const double& radiusS, + const int numSubdivisions = 63); + + /** + Adds or subtracts 2*pi as many times as need to shift the specified + angle to a value such as: -pi <= value < pi + */ + double NormalizeAngle(double angle); + + /** + Returns the angle magnitude between the p1,c and p2,c lines. + The returned angle is between 0 and 2*pi + + If the angle is between 0 and pi, this means that the shortest arc + from p1 to p2 is clockwise. + + If the angle is between pi and 2*pi, this means that the shortest arc + from p1 to p2 is COUNTERclockwise. + + */ + double MeasureAngle( + const ScenePoint2D& p1 + , const ScenePoint2D& c + , const ScenePoint2D& p2); + + /** + RadiansToDegrees + */ + double RadiansToDegrees(double angleRad); + + /** + This function will return the coordinates of a point that: + - belongs to the two bisecting lines of the p1 c p2 angle. + - is a distance d from c. + Among the four possible points, the one returned will be the one belonging + to the *smallest* half-plane defined by the [c,p1[ and [c,p2[ half-lines. + */ + void GetPositionOnBisectingLine( + ScenePoint2D& result + , const ScenePoint2D& p1 + , const ScenePoint2D& c + , const ScenePoint2D& p2 + , const double d); +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/MeasureTrackers.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureTrackers.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,65 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "MeasureTrackers.h" +#include + +using namespace Orthanc; + +namespace OrthancStone +{ + + CreateMeasureTracker::CreateMeasureTracker( + Scene2D& scene, + std::vector& undoStack, + std::vector& measureTools) + : scene_(scene) + , active_(true) + , undoStack_(undoStack) + , measureTools_(measureTools) + , commitResult_(true) + { + } + + void CreateMeasureTracker::Cancel() + { + commitResult_ = false; + active_ = false; + } + + bool CreateMeasureTracker::IsActive() const + { + return active_; + } + + CreateMeasureTracker::~CreateMeasureTracker() + { + // if the tracker completes successfully, we add the command + // to the undo stack + + // otherwise, we simply undo it + if (commitResult_) + undoStack_.push_back(command_); + else + command_->Undo(); + } +} + + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Common/MeasureTrackers.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Common/MeasureTrackers.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,57 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include "IFlexiblePointerTracker.h" +#include "../../Framework/Scene2D/Scene2D.h" +#include "../../Framework/Scene2D/PointerEvent.h" + +#include "MeasureTools.h" +#include "MeasureCommands.h" + +#include + +namespace OrthancStone +{ + class CreateMeasureTracker : public IFlexiblePointerTracker + { + public: + virtual void Cancel() ORTHANC_OVERRIDE; + virtual bool IsActive() const ORTHANC_OVERRIDE; + protected: + CreateMeasureTracker( + Scene2D& scene, + std::vector& undoStack, + std::vector& measureTools); + + ~CreateMeasureTracker(); + + protected: + CreateMeasureCommandPtr command_; + Scene2D& scene_; + bool active_; + private: + std::vector& undoStack_; + std::vector& measureTools_; + bool commitResult_; + }; +} + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Sdl/BasicScene.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/BasicScene.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,377 @@ +/** + * 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 . + **/ + + +// From Stone +#include "../../Applications/Sdl/SdlOpenGLWindow.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2D/Scene2D.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/StoneInitialization.h" +#include "../../Framework/Messages/MessageBroker.h" + +// From Orthanc framework +#include +#include +#include +#include +#include + +#include +#include + +static const unsigned int FONT_SIZE = 32; +static const int LAYER_POSITION = 150; + + +void PrepareScene(OrthancStone::Scene2D& scene) +{ + using namespace OrthancStone; + + // Texture of 2x2 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast(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 l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-3, 2); + l->SetPixelSpacing(1.5, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene.SetLayer(14, l.release()); + } + + // Texture of 1x1 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + std::auto_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-2, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene.SetLayer(13, l.release()); + } + + // Some lines + { + std::auto_ptr layer(new PolylineSceneLayer); + + layer->SetThickness(1); + + PolylineSceneLayer::Chain chain; + chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5)); + chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5)); + layer->AddChain(chain, true); + + chain.clear(); + chain.push_back(ScenePoint2D(-5, -5)); + chain.push_back(ScenePoint2D(5, -5)); + chain.push_back(ScenePoint2D(5, 5)); + chain.push_back(ScenePoint2D(-5, 5)); + layer->AddChain(chain, true); + + double dy = 1.01; + chain.clear(); + chain.push_back(ScenePoint2D(-4, -4)); + chain.push_back(ScenePoint2D(4, -4 + dy)); + chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy)); + chain.push_back(ScenePoint2D(4, 2)); + layer->AddChain(chain, false); + + layer->SetColor(0,255, 255); + scene.SetLayer(50, layer.release()); + } + + // Some text + { + std::auto_ptr layer(new TextSceneLayer); + layer->SetText("Hello"); + scene.SetLayer(100, layer.release()); + } +} + + +void TakeScreenshot(const std::string& target, + const OrthancStone::Scene2D& scene, + unsigned int canvasWidth, + unsigned int canvasHeight) +{ + // Take a screenshot, then save it as PNG file + OrthancStone::CairoCompositor compositor(scene, canvasWidth, canvasHeight); + compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE, Orthanc::Encoding_Latin1); + compositor.Refresh(); + + Orthanc::ImageAccessor canvas; + compositor.GetCanvas().GetReadOnlyAccessor(canvas); + + Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false); + Orthanc::ImageProcessing::Convert(png, canvas); + + Orthanc::PngWriter writer; + writer.WriteToFile(target, png); +} + + +void HandleApplicationEvent(OrthancStone::Scene2D& scene, + const OrthancStone::OpenGLCompositor& compositor, + const SDL_Event& event, + std::auto_ptr& activeTracker) +{ + if (event.type == SDL_MOUSEMOTION) + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + if (activeTracker.get() == NULL && + SDL_SCANCODE_LCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_LCTRL]) + { + // The "left-ctrl" key is down, while no tracker is present + + OrthancStone::PointerEvent e; + e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + + OrthancStone::ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); + + char buf[64]; + sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY()); + + if (scene.HasLayer(LAYER_POSITION)) + { + OrthancStone::TextSceneLayer& layer = + dynamic_cast(scene.GetLayer(LAYER_POSITION)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::auto_ptr layer(new OrthancStone::TextSceneLayer); + layer->SetColor(0, 255, 0); + layer->SetText(buf); + layer->SetBorder(20); + layer->SetAnchor(OrthancStone::BitmapAnchor_BottomCenter); + layer->SetPosition(p.GetX(), p.GetY()); + scene.SetLayer(LAYER_POSITION, layer.release()); + } + } + else + { + scene.DeleteLayer(LAYER_POSITION); + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + OrthancStone::PointerEvent e; + e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + + switch (event.button.button) + { + case SDL_BUTTON_MIDDLE: + activeTracker.reset(new OrthancStone::PanSceneTracker(scene, e)); + break; + + case SDL_BUTTON_RIGHT: + activeTracker.reset(new OrthancStone::ZoomSceneTracker(scene, e, + compositor.GetCanvasHeight())); + break; + + case SDL_BUTTON_LEFT: + activeTracker.reset(new OrthancStone::RotateSceneTracker(scene, e)); + break; + + default: + break; + } + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_s: + scene.FitContent(compositor.GetCanvasWidth(), + compositor.GetCanvasHeight()); + break; + + case SDLK_c: + TakeScreenshot("screenshot.png", scene, + compositor.GetCanvasWidth(), + compositor.GetCanvasHeight()); + break; + + default: + break; + } + } +} + + +static void GLAPIENTRY +OpenGLMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam ) +{ + if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) + { + fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", + ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ), + type, severity, message ); + } +} + + +void Run(OrthancStone::Scene2D& scene) +{ + OrthancStone::SdlOpenGLWindow window("Hello", 1024, 768); + + scene.FitContent(window.GetCanvasWidth(), window.GetCanvasHeight()); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + OrthancStone::OpenGLCompositor compositor(window, scene); + compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE, Orthanc::Encoding_Latin1); + + std::auto_ptr tracker; + + bool stop = false; + while (!stop) + { + compositor.Refresh(); + + SDL_Event event; + while (!stop && + SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + stop = true; + break; + } + else if (event.type == SDL_MOUSEMOTION) + { + if (tracker.get() != NULL) + { + OrthancStone::PointerEvent e; + e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + tracker->Update(e); + } + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + if (tracker.get() != NULL) + { + tracker->Release(); + tracker.reset(NULL); + } + } + else if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + tracker.reset(NULL); + compositor.UpdateSize(); + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + window.GetWindow().ToggleMaximize(); + break; + + case SDLK_q: + stop = true; + break; + + default: + break; + } + } + + HandleApplicationEvent(scene, compositor, event, tracker); + } + + SDL_Delay(1); + } +} + + + + +/** + * IMPORTANT: The full arguments to "main()" are needed for SDL on + * Windows. Otherwise, one gets the linking error "undefined reference + * to `SDL_main'". https://wiki.libsdl.org/FAQWindows + **/ +int main(int argc, char* argv[]) +{ + OrthancStone::StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + + try + { + OrthancStone::MessageBroker broker; + OrthancStone::Scene2D scene(broker); + PrepareScene(scene); + Run(scene); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + OrthancStone::StoneFinalize(); + + return 0; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Sdl/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/CMakeLists.txt Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,128 @@ +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_SDL_CONSOLE OFF CACHE BOOL "Enable the use of the MIT-licensed SDL_Console") +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) +SET(ENABLE_SDL ON) +SET(ENABLE_WEB_CLIENT ON) +SET(ORTHANC_SANDBOXED OFF) +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) + +##################################################################### +## Build the samples +##################################################################### + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +add_executable(BasicScene + BasicScene.cpp + ) + +target_link_libraries(BasicScene OrthancStone) + +if(ENABLE_SDL_CONSOLE) + add_definitions( + -DENABLE_SDL_CONSOLE=1 + ) + LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.c") + LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.h") +endif() + +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/AngleMeasureTool.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/AngleMeasureTool.h") + +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateAngleMeasureTracker.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateAngleMeasureTracker.h") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateCircleMeasureTracker.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateCircleMeasureTracker.h") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateLineMeasureTracker.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateLineMeasureTracker.h") + +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateMeasureTracker.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateMeasureTracker.h") + +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateSimpleTrackerAdapter.cpp") + +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditAngleMeasureTracker.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditAngleMeasureTracker.h") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditCircleMeasureTracker.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditCircleMeasureTracker.h") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditLineMeasureTracker.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditLineMeasureTracker.h") + +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/IFlexiblePointerTracker.h") + +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/LineMeasureTool.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/LineMeasureTool.h") + +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureCommands.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureCommands.h") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTools.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTools.h") + +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureToolsToolbox.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureToolsToolbox.h") + +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTrackers.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTrackers.h") + +LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSample.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.h") + +if (MSVC AND MSVC_VERSION GREATER 1700) + LIST(APPEND TRACKERSAMPLE_SOURCE "cpp.hint") +endif() + +add_executable(TrackerSample + ${TRACKERSAMPLE_SOURCE} + ) + +target_link_libraries(TrackerSample OrthancStone) + +add_executable(Loader + Loader.cpp + ) + +target_link_libraries(Loader OrthancStone) diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Sdl/Loader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/Loader.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,1962 @@ +/** + * 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 . + **/ + +// From Stone +#include "../../Framework/Messages/ICallable.h" +#include "../../Framework/Messages/IMessage.h" +#include "../../Framework/Messages/IObservable.h" +#include "../../Framework/Messages/MessageBroker.h" +#include "../../Framework/StoneInitialization.h" +#include "../../Framework/Toolbox/GeometryToolbox.h" +#include "../../Framework/Volumes/ImageBuffer3D.h" +#include "../../Framework/Toolbox/SlicesSorter.h" + +// From Orthanc framework +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + + + +namespace Refactoring +{ + class IOracleCommand : public boost::noncopyable + { + public: + enum Type + { + Type_OrthancRestApi, + Type_GetOrthancImage, + Type_GetOrthancWebViewerJpeg + }; + + virtual ~IOracleCommand() + { + } + + virtual Type GetType() const = 0; + }; + + + class IMessageEmitter : public boost::noncopyable + { + public: + virtual ~IMessageEmitter() + { + } + + virtual void EmitMessage(const OrthancStone::IObserver& observer, + const OrthancStone::IMessage& message) = 0; + }; + + + class IOracle : public boost::noncopyable + { + public: + virtual ~IOracle() + { + } + + virtual void Schedule(const OrthancStone::IObserver& receiver, + IOracleCommand* command) = 0; // Takes ownership + }; + + + + + class OracleCommandWithPayload : public IOracleCommand + { + private: + std::auto_ptr payload_; + + public: + void SetPayload(Orthanc::IDynamicObject* payload) + { + if (payload == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + payload_.reset(payload); + } + } + + bool HasPayload() const + { + return (payload_.get() != NULL); + } + + const Orthanc::IDynamicObject& GetPayload() const + { + if (HasPayload()) + { + return *payload_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + }; + + + + class OracleCommandExceptionMessage : public OrthancStone::IMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const IOracleCommand& command_; + Orthanc::OrthancException exception_; + + public: + OracleCommandExceptionMessage(const IOracleCommand& command, + const Orthanc::OrthancException& exception) : + command_(command), + exception_(exception) + { + } + + OracleCommandExceptionMessage(const IOracleCommand& command, + const Orthanc::ErrorCode& error) : + command_(command), + exception_(error) + { + } + + const IOracleCommand& GetCommand() const + { + return command_; + } + + const Orthanc::OrthancException& GetException() const + { + return exception_; + } + }; + + + typedef std::map HttpHeaders; + + class OrthancRestApiCommand : public OracleCommandWithPayload + { + public: + class SuccessMessage : public OrthancStone::OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + HttpHeaders headers_; + std::string answer_; + + public: + SuccessMessage(const OrthancRestApiCommand& command, + const HttpHeaders& answerHeaders, + std::string& answer /* will be swapped to avoid a memcpy() */) : + OriginMessage(command), + headers_(answerHeaders), + answer_(answer) + { + } + + const std::string& GetAnswer() const + { + return answer_; + } + + void ParseJsonBody(Json::Value& target) const + { + Json::Reader reader; + if (!reader.parse(answer_, target)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + const HttpHeaders& GetAnswerHeaders() const + { + return headers_; + } + }; + + + private: + Orthanc::HttpMethod method_; + std::string uri_; + std::string body_; + HttpHeaders headers_; + unsigned int timeout_; + + std::auto_ptr< OrthancStone::MessageHandler > successCallback_; + std::auto_ptr< OrthancStone::MessageHandler > failureCallback_; + + public: + OrthancRestApiCommand() : + method_(Orthanc::HttpMethod_Get), + uri_("/"), + timeout_(10) + { + } + + virtual Type GetType() const + { + return Type_OrthancRestApi; + } + + void SetMethod(Orthanc::HttpMethod method) + { + method_ = method; + } + + void SetUri(const std::string& uri) + { + uri_ = uri; + } + + void SetBody(const std::string& body) + { + body_ = body; + } + + void SetBody(const Json::Value& json) + { + Json::FastWriter writer; + body_ = writer.write(json); + } + + void SetHttpHeaders(const HttpHeaders& headers) + { + headers_ = headers; + } + + void SetHttpHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + Orthanc::HttpMethod GetMethod() const + { + return method_; + } + + const std::string& GetUri() const + { + return uri_; + } + + const std::string& GetBody() const + { + if (method_ == Orthanc::HttpMethod_Post || + method_ == Orthanc::HttpMethod_Put) + { + return body_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + const HttpHeaders& GetHttpHeaders() const + { + return headers_; + } + + void SetTimeout(unsigned int seconds) + { + timeout_ = seconds; + } + + unsigned int GetTimeout() const + { + return timeout_; + } + }; + + + + + class GetOrthancImageCommand : public OracleCommandWithPayload + { + public: + class SuccessMessage : public OrthancStone::OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + std::auto_ptr image_; + Orthanc::MimeType mime_; + + public: + SuccessMessage(const GetOrthancImageCommand& command, + Orthanc::ImageAccessor* image, // Takes ownership + Orthanc::MimeType mime) : + OriginMessage(command), + image_(image), + mime_(mime) + { + if (image == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + const Orthanc::ImageAccessor& GetImage() const + { + return *image_; + } + + Orthanc::MimeType GetMimeType() const + { + return mime_; + } + }; + + + private: + std::string uri_; + HttpHeaders headers_; + unsigned int timeout_; + + std::auto_ptr< OrthancStone::MessageHandler > successCallback_; + std::auto_ptr< OrthancStone::MessageHandler > failureCallback_; + + public: + GetOrthancImageCommand() : + uri_("/"), + timeout_(10) + { + } + + virtual Type GetType() const + { + return Type_GetOrthancImage; + } + + void SetUri(const std::string& uri) + { + uri_ = uri; + } + + void SetHttpHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + const std::string& GetUri() const + { + return uri_; + } + + const HttpHeaders& GetHttpHeaders() const + { + return headers_; + } + + void SetTimeout(unsigned int seconds) + { + timeout_ = seconds; + } + + unsigned int GetTimeout() const + { + return timeout_; + } + + void ProcessHttpAnswer(IMessageEmitter& emitter, + const OrthancStone::IObserver& receiver, + const std::string& answer, + const HttpHeaders& answerHeaders) const + { + Orthanc::MimeType contentType = Orthanc::MimeType_Binary; + + for (HttpHeaders::const_iterator it = answerHeaders.begin(); + it != answerHeaders.end(); ++it) + { + std::string s; + Orthanc::Toolbox::ToLowerCase(s, it->first); + + if (s == "content-type") + { + contentType = Orthanc::StringToMimeType(it->second); + break; + } + } + + std::auto_ptr image; + + switch (contentType) + { + case Orthanc::MimeType_Png: + { + image.reset(new Orthanc::PngReader); + dynamic_cast(*image).ReadFromMemory(answer); + break; + } + + case Orthanc::MimeType_Pam: + { + image.reset(new Orthanc::PamReader); + dynamic_cast(*image).ReadFromMemory(answer); + break; + } + + case Orthanc::MimeType_Jpeg: + { + image.reset(new Orthanc::JpegReader); + dynamic_cast(*image).ReadFromMemory(answer); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "Unsupported HTTP Content-Type for an image: " + + std::string(Orthanc::EnumerationToString(contentType))); + } + + SuccessMessage message(*this, image.release(), contentType); + emitter.EmitMessage(receiver, message); + } + }; + + + + class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload + { + public: + class SuccessMessage : public OrthancStone::OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + std::auto_ptr image_; + + public: + SuccessMessage(const GetOrthancWebViewerJpegCommand& command, + Orthanc::ImageAccessor* image) : // Takes ownership + OriginMessage(command), + image_(image) + { + if (image == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + const Orthanc::ImageAccessor& GetImage() const + { + return *image_; + } + }; + + private: + std::string instanceId_; + unsigned int frame_; + unsigned int quality_; + HttpHeaders headers_; + unsigned int timeout_; + Orthanc::PixelFormat expectedFormat_; + + std::auto_ptr< OrthancStone::MessageHandler > successCallback_; + std::auto_ptr< OrthancStone::MessageHandler > failureCallback_; + + public: + GetOrthancWebViewerJpegCommand() : + frame_(0), + quality_(95), + timeout_(10), + expectedFormat_(Orthanc::PixelFormat_Grayscale8) + { + } + + virtual Type GetType() const + { + return Type_GetOrthancWebViewerJpeg; + } + + void SetExpectedFormat(Orthanc::PixelFormat format) + { + expectedFormat_ = format; + } + + void SetInstance(const std::string& instanceId) + { + instanceId_ = instanceId; + } + + void SetFrame(unsigned int frame) + { + frame_ = frame; + } + + void SetQuality(unsigned int quality) + { + if (quality <= 0 || + quality > 100) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + quality_ = quality; + } + } + + void SetHttpHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + Orthanc::PixelFormat GetExpectedFormat() const + { + return expectedFormat_; + } + + const std::string& GetInstanceId() const + { + return instanceId_; + } + + unsigned int GetFrame() const + { + return frame_; + } + + unsigned int GetQuality() const + { + return quality_; + } + + const HttpHeaders& GetHttpHeaders() const + { + return headers_; + } + + void SetTimeout(unsigned int seconds) + { + timeout_ = seconds; + } + + unsigned int GetTimeout() const + { + return timeout_; + } + + std::string GetUri() const + { + return ("/web-viewer/instances/jpeg" + boost::lexical_cast(quality_) + + "-" + instanceId_ + "_" + boost::lexical_cast(frame_)); + } + + void ProcessHttpAnswer(IMessageEmitter& emitter, + const OrthancStone::IObserver& receiver, + const std::string& answer) const + { + // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()" + + Json::Value encoded; + + { + Json::Reader reader; + if (!reader.parse(answer, encoded)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + if (encoded.type() != Json::objectValue || + !encoded.isMember("Orthanc") || + encoded["Orthanc"].type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + const Json::Value& info = encoded["Orthanc"]; + if (!info.isMember("PixelData") || + !info.isMember("Stretched") || + !info.isMember("Compression") || + info["Compression"].type() != Json::stringValue || + info["PixelData"].type() != Json::stringValue || + info["Stretched"].type() != Json::booleanValue || + info["Compression"].asString() != "Jpeg") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + bool isSigned = false; + bool isStretched = info["Stretched"].asBool(); + + if (info.isMember("IsSigned")) + { + if (info["IsSigned"].type() != Json::booleanValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + isSigned = info["IsSigned"].asBool(); + } + } + + std::auto_ptr reader; + + { + std::string jpeg; + Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); + + reader.reset(new Orthanc::JpegReader); + dynamic_cast(*reader).ReadFromMemory(jpeg); + } + + if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image + { + if (expectedFormat_ != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (isSigned || isStretched) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + SuccessMessage message(*this, reader.release()); + emitter.EmitMessage(receiver, message); + return; + } + } + + if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (!isStretched) + { + if (expectedFormat_ != reader->GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + SuccessMessage message(*this, reader.release()); + emitter.EmitMessage(receiver, message); + return; + } + } + + int32_t stretchLow = 0; + int32_t stretchHigh = 0; + + if (!info.isMember("StretchLow") || + !info.isMember("StretchHigh") || + info["StretchLow"].type() != Json::intValue || + info["StretchHigh"].type() != Json::intValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + stretchLow = info["StretchLow"].asInt(); + stretchHigh = info["StretchHigh"].asInt(); + + if (stretchLow < -32768 || + stretchHigh > 65535 || + (stretchLow < 0 && stretchHigh > 32767)) + { + // This range cannot be represented with a uint16_t or an int16_t + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + // Decode a grayscale JPEG 8bpp image coming from the Web viewer + std::auto_ptr image + (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false)); + + Orthanc::ImageProcessing::Convert(*image, *reader); + reader.reset(); + + float scaling = static_cast(stretchHigh - stretchLow) / 255.0f; + + if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling)) + { + float offset = static_cast(stretchLow) / scaling; + Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); + } + + SuccessMessage message(*this, image.release()); + emitter.EmitMessage(receiver, message); + } + }; + + + + + + class NativeOracle : public IOracle + { + private: + class Item : public Orthanc::IDynamicObject + { + private: + const OrthancStone::IObserver& receiver_; + std::auto_ptr command_; + + public: + Item(const OrthancStone::IObserver& receiver, + IOracleCommand* command) : + receiver_(receiver), + command_(command) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + const OrthancStone::IObserver& GetReceiver() const + { + return receiver_; + } + + const IOracleCommand& GetCommand() const + { + assert(command_.get() != NULL); + return *command_; + } + }; + + + enum State + { + State_Setup, + State_Running, + State_Stopped + }; + + + IMessageEmitter& emitter_; + Orthanc::WebServiceParameters orthanc_; + Orthanc::SharedMessageQueue queue_; + State state_; + boost::mutex mutex_; + std::vector workers_; + + + void CopyHttpHeaders(Orthanc::HttpClient& client, + const HttpHeaders& headers) + { + for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ ) + { + client.AddHeader(it->first, it->second); + } + } + + + void DecodeAnswer(std::string& answer, + const HttpHeaders& headers) + { + Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None; + + for (HttpHeaders::const_iterator it = headers.begin(); + it != headers.end(); ++it) + { + std::string s; + Orthanc::Toolbox::ToLowerCase(s, it->first); + + if (s == "content-encoding") + { + if (it->second == "gzip") + { + contentEncoding = Orthanc::HttpCompression_Gzip; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "Unsupported HTTP Content-Encoding: " + it->second); + } + + break; + } + } + + if (contentEncoding == Orthanc::HttpCompression_Gzip) + { + std::string compressed; + answer.swap(compressed); + + Orthanc::GzipCompressor compressor; + compressor.Uncompress(answer, compressed.c_str(), compressed.size()); + } + } + + + void Execute(const OrthancStone::IObserver& receiver, + const OrthancRestApiCommand& command) + { + Orthanc::HttpClient client(orthanc_, command.GetUri()); + client.SetMethod(command.GetMethod()); + client.SetTimeout(command.GetTimeout()); + + CopyHttpHeaders(client, command.GetHttpHeaders()); + + if (command.GetMethod() == Orthanc::HttpMethod_Post || + command.GetMethod() == Orthanc::HttpMethod_Put) + { + client.SetBody(command.GetBody()); + } + + std::string answer; + HttpHeaders answerHeaders; + client.ApplyAndThrowException(answer, answerHeaders); + + DecodeAnswer(answer, answerHeaders); + + OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer); + emitter_.EmitMessage(receiver, message); + } + + + void Execute(const OrthancStone::IObserver& receiver, + const GetOrthancImageCommand& command) + { + Orthanc::HttpClient client(orthanc_, command.GetUri()); + client.SetTimeout(command.GetTimeout()); + + CopyHttpHeaders(client, command.GetHttpHeaders()); + + std::string answer; + HttpHeaders answerHeaders; + client.ApplyAndThrowException(answer, answerHeaders); + + DecodeAnswer(answer, answerHeaders); + + command.ProcessHttpAnswer(emitter_, receiver, answer, answerHeaders); + } + + + void Execute(const OrthancStone::IObserver& receiver, + const GetOrthancWebViewerJpegCommand& command) + { + Orthanc::HttpClient client(orthanc_, command.GetUri()); + client.SetTimeout(command.GetTimeout()); + + CopyHttpHeaders(client, command.GetHttpHeaders()); + + std::string answer; + HttpHeaders answerHeaders; + client.ApplyAndThrowException(answer, answerHeaders); + + DecodeAnswer(answer, answerHeaders); + + command.ProcessHttpAnswer(emitter_, receiver, answer); + } + + + void Step() + { + std::auto_ptr object(queue_.Dequeue(100)); + + if (object.get() != NULL) + { + const Item& item = dynamic_cast(*object); + + try + { + switch (item.GetCommand().GetType()) + { + case IOracleCommand::Type_OrthancRestApi: + Execute(item.GetReceiver(), + dynamic_cast(item.GetCommand())); + break; + + case IOracleCommand::Type_GetOrthancImage: + Execute(item.GetReceiver(), + dynamic_cast(item.GetCommand())); + break; + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + Execute(item.GetReceiver(), + dynamic_cast(item.GetCommand())); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception within the oracle: " << e.What(); + emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage(item.GetCommand(), e)); + } + catch (...) + { + LOG(ERROR) << "Native exception within the oracle"; + emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage + (item.GetCommand(), Orthanc::ErrorCode_InternalError)); + } + } + } + + + static void Worker(NativeOracle* that) + { + assert(that != NULL); + + for (;;) + { + { + boost::mutex::scoped_lock lock(that->mutex_); + if (that->state_ != State_Running) + { + return; + } + } + + that->Step(); + } + } + + + void StopInternal() + { + { + boost::mutex::scoped_lock lock(mutex_); + + if (state_ == State_Setup || + state_ == State_Stopped) + { + return; + } + else + { + state_ = State_Stopped; + } + } + + for (size_t i = 0; i < workers_.size(); i++) + { + if (workers_[i] != NULL) + { + if (workers_[i]->joinable()) + { + workers_[i]->join(); + } + + delete workers_[i]; + } + } + } + + + public: + NativeOracle(IMessageEmitter& emitter) : + emitter_(emitter), + state_(State_Setup), + workers_(4) + { + } + + virtual ~NativeOracle() + { + StopInternal(); + } + + void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc) + { + boost::mutex::scoped_lock lock(mutex_); + + if (state_ != State_Setup) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + orthanc_ = orthanc; + } + } + + void SetWorkersCount(unsigned int count) + { + boost::mutex::scoped_lock lock(mutex_); + + if (count <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else if (state_ != State_Setup) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + workers_.resize(count); + } + } + + void Start() + { + boost::mutex::scoped_lock lock(mutex_); + + if (state_ != State_Setup) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + state_ = State_Running; + + for (unsigned int i = 0; i < workers_.size(); i++) + { + workers_[i] = new boost::thread(Worker, this); + } + } + } + + void Stop() + { + StopInternal(); + } + + virtual void Schedule(const OrthancStone::IObserver& receiver, + IOracleCommand* command) + { + queue_.Enqueue(new Item(receiver, command)); + } + }; + + + + class NativeApplicationContext : public IMessageEmitter + { + private: + boost::shared_mutex mutex_; + OrthancStone::MessageBroker broker_; + OrthancStone::IObservable oracleObservable_; + + public: + NativeApplicationContext() : + oracleObservable_(broker_) + { + } + + + virtual void EmitMessage(const OrthancStone::IObserver& observer, + const OrthancStone::IMessage& message) + { + try + { + boost::unique_lock 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 lock_; + + public: + ReaderLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + }; + + + class WriterLock : public boost::noncopyable + { + private: + NativeApplicationContext& that_; + boost::unique_lock lock_; + + public: + WriterLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + + OrthancStone::MessageBroker& GetBroker() + { + return that_.broker_; + } + + OrthancStone::IObservable& GetOracleObservable() + { + return that_.oracleObservable_; + } + }; + }; + + + + class DicomInstanceParameters : + public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */ + { + private: + struct Data // Struct to ease the copy constructor + { + Orthanc::DicomImageInformation imageInformation_; + OrthancStone::SopClassUid sopClassUid_; + double thickness_; + double pixelSpacingX_; + double pixelSpacingY_; + OrthancStone::CoordinateSystem3D geometry_; + OrthancStone::Vector frameOffsets_; + bool isColor_; + bool hasRescale_; + double rescaleOffset_; + double rescaleSlope_; + bool hasDefaultWindowing_; + float defaultWindowingCenter_; + float defaultWindowingWidth_; + Orthanc::PixelFormat expectedPixelFormat_; + + void ComputeDoseOffsets(const Orthanc::DicomMap& dicom) + { + // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html + + { + std::string increment; + + if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) + { + Orthanc::Toolbox::ToUpperCase(increment); + if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag + { + LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; + return; + } + } + } + + if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || + frameOffsets_.size() < imageInformation_.GetNumberOfFrames()) + { + LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)"; + frameOffsets_.clear(); + } + else + { + if (frameOffsets_.size() >= 2) + { + thickness_ = frameOffsets_[1] - frameOffsets_[0]; + + if (thickness_ < 0) + { + thickness_ = -thickness_; + } + } + } + } + + Data(const Orthanc::DicomMap& dicom) : + imageInformation_(dicom) + { + if (imageInformation_.GetNumberOfFrames() <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + std::string s; + if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + sopClassUid_ = OrthancStone::StringToSopClassUid(s); + } + + if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS)) + { + thickness_ = 100.0 * std::numeric_limits::epsilon(); + } + + OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom); + + std::string position, orientation; + if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && + dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) + { + geometry_ = OrthancStone::CoordinateSystem3D(position, orientation); + } + + if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) + { + ComputeDoseOffsets(dicom); + } + + isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && + imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); + + double doseGridScaling; + + if (dicom.ParseDouble(rescaleOffset_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && + dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) + { + hasRescale_ = true; + } + else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) + { + hasRescale_ = true; + rescaleOffset_ = 0; + rescaleSlope_ = doseGridScaling; + } + else + { + hasRescale_ = false; + } + + OrthancStone::Vector c, w; + if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && + OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) && + c.size() > 0 && + w.size() > 0) + { + hasDefaultWindowing_ = true; + defaultWindowingCenter_ = static_cast(c[0]); + defaultWindowingWidth_ = static_cast(w[0]); + } + else + { + hasDefaultWindowing_ = false; + } + + if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) + { + switch (imageInformation_.GetBitsStored()) + { + case 16: + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + break; + + case 32: + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + else if (isColor_) + { + expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; + } + else if (imageInformation_.IsSigned()) + { + expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; + } + else + { + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + } + } + + OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const + { + if (frame == 0) + { + return geometry_; + } + else if (frame >= imageInformation_.GetNumberOfFrames()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) + { + if (frame >= frameOffsets_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + return OrthancStone::CoordinateSystem3D( + geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(), + geometry_.GetAxisX(), + geometry_.GetAxisY()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + // TODO - Is this necessary? + bool FrameContainsPlane(unsigned int frame, + const OrthancStone::CoordinateSystem3D& plane) const + { + if (frame >= imageInformation_.GetNumberOfFrames()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + OrthancStone::CoordinateSystem3D tmp = geometry_; + + if (frame != 0) + { + tmp = GetFrameGeometry(frame); + } + + double distance; + + return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) && + distance <= thickness_ / 2.0); + } + }; + + Data data_; + + + public: + DicomInstanceParameters(const DicomInstanceParameters& other) : + data_(other.data_) + { + } + + + DicomInstanceParameters(const Orthanc::DicomMap& dicom) : + data_(dicom) + { + } + + const Orthanc::DicomImageInformation& GetImageInformation() const + { + return data_.imageInformation_; + } + + OrthancStone::SopClassUid GetSopClassUid() const + { + return data_.sopClassUid_; + } + + double GetThickness() const + { + return data_.thickness_; + } + + double GetPixelSpacingX() const + { + return data_.pixelSpacingX_; + } + + double GetPixelSpacingY() const + { + return data_.pixelSpacingY_; + } + + const OrthancStone::CoordinateSystem3D& GetGeometry() const + { + return data_.geometry_; + } + + OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const + { + return data_.GetFrameGeometry(frame); + } + + // TODO - Is this necessary? + bool FrameContainsPlane(unsigned int frame, + const OrthancStone::CoordinateSystem3D& plane) const + { + return data_.FrameContainsPlane(frame, plane); + } + + bool IsColor() const + { + return data_.isColor_; + } + + bool HasRescale() const + { + return data_.hasRescale_; + } + + double GetRescaleOffset() const + { + if (data_.hasRescale_) + { + return data_.rescaleOffset_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + double GetRescaleSlope() const + { + if (data_.hasRescale_) + { + return data_.rescaleSlope_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + bool HasDefaultWindowing() const + { + return data_.hasDefaultWindowing_; + } + + float GetDefaultWindowingCenter() const + { + if (data_.hasDefaultWindowing_) + { + return data_.defaultWindowingCenter_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + float GetDefaultWindowingWidth() const + { + if (data_.hasDefaultWindowing_) + { + return data_.defaultWindowingWidth_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + Orthanc::PixelFormat GetExpectedPixelFormat() const + { + return data_.expectedPixelFormat_; + } + }; + + + class DicomVolumeImage : public boost::noncopyable + { + private: + std::auto_ptr image_; + std::vector slices_; + + static const DicomInstanceParameters& + GetSliceParameters(const OrthancStone::SlicesSorter& slices, + size_t index) + { + return dynamic_cast(slices.GetSlicePayload(index)); + } + + static void CheckSlice(const OrthancStone::SlicesSorter& slices, + size_t index, + const OrthancStone::CoordinateSystem3D& reference, + const DicomInstanceParameters& a) + { + const OrthancStone::CoordinateSystem3D& slice = slices.GetSliceGeometry(index); + const DicomInstanceParameters& b = GetSliceParameters(slices, index); + + if (!OrthancStone::GeometryToolbox::IsParallel(reference.GetNormal(), slice.GetNormal())) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, + "A slice in the volume image is not parallel to the others"); + } + + if (a.GetExpectedPixelFormat() != b.GetExpectedPixelFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, + "The pixel format changes across the slices of the volume image"); + } + + if (a.GetImageInformation().GetWidth() != b.GetImageInformation().GetWidth() || + a.GetImageInformation().GetHeight() != b.GetImageInformation().GetHeight()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize, + "The width/height of slices are not constant in the volume image"); + } + + if (!OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) || + !OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingY(), b.GetPixelSpacingY())) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, + "The pixel spacing of the slices change across the volume image"); + } + } + + + static void CheckVolume(const OrthancStone::SlicesSorter& slices) + { + for (size_t i = 0; i < slices.GetSlicesCount(); i++) + { + if (GetSliceParameters(slices, i).GetImageInformation().GetNumberOfFrames() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, + "This class does not support multi-frame images"); + } + } + + if (slices.GetSlicesCount() != 0) + { + const OrthancStone::CoordinateSystem3D& reference = slices.GetSliceGeometry(0); + const DicomInstanceParameters& dicom = GetSliceParameters(slices, 0); + + for (size_t i = 1; i < slices.GetSlicesCount(); i++) + { + CheckSlice(slices, i, reference, dicom); + } + } + } + + + void Clear() + { + image_.reset(); + + for (size_t i = 0; i < slices_.size(); i++) + { + assert(slices_[i] != NULL); + delete slices_[i]; + } + } + + + public: + DicomVolumeImage() + { + } + + ~DicomVolumeImage() + { + Clear(); + } + + // WARNING: The payload of "slices" must be of class "DicomInstanceParameters" + void SetGeometry(OrthancStone::SlicesSorter& slices) + { + Clear(); + + if (!slices.Sort()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "Cannot sort the 3D slices of a DICOM series"); + } + + slices_.reserve(slices.GetSlicesCount()); + + for (size_t i = 0; i < slices.GetSlicesCount(); i++) + { + slices_.push_back(new DicomInstanceParameters(GetSliceParameters(slices, i))); + } + + CheckVolume(slices); + + const double spacingZ = slices.ComputeSpacingBetweenSlices(); + LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; + + const DicomInstanceParameters& parameters = GetSliceParameters(slices, 0); + + image_.reset(new OrthancStone::ImageBuffer3D(parameters.GetExpectedPixelFormat(), + parameters.GetImageInformation().GetWidth(), + parameters.GetImageInformation().GetHeight(), + slices.GetSlicesCount(), false /* don't compute range */)); + + image_->SetAxialGeometry(slices.GetSliceGeometry(0)); + image_->SetVoxelDimensions(parameters.GetPixelSpacingX(), parameters.GetPixelSpacingY(), spacingZ); + image_->Clear(); + } + + bool IsGeometryReady() const + { + return (image_.get() != NULL); + } + + const OrthancStone::ImageBuffer3D& GetImage() const + { + if (!IsGeometryReady()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *image_; + } + } + }; + + + + class AxialVolumeOrthancLoader : public OrthancStone::IObserver + { + private: + class MessageHandler : public Orthanc::IDynamicObject + { + public: + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const = 0; + }; + + void Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + dynamic_cast(message.GetOrigin().GetPayload()).Handle(message); + } + + + class LoadSeriesGeometryHandler : public MessageHandler + { + private: + AxialVolumeOrthancLoader& that_; + + public: + LoadSeriesGeometryHandler(AxialVolumeOrthancLoader& that) : + that_(that) + { + } + + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const + { + Json::Value value; + message.ParseJsonBody(value); + + if (value.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + Json::Value::Members instances = value.getMemberNames(); + + OrthancStone::SlicesSorter slices; + + for (size_t i = 0; i < instances.size(); i++) + { + Orthanc::DicomMap dicom; + dicom.FromDicomAsJson(value[instances[i]]); + + std::auto_ptr instance(new DicomInstanceParameters(dicom)); + + OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry(); + slices.AddSlice(geometry, instance.release()); + } + + that_.image_.SetGeometry(slices); + } + }; + + + class LoadInstanceGeometryHandler : public MessageHandler + { + private: + AxialVolumeOrthancLoader& that_; + + public: + LoadInstanceGeometryHandler(AxialVolumeOrthancLoader& that) : + that_(that) + { + } + + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const + { + Json::Value value; + message.ParseJsonBody(value); + + if (value.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + Orthanc::DicomMap dicom; + dicom.FromDicomAsJson(value); + + DicomInstanceParameters instance(dicom); + } + }; + + + bool active_; + DicomVolumeImage image_; + + public: + AxialVolumeOrthancLoader(OrthancStone::IObservable& oracle) : + IObserver(oracle.GetBroker()), + active_(false) + { + oracle.RegisterObserverCallback( + new OrthancStone::Callable + (*this, &AxialVolumeOrthancLoader::Handle)); + } + + void LoadSeries(IOracle& oracle, + const std::string& seriesId) + { + if (active_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + active_ = true; + + std::auto_ptr command(new Refactoring::OrthancRestApiCommand); + command->SetUri("/series/" + seriesId + "/instances-tags"); + command->SetPayload(new LoadSeriesGeometryHandler(*this)); + + oracle.Schedule(*this, command.release()); + } + + void LoadInstance(IOracle& oracle, + const std::string& instanceId) + { + if (active_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + active_ = true; + + // Tag "3004-000c" is "Grid Frame Offset Vector", which is + // mandatory to read RT DOSE, but is too long to be returned by default + + // TODO => Should be part of a second call if needed + + std::auto_ptr command(new Refactoring::OrthancRestApiCommand); + command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c"); + command->SetPayload(new LoadInstanceGeometryHandler(*this)); + + oracle.Schedule(*this, command.release()); + } + }; + +} + + + +class Toto : public OrthancStone::IObserver +{ +private: + void Handle(const Refactoring::OrthancRestApiCommand::SuccessMessage& message) + { + Json::Value v; + message.ParseJsonBody(v); + + printf("ICI [%s]\n", v.toStyledString().c_str()); + } + + void Handle(const Refactoring::GetOrthancImageCommand::SuccessMessage& message) + { + printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); + } + + void Handle(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message) + { + printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); + } + + void Handle(const Refactoring::OracleCommandExceptionMessage& message) + { + printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType()); + + switch (message.GetCommand().GetType()) + { + case Refactoring::IOracleCommand::Type_GetOrthancWebViewerJpeg: + printf("URI: [%s]\n", dynamic_cast + (message.GetCommand()).GetUri().c_str()); + break; + + default: + break; + } + } + +public: + Toto(OrthancStone::IObservable& oracle) : + IObserver(oracle.GetBroker()) + { + oracle.RegisterObserverCallback + (new OrthancStone::Callable + (*this, &Toto::Handle)); + + oracle.RegisterObserverCallback + (new OrthancStone::Callable + (*this, &Toto::Handle)); + + oracle.RegisterObserverCallback + (new OrthancStone::Callable + (*this, &Toto::Handle)); + + oracle.RegisterObserverCallback + (new OrthancStone::Callable + (*this, &Toto::Handle)); + } +}; + + +void Run(Refactoring::NativeApplicationContext& context) +{ + std::auto_ptr toto; + std::auto_ptr loader1, loader2; + + { + Refactoring::NativeApplicationContext::WriterLock lock(context); + toto.reset(new Toto(lock.GetOracleObservable())); + loader1.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable())); + loader2.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable())); + } + + Refactoring::NativeOracle oracle(context); + + { + Orthanc::WebServiceParameters p; + //p.SetUrl("http://localhost:8043/"); + p.SetCredentials("orthanc", "orthanc"); + oracle.SetOrthancParameters(p); + } + + oracle.Start(); + + if (1) + { + Json::Value v = Json::objectValue; + v["Level"] = "Series"; + v["Query"] = Json::objectValue; + + std::auto_ptr command(new Refactoring::OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetUri("/tools/find"); + command->SetBody(v); + + oracle.Schedule(*toto, command.release()); + } + + if (1) + { + std::auto_ptr command(new Refactoring::GetOrthancImageCommand); + command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg))); + command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); + oracle.Schedule(*toto, command.release()); + } + + if (1) + { + std::auto_ptr command(new Refactoring::GetOrthancImageCommand); + command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); + command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); + oracle.Schedule(*toto, command.release()); + } + + if (1) + { + std::auto_ptr command(new Refactoring::GetOrthancImageCommand); + command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); + command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); + oracle.Schedule(*toto, command.release()); + } + + if (1) + { + std::auto_ptr command(new Refactoring::GetOrthancImageCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); + command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); + oracle.Schedule(*toto, command.release()); + } + + if (1) + { + std::auto_ptr command(new Refactoring::GetOrthancImageCommand); + command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); + command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); + oracle.Schedule(*toto, command.release()); + } + + if (1) + { + std::auto_ptr command(new Refactoring::GetOrthancWebViewerJpegCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e"); + command->SetQuality(90); + oracle.Schedule(*toto, command.release()); + } + + + // 2017-11-17-Anonymized + //loader1->LoadSeries(oracle, "cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT + loader2->LoadInstance(oracle, "41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE + + // Delphine + loader1->LoadSeries(oracle, "5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT + + LOG(WARNING) << "...Waiting for Ctrl-C..."; + Orthanc::SystemToolbox::ServerBarrier(); + //boost::this_thread::sleep(boost::posix_time::seconds(1)); + + oracle.Stop(); +} + + + +/** + * 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[]) +{ + OrthancStone::StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + + try + { + Refactoring::NativeApplicationContext context; + Run(context); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + OrthancStone::StoneFinalize(); + + return 0; +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Sdl/TrackerSample.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/TrackerSample.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,98 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "TrackerSampleApp.h" + + // From Stone +#include "../../Applications/Sdl/SdlOpenGLWindow.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/StoneInitialization.h" + +#include +#include + + +#include +#include + +#include +#include + +/* +TODO: + +- to decouple the trackers from the sample, we need to supply them with + the scene rather than the app + +- in order to do that, we need a GetNextFreeZIndex function (or something + along those lines) in the scene object + +*/ + + +using namespace Orthanc; +using namespace OrthancStone; + + + + +boost::weak_ptr g_app; + +void TrackerSample_SetInfoDisplayMessage(std::string key, std::string value) +{ + boost::shared_ptr app = g_app.lock(); + if (app) + { + app->SetInfoDisplayMessage(key, value); + } +} + +/** + * IMPORTANT: The full arguments to "main()" are needed for SDL on + * Windows. Otherwise, one gets the linking error "undefined reference + * to `SDL_main'". https://wiki.libsdl.org/FAQWindows + **/ +int main(int argc, char* argv[]) +{ + StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + Orthanc::Logging::EnableTraceLevel(true); + + try + { + MessageBroker broker; + boost::shared_ptr app(new TrackerSampleApp(broker)); + g_app = app; + app->PrepareScene(); + app->Run(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} + + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Sdl/TrackerSampleApp.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/TrackerSampleApp.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,580 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "TrackerSampleApp.h" + +#include "../Common/CreateLineMeasureTracker.h" +#include "../Common/CreateAngleMeasureTracker.h" + +#include "../../Applications/Sdl/SdlOpenGLWindow.h" + +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2D/Scene2D.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/StoneInitialization.h" + + // From Orthanc framework +#include +#include +#include +#include +#include + +#include +#include + +using namespace Orthanc; + +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]; + } + + Scene2D& TrackerSampleApp::GetScene() + { + return scene_; + } + + void TrackerSampleApp::SelectNextTool() + { + currentTool_ = static_cast(currentTool_ + 1); + if (currentTool_ == GuiTool_LAST) + currentTool_ = static_cast(0);; + printf("Current tool is now: %s\n", MeasureToolToString(currentTool_)); + } + + void TrackerSampleApp::DisplayInfoText() + { + // do not try to use stuff too early! + if (compositor_.get() == NULL) + return; + + std::stringstream msg; + for (auto kv : infoTextMap_) + { + msg << kv.first << " : " << kv.second << std::endl; + } + auto msgS = msg.str(); + + TextSceneLayer* layerP = NULL; + if (scene_.HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = dynamic_cast( + scene_.GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); + layerP = &layer; + } + else + { + std::auto_ptr 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); + scene_.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); + scene_.GetCanvasToSceneTransform().Apply(cX,cY); + layerP->SetPosition(cX, cY); + } + + void TrackerSampleApp::DisplayFloatingCtrlInfoText(const PointerEvent& e) + { + ScenePoint2D p = e.GetMainPosition().Apply(scene_.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 (scene_.HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = + dynamic_cast(scene_.GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::auto_ptr 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(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release()); + } + } + + void TrackerSampleApp::HideInfoText() + { + scene_.DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX); + } + + void TrackerSampleApp::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_LCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_LCTRL]) + { + // 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)); + + 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)); + + //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_->IsActive()) + activeTracker_ = NULL; + } + } + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + if (activeTracker_) + { + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates(event.button.x, event.button.y)); + activeTracker_->PointerUp(e); + if (!activeTracker_->IsActive()) + activeTracker_ = NULL; + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates( + event.button.x, event.button.y)); + if (activeTracker_) + { + activeTracker_->PointerDown(e); + if (!activeTracker_->IsActive()) + activeTracker_ = NULL; + } + 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_->IsActive()) + activeTracker_ = NULL; + } + 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_s: + scene_.FitContent(compositor_->GetCanvasWidth(), + compositor_->GetCanvasHeight()); + break; + + case SDLK_c: + TakeScreenshot( + "screenshot.png", + compositor_->GetCanvasWidth(), + compositor_->GetCanvasHeight()); + break; + + default: + break; + } + } + } + + + void TrackerSampleApp::OnSceneTransformChanged(const Scene2D::SceneTransformChanged& message) + { + DisplayInfoText(); + } + + FlexiblePointerTrackerPtr TrackerSampleApp::CreateSuitableTracker( + const SDL_Event & event, + const PointerEvent & e) + { + switch (event.button.button) + { + case SDL_BUTTON_MIDDLE: + return CreateSimpleTrackerAdapter(PointerTrackerPtr( + new PanSceneTracker(scene_, e))); + + case SDL_BUTTON_RIGHT: + return CreateSimpleTrackerAdapter(PointerTrackerPtr( + new ZoomSceneTracker(scene_, e, compositor_->GetCanvasHeight()))); + + case SDL_BUTTON_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?) + FlexiblePointerTrackerPtr hitTestTracker = TrackerHitTest(e); + + if (hitTestTracker != NULL) + { + //LOG(TRACE) << "hitTestTracker != NULL"; + return hitTestTracker; + } + else + { + switch (currentTool_) + { + case GuiTool_Rotate: + //LOG(TRACE) << "Creating RotateSceneTracker"; + return CreateSimpleTrackerAdapter(PointerTrackerPtr( + new RotateSceneTracker(scene_, e))); + case GuiTool_Pan: + return CreateSimpleTrackerAdapter(PointerTrackerPtr( + new PanSceneTracker(scene_, e))); + case GuiTool_Zoom: + return CreateSimpleTrackerAdapter(PointerTrackerPtr( + new ZoomSceneTracker(scene_, e, compositor_->GetCanvasHeight()))); + //case GuiTool_AngleMeasure: + // return new AngleMeasureTracker(scene_, measureTools_, undoStack_, e); + //case GuiTool_CircleMeasure: + // return new CircleMeasureTracker(scene_, measureTools_, undoStack_, e); + //case GuiTool_EllipseMeasure: + // return new EllipseMeasureTracker(scene_, measureTools_, undoStack_, e); + case GuiTool_LineMeasure: + return FlexiblePointerTrackerPtr(new CreateLineMeasureTracker( + IObserver::GetBroker(), scene_, undoStack_, measureTools_, e)); + case GuiTool_AngleMeasure: + return FlexiblePointerTrackerPtr(new CreateAngleMeasureTracker( + IObserver::GetBroker(), scene_, undoStack_, measureTools_, e)); + return NULL; + case GuiTool_CircleMeasure: + LOG(ERROR) << "Not implemented yet!"; + return NULL; + case GuiTool_EllipseMeasure: + LOG(ERROR) << "Not implemented yet!"; + return NULL; + default: + throw OrthancException(ErrorCode_InternalError, "Wrong tool!"); + } + } + } + default: + return NULL; + } + } + + + TrackerSampleApp::TrackerSampleApp(MessageBroker& broker) : IObserver(broker) + , scene_(broker) + , currentTool_(GuiTool_Rotate) + { + scene_.RegisterObserverCallback( + new Callable + (*this, &TrackerSampleApp::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 TrackerSampleApp::PrepareScene() + { + // Texture of 2x2 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t* p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast(i.GetRow(1)); + p[0] = 0; + p[1] = 0; + p[2] = 255; + + p[3] = 255; + p[4] = 0; + p[5] = 0; + + scene_.SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i)); + + std::auto_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-3, 2); + l->SetPixelSpacing(1.5, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene_.SetLayer(TEXTURE_2x2_2_ZINDEX, l.release()); + } + + // Texture of 1x1 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); + + uint8_t* p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + std::auto_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-2, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene_.SetLayer(TEXTURE_1x1_ZINDEX, l.release()); + } + + // Some lines + { + std::auto_ptr layer(new PolylineSceneLayer); + + layer->SetThickness(1); + + PolylineSceneLayer::Chain chain; + chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5)); + chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5)); + layer->AddChain(chain, true); + + chain.clear(); + chain.push_back(ScenePoint2D(-5, -5)); + chain.push_back(ScenePoint2D(5, -5)); + chain.push_back(ScenePoint2D(5, 5)); + chain.push_back(ScenePoint2D(-5, 5)); + layer->AddChain(chain, true); + + double dy = 1.01; + chain.clear(); + chain.push_back(ScenePoint2D(-4, -4)); + chain.push_back(ScenePoint2D(4, -4 + dy)); + chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy)); + chain.push_back(ScenePoint2D(4, 2)); + layer->AddChain(chain, false); + + layer->SetColor(0, 255, 255); + scene_.SetLayer(LINESET_1_ZINDEX, layer.release()); + } + + // Some text + { + std::auto_ptr layer(new TextSceneLayer); + layer->SetText("Hello"); + scene_.SetLayer(LINESET_2_ZINDEX, layer.release()); + } + } + + + void TrackerSampleApp::DisableTracker() + { + if (activeTracker_) + { + activeTracker_->Cancel(); + activeTracker_ = NULL; + } + } + + void TrackerSampleApp::TakeScreenshot(const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight) + { + CairoCompositor compositor(scene_, 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); + } + + + FlexiblePointerTrackerPtr TrackerSampleApp::TrackerHitTest(const PointerEvent & e) + { + // std::vector measureTools_; + return nullptr; + } + + 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); + } + } + + static bool g_stopApplication = false; + + void TrackerSampleApp::Run() + { + // False means we do NOT let Windows treat this as a legacy application + // that needs to be scaled + SdlOpenGLWindow window("Hello", 1024, 1024, false); + + GetScene().FitContent(window.GetCanvasWidth(), window.GetCanvasHeight()); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + compositor_.reset(new OpenGLCompositor(window, GetScene())); + + 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); + + while (!g_stopApplication) + { + compositor_->Refresh(); + + SDL_Event event; + while (!g_stopApplication && SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + g_stopApplication = true; + break; + } + else if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + DisableTracker(); // was: tracker.reset(NULL); + compositor_->UpdateSize(); + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + window.GetWindow().ToggleMaximize(); + break; + + case SDLK_q: + g_stopApplication = true; + break; + default: + break; + } + } + HandleApplicationEvent(event); + } + SDL_Delay(1); + } + compositor_.reset(NULL); + } + + void TrackerSampleApp::SetInfoDisplayMessage( + std::string key, std::string value) + { + if (value == "") + infoTextMap_.erase(key); + else + infoTextMap_[key] = value; + DisplayInfoText(); + } + +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Sdl/TrackerSampleApp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/TrackerSampleApp.h Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,134 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include +#include + +#include "../Common/IFlexiblePointerTracker.h" +#include "../Common/MeasureTools.h" + +#include + +#include +#include +#include + +namespace OrthancStone +{ + class TrackerCommand; + typedef boost::shared_ptr TrackerCommandPtr; + + 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 TrackerSampleApp : public IObserver + , public boost::enable_shared_from_this + { + public: + // 12 because. + TrackerSampleApp(MessageBroker& broker); + void PrepareScene(); + void Run(); + void SetInfoDisplayMessage(std::string key, std::string value); + void DisableTracker(); + + Scene2D& GetScene(); + + 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 Scene2D::SceneTransformChanged& message); + + private: + void SelectNextTool(); + + + FlexiblePointerTrackerPtr TrackerHitTest(const PointerEvent& e); + + FlexiblePointerTrackerPtr CreateSuitableTracker( + const SDL_Event& event, + 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(TrackerCommandPtr cmd); + void Undo(); + void Redo(); + + private: + void DisplayFloatingCtrlInfoText(const PointerEvent& e); + void DisplayInfoText(); + void HideInfoText(); + + private: + std::auto_ptr compositor_; + /** + WARNING: the measuring tools do store a reference to the scene, and it + paramount that the scene gets destroyed AFTER the measurement tools. + */ + Scene2D scene_; + + std::map infoTextMap_; + FlexiblePointerTrackerPtr activeTracker_; + std::vector undoStack_; + + // we store the measure tools here so that they don't get deleted + std::vector measureTools_; + + //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_; + }; + +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/Sdl/cpp.hint --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/cpp.hint Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,2 @@ +#define ORTHANC_OVERRIDE +#define ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(FILE, LINE, NAME) class NAME : public ::OrthancStone::IMessage {}; diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/WebAssembly/BasicScene.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/BasicScene.cpp Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,416 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + + +#include +#include + +// From Stone +#include "../../Applications/Sdl/SdlOpenGLWindow.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2D/Scene2D.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/StoneInitialization.h" +#include "../../Framework/OpenGL/WebAssemblyOpenGLContext.h" + +// From Orthanc framework +#include +#include +#include + +#include + +static const unsigned int FONT_SIZE = 32; + + +void PrepareScene(OrthancStone::Scene2D& scene) +{ + using namespace OrthancStone; + + // Texture of 2x2 size + if (1) + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast(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 l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-3, 2); + l->SetPixelSpacing(1.5, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene.SetLayer(14, l.release()); + } + + // Texture of 1x1 size + if (1) + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + std::auto_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-2, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene.SetLayer(13, l.release()); + } + + // Some lines + { + std::auto_ptr layer(new PolylineSceneLayer); + + layer->SetThickness(1); + + PolylineSceneLayer::Chain chain; + chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5)); + chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5)); + layer->AddChain(chain, true); + + chain.clear(); + chain.push_back(ScenePoint2D(-5, -5)); + chain.push_back(ScenePoint2D(5, -5)); + chain.push_back(ScenePoint2D(5, 5)); + chain.push_back(ScenePoint2D(-5, 5)); + layer->AddChain(chain, true); + + double dy = 1.01; + chain.clear(); + chain.push_back(ScenePoint2D(-4, -4)); + chain.push_back(ScenePoint2D(4, -4 + dy)); + chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy)); + chain.push_back(ScenePoint2D(4, 2)); + layer->AddChain(chain, false); + + layer->SetColor(0,255, 255); + scene.SetLayer(50, layer.release()); + } + + // Some text + if (1) + { + std::auto_ptr layer(new TextSceneLayer); + layer->SetText("Hello"); + scene.SetLayer(100, layer.release()); + } +} + + + + +namespace OrthancStone +{ + class WebAssemblyViewport : public boost::noncopyable + { + private: + OpenGL::WebAssemblyOpenGLContext context_; + Scene2D scene_; + OpenGLCompositor compositor_; + + void SetupEvents(const std::string& canvas); + + public: + WebAssemblyViewport(MessageBroker& broker, + const std::string& canvas) : + context_(canvas), + scene_(broker), + compositor_(context_, scene_) + { + compositor_.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE, Orthanc::Encoding_Latin1); + SetupEvents(canvas); + } + + Scene2D& GetScene() + { + return scene_; + } + + void UpdateSize() + { + context_.UpdateSize(); + compositor_.UpdateSize(); + Refresh(); + } + + void Refresh() + { + compositor_.Refresh(); + } + + const std::string& GetCanvasIdentifier() const + { + return context_.GetCanvasIdentifier(); + } + + ScenePoint2D GetPixelCenterCoordinates(int x, int y) const + { + return compositor_.GetPixelCenterCoordinates(x, y); + } + + unsigned int GetCanvasWidth() const + { + return context_.GetCanvasWidth(); + } + + unsigned int GetCanvasHeight() const + { + return context_.GetCanvasHeight(); + } + }; + + + + class ActiveTracker : public boost::noncopyable + { + private: + std::auto_ptr tracker_; + std::string canvasIdentifier_; + bool insideCanvas_; + + public: + ActiveTracker(IPointerTracker* tracker, + const WebAssemblyViewport& viewport) : + tracker_(tracker), + canvasIdentifier_(viewport.GetCanvasIdentifier()), + insideCanvas_(true) + { + if (tracker_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + void Update(const PointerEvent& event) + { + tracker_->Update(event); + } + + void Release() + { + tracker_->Release(); + } + }; +} + + + +static OrthancStone::PointerEvent* ConvertMouseEvent(const EmscriptenMouseEvent& source, + OrthancStone::WebAssemblyViewport& viewport) +{ + std::auto_ptr target(new OrthancStone::PointerEvent); + + target->AddPosition(viewport.GetPixelCenterCoordinates(source.targetX, source.targetY)); + target->SetAltModifier(source.altKey); + target->SetControlModifier(source.ctrlKey); + target->SetShiftModifier(source.shiftKey); + + return target.release(); +} + + +std::auto_ptr tracker_; + + +EM_BOOL OnMouseEvent(int eventType, + const EmscriptenMouseEvent *mouseEvent, + void *userData) +{ + if (mouseEvent != NULL && + userData != NULL) + { + OrthancStone::WebAssemblyViewport& viewport = + *reinterpret_cast(userData); + + switch (eventType) + { + case EMSCRIPTEN_EVENT_CLICK: + { + static unsigned int count = 0; + char buf[64]; + sprintf(buf, "click %d", count++); + + std::auto_ptr layer(new OrthancStone::TextSceneLayer); + layer->SetText(buf); + viewport.GetScene().SetLayer(100, layer.release()); + viewport.Refresh(); + break; + } + + case EMSCRIPTEN_EVENT_MOUSEDOWN: + { + std::auto_ptr t; + + { + std::auto_ptr event(ConvertMouseEvent(*mouseEvent, viewport)); + + switch (mouseEvent->button) + { + case 0: // Left button + t.reset(new OrthancStone::RotateSceneTracker(viewport.GetScene(), *event)); + break; + + case 1: // Middle button + t.reset(new OrthancStone::PanSceneTracker(viewport.GetScene(), *event)); + break; + + case 2: // Right button + t.reset(new OrthancStone::ZoomSceneTracker + (viewport.GetScene(), *event, viewport.GetCanvasWidth())); + break; + + default: + break; + } + } + + if (t.get() != NULL) + { + tracker_.reset(new OrthancStone::ActiveTracker(t.release(), viewport)); + viewport.Refresh(); + } + + break; + } + + case EMSCRIPTEN_EVENT_MOUSEMOVE: + if (tracker_.get() != NULL) + { + std::auto_ptr event(ConvertMouseEvent(*mouseEvent, viewport)); + tracker_->Update(*event); + viewport.Refresh(); + } + break; + + case EMSCRIPTEN_EVENT_MOUSEUP: + if (tracker_.get() != NULL) + { + tracker_->Release(); + viewport.Refresh(); + tracker_.reset(); + } + break; + + default: + break; + } + } + + return true; +} + + +void OrthancStone::WebAssemblyViewport::SetupEvents(const std::string& canvas) +{ + if (0) + { + emscripten_set_click_callback(canvas.c_str(), this, false, OnMouseEvent); + } + else + { + emscripten_set_mousedown_callback(canvas.c_str(), this, false, OnMouseEvent); + emscripten_set_mousemove_callback(canvas.c_str(), this, false, OnMouseEvent); + emscripten_set_mouseup_callback(canvas.c_str(), this, false, OnMouseEvent); + } +} + + + + +std::auto_ptr viewport1_; +std::auto_ptr viewport2_; +std::auto_ptr viewport3_; +OrthancStone::MessageBroker broker_; + + +EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) +{ + if (viewport1_.get() != NULL) + { + viewport1_->UpdateSize(); + } + + if (viewport2_.get() != NULL) + { + viewport2_->UpdateSize(); + } + + if (viewport3_.get() != NULL) + { + viewport3_->UpdateSize(); + } + + return true; +} + + + +extern "C" +{ + int main(int argc, char const *argv[]) + { + OrthancStone::StoneInitialize(); + EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded"));); + } + + EMSCRIPTEN_KEEPALIVE + void Initialize() + { + viewport1_.reset(new OrthancStone::WebAssemblyViewport(broker_, "mycanvas1")); + PrepareScene(viewport1_->GetScene()); + viewport1_->UpdateSize(); + + viewport2_.reset(new OrthancStone::WebAssemblyViewport(broker_, "mycanvas2")); + PrepareScene(viewport2_->GetScene()); + viewport2_->UpdateSize(); + + viewport3_.reset(new OrthancStone::WebAssemblyViewport(broker_, "mycanvas3")); + PrepareScene(viewport3_->GetScene()); + viewport3_->UpdateSize(); + + emscripten_set_resize_callback("#window", NULL, false, OnWindowResize); + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/WebAssembly/BasicScene.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/BasicScene.html Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,69 @@ + + + + + + + + + + + + Stone of Orthanc + + + + + + + + + + + + + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/WebAssembly/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/CMakeLists.txt Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,89 @@ +cmake_minimum_required(VERSION 2.8.3) + + +##################################################################### +## Configuration of the Emscripten compiler for WebAssembly target +##################################################################### + +set(WASM_FLAGS "-s WASM=1 -s FETCH=1") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") + + +##################################################################### +## 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(ORTHANC_SANDBOXED ON) +SET(ENABLE_WASM ON) + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) + + +##################################################################### +## Build the samples +##################################################################### + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +add_executable(BasicScene + BasicScene.cpp + ) + +target_link_libraries(BasicScene OrthancStone) + +install( + TARGETS BasicScene + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} + ) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/BasicScene.wasm + ${CMAKE_SOURCE_DIR}/BasicScene.html + ${CMAKE_SOURCE_DIR}/Configuration.json + ${CMAKE_SOURCE_DIR}/index.html + DESTINATION ${CMAKE_INSTALL_PREFIX} + ) diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/WebAssembly/Configuration.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/Configuration.json Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,17 @@ +{ + "Plugins": [ + "/usr/local/share/orthanc/plugins/libOrthancWebViewer.so", + "/usr/local/share/orthanc/plugins/libServeFolders.so" + ], + "StorageDirectory" : "/var/lib/orthanc/db", + "IndexDirectory" : "/var/lib/orthanc/db", + "RemoteAccessAllowed" : true, + "AuthenticationEnabled" : false, + "ServeFolders" : { + "AllowCache" : false, + "GenerateETag" : true, + "Folders" : { + "/stone" : "/root/stone" + } + } +} diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/WebAssembly/NOTES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/NOTES.txt Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,4 @@ +$ source ~/Downloads/emsdk/emsdk_env.sh +$ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON .. -DCMAKE_INSTALL_PREFIX=/tmp/stone +$ ninja install +$ sudo docker run -p 4242:4242 -p 8042:8042 --rm -v /tmp/stone:/root/stone:ro jodogne/orthanc-plugins:1.5.6 /root/stone/Configuration.json --verbose diff -r 9e3bb8b4f726 -r 3f13f7f1b55d Samples/WebAssembly/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/index.html Thu May 16 09:11:14 2019 +0200 @@ -0,0 +1,15 @@ + + + + + + + Stone of Orthanc + + +

Available samples

+ + + diff -r 9e3bb8b4f726 -r 3f13f7f1b55d UnitTestsSources/TestMessageBroker.cpp --- a/UnitTestsSources/TestMessageBroker.cpp Tue May 14 18:24:12 2019 +0200 +++ b/UnitTestsSources/TestMessageBroker.cpp Thu May 16 09:11:14 2019 +0200 @@ -34,32 +34,25 @@ using namespace OrthancStone; - enum CustomMessageType - { - CustomMessageType_First = MessageType_CustomMessage + 1, - - CustomMessageType_Completed, - CustomMessageType_Increment - }; - - class MyObservable : public IObservable { public: - struct MyCustomMessage: public BaseMessage + struct MyCustomMessage : public IMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + int payload_; - MyCustomMessage(int payload) - : BaseMessage(), - payload_(payload) - {} + MyCustomMessage(int payload) : + payload_(payload) + { + } }; - MyObservable(MessageBroker& broker) - : IObservable(broker) - {} - + MyObservable(MessageBroker& broker) : + IObservable(broker) + { + } }; class MyObserver : public IObserver @@ -94,15 +87,18 @@ class MyPromiseSource : public IObservable { Promise* currentPromise_; + public: - struct MyPromiseMessage: public BaseMessage + struct MyPromiseMessage: public IMessage { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + int increment; - MyPromiseMessage(int increment) - : BaseMessage(), - increment(increment) - {} + MyPromiseMessage(int increment) : + increment(increment) + { + } }; MyPromiseSource(MessageBroker& broker) @@ -160,18 +156,18 @@ observable.RegisterObserverCallback(new Callable(observer, &MyObserver::HandleCompletedMessage)); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(12, testCounter); // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(20, testCounter); // Unregister the observer; make sure it's not called anymore observable.Unregister(&observer); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(0, testCounter); } @@ -186,12 +182,12 @@ intermediate.RegisterObserverCallback(new Callable(observer, &MyObserver::HandleCompletedMessage)); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(12, testCounter); // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(20, testCounter); } @@ -205,7 +201,7 @@ observable.RegisterObserverCallback(new Callable(*observer, &MyObserver::HandleCompletedMessage)); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(12, testCounter); // delete the observer and check that the callback is not called anymore @@ -213,7 +209,7 @@ // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(0, testCounter); } @@ -228,12 +224,12 @@ intermediate->RegisterObserverCallback(new Callable(observer, &MyObserver::HandleCompletedMessage)); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(12, testCounter); delete intermediate; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(12, testCounter); } @@ -248,12 +244,12 @@ intermediate.RegisterObserverCallback(new Callable(observer, &MyObserver::HandleCompletedMessage)); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(12, testCounter); // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(20, testCounter); } @@ -324,7 +320,7 @@ observable.RegisterObserverCallback(new LambdaCallable(*observer, [&](const MyObservable::MyCustomMessage& message) {testCounter += 2 * message.payload_;})); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(24, testCounter); // delete the observer and check that the callback is not called anymore @@ -332,7 +328,7 @@ // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(0, testCounter); } @@ -362,7 +358,7 @@ MyObserverWithLambda* observer = new MyObserverWithLambda(broker, 3, observable); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(36, testCounter); // delete the observer and check that the callback is not called anymore @@ -370,7 +366,7 @@ // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(0, testCounter); }